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
This commit is contained in:
Jozufozu 2023-03-29 14:11:29 -07:00
parent 671e1a002c
commit 6b9b2a9927
5 changed files with 86 additions and 29 deletions

View file

@ -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;

View file

@ -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<T extends InstancedPart> {
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<T extends InstancedPart> {
int baseInstance = 0;
for (var batch : drawSet.indirectDraws) {
batch.prepare(baseInstance);
baseInstance += batch.instancer.instanceCount;
baseInstance += batch.instancer().instanceCount;
}
return baseInstance;
}

View file

@ -8,10 +8,10 @@ import com.jozufozu.flywheel.api.material.Material;
import com.jozufozu.flywheel.core.ComponentRegistry;
public final class IndirectDraw<T extends InstancedPart> {
final IndirectInstancer<T> instancer;
final IndirectMeshPool.BufferedMesh mesh;
final Material material;
final RenderStage stage;
private final IndirectInstancer<T> 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<T extends InstancedPart> {
MemoryUtil.memPutInt(ptr + 40, fragmentMaterialID); // fragmentMaterialID
}
public IndirectInstancer<T> instancer() {
return instancer;
}
public IndirectMeshPool.BufferedMesh mesh() {
return mesh;
}
public Material material() {
return material;
}
public RenderStage stage() {
return stage;
}
}

View file

@ -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<T extends InstancedPart> {
final List<IndirectDraw<T>> indirectDraws = new ArrayList<>();
final Map<RenderStage, List<MultiDraw>> multiDraws = new EnumMap<>(RenderStage.class);
public boolean isEmpty() {
return indirectDraws.isEmpty();
}
@ -26,31 +32,52 @@ public class IndirectDrawSet<T extends InstancedPart> {
public void add(IndirectInstancer<T> 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<T>::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();
}
}
}

View file

@ -83,14 +83,18 @@ public class ComponentRegistry {
public static class MaterialRegistry {
private final Set<Material> materials = new HashSet<>();
private final List<Material> materialsOrdered = new ArrayList<>();
private final MaterialSources vertexSources = new MaterialSources();
private final MaterialSources fragmentSources = new MaterialSources();
public <T extends Material> 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<ResourceLocation> registered = new HashSet<>();
private final List<ResourceLocation> orderedSources = new ArrayList<>();