From 6b9b2a9927175f8625a9d6efd848758f9e64f56f Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Wed, 29 Mar 2023 14:11:29 -0700 Subject: [PATCH] So many draws - Sort draws in an IndirectDrawSet by RenderStage and Material - Loop through ahead of time and determine what individual draws can be grouped together into a MultiDraw - Contiguous segments with the same RenderStage and Material can be merged into one MultiDraw call - Add material ID lookup for sorting purposes --- .../backend/instancing/InstanceManager.java | 1 - .../indirect/IndirectCullingGroup.java | 13 +++- .../instancing/indirect/IndirectDraw.java | 24 ++++++-- .../instancing/indirect/IndirectDrawSet.java | 61 +++++++++++++------ .../flywheel/core/ComponentRegistry.java | 16 +++-- 5 files changed, 86 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java index 7cce94249..b38db6b34 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/InstanceManager.java @@ -2,7 +2,6 @@ package com.jozufozu.flywheel.backend.instancing; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectCullingGroup.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectCullingGroup.java index 40238c5c8..1e042eaff 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectCullingGroup.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectCullingGroup.java @@ -3,7 +3,13 @@ package com.jozufozu.flywheel.backend.instancing.indirect; import static org.lwjgl.opengl.GL42.GL_COMMAND_BARRIER_BIT; import static org.lwjgl.opengl.GL42.glMemoryBarrier; import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BARRIER_BIT; -import static org.lwjgl.opengl.GL46.*; +import static org.lwjgl.opengl.GL46.glBindVertexArray; +import static org.lwjgl.opengl.GL46.glCreateVertexArrays; +import static org.lwjgl.opengl.GL46.glDeleteVertexArrays; +import static org.lwjgl.opengl.GL46.glDispatchCompute; +import static org.lwjgl.opengl.GL46.glEnableVertexArrayAttrib; +import static org.lwjgl.opengl.GL46.glVertexArrayElementBuffer; +import static org.lwjgl.opengl.GL46.glVertexArrayVertexBuffer; import com.jozufozu.flywheel.api.RenderStage; import com.jozufozu.flywheel.api.instancer.InstancedPart; @@ -142,7 +148,8 @@ public class IndirectCullingGroup { for (int i = 0, batchesSize = drawSet.indirectDraws.size(); i < batchesSize; i++) { var batch = drawSet.indirectDraws.get(i); - var instanceCount = batch.instancer.getInstanceCount(); + var instanceCount = batch.instancer() + .getInstanceCount(); batch.writeObjects(objectPtr, batchIDPtr, i); objectPtr += instanceCount * objectStride; @@ -166,7 +173,7 @@ public class IndirectCullingGroup { int baseInstance = 0; for (var batch : drawSet.indirectDraws) { batch.prepare(baseInstance); - baseInstance += batch.instancer.instanceCount; + baseInstance += batch.instancer().instanceCount; } return baseInstance; } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDraw.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDraw.java index 6cb31ac81..ce7d7f099 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDraw.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDraw.java @@ -8,10 +8,10 @@ import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.core.ComponentRegistry; public final class IndirectDraw { - final IndirectInstancer instancer; - final IndirectMeshPool.BufferedMesh mesh; - final Material material; - final RenderStage stage; + private final IndirectInstancer instancer; + private final IndirectMeshPool.BufferedMesh mesh; + private final Material material; + private final RenderStage stage; int baseInstance = -1; final int vertexMaterialID; @@ -62,4 +62,20 @@ public final class IndirectDraw { MemoryUtil.memPutInt(ptr + 40, fragmentMaterialID); // fragmentMaterialID } + + public IndirectInstancer instancer() { + return instancer; + } + + public IndirectMeshPool.BufferedMesh mesh() { + return mesh; + } + + public Material material() { + return material; + } + + public RenderStage stage() { + return stage; + } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDrawSet.java b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDrawSet.java index 46f9d65cd..c5715ea84 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDrawSet.java +++ b/src/main/java/com/jozufozu/flywheel/backend/instancing/indirect/IndirectDrawSet.java @@ -5,17 +5,23 @@ import static org.lwjgl.opengl.GL11.GL_UNSIGNED_INT; import static org.lwjgl.opengl.GL43.glMultiDrawElementsIndirect; import java.util.ArrayList; +import java.util.Comparator; +import java.util.EnumMap; import java.util.List; +import java.util.Map; import com.jozufozu.flywheel.api.RenderStage; import com.jozufozu.flywheel.api.instancer.InstancedPart; import com.jozufozu.flywheel.api.material.Material; +import com.jozufozu.flywheel.core.ComponentRegistry; import com.jozufozu.flywheel.util.Textures; public class IndirectDrawSet { final List> indirectDraws = new ArrayList<>(); + final Map> multiDraws = new EnumMap<>(RenderStage.class); + public boolean isEmpty() { return indirectDraws.isEmpty(); } @@ -26,31 +32,52 @@ public class IndirectDrawSet { public void add(IndirectInstancer instancer, Material material, RenderStage stage, IndirectMeshPool.BufferedMesh bufferedMesh) { indirectDraws.add(new IndirectDraw<>(instancer, material, stage, bufferedMesh)); + determineMultiDraws(); } public void submit(RenderStage stage) { - final int stride = (int) IndirectBuffers.DRAW_COMMAND_STRIDE; - for (int i = 0, indirectDrawsSize = indirectDraws.size(); i < indirectDrawsSize; i++) { - var batch = indirectDraws.get(i); - if (batch.stage != stage) { - continue; - } + if (!multiDraws.containsKey(stage)) { + return; + } - var material = batch.material; - material.setup(); - Textures.bindActiveTextures(); - glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, i * stride, 1, stride); - material.clear(); + for (var multiDraw : multiDraws.get(stage)) { + multiDraw.submit(); + } + } + + public void determineMultiDraws() { + // TODO: Better material equality. Really we only need to bin by the results of the setup method. + multiDraws.clear(); + // sort by stage, then material + indirectDraws.sort(Comparator.comparing(IndirectDraw::stage) + .thenComparing(draw -> ComponentRegistry.materials.getMaterialID(draw.material()))); + + for (int start = 0, i = 0; i < indirectDraws.size(); i++) { + var draw = indirectDraws.get(i); + var material = draw.material(); + var stage = draw.stage(); + + // if the next draw call has a different RenderStage or Material, start a new MultiDraw + if (i == indirectDraws.size() - 1 || stage != indirectDraws.get(i + 1) + .stage() || !material.equals(indirectDraws.get(i + 1) + .material())) { + multiDraws.computeIfAbsent(stage, s -> new ArrayList<>()) + .add(new MultiDraw(material, start, i + 1)); + start = i + 1; + } } } public boolean contains(RenderStage stage) { - for (var draw : indirectDraws) { - if (draw.stage == stage) { - return true; - } - } + return multiDraws.containsKey(stage); + } - return false; + private record MultiDraw(Material material, int start, int end) { + void submit() { + material.setup(); + Textures.bindActiveTextures(); + glMultiDrawElementsIndirect(GL_TRIANGLES, GL_UNSIGNED_INT, start * IndirectBuffers.DRAW_COMMAND_STRIDE, end - start, (int) IndirectBuffers.DRAW_COMMAND_STRIDE); + material.clear(); + } } } diff --git a/src/main/java/com/jozufozu/flywheel/core/ComponentRegistry.java b/src/main/java/com/jozufozu/flywheel/core/ComponentRegistry.java index 8a05cea34..a46ec70bc 100644 --- a/src/main/java/com/jozufozu/flywheel/core/ComponentRegistry.java +++ b/src/main/java/com/jozufozu/flywheel/core/ComponentRegistry.java @@ -83,14 +83,18 @@ public class ComponentRegistry { public static class MaterialRegistry { private final Set materials = new HashSet<>(); + private final List materialsOrdered = new ArrayList<>(); private final MaterialSources vertexSources = new MaterialSources(); private final MaterialSources fragmentSources = new MaterialSources(); public T add(T material) { - materials.add(material); - - vertexSources.register(material.vertexShader()); - fragmentSources.register(material.fragmentShader()); + if (materials.add(material)) { + materialsOrdered.add(material); + vertexSources.register(material.vertexShader()); + fragmentSources.register(material.fragmentShader()); + } else { + throw new IllegalArgumentException("Material already registered: " + material); + } return material; } @@ -117,6 +121,10 @@ public class ComponentRegistry { return fragmentSources.orderedSources.indexOf(material.fragmentShader()); } + public int getMaterialID(Material material) { + return materialsOrdered.indexOf(material); + } + private static class MaterialSources { private final Set registered = new HashSet<>(); private final List orderedSources = new ArrayList<>();