From 2039a964e241ed40e837675b25f7390c428df3b6 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 26 Nov 2023 12:07:05 -0800 Subject: [PATCH] Store bought engines - Add InstancerStorage so engines can share common code. - Instanced and Indirect DrawManagers extend InstancerStorage, while BatchingEngine keeps an anonymous class for it. - AbstractEngine now has a InstancerStorage getter and does some delegation so the implementations don't have to. - InstancedInstancer directly stores the list of DrawCalls it belongs to. - InstancingEngine no longer accepts a context parameter. - Make the /flywheel backend command default to the flywheel nampspace. --- .../jozufozu/flywheel/backend/Backends.java | 3 +- .../backend/engine/AbstractEngine.java | 15 ++- .../backend/engine/AbstractInstancer.java | 4 + .../backend/engine/InstancerStorage.java | 96 ++++++++++++++++++ .../engine/batching/BatchingEngine.java | 70 +++++-------- .../engine/indirect/IndirectDrawManager.java | 70 +++++-------- .../engine/indirect/IndirectEngine.java | 49 ++++----- .../instancing/InstancedDrawManager.java | 99 +++---------------- .../engine/instancing/InstancedInstancer.java | 12 +++ .../engine/instancing/InstancingEngine.java | 60 +++++------ .../flywheel/config/BackendArgument.java | 43 +++++++- 11 files changed, 274 insertions(+), 247 deletions(-) create mode 100644 src/main/java/com/jozufozu/flywheel/backend/engine/InstancerStorage.java diff --git a/src/main/java/com/jozufozu/flywheel/backend/Backends.java b/src/main/java/com/jozufozu/flywheel/backend/Backends.java index 49d2281e0..04e7b1503 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/Backends.java +++ b/src/main/java/com/jozufozu/flywheel/backend/Backends.java @@ -10,7 +10,6 @@ import com.jozufozu.flywheel.backend.engine.indirect.IndirectEngine; import com.jozufozu.flywheel.backend.engine.instancing.InstancingEngine; import com.jozufozu.flywheel.gl.GlCompat; import com.jozufozu.flywheel.lib.backend.SimpleBackend; -import com.jozufozu.flywheel.lib.context.Contexts; import com.jozufozu.flywheel.lib.util.ShadersModHandler; import net.minecraft.ChatFormatting; @@ -33,7 +32,7 @@ public class Backends { public static final Backend INSTANCING = SimpleBackend.builder() .engineMessage(Component.literal("Using Instancing Engine") .withStyle(ChatFormatting.GREEN)) - .engineFactory(level -> new InstancingEngine(256, Contexts.WORLD)) + .engineFactory(level -> new InstancingEngine(256)) .fallback(() -> Backends.BATCHING) .supported(() -> !ShadersModHandler.isShaderPackInUse() && GlCompat.supportsInstancing() && InstancingPrograms.allLoaded()) .register(Flywheel.rl("instancing")); diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/AbstractEngine.java b/src/main/java/com/jozufozu/flywheel/backend/engine/AbstractEngine.java index 404fe524d..d96cefad4 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/AbstractEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/AbstractEngine.java @@ -1,6 +1,11 @@ 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.model.Model; import net.minecraft.client.Camera; import net.minecraft.core.BlockPos; @@ -15,6 +20,11 @@ public abstract class AbstractEngine implements Engine { sqrMaxOriginDistance = maxOriginDistance * maxOriginDistance; } + @Override + public Instancer instancer(InstanceType type, Model model, RenderStage stage) { + return getStorage().getInstancer(type, model, stage); + } + @Override public boolean updateRenderOrigin(Camera camera) { Vec3 cameraPos = camera.getPosition(); @@ -28,7 +38,7 @@ public abstract class AbstractEngine implements Engine { } renderOrigin = BlockPos.containing(cameraPos); - onRenderOriginChanged(); + getStorage().onRenderOriginChanged(); return true; } @@ -37,6 +47,5 @@ public abstract class AbstractEngine implements Engine { return renderOrigin; } - protected void onRenderOriginChanged() { - } + protected abstract InstancerStorage> getStorage(); } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/AbstractInstancer.java b/src/main/java/com/jozufozu/flywheel/backend/engine/AbstractInstancer.java index 6e294e824..ab3734a97 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/AbstractInstancer.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/AbstractInstancer.java @@ -108,4 +108,8 @@ public abstract class AbstractInstancer implements Instancer public String toString() { return "AbstractInstancer[" + getInstanceCount() + ']'; } + + public void delete() { + + } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/InstancerStorage.java b/src/main/java/com/jozufozu/flywheel/backend/engine/InstancerStorage.java new file mode 100644 index 000000000..2e524145b --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/InstancerStorage.java @@ -0,0 +1,96 @@ +package com.jozufozu.flywheel.backend.engine; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +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.model.Model; + +public abstract class InstancerStorage> { + /** + * A map of instancer keys to instancers. + *
+ * This map is populated as instancers are requested and contains both initialized and uninitialized instancers. + * Write access to this map must be synchronized on {@link #creationLock}. + *
+ * See {@link #getInstancer} for insertion details. + */ + private final Map, N> instancers = new HashMap<>(); + /** + * A list of instancers that have not yet been initialized. + *
+ * All new instancers land here before having resources allocated in {@link #flush}. + * Write access to this list must be synchronized on {@link #creationLock}. + */ + private final List> uninitializedInstancers = new ArrayList<>(); + /** + * Mutex for {@link #instancers} and {@link #uninitializedInstancers}. + */ + private final Object creationLock = new Object(); + + + /** + * A list of initialized instancers. + *
+ * These are instancers that may need to be cleared or deleted. + */ + private final List initializedInstancers = new ArrayList<>(); + + @SuppressWarnings("unchecked") + public Instancer getInstancer(InstanceType type, Model model, RenderStage stage) { + InstancerKey key = new InstancerKey<>(type, model, stage); + + N instancer = instancers.get(key); + // Happy path: instancer is already initialized. + if (instancer != null) { + return (Instancer) instancer; + } + + // Unhappy path: instancer is not initialized, need to sync to make sure we don't create duplicates. + synchronized (creationLock) { + // Someone else might have initialized it while we were waiting for the lock. + instancer = instancers.get(key); + if (instancer != null) { + return (Instancer) instancer; + } + + // Create a new instancer and add it to the uninitialized list. + instancer = create(type); + instancers.put(key, instancer); + uninitializedInstancers.add(new UninitializedInstancer<>(key, instancer, model, stage)); + return (Instancer) instancer; + } + } + + public void invalidate() { + instancers.clear(); + uninitializedInstancers.clear(); + + initializedInstancers.forEach(AbstractInstancer::delete); + initializedInstancers.clear(); + } + + public void flush() { + for (var instancer : uninitializedInstancers) { + add(instancer.key(), instancer.instancer(), instancer.model(), instancer.stage()); + initializedInstancers.add(instancer.instancer()); + } + uninitializedInstancers.clear(); + } + + public void onRenderOriginChanged() { + initializedInstancers.forEach(AbstractInstancer::clear); + } + + protected abstract N create(InstanceType type); + + protected abstract void add(InstancerKey key, N instancer, Model model, RenderStage stage); + + private record UninitializedInstancer(InstancerKey key, N instancer, Model model, RenderStage stage) { + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchingEngine.java b/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchingEngine.java index 3357f6b42..0122ceeac 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchingEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/batching/BatchingEngine.java @@ -1,6 +1,5 @@ package com.jozufozu.flywheel.backend.engine.batching; -import java.util.ArrayList; import java.util.EnumMap; import java.util.HashMap; import java.util.List; @@ -10,13 +9,14 @@ 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.model.Mesh; 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.backend.engine.AbstractEngine; +import com.jozufozu.flywheel.backend.engine.AbstractInstancer; import com.jozufozu.flywheel.backend.engine.InstancerKey; +import com.jozufozu.flywheel.backend.engine.InstancerStorage; import com.jozufozu.flywheel.lib.task.Flag; import com.jozufozu.flywheel.lib.task.NamedFlag; import com.jozufozu.flywheel.lib.task.SimplyComposedPlan; @@ -27,9 +27,27 @@ import net.minecraft.client.renderer.RenderType; public class BatchingEngine extends AbstractEngine implements SimplyComposedPlan { private final BatchedDrawTracker drawTracker = new BatchedDrawTracker(); - private final Map, BatchedInstancer> instancers = new HashMap<>(); - private final List uninitializedInstancers = new ArrayList<>(); - private final List> initializedInstancers = new ArrayList<>(); + + // TODO: reintroduce BatchedDrawManager + private final InstancerStorage> storage = new InstancerStorage<>() { + @Override + protected BatchedInstancer create(InstanceType type) { + return new BatchedInstancer<>(type); + } + + @Override + protected void add(InstancerKey key, BatchedInstancer instancer, Model model, RenderStage stage) { + var stagePlan = stagePlans.computeIfAbsent(stage, renderStage -> new BatchedStagePlan(renderStage, drawTracker)); + var meshes = model.getMeshes(); + for (var entry : meshes.entrySet()) { + var material = entry.getKey(); + RenderType renderType = material.getFallbackRenderType(); + var transformCall = new TransformCall<>(instancer, material, alloc(entry.getValue(), renderType.format())); + stagePlan.put(renderType, transformCall); + } + } + }; + private final Map stagePlans = new EnumMap<>(RenderStage.class); private final Map meshPools = new HashMap<>(); @@ -39,20 +57,6 @@ public class BatchingEngine extends AbstractEngine implements SimplyComposedPlan super(maxOriginDistance); } - @Override - @SuppressWarnings("unchecked") - public Instancer instancer(InstanceType type, Model model, RenderStage stage) { - InstancerKey key = new InstancerKey<>(type, model, stage); - BatchedInstancer instancer = (BatchedInstancer) instancers.get(key); - // FIXME: This needs to be synchronized like InstancingEngine - if (instancer == null) { - instancer = new BatchedInstancer<>(type); - instancers.put(key, instancer); - uninitializedInstancers.add(new UninitializedInstancer(instancer, model, stage)); - } - return instancer; - } - @Override public void execute(TaskExecutor taskExecutor, RenderContext context, Runnable onCompletion) { flush(); @@ -100,49 +104,29 @@ public class BatchingEngine extends AbstractEngine implements SimplyComposedPlan } @Override - protected void onRenderOriginChanged() { - initializedInstancers.forEach(BatchedInstancer::clear); + protected InstancerStorage> getStorage() { + return storage; } @Override public void delete() { - instancers.clear(); + storage.invalidate(); meshPools.values() .forEach(BatchedMeshPool::delete); meshPools.clear(); - - initializedInstancers.clear(); } private void flush() { - for (var instancer : uninitializedInstancers) { - add(instancer.instancer(), instancer.model(), instancer.stage()); - } - uninitializedInstancers.clear(); + storage.flush(); for (var pool : meshPools.values()) { pool.flush(); } } - private void add(BatchedInstancer instancer, Model model, RenderStage stage) { - var stagePlan = stagePlans.computeIfAbsent(stage, renderStage -> new BatchedStagePlan(renderStage, drawTracker)); - var meshes = model.getMeshes(); - for (var entry : meshes.entrySet()) { - var material = entry.getKey(); - RenderType renderType = material.getFallbackRenderType(); - var transformCall = new TransformCall<>(instancer, material, alloc(entry.getValue(), renderType.format())); - stagePlan.put(renderType, transformCall); - } - initializedInstancers.add(instancer); - } - private BatchedMeshPool.BufferedMesh alloc(Mesh mesh, VertexFormat format) { return meshPools.computeIfAbsent(format, BatchedMeshPool::new) .alloc(mesh); } - - private record UninitializedInstancer(BatchedInstancer instancer, Model model, RenderStage stage) { - } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDrawManager.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDrawManager.java index 953394377..631d18b5c 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDrawManager.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDrawManager.java @@ -1,43 +1,22 @@ package com.jozufozu.flywheel.backend.engine.indirect; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; 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.model.Model; import com.jozufozu.flywheel.api.vertex.VertexType; import com.jozufozu.flywheel.backend.engine.InstancerKey; +import com.jozufozu.flywheel.backend.engine.InstancerStorage; import com.jozufozu.flywheel.lib.util.Pair; -public class IndirectDrawManager { - private final Map, IndirectInstancer> instancers = new HashMap<>(); - private final List uninitializedInstancers = new ArrayList<>(); - private final List> initializedInstancers = new ArrayList<>(); +public class IndirectDrawManager extends InstancerStorage> { public final Map, VertexType>, IndirectCullingGroup> renderLists = new HashMap<>(); - @SuppressWarnings("unchecked") - public Instancer getInstancer(InstanceType type, Model model, RenderStage stage) { - InstancerKey key = new InstancerKey<>(type, model, stage); - // FIXME: This needs to be synchronized like InstancingEngine - IndirectInstancer instancer = (IndirectInstancer) instancers.get(key); - if (instancer == null) { - instancer = new IndirectInstancer<>(type); - instancers.put(key, instancer); - uninitializedInstancers.add(new UninitializedInstancer(instancer, model, stage)); - } - return instancer; - } - public void flush() { - for (var instancer : uninitializedInstancers) { - add(instancer.instancer(), instancer.model(), instancer.stage()); - } - uninitializedInstancers.clear(); + super.flush(); for (IndirectCullingGroup value : renderLists.values()) { value.beginFrame(); @@ -45,33 +24,11 @@ public class IndirectDrawManager { } public void invalidate() { - instancers.clear(); + super.invalidate(); renderLists.values() .forEach(IndirectCullingGroup::delete); renderLists.clear(); - - initializedInstancers.clear(); - } - - public void clearInstancers() { - initializedInstancers.forEach(IndirectInstancer::clear); - } - - @SuppressWarnings("unchecked") - private void add(IndirectInstancer instancer, Model model, RenderStage stage) { - var meshes = model.getMeshes(); - for (var entry : meshes.entrySet()) { - var material = entry.getKey(); - var mesh = entry.getValue(); - - var indirectList = (IndirectCullingGroup) renderLists.computeIfAbsent(Pair.of(instancer.type, mesh.vertexType()), p -> new IndirectCullingGroup<>(p.first(), p.second())); - - indirectList.add(instancer, stage, material, mesh); - - break; // TODO: support multiple meshes per model - } - initializedInstancers.add(instancer); } public boolean hasStage(RenderStage stage) { @@ -83,6 +40,23 @@ public class IndirectDrawManager { return false; } - private record UninitializedInstancer(IndirectInstancer instancer, Model model, RenderStage stage) { + @Override + protected IndirectInstancer create(InstanceType type) { + return new IndirectInstancer<>(type); + } + + @Override + protected void add(InstancerKey key, IndirectInstancer instancer, Model model, RenderStage stage) { + var meshes = model.getMeshes(); + for (var entry : meshes.entrySet()) { + var material = entry.getKey(); + var mesh = entry.getValue(); + + var indirectList = (IndirectCullingGroup) renderLists.computeIfAbsent(Pair.of(key.type(), mesh.vertexType()), p -> new IndirectCullingGroup<>(p.first(), p.second())); + + indirectList.add((IndirectInstancer) instancer, stage, material, mesh); + + break; // TODO: support multiple meshes per model + } } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectEngine.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectEngine.java index 2977bf8ce..02d7acfb2 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectEngine.java @@ -7,17 +7,15 @@ import org.lwjgl.opengl.GL32; 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.model.Model; import com.jozufozu.flywheel.api.task.Plan; import com.jozufozu.flywheel.api.task.TaskExecutor; 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.gl.GlStateTracker; import com.jozufozu.flywheel.gl.GlTextureUnit; import com.jozufozu.flywheel.lib.task.Flag; import com.jozufozu.flywheel.lib.task.NamedFlag; -import com.jozufozu.flywheel.lib.task.RaisePlan; import com.jozufozu.flywheel.lib.task.SyncedPlan; import com.mojang.blaze3d.systems.RenderSystem; @@ -31,44 +29,37 @@ public class IndirectEngine extends AbstractEngine { super(maxOriginDistance); } - @Override - public Instancer instancer(InstanceType type, Model model, RenderStage stage) { - return drawManager.getInstancer(type, model, stage); - } - @Override public Plan createFramePlan() { - return SyncedPlan.of(this::flushDrawManager) - .then(RaisePlan.raise(flushFlag)); + return SyncedPlan.of(this::flushDrawManager); } private void flushDrawManager() { try (var state = GlStateTracker.getRestoreState()) { drawManager.flush(); } + flushFlag.raise(); } @Override public void renderStage(TaskExecutor executor, RenderContext context, RenderStage stage) { - if (drawManager.hasStage(stage)) { - executor.syncUntil(flushFlag::isRaised); - - try (var restoreState = GlStateTracker.getRestoreState()) { - setup(); - - for (var list : drawManager.renderLists.values()) { - list.submit(stage); - } - } - } - + executor.syncUntil(flushFlag::isRaised); if (stage.isLast()) { - // Need to sync here to ensure this frame has everything executed - // in case we didn't have any stages to draw this frame. - executor.syncUntil(flushFlag::isRaised); flushFlag.lower(); } - } + + if (!drawManager.hasStage(stage)) { + return; + } + + try (var restoreState = GlStateTracker.getRestoreState()) { + setup(); + + for (var list : drawManager.renderLists.values()) { + list.submit(stage); + } + } + } @Override public void renderCrumblingInstances(TaskExecutor executor, RenderContext context, List instances, int progress) { @@ -87,8 +78,8 @@ public class IndirectEngine extends AbstractEngine { } @Override - protected void onRenderOriginChanged() { - drawManager.clearInstancers(); + protected InstancerStorage> getStorage() { + return drawManager; } @Override diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedDrawManager.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedDrawManager.java index 477b6cbe3..728359f25 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedDrawManager.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedDrawManager.java @@ -1,11 +1,9 @@ package com.jozufozu.flywheel.backend.engine.instancing; -import java.util.ArrayList; import java.util.Collection; import java.util.EnumMap; import java.util.HashMap; import java.util.Iterator; -import java.util.List; import java.util.Map; import org.jetbrains.annotations.NotNull; @@ -16,40 +14,14 @@ import com.google.common.collect.ListMultimap; 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.model.Mesh; import com.jozufozu.flywheel.api.model.Model; import com.jozufozu.flywheel.api.vertex.VertexType; import com.jozufozu.flywheel.backend.engine.InstancerKey; +import com.jozufozu.flywheel.backend.engine.InstancerStorage; -public class InstancedDrawManager { - /** - * A map of instancer keys to instancers. - *
- * This map is populated as instancers are requested and contains both initialized and uninitialized instancers. - * Write access to this map must be synchronized on {@link #creationLock}. - *
- * See {@link #getInstancer} for insertion details. - */ - private final Map, InstancedInstancer> instancers = new HashMap<>(); - /** - * A list of instancers that have not yet been initialized. - *
- * All new instancers land here before having resources allocated in {@link #flush}. - * Write access to this list must be synchronized on {@link #creationLock}. - */ - private final List uninitializedInstancers = new ArrayList<>(); - /** - * Mutex for {@link #instancers} and {@link #uninitializedInstancers}. - */ - private final Object creationLock = new Object(); +public class InstancedDrawManager extends InstancerStorage> { - /** - * A map of initialized instancers to their draw calls. - *
- * This map is populated in {@link #flush} and contains only initialized instancers. - */ - private final Map, List> initializedInstancers = new HashMap<>(); /** * The set of draw calls to make in each {@link RenderStage}. */ @@ -64,37 +36,8 @@ public class InstancedDrawManager { return drawSets.getOrDefault(stage, DrawSet.EMPTY); } - @SuppressWarnings("unchecked") - public Instancer getInstancer(InstanceType type, Model model, RenderStage stage) { - InstancerKey key = new InstancerKey<>(type, model, stage); - - InstancedInstancer instancer = (InstancedInstancer) instancers.get(key); - // Happy path: instancer is already initialized. - if (instancer != null) { - return instancer; - } - - // Unhappy path: instancer is not initialized, need to sync to make sure we don't create duplicates. - synchronized (creationLock) { - // Someone else might have initialized it while we were waiting for the lock. - instancer = (InstancedInstancer) instancers.get(key); - if (instancer != null) { - return instancer; - } - - // Create a new instancer and add it to the uninitialized list. - instancer = new InstancedInstancer<>(type); - instancers.put(key, instancer); - uninitializedInstancers.add(new UninitializedInstancer(instancer, model, stage)); - return instancer; - } - } - public void flush() { - for (var instancer : uninitializedInstancers) { - add(instancer.instancer(), instancer.model(), instancer.stage()); - } - uninitializedInstancers.clear(); + super.flush(); for (var pool : meshPools.values()) { pool.flush(); @@ -102,7 +45,7 @@ public class InstancedDrawManager { } public void invalidate() { - instancers.clear(); + super.invalidate(); meshPools.values() .forEach(InstancedMeshPool::delete); @@ -112,23 +55,24 @@ public class InstancedDrawManager { .forEach(DrawSet::delete); drawSets.clear(); - initializedInstancers.keySet() - .forEach(InstancedInstancer::delete); - initializedInstancers.clear(); - eboCache.invalidate(); } - public void clearInstancers() { - initializedInstancers.keySet() - .forEach(InstancedInstancer::clear); + private InstancedMeshPool.BufferedMesh alloc(Mesh mesh) { + return meshPools.computeIfAbsent(mesh.vertexType(), InstancedMeshPool::new) + .alloc(mesh, eboCache); } - private void add(InstancedInstancer instancer, Model model, RenderStage stage) { + @Override + protected InstancedInstancer create(InstanceType type) { + return new InstancedInstancer<>(type); + } + + @Override + protected void add(InstancerKey key, InstancedInstancer instancer, Model model, RenderStage stage) { instancer.init(); DrawSet drawSet = drawSets.computeIfAbsent(stage, DrawSet::new); - List drawCalls = new ArrayList<>(); var meshes = model.getMeshes(); for (var entry : meshes.entrySet()) { @@ -138,18 +82,8 @@ public class InstancedDrawManager { DrawCall drawCall = new DrawCall(instancer, mesh, shaderState); drawSet.put(shaderState, drawCall); - drawCalls.add(drawCall); + instancer.addDrawCall(drawCall); } - initializedInstancers.put(instancer, drawCalls); - } - - private InstancedMeshPool.BufferedMesh alloc(Mesh mesh) { - return meshPools.computeIfAbsent(mesh.vertexType(), InstancedMeshPool::new) - .alloc(mesh, eboCache); - } - - public List drawCallsForInstancer(InstancedInstancer instancer) { - return initializedInstancers.getOrDefault(instancer, List.of()); } public static class DrawSet implements Iterable>> { @@ -187,7 +121,4 @@ public class InstancedDrawManager { .iterator(); } } - - private record UninitializedInstancer(InstancedInstancer instancer, Model model, RenderStage stage) { - } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedInstancer.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedInstancer.java index f34c31d0b..2c9bb89f2 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedInstancer.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedInstancer.java @@ -1,6 +1,8 @@ package com.jozufozu.flywheel.backend.engine.instancing; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; import com.jozufozu.flywheel.Flywheel; @@ -21,6 +23,8 @@ public class InstancedInstancer extends AbstractInstancer private final Set boundTo = new HashSet<>(); private GlBuffer vbo; + private final List drawCalls = new ArrayList<>(); + public InstancedInstancer(InstanceType type) { super(type); instanceFormat = type.getLayout(); @@ -111,4 +115,12 @@ public class InstancedInstancer extends AbstractInstancer vbo.delete(); vbo = null; } + + public void addDrawCall(DrawCall drawCall) { + drawCalls.add(drawCall); + } + + public List drawCalls() { + return drawCalls; + } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java index 18a8e845f..2d28354eb 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancingEngine.java @@ -12,23 +12,20 @@ import com.jozufozu.flywheel.api.context.Context; 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.model.Model; 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.InstanceHandleImpl; +import com.jozufozu.flywheel.backend.engine.InstancerStorage; import com.jozufozu.flywheel.backend.engine.UniformBuffer; -import com.jozufozu.flywheel.backend.engine.indirect.Textures; import com.jozufozu.flywheel.gl.GlStateTracker; import com.jozufozu.flywheel.gl.GlTextureUnit; import com.jozufozu.flywheel.lib.context.Contexts; import com.jozufozu.flywheel.lib.material.MaterialIndices; import com.jozufozu.flywheel.lib.task.Flag; import com.jozufozu.flywheel.lib.task.NamedFlag; -import com.jozufozu.flywheel.lib.task.RaisePlan; import com.jozufozu.flywheel.lib.task.SyncedPlan; import com.mojang.blaze3d.systems.RenderSystem; @@ -36,54 +33,45 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.resources.model.ModelBakery; public class InstancingEngine extends AbstractEngine { - private final Context context; private final InstancedDrawManager drawManager = new InstancedDrawManager(); private final Flag flushFlag = new NamedFlag("flushed"); - public InstancingEngine(int maxOriginDistance, Context context) { + public InstancingEngine(int maxOriginDistance) { super(maxOriginDistance); - this.context = context; - } - - @Override - public Instancer instancer(InstanceType type, Model model, RenderStage stage) { - return drawManager.getInstancer(type, model, stage); - } + } @Override public Plan createFramePlan() { - return SyncedPlan.of(this::flushDrawManager) - .then(RaisePlan.raise(flushFlag)); + return SyncedPlan.of(this::flushDrawManager); } private void flushDrawManager() { try (var restoreState = GlStateTracker.getRestoreState()) { drawManager.flush(); } + flushFlag.raise(); } @Override public void renderStage(TaskExecutor executor, RenderContext context, RenderStage stage) { - var drawSet = drawManager.get(stage); - - if (!drawSet.isEmpty()) { - executor.syncUntil(flushFlag::isRaised); - - try (var state = GlStateTracker.getRestoreState()) { - setup(); - - render(drawSet); - } - } - + executor.syncUntil(flushFlag::isRaised); if (stage.isLast()) { - // Need to sync here to ensure this frame has everything executed - // in case we didn't have any stages to draw this frame. - executor.syncUntil(flushFlag::isRaised); flushFlag.lower(); } - } + + var drawSet = drawManager.get(stage); + + if (drawSet.isEmpty()) { + return; + } + + try (var state = GlStateTracker.getRestoreState()) { + setup(); + + render(drawSet); + } + } @Override public void renderCrumblingInstances(TaskExecutor executor, RenderContext context, List instances, int progress) { @@ -139,7 +127,7 @@ public class InstancingEngine extends AbstractEngine { continue; } - List draws = drawManager.drawCallsForInstancer(instancer); + List draws = instancer.drawCalls(); draws.removeIf(DrawCall::isInvalid); @@ -173,7 +161,7 @@ public class InstancingEngine extends AbstractEngine { continue; } - setup(shader, context); + setup(shader, Contexts.WORLD); shader.material().setup(); @@ -201,8 +189,8 @@ public class InstancingEngine extends AbstractEngine { } @Override - protected void onRenderOriginChanged() { - drawManager.clearInstancers(); + protected InstancerStorage> getStorage() { + return drawManager; } @Override diff --git a/src/main/java/com/jozufozu/flywheel/config/BackendArgument.java b/src/main/java/com/jozufozu/flywheel/config/BackendArgument.java index 3be4a1b35..aae2dc1bf 100644 --- a/src/main/java/com/jozufozu/flywheel/config/BackendArgument.java +++ b/src/main/java/com/jozufozu/flywheel/config/BackendArgument.java @@ -4,21 +4,39 @@ import java.util.Collection; import java.util.List; import java.util.concurrent.CompletableFuture; +import org.jetbrains.annotations.NotNull; + +import com.jozufozu.flywheel.Flywheel; import com.jozufozu.flywheel.api.backend.Backend; +import com.jozufozu.flywheel.lib.util.ResourceUtil; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.minecraft.ResourceLocationException; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; public class BackendArgument implements ArgumentType { - private static final List STRING_IDS = Backend.REGISTRY.getAllIds().stream().map(ResourceLocation::toString).toList(); + private static final List STRING_IDS = Backend.REGISTRY.getAllIds() + .stream() + .map(rl -> { + if (Flywheel.ID + .equals(rl.getNamespace())) { + return rl.getPath(); + } else { + return rl.toString(); + } + }) + .toList(); + + private static final SimpleCommandExceptionType ERROR_INVALID = new SimpleCommandExceptionType(Component.translatable("argument.id.invalid")); public static final DynamicCommandExceptionType ERROR_UNKNOWN_BACKEND = new DynamicCommandExceptionType(arg -> { return Component.literal("Unknown backend '" + arg + "'"); @@ -28,7 +46,7 @@ public class BackendArgument implements ArgumentType { @Override public Backend parse(StringReader reader) throws CommandSyntaxException { - ResourceLocation id = ResourceLocation.read(reader); + ResourceLocation id = getRead(reader); Backend backend = Backend.REGISTRY.get(id); if (backend == null) { @@ -38,6 +56,27 @@ public class BackendArgument implements ArgumentType { return backend; } + /** + * Copied from {@link ResourceLocation#read}, but defaults to flywheel namespace. + */ + @NotNull + private static ResourceLocation getRead(StringReader reader) throws CommandSyntaxException { + int i = reader.getCursor(); + + while(reader.canRead() && ResourceLocation.isAllowedInResourceLocation(reader.peek())) { + reader.skip(); + } + + String s = reader.getString().substring(i, reader.getCursor()); + + try { + return ResourceUtil.defaultToFlywheelNamespace(s); + } catch (ResourceLocationException resourcelocationexception) { + reader.setCursor(i); + throw ERROR_INVALID.createWithContext(reader); + } + } + @Override public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { return SharedSuggestionProvider.suggest(STRING_IDS, builder);