Protect the environment

- Environment/Embedding ownership is complicated, reference counting to
  the rescue!
  - VisualEmbeddings are acquired on construction and deleted manually
  - Instancers acquire/delete their environments
  - EmbeddedEnvironment now has three (3) delete methods
- Add EnvironmentStorage to create, track, update, and delete
  environments
- Remove InstancingEngine/IndirectEngine and move shared logic to
  EngineImpl
  - There's probably more that can be done to make DrawManager cleaner
    now
This commit is contained in:
Jozufozu 2024-03-03 14:53:07 -08:00
parent b90b80c8b0
commit e4df896710
23 changed files with 321 additions and 284 deletions

View file

@ -48,4 +48,13 @@ public interface VisualEmbedding extends VisualizationContext {
* @param sizeZ The size of the box in the z direction.
*/
void invalidateLight(int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ);
/**
* Delete this embedding.
*
* <p>After this method exits, the embedding will continue to function in the state it was in before
* this method was called. Once all child instancers are deleted, the resources owned by this embedding
* will be freed. Creating new instancers after calling this method will throw an error.</p>
*/
void delete();
}

View file

@ -4,8 +4,9 @@ import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.backend.Backend;
import com.jozufozu.flywheel.backend.compile.IndirectPrograms;
import com.jozufozu.flywheel.backend.compile.InstancingPrograms;
import com.jozufozu.flywheel.backend.engine.indirect.IndirectEngine;
import com.jozufozu.flywheel.backend.engine.instancing.InstancingEngine;
import com.jozufozu.flywheel.backend.engine.EngineImpl;
import com.jozufozu.flywheel.backend.engine.indirect.IndirectDrawManager;
import com.jozufozu.flywheel.backend.engine.instancing.InstancedDrawManager;
import com.jozufozu.flywheel.backend.gl.GlCompat;
import com.jozufozu.flywheel.lib.backend.SimpleBackend;
import com.jozufozu.flywheel.lib.util.ShadersModHandler;
@ -15,7 +16,7 @@ public final class Backends {
* Use GPU instancing to render everything.
*/
public static final Backend INSTANCING = SimpleBackend.builder()
.engineFactory(level -> new InstancingEngine(InstancingPrograms.get(), 256))
.engineFactory(level -> new EngineImpl(new InstancedDrawManager(InstancingPrograms.get()), 256))
.supported(() -> !ShadersModHandler.isShaderPackInUse() && GlCompat.supportsInstancing() && InstancingPrograms.allLoaded())
.register(Flywheel.rl("instancing"));
@ -23,7 +24,7 @@ public final class Backends {
* Use Compute shaders to cull instances.
*/
public static final Backend INDIRECT = SimpleBackend.builder()
.engineFactory(level -> new IndirectEngine(IndirectPrograms.get(), 256))
.engineFactory(level -> new EngineImpl(new IndirectDrawManager(IndirectPrograms.get()), 256))
.fallback(() -> Backends.INSTANCING)
.supported(() -> !ShadersModHandler.isShaderPackInUse() && GlCompat.supportsIndirect() && IndirectPrograms.allLoaded())
.register(Flywheel.rl("indirect"));

View file

@ -138,7 +138,7 @@ public class IndirectPrograms extends AtomicReferenceCounted {
}
@Override
protected void delete() {
protected void _delete() {
pipeline.values()
.forEach(GlProgram::delete);
culling.values()

View file

@ -66,7 +66,7 @@ public class InstancingPrograms extends AtomicReferenceCounted {
}
@Override
protected void delete() {
protected void _delete() {
pipeline.values()
.forEach(GlProgram::delete);
}

View file

@ -1,85 +0,0 @@
package com.jozufozu.flywheel.backend.engine;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.instance.InstancerProvider;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.visualization.VisualEmbedding;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.backend.engine.embed.EmbeddedEnvironment;
import com.jozufozu.flywheel.backend.engine.embed.Environment;
import net.minecraft.client.Camera;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.world.phys.Vec3;
public abstract class AbstractEngine implements Engine {
protected final int sqrMaxOriginDistance;
protected BlockPos renderOrigin = BlockPos.ZERO;
public AbstractEngine(int maxOriginDistance) {
sqrMaxOriginDistance = maxOriginDistance * maxOriginDistance;
}
public <I extends Instance> Instancer<I> instancer(Environment environment, InstanceType<I> type, Model model, RenderStage stage) {
return getStorage().getInstancer(environment, type, model, stage);
}
@Override
public VisualizationContext createVisualizationContext(RenderStage stage) {
return new VisualizationContextImpl(stage);
}
@Override
public boolean updateRenderOrigin(Camera camera) {
Vec3 cameraPos = camera.getPosition();
double dx = renderOrigin.getX() - cameraPos.x;
double dy = renderOrigin.getY() - cameraPos.y;
double dz = renderOrigin.getZ() - cameraPos.z;
double distanceSqr = dx * dx + dy * dy + dz * dz;
if (distanceSqr <= sqrMaxOriginDistance) {
return false;
}
renderOrigin = BlockPos.containing(cameraPos);
getStorage().onRenderOriginChanged();
return true;
}
@Override
public Vec3i renderOrigin() {
return renderOrigin;
}
protected abstract InstancerStorage<? extends AbstractInstancer<?>> getStorage();
private class VisualizationContextImpl implements VisualizationContext {
private final InstancerProviderImpl instancerProvider;
private final RenderStage stage;
public VisualizationContextImpl(RenderStage stage) {
instancerProvider = new InstancerProviderImpl(AbstractEngine.this, stage);
this.stage = stage;
}
@Override
public InstancerProvider instancerProvider() {
return instancerProvider;
}
@Override
public Vec3i renderOrigin() {
return AbstractEngine.this.renderOrigin();
}
@Override
public VisualEmbedding createEmbedding() {
return new EmbeddedEnvironment(AbstractEngine.this, stage);
}
}
}

View file

@ -25,6 +25,8 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
protected AbstractInstancer(InstanceType<I> type, Environment environment) {
this.type = type;
this.environment = environment;
environment.acquire();
}
@Override
@ -159,6 +161,10 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
deleted.clear();
}
public void delete() {
environment.release();
}
@Override
public String toString() {
return "AbstractInstancer[" + instanceCount() + ']';

View file

@ -6,6 +6,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.jozufozu.flywheel.Flywheel;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
@ -13,17 +14,13 @@ import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.backend.engine.embed.Environment;
import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
import it.unimi.dsi.fastutil.objects.ReferenceSet;
public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
public abstract class DrawManager<N extends AbstractInstancer<?>> {
/**
* A map of instancer keys to instancers.
* <br>
* This map is populated as instancers are requested and contains both initialized and uninitialized instancers.
*/
protected final Map<InstancerKey<?>, N> instancers = new ConcurrentHashMap<>();
protected final ReferenceSet<Environment> environments = new ReferenceLinkedOpenHashSet<>();
/**
* A list of instancers that have not yet been initialized.
* <br>
@ -37,12 +34,6 @@ public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
}
public void delete() {
// FIXME: The ownership of environments is a bit weird. Their resources are created and destroyed by the engine,
// but the engine doesn't own the things themselves. This makes it hard for the engine to know when to delete
// environments. For now, we just delete all environments when the engine is deleted, but this is not ideal.
environments.forEach(Environment::delete);
environments.clear();
instancers.clear();
initializationQueue.clear();
}
@ -61,6 +52,10 @@ public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
.forEach(AbstractInstancer::clear);
}
public abstract void renderCrumbling(List<Engine.CrumblingBlock> crumblingBlocks);
public abstract void renderStage(RenderStage stage);
protected abstract <I extends Instance> N create(InstancerKey<I> type);
protected abstract <I extends Instance> void initialize(InstancerKey<I> key, N instancer);
@ -68,15 +63,13 @@ public abstract class InstancerStorage<N extends AbstractInstancer<?>> {
private N createAndDeferInit(InstancerKey<?> key) {
var out = create(key);
environments.add(key.environment());
// Only queue the instancer for initialization if it has anything to render.
if (checkAndWarnEmptyModel(key.model())) {
if (checkAndWarnEmptyModel(key.model())) {
// Thread safety: this method is called atomically from within computeIfAbsent,
// so we don't need extra synchronization to protect the queue.
initializationQueue.add(new UninitializedInstancer<>(key, out));
}
return out;
return out;
}
protected record UninitializedInstancer<N, I extends Instance>(InstancerKey<I> key, N instancer) {

View file

@ -0,0 +1,145 @@
package com.jozufozu.flywheel.backend.engine;
import java.util.List;
import com.jozufozu.flywheel.api.backend.Engine;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.instance.Instance;
import com.jozufozu.flywheel.api.instance.InstanceType;
import com.jozufozu.flywheel.api.instance.Instancer;
import com.jozufozu.flywheel.api.instance.InstancerProvider;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.task.Plan;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.api.visualization.VisualEmbedding;
import com.jozufozu.flywheel.api.visualization.VisualizationContext;
import com.jozufozu.flywheel.backend.engine.embed.EmbeddedEnvironment;
import com.jozufozu.flywheel.backend.engine.embed.Environment;
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.lib.task.Flag;
import com.jozufozu.flywheel.lib.task.NamedFlag;
import com.jozufozu.flywheel.lib.task.SyncedPlan;
import net.minecraft.client.Camera;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Vec3i;
import net.minecraft.world.phys.Vec3;
public class EngineImpl implements Engine {
private final int sqrMaxOriginDistance;
private final DrawManager<? extends AbstractInstancer<?>> drawManager;
private final EnvironmentStorage environmentStorage = new EnvironmentStorage(this);
private final Flag flushFlag = new NamedFlag("flushed");
private BlockPos renderOrigin = BlockPos.ZERO;
public EngineImpl(DrawManager<? extends AbstractInstancer<?>> drawManager, int maxOriginDistance) {
this.drawManager = drawManager;
sqrMaxOriginDistance = maxOriginDistance * maxOriginDistance;
}
@Override
public Plan<RenderContext> createFramePlan() {
return SyncedPlan.of(this::flush);
}
@Override
public void renderStage(TaskExecutor executor, RenderContext context, RenderStage stage) {
executor.syncUntil(flushFlag::isRaised);
if (stage.isLast()) {
flushFlag.lower();
}
drawManager.renderStage(stage);
}
@Override
public void renderCrumbling(TaskExecutor executor, RenderContext context, List<CrumblingBlock> crumblingBlocks) {
// Need to wait for flush before we can inspect instancer state.
executor.syncUntil(flushFlag::isRaised);
drawManager.renderCrumbling(crumblingBlocks);
}
@Override
public VisualizationContext createVisualizationContext(RenderStage stage) {
return new VisualizationContextImpl(stage);
}
@Override
public boolean updateRenderOrigin(Camera camera) {
Vec3 cameraPos = camera.getPosition();
double dx = renderOrigin.getX() - cameraPos.x;
double dy = renderOrigin.getY() - cameraPos.y;
double dz = renderOrigin.getZ() - cameraPos.z;
double distanceSqr = dx * dx + dy * dy + dz * dz;
if (distanceSqr <= sqrMaxOriginDistance) {
return false;
}
renderOrigin = BlockPos.containing(cameraPos);
drawManager.onRenderOriginChanged();
return true;
}
@Override
public Vec3i renderOrigin() {
return renderOrigin;
}
@Override
public void delete() {
drawManager.delete();
environmentStorage.delete();
}
public <I extends Instance> Instancer<I> instancer(Environment environment, InstanceType<I> type, Model model, RenderStage stage) {
return drawManager.getInstancer(environment, type, model, stage);
}
private void flush(RenderContext ctx) {
try (var state = GlStateTracker.getRestoreState()) {
Uniforms.updateContext(ctx);
drawManager.flush();
environmentStorage.flush();
}
flushFlag.raise();
}
public VisualEmbedding createEmbedding(RenderStage renderStage) {
return environmentStorage.create(renderStage);
}
public void freeEmbedding(EmbeddedEnvironment embeddedEnvironment) {
environmentStorage.enqueueDeletion(embeddedEnvironment);
}
private class VisualizationContextImpl implements VisualizationContext {
private final InstancerProviderImpl instancerProvider;
private final RenderStage stage;
public VisualizationContextImpl(RenderStage stage) {
instancerProvider = new InstancerProviderImpl(EngineImpl.this, stage);
this.stage = stage;
}
@Override
public InstancerProvider instancerProvider() {
return instancerProvider;
}
@Override
public Vec3i renderOrigin() {
return EngineImpl.this.renderOrigin();
}
@Override
public VisualEmbedding createEmbedding() {
return EngineImpl.this.createEmbedding(stage);
}
}
}

View file

@ -0,0 +1,51 @@
package com.jozufozu.flywheel.backend.engine;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.visualization.VisualEmbedding;
import com.jozufozu.flywheel.backend.engine.embed.EmbeddedEnvironment;
import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
import it.unimi.dsi.fastutil.objects.ReferenceSet;
import it.unimi.dsi.fastutil.objects.ReferenceSets;
public class EnvironmentStorage {
protected final ReferenceSet<EmbeddedEnvironment> environments = ReferenceSets.synchronize(new ReferenceLinkedOpenHashSet<>());
private final Queue<EmbeddedEnvironment> forDeletion = new ConcurrentLinkedQueue<>();
private final EngineImpl engine;
public EnvironmentStorage(EngineImpl engine) {
this.engine = engine;
}
public VisualEmbedding create(RenderStage stage) {
var out = new EmbeddedEnvironment(engine, stage);
environments.add(out);
return out;
}
public void enqueueDeletion(EmbeddedEnvironment environment) {
environments.remove(environment);
forDeletion.add(environment);
}
public void flush() {
EmbeddedEnvironment env;
while ((env = forDeletion.poll()) != null) {
env.actuallyDelete();
}
environments.forEach(EmbeddedEnvironment::flush);
}
public void delete() {
environments.forEach(EmbeddedEnvironment::actuallyDelete);
environments.clear();
}
}

View file

@ -8,7 +8,7 @@ import com.jozufozu.flywheel.api.instance.InstancerProvider;
import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.backend.engine.embed.GlobalEnvironment;
public record InstancerProviderImpl(AbstractEngine engine, RenderStage renderStage) implements InstancerProvider {
public record InstancerProviderImpl(EngineImpl engine, RenderStage renderStage) implements InstancerProvider {
@Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model) {
return engine.instancer(GlobalEnvironment.INSTANCE, type, model, renderStage);

View file

@ -189,7 +189,7 @@ public class MeshPool {
}
@Override
protected void delete() {
protected void _delete() {
MeshPool.this.dirty = true;
MeshPool.this.anyToRemove = true;
}

View file

@ -14,13 +14,14 @@ import com.jozufozu.flywheel.api.model.Model;
import com.jozufozu.flywheel.api.visualization.VisualEmbedding;
import com.jozufozu.flywheel.backend.Samplers;
import com.jozufozu.flywheel.backend.compile.ContextShader;
import com.jozufozu.flywheel.backend.engine.AbstractEngine;
import com.jozufozu.flywheel.backend.engine.EngineImpl;
import com.jozufozu.flywheel.backend.gl.shader.GlProgram;
import com.jozufozu.flywheel.backend.util.AtomicReferenceCounted;
import net.minecraft.core.Vec3i;
import net.minecraft.world.level.BlockAndTintGetter;
public class EmbeddedEnvironment implements Environment, VisualEmbedding {
public class EmbeddedEnvironment extends AtomicReferenceCounted implements Environment, VisualEmbedding {
private final Matrix4f pose = new Matrix4f();
private final Matrix3f normal = new Matrix3f();
@ -28,10 +29,10 @@ public class EmbeddedEnvironment implements Environment, VisualEmbedding {
private final EmbeddedLightTexture lightTexture = new EmbeddedLightTexture();
private final InstancerProvider instancerProvider;
private final AbstractEngine engine;
private final EngineImpl engine;
private final RenderStage renderStage;
public EmbeddedEnvironment(AbstractEngine engine, RenderStage renderStage) {
public EmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) {
this.engine = engine;
this.renderStage = renderStage;
@ -42,6 +43,22 @@ public class EmbeddedEnvironment implements Environment, VisualEmbedding {
return engine.instancer(EmbeddedEnvironment.this, type, model, renderStage);
}
};
// Acquire the reference owned by the visual that created this.
acquire();
}
public void flush() {
if (lightVolume.empty()) {
return;
}
Samplers.EMBEDDED_LIGHT.makeActive();
lightTexture.bind();
lightTexture.ensureCapacity(lightVolume.sizeX, lightVolume.sizeY, lightVolume.sizeZ);
lightTexture.upload(lightVolume.ptr(), lightVolume.sizeX, lightVolume.sizeY, lightVolume.sizeZ);
}
@Override
@ -72,10 +89,6 @@ public class EmbeddedEnvironment implements Environment, VisualEmbedding {
lightTexture.bind();
lightTexture.ensureCapacity(lightVolume.sizeX, lightVolume.sizeY, lightVolume.sizeZ);
lightTexture.upload(lightVolume.ptr(), lightVolume.sizeX, lightVolume.sizeY, lightVolume.sizeZ);
float oneOverSizeX = 1f / (float) lightTexture.sizeX;
float oneOverSizeY = 1f / (float) lightTexture.sizeY;
float oneOverSizeZ = 1f / (float) lightTexture.sizeZ;
@ -105,11 +118,35 @@ public class EmbeddedEnvironment implements Environment, VisualEmbedding {
@Override
public VisualEmbedding createEmbedding() {
return new EmbeddedEnvironment(engine, renderStage);
return engine.createEmbedding(renderStage);
}
/**
* Called by visuals
*/
@Override
public void delete() {
// Release the reference owned by the visual that created this.
// Note that visuals don't explicitly call acquire, instead the
// storage acquired a reference when this was constructed.
release();
}
/**
* Called when referenceCount goes to 0
*/
@Override
public void _delete() {
engine.freeEmbedding(this);
}
/**
* Called in EnvironmentStorage#flush
*/
public void actuallyDelete() {
// We could technically free the light volume right away in _delete, but
// the control flow here is so convoluted that it's probably best to do
// everything in one place.
lightVolume.delete();
lightTexture.delete();
}

View file

@ -10,5 +10,7 @@ public interface Environment {
void setupCull(GlProgram cullProgram);
void delete();
void acquire();
void release();
}

View file

@ -25,7 +25,12 @@ public class GlobalEnvironment implements Environment {
}
@Override
public void delete() {
public void acquire() {
}
@Override
public void release() {
}
}

View file

@ -77,9 +77,7 @@ public class IndirectCullingGroup<I extends Instance> {
if (instanceCount == 0) {
iterator.remove();
for (IndirectDraw draw : instancer.draws()) {
draw.delete();
}
instancer.delete();
continue;
}

View file

@ -19,9 +19,9 @@ import com.jozufozu.flywheel.backend.Samplers;
import com.jozufozu.flywheel.backend.compile.ContextShader;
import com.jozufozu.flywheel.backend.compile.IndirectPrograms;
import com.jozufozu.flywheel.backend.engine.CommonCrumbling;
import com.jozufozu.flywheel.backend.engine.DrawManager;
import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl;
import com.jozufozu.flywheel.backend.engine.InstancerKey;
import com.jozufozu.flywheel.backend.engine.InstancerStorage;
import com.jozufozu.flywheel.backend.engine.MaterialRenderState;
import com.jozufozu.flywheel.backend.engine.MeshPool;
import com.jozufozu.flywheel.backend.engine.TextureBinder;
@ -39,7 +39,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.client.resources.model.ModelBakery;
public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>> {
public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
private final IndirectPrograms programs;
private final StagingBuffer stagingBuffer;
private final MeshPool meshPool;
@ -49,6 +49,7 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
public IndirectDrawManager(IndirectPrograms programs) {
this.programs = programs;
programs.acquire();
stagingBuffer = new StagingBuffer(this.programs);
meshPool = new MeshPool();
@ -81,17 +82,23 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
}
public void renderStage(RenderStage stage) {
TextureBinder.bindLightAndOverlay();
vertexArray.bindForDraw();
Uniforms.bindForDraw();
for (var group : cullingGroups.values()) {
group.submit(stage);
if (!hasStage(stage)) {
return;
}
MaterialRenderState.reset();
TextureBinder.resetLightAndOverlay();
try (var restoreState = GlStateTracker.getRestoreState()) {
TextureBinder.bindLightAndOverlay();
vertexArray.bindForDraw();
Uniforms.bindForDraw();
for (var group : cullingGroups.values()) {
group.submit(stage);
}
MaterialRenderState.reset();
TextureBinder.resetLightAndOverlay();
}
}
@Override
@ -136,6 +143,8 @@ public class IndirectDrawManager extends InstancerStorage<IndirectInstancer<?>>
meshPool.delete();
crumblingDrawBuffer.delete();
programs.release();
}
public void renderCrumbling(List<Engine.CrumblingBlock> crumblingBlocks) {

View file

@ -1,77 +0,0 @@
package com.jozufozu.flywheel.backend.engine.indirect;
import java.util.List;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.task.Plan;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.backend.compile.IndirectPrograms;
import com.jozufozu.flywheel.backend.engine.AbstractEngine;
import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
import com.jozufozu.flywheel.backend.engine.InstancerStorage;
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.lib.task.Flag;
import com.jozufozu.flywheel.lib.task.NamedFlag;
import com.jozufozu.flywheel.lib.task.SyncedPlan;
public class IndirectEngine extends AbstractEngine {
private final IndirectPrograms programs;
private final IndirectDrawManager drawManager;
private final Flag flushFlag = new NamedFlag("flushed");
public IndirectEngine(IndirectPrograms programs, int maxOriginDistance) {
super(maxOriginDistance);
programs.acquire();
this.programs = programs;
drawManager = new IndirectDrawManager(this.programs);
}
@Override
public Plan<RenderContext> createFramePlan() {
return SyncedPlan.of(this::flushDrawManager);
}
private void flushDrawManager(RenderContext ctx) {
try (var state = GlStateTracker.getRestoreState()) {
Uniforms.updateContext(ctx);
drawManager.flush();
}
flushFlag.raise();
}
@Override
public void renderStage(TaskExecutor executor, RenderContext context, RenderStage stage) {
executor.syncUntil(flushFlag::isRaised);
if (stage.isLast()) {
flushFlag.lower();
}
if (!drawManager.hasStage(stage)) {
return;
}
try (var restoreState = GlStateTracker.getRestoreState()) {
drawManager.renderStage(stage);
}
}
@Override
public void renderCrumbling(TaskExecutor executor, RenderContext context, List<CrumblingBlock> crumblingBlocks) {
executor.syncUntil(flushFlag::isRaised);
drawManager.renderCrumbling(crumblingBlocks);
}
@Override
protected InstancerStorage<? extends AbstractInstancer<?>> getStorage() {
return drawManager;
}
@Override
public void delete() {
drawManager.delete();
programs.release();
}
}

View file

@ -101,4 +101,13 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
MemoryUtil.memPutInt(ptr, index);
writer.write(ptr + IndirectBuffers.INT_SIZE, instance);
}
@Override
public void delete() {
super.delete();
for (IndirectDraw draw : draws()) {
draw.delete();
}
}
}

View file

@ -23,9 +23,9 @@ import com.jozufozu.flywheel.backend.ShaderIndices;
import com.jozufozu.flywheel.backend.compile.ContextShader;
import com.jozufozu.flywheel.backend.compile.InstancingPrograms;
import com.jozufozu.flywheel.backend.engine.CommonCrumbling;
import com.jozufozu.flywheel.backend.engine.DrawManager;
import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl;
import com.jozufozu.flywheel.backend.engine.InstancerKey;
import com.jozufozu.flywheel.backend.engine.InstancerStorage;
import com.jozufozu.flywheel.backend.engine.MaterialEncoder;
import com.jozufozu.flywheel.backend.engine.MaterialRenderState;
import com.jozufozu.flywheel.backend.engine.MeshPool;
@ -41,7 +41,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.minecraft.client.resources.model.ModelBakery;
public class InstancedDrawManager extends InstancerStorage<InstancedInstancer<?>> {
public class InstancedDrawManager extends DrawManager<InstancedInstancer<?>> {
/**
* The set of draw calls to make in each {@link RenderStage}.
*/

View file

@ -107,6 +107,8 @@ public class InstancedInstancer<I extends Instance> extends AbstractInstancer<I>
}
public void delete() {
super.delete();
if (vbo == null) {
return;
}

View file

@ -1,68 +0,0 @@
package com.jozufozu.flywheel.backend.engine.instancing;
import java.util.List;
import com.jozufozu.flywheel.api.event.RenderContext;
import com.jozufozu.flywheel.api.event.RenderStage;
import com.jozufozu.flywheel.api.task.Plan;
import com.jozufozu.flywheel.api.task.TaskExecutor;
import com.jozufozu.flywheel.backend.compile.InstancingPrograms;
import com.jozufozu.flywheel.backend.engine.AbstractEngine;
import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
import com.jozufozu.flywheel.backend.engine.InstancerStorage;
import com.jozufozu.flywheel.backend.engine.uniform.Uniforms;
import com.jozufozu.flywheel.backend.gl.GlStateTracker;
import com.jozufozu.flywheel.lib.task.Flag;
import com.jozufozu.flywheel.lib.task.NamedFlag;
import com.jozufozu.flywheel.lib.task.SyncedPlan;
public class InstancingEngine extends AbstractEngine {
private final InstancedDrawManager drawManager;
private final Flag flushFlag = new NamedFlag("flushed");
public InstancingEngine(InstancingPrograms programs, int maxOriginDistance) {
super(maxOriginDistance);
drawManager = new InstancedDrawManager(programs);
}
@Override
public Plan<RenderContext> createFramePlan() {
return SyncedPlan.of(this::flushDrawManager);
}
private void flushDrawManager(RenderContext ctx) {
try (var restoreState = GlStateTracker.getRestoreState()) {
Uniforms.updateContext(ctx);
drawManager.flush();
}
flushFlag.raise();
}
@Override
public void renderStage(TaskExecutor executor, RenderContext context, RenderStage stage) {
executor.syncUntil(flushFlag::isRaised);
if (stage.isLast()) {
flushFlag.lower();
}
drawManager.renderStage(stage);
}
@Override
public void renderCrumbling(TaskExecutor executor, RenderContext context, List<CrumblingBlock> crumblingBlocks) {
// Need to wait for flush before we can inspect instancer state.
executor.syncUntil(flushFlag::isRaised);
drawManager.renderCrumbling(crumblingBlocks);
}
@Override
protected InstancerStorage<? extends AbstractInstancer<?>> getStorage() {
return drawManager;
}
@Override
public void delete() {
drawManager.delete();
}
}

View file

@ -30,11 +30,11 @@ public abstract class AtomicReferenceCounted {
int newCount = referenceCount.decrementAndGet();
if (newCount == 0) {
isDeleted = true;
delete();
_delete();
} else if (newCount < 0) {
throw new IllegalStateException("Tried to delete instance of '" + getClass().getName() + "' more times than it was acquired!");
}
}
protected abstract void delete();
protected abstract void _delete();
}

View file

@ -28,11 +28,11 @@ public abstract class ReferenceCounted {
int newCount = --referenceCount;
if (newCount == 0) {
isDeleted = true;
delete();
_delete();
} else if (newCount < 0) {
throw new IllegalStateException("Tried to delete instance of '" + getClass().getName() + "' more times than it was acquired!");
}
}
protected abstract void delete();
protected abstract void _delete();
}