From cdfddba35ae745fcdd5f39924a6e43011b4ec9ee Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Mon, 20 Jun 2022 12:16:37 -0700 Subject: [PATCH] Better draw call organization - InstancingEngine takes over tracking individual draw calls - Many draw calls are associated with a single ShaderState - Each ShaderState will bind one shader program - Make Material a record - Inline Renderable and move InstancedModel.Layer to DrawCall --- .../flywheel/api/material/Material.java | 22 +---- .../instancing/instancing/DrawCall.java | 68 +++++++++++++++ .../instancing/GPUInstancerFactory.java | 45 ++-------- .../instancing/instancing/InstancedModel.java | 87 ++----------------- .../instancing/InstancingEngine.java | 73 ++++++++++------ .../instancing/instancing/RenderLists.java | 49 +++++++++++ .../{Renderable.java => ShaderState.java} | 11 +-- .../core/crumbling/CrumblingRenderer.java | 28 +----- .../flywheel/vanilla/ChestInstance.java | 5 +- 9 files changed, 185 insertions(+), 203 deletions(-) create mode 100644 src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/DrawCall.java create mode 100644 src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/RenderLists.java rename src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/{Renderable.java => ShaderState.java} (54%) diff --git a/src/main/java/com/jozufozu/flywheel/api/material/Material.java b/src/main/java/com/jozufozu/flywheel/api/material/Material.java index 397be99b9..c9b80ef43 100644 --- a/src/main/java/com/jozufozu/flywheel/api/material/Material.java +++ b/src/main/java/com/jozufozu/flywheel/api/material/Material.java @@ -4,26 +4,6 @@ import com.jozufozu.flywheel.core.source.FileResolution; import net.minecraft.client.renderer.RenderType; -public class Material { - protected final RenderType renderType; - protected final FileResolution vertexShader; - protected final FileResolution fragmentShader; +public record Material(RenderType renderType, FileResolution vertexShader, FileResolution fragmentShader) { - public Material(RenderType renderType, FileResolution vertexShader, FileResolution fragmentShader) { - this.renderType = renderType; - this.vertexShader = vertexShader; - this.fragmentShader = fragmentShader; - } - - public RenderType getRenderType() { - return renderType; - } - - public FileResolution getVertexShader() { - return vertexShader; - } - - public FileResolution getFragmentShader() { - return fragmentShader; - } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/DrawCall.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/DrawCall.java new file mode 100644 index 000000000..5ec95fb2c --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/DrawCall.java @@ -0,0 +1,68 @@ +package com.jozufozu.flywheel.backend.instancing.instancing; + +import com.jozufozu.flywheel.api.material.Material; +import com.jozufozu.flywheel.api.vertex.VertexType; +import com.jozufozu.flywheel.backend.gl.GlStateTracker; +import com.jozufozu.flywheel.backend.gl.GlVertexArray; +import com.jozufozu.flywheel.backend.model.MeshPool; +import com.jozufozu.flywheel.core.model.Mesh; + +public class DrawCall { + + final Material material; + private final GPUInstancer instancer; + MeshPool.BufferedMesh bufferedMesh; + GlVertexArray vao; + + DrawCall(GPUInstancer instancer, Material material, Mesh mesh) { + this.instancer = instancer; + this.material = material; + this.vao = new GlVertexArray(); + this.bufferedMesh = MeshPool.getInstance() + .alloc(mesh); + this.instancer.attributeBaseIndex = this.bufferedMesh.getAttributeCount(); + this.vao.enableArrays(this.bufferedMesh.getAttributeCount() + instancer.instanceFormat.getAttributeCount()); + } + + public Material getMaterial() { + return material; + } + + public VertexType getVertexType() { + return bufferedMesh.getVertexType(); + } + + public void render() { + if (invalid()) return; + + try (var ignored = GlStateTracker.getRestoreState()) { + + this.instancer.renderSetup(vao); + + if (this.instancer.glInstanceCount > 0) { + bufferedMesh.drawInstances(vao, this.instancer.glInstanceCount); + } + } + } + + public boolean shouldRemove() { + return invalid(); + } + + /** + * Only {@code true} if the InstancedModel has been destroyed. + */ + private boolean invalid() { + return this.instancer.vbo == null || bufferedMesh == null || vao == null; + } + + public void delete() { + if (invalid()) return; + + vao.delete(); + bufferedMesh.delete(); + + vao = null; + bufferedMesh = null; + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/GPUInstancerFactory.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/GPUInstancerFactory.java index 4fd6f265c..843d91316 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/GPUInstancerFactory.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/GPUInstancerFactory.java @@ -1,15 +1,9 @@ package com.jozufozu.flywheel.backend.instancing.instancing; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; -import java.util.Set; +import java.util.function.Consumer; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.Multimap; import com.jozufozu.flywheel.api.InstancedPart; import com.jozufozu.flywheel.api.Instancer; import com.jozufozu.flywheel.api.InstancerFactory; @@ -26,13 +20,11 @@ public class GPUInstancerFactory implements InstancerFa protected final Map> models = new HashMap<>(); protected final StructType type; + private final Consumer> creationListener; - protected final List> uninitialized = new ArrayList<>(); - - private final ListMultimap renderLists = ArrayListMultimap.create(); - - public GPUInstancerFactory(StructType type) { + public GPUInstancerFactory(StructType type, Consumer> creationListener) { this.type = type; + this.creationListener = creationListener; } @Override @@ -58,7 +50,6 @@ public class GPUInstancerFactory implements InstancerFa public void delete() { models.values().forEach(InstancedModel::delete); models.clear(); - renderLists.clear(); } /** @@ -71,35 +62,9 @@ public class GPUInstancerFactory implements InstancerFa .forEach(GPUInstancer::clear); } - public void init() { - for (var instanced : uninitialized) { - instanced.init(); - - for (Renderable renderable : instanced.getLayers()) { - renderLists.put(renderable.getMaterial() - .getRenderType(), renderable); - } - } - uninitialized.clear(); - } - private InstancedModel createInstancer(ModelSupplier model) { var instancer = new InstancedModel<>(type, model); - uninitialized.add(instancer); + this.creationListener.accept(instancer); return instancer; } - - /** - * Adds all the RenderTypes that this InstancerFactory will render to the given set. - * @param layersToProcess The set of RenderTypes that the InstancingEngine will process. - */ - public void gatherLayers(Set layersToProcess) { - layersToProcess.addAll(renderLists.keySet()); - } - - public List getRenderList(RenderType type) { - var out = renderLists.get(type); - out.removeIf(Renderable::shouldRemove); - return out; - } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedModel.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedModel.java index 50b5e4015..f94f345ef 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedModel.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancedModel.java @@ -1,108 +1,35 @@ package com.jozufozu.flywheel.backend.instancing.instancing; import java.util.List; -import java.util.Map; -import com.google.common.collect.ImmutableMap; import com.jozufozu.flywheel.api.InstancedPart; -import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.struct.StructType; -import com.jozufozu.flywheel.api.vertex.VertexType; -import com.jozufozu.flywheel.backend.gl.GlStateTracker; -import com.jozufozu.flywheel.backend.gl.GlVertexArray; -import com.jozufozu.flywheel.backend.model.MeshPool; -import com.jozufozu.flywheel.core.model.Mesh; import com.jozufozu.flywheel.core.model.ModelSupplier; -import com.jozufozu.flywheel.util.Pair; public class InstancedModel { public final GPUInstancer instancer; public final ModelSupplier model; - private List layers; + private final StructType type; + private List layers; public InstancedModel(StructType type, ModelSupplier model) { this.model = model; this.instancer = new GPUInstancer<>(this, type); + this.type = type; } - public void init() { + public void init(RenderLists renderLists) { instancer.init(); - buildLayers(); - } - - public List getLayers() { - return layers; - } - - private void buildLayers() { layers = model.get() .entrySet() .stream() - .map(entry -> new Layer(entry.getKey(), entry.getValue())) + .map(entry -> new DrawCall(instancer, entry.getKey(), entry.getValue())) .toList(); - } - private class Layer implements Renderable { - - final Material material; - MeshPool.BufferedMesh bufferedMesh; - GlVertexArray vao; - - private Layer(Material material, Mesh mesh) { - this.material = material; - vao = new GlVertexArray(); - bufferedMesh = MeshPool.getInstance() - .alloc(mesh); - instancer.attributeBaseIndex = bufferedMesh.getAttributeCount(); - vao.enableArrays(bufferedMesh.getAttributeCount() + instancer.instanceFormat.getAttributeCount()); - } - - @Override - public Material getMaterial() { - return material; - } - - @Override - public VertexType getVertexType() { - return bufferedMesh.getVertexType(); - } - - @Override - public void render() { - if (invalid()) return; - - try (var ignored = GlStateTracker.getRestoreState()) { - - instancer.renderSetup(vao); - - if (instancer.glInstanceCount > 0) { - bufferedMesh.drawInstances(vao, instancer.glInstanceCount); - } - } - } - - @Override - public boolean shouldRemove() { - return invalid(); - } - - /** - * Only {@code true} if the InstancedModel has been destroyed. - */ - private boolean invalid() { - return instancer.vbo == null || bufferedMesh == null || vao == null; - } - - public void delete() { - if (invalid()) return; - - vao.delete(); - bufferedMesh.delete(); - - vao = null; - bufferedMesh = null; + for (DrawCall layer : layers) { + renderLists.add(new ShaderState(layer.material, layer.getVertexType(), type), layer); } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java index ee34b31f7..7c54d27b4 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/InstancingEngine.java @@ -2,16 +2,16 @@ package com.jozufozu.flywheel.backend.instancing.instancing; import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.SortedSet; import javax.annotation.Nonnull; import org.jetbrains.annotations.NotNull; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Multimaps; import com.jozufozu.flywheel.api.InstancedPart; import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.api.struct.StructType; @@ -34,6 +34,7 @@ import com.jozufozu.flywheel.core.crumbling.CrumblingProgram; import com.jozufozu.flywheel.core.model.Mesh; import com.jozufozu.flywheel.core.model.ModelSupplier; import com.jozufozu.flywheel.core.shader.WorldProgram; +import com.jozufozu.flywheel.core.source.FileResolution; import com.jozufozu.flywheel.core.vertex.Formats; import com.jozufozu.flywheel.mixin.LevelRendererAccessor; import com.jozufozu.flywheel.util.Textures; @@ -65,7 +66,8 @@ public class InstancingEngine

implements Engine { protected final Map, GPUInstancerFactory> factories = new HashMap<>(); - protected final Set layersToProcess = new HashSet<>(); + protected final List> uninitializedModels = new ArrayList<>(); + protected final RenderLists renderLists = new RenderLists(); private final WeakHashSet listeners; private int vertexCount; @@ -81,7 +83,12 @@ public class InstancingEngine

implements Engine { @NotNull @Override public GPUInstancerFactory factory(StructType type) { - return (GPUInstancerFactory) factories.computeIfAbsent(type, GPUInstancerFactory::new); + return (GPUInstancerFactory) factories.computeIfAbsent(type, this::createFactory); + } + + @NotNull + private GPUInstancerFactory createFactory(StructType type) { + return new GPUInstancerFactory<>(type, uninitializedModels::add); } @Override @@ -94,15 +101,14 @@ public class InstancingEngine

implements Engine { var vp = context.viewProjection().copy(); vp.multiplyWithTranslation((float) -camX, (float) -camY, (float) -camZ); - for (RenderType renderType : layersToProcess) { + for (RenderType renderType : renderLists.drainLayers()) { render(renderType, camX, camY, camZ, vp, context.level()); } - layersToProcess.clear(); } @Override public void renderSpecificType(TaskEngine taskEngine, RenderContext context, RenderType type) { - if (!layersToProcess.remove(type)) { + if (!renderLists.process(type)) { return; } @@ -121,37 +127,50 @@ public class InstancingEngine

implements Engine { vertexCount = 0; instanceCount = 0; + var multimap = renderLists.get(type); + + if (multimap.isEmpty()) { + return; + } + + render(type, multimap, camX, camY, camZ, viewProjection, level); + } + + protected void render(RenderType type, ListMultimap multimap, double camX, double camY, double camZ, Matrix4f viewProjection, ClientLevel level) { type.setupRenderState(); Textures.bindActiveTextures(); CoreShaderInfo coreShaderInfo = CoreShaderInfo.get(); - for (var entry : factories.entrySet()) { - var instanceType = entry.getKey(); - var factory = entry.getValue(); + for (var entry : Multimaps.asMap(multimap).entrySet()) { + var shader = entry.getKey(); + var drawCalls = entry.getValue(); - var toRender = factory.getRenderList(type); + drawCalls.removeIf(DrawCall::shouldRemove); - if (toRender.isEmpty()) { + if (drawCalls.isEmpty()) { continue; } - for (Renderable renderable : toRender) { - setup(instanceType, renderable.getMaterial(), coreShaderInfo, camX, camY, camZ, viewProjection, level, renderable.getVertexType()); + setup(shader, coreShaderInfo, camX, camY, camZ, viewProjection, level); - renderable.render(); + for (var drawCall : drawCalls) { + drawCall.render(); } - instanceCount += factory.getInstanceCount(); - vertexCount += factory.getVertexCount(); } type.clearRenderState(); } - protected P setup(StructType instanceType, Material material, CoreShaderInfo coreShaderInfo, double camX, double camY, double camZ, Matrix4f viewProjection, ClientLevel level, VertexType vertexType) { + protected P setup(ShaderState desc, CoreShaderInfo coreShaderInfo, double camX, double camY, double camZ, Matrix4f viewProjection, ClientLevel level) { - P program = context.getProgram(new ProgramCompiler.Context(vertexType, instanceType.getInstanceShader(), - material.getVertexShader(), material.getFragmentShader(), coreShaderInfo.getAdjustedAlphaDiscard(), + VertexType vertexType = desc.vertex(); + FileResolution instanceShader = desc.instance() + .getInstanceShader(); + Material material = desc.material(); + + P program = context.getProgram(new ProgramCompiler.Context(vertexType, instanceShader, + material.vertexShader(), material.fragmentShader(), coreShaderInfo.getAdjustedAlphaDiscard(), coreShaderInfo.fogType(), GameStateRegistry.takeSnapshot())); program.bind(); @@ -190,12 +209,12 @@ public class InstancingEngine

implements Engine { public void beginFrame(Camera info) { checkOriginDistance(info); - - for (GPUInstancerFactory factory : factories.values()) { - factory.init(); - - factory.gatherLayers(layersToProcess); + for (var factory : uninitializedModels) { + factory.init(renderLists); } + uninitializedModels.clear(); + + renderLists.prepare(); MeshPool.getInstance() .flush(); @@ -279,13 +298,13 @@ public class InstancingEngine

implements Engine { continue; } - material.getRenderType().setupRenderState(); + material.renderType().setupRenderState(); CoreShaderInfo coreShaderInfo = CoreShaderInfo.get(); CrumblingProgram program = Contexts.CRUMBLING.getProgram(new ProgramCompiler.Context(Formats.POS_TEX_NORMAL, - structType.getInstanceShader(), material.getVertexShader(), material.getFragmentShader(), + structType.getInstanceShader(), material.vertexShader(), material.fragmentShader(), coreShaderInfo.getAdjustedAlphaDiscard(), coreShaderInfo.fogType(), GameStateRegistry.takeSnapshot())); diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/RenderLists.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/RenderLists.java new file mode 100644 index 000000000..a3f003ed6 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/RenderLists.java @@ -0,0 +1,49 @@ +package com.jozufozu.flywheel.backend.instancing.instancing; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; + +import net.minecraft.client.renderer.RenderType; + +public class RenderLists { + + private Map> renderLists = new HashMap<>(); + public final Set layersToProcess = new HashSet<>(); + + public ListMultimap get(RenderType type) { + return renderLists.computeIfAbsent(type, k -> ArrayListMultimap.create()); + } + + public void add(ShaderState shaderState, DrawCall layer) { + RenderType renderType = shaderState.material() + .renderType(); + + get(renderType).put(shaderState, layer); + } + + public void prepare() { + layersToProcess.clear(); + + layersToProcess.addAll(renderLists.keySet()); + } + + public Iterable drainLayers() { + var out = new HashSet<>(layersToProcess); + layersToProcess.clear(); + return out; + } + + /** + * Check and mark a layer as processed. + * @param type The layer to check. + * @return {@code true} if the layer should be processed. + */ + public boolean process(RenderType type) { + return layersToProcess.remove(type); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/Renderable.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/ShaderState.java similarity index 54% rename from src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/Renderable.java rename to src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/ShaderState.java index 68a7ae84a..70bbdea73 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/Renderable.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/instancing/ShaderState.java @@ -1,15 +1,8 @@ package com.jozufozu.flywheel.backend.instancing.instancing; import com.jozufozu.flywheel.api.material.Material; +import com.jozufozu.flywheel.api.struct.StructType; import com.jozufozu.flywheel.api.vertex.VertexType; -public interface Renderable { - - void render(); - - boolean shouldRemove(); - - Material getMaterial(); - - VertexType getVertexType(); +public record ShaderState(Material material, VertexType vertex, StructType instance) { } diff --git a/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingRenderer.java b/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingRenderer.java index 06692e93b..dd5b8e192 100644 --- a/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingRenderer.java +++ b/src/main/java/com/jozufozu/flywheel/core/crumbling/CrumblingRenderer.java @@ -9,14 +9,11 @@ import com.jozufozu.flywheel.backend.gl.GlStateTracker; import com.jozufozu.flywheel.backend.instancing.InstanceManager; import com.jozufozu.flywheel.backend.instancing.SerialTaskEngine; import com.jozufozu.flywheel.backend.instancing.instancing.InstancingEngine; -import com.jozufozu.flywheel.backend.instancing.instancing.Renderable; import com.jozufozu.flywheel.core.Contexts; -import com.jozufozu.flywheel.core.CoreShaderInfoMap.CoreShaderInfo; import com.jozufozu.flywheel.core.RenderContext; import com.jozufozu.flywheel.event.ReloadRenderersEvent; import com.jozufozu.flywheel.mixin.LevelRendererAccessor; import com.jozufozu.flywheel.util.Lazy; -import com.jozufozu.flywheel.util.Textures; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Matrix4f; @@ -168,30 +165,13 @@ public class CrumblingRenderer { return; } - currentLayer.setupRenderState(); - Textures.bindActiveTextures(); - CoreShaderInfo coreShaderInfo = CoreShaderInfo.get(); - - for (var entry : factories.entrySet()) { - var instanceType = entry.getKey(); - var factory = entry.getValue(); - - var toRender = factory.getRenderList(type); - - if (toRender.isEmpty()) { - continue; - } - - for (var renderable : toRender) { - - setup(instanceType, renderable.getMaterial(), coreShaderInfo, camX, camY, camZ, viewProjection, level, renderable.getVertexType()); - - renderable.render(); - } + var multimap = renderLists.get(type); + if (multimap.isEmpty()) { + return; } - currentLayer.clearRenderState(); + render(currentLayer, multimap, camX, camY, camZ, viewProjection, level); } } } diff --git a/src/main/java/com/jozufozu/flywheel/vanilla/ChestInstance.java b/src/main/java/com/jozufozu/flywheel/vanilla/ChestInstance.java index a03163ec2..d444378ea 100644 --- a/src/main/java/com/jozufozu/flywheel/vanilla/ChestInstance.java +++ b/src/main/java/com/jozufozu/flywheel/vanilla/ChestInstance.java @@ -37,8 +37,9 @@ import net.minecraft.world.level.block.state.properties.ChestType; public class ChestInstance extends BlockEntityInstance implements DynamicInstance { - private static final BiFunction LID = Util.memoize((type, mat) -> new BasicModelSupplier(() -> createLidModel(type, mat.sprite()), new com.jozufozu.flywheel.api.material.Material(Sheets.chestSheet(), MaterialShaders.SHADED_VERTEX, MaterialShaders.DEFAULT_FRAGMENT))); - private static final BiFunction BASE = Util.memoize((type, mat) -> new BasicModelSupplier(() -> createBaseModel(type, mat.sprite()), new com.jozufozu.flywheel.api.material.Material(Sheets.chestSheet(), MaterialShaders.SHADED_VERTEX, MaterialShaders.DEFAULT_FRAGMENT))); + private static final com.jozufozu.flywheel.api.material.Material CHEST_MATERIAL = new com.jozufozu.flywheel.api.material.Material(Sheets.chestSheet(), MaterialShaders.SHADED_VERTEX, MaterialShaders.DEFAULT_FRAGMENT); + private static final BiFunction LID = Util.memoize((type, mat) -> new BasicModelSupplier(() -> createLidModel(type, mat.sprite()), CHEST_MATERIAL)); + private static final BiFunction BASE = Util.memoize((type, mat) -> new BasicModelSupplier(() -> createBaseModel(type, mat.sprite()), CHEST_MATERIAL)); private final OrientedPart body; private final TransformedPart lid;