From 068692b4b987aafb58d8f03016a24edf777c9259 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Mon, 19 Feb 2024 16:51:11 -0600 Subject: [PATCH] Finding some common ground - Make MeshPool common between instancing and indirect - Remove empty instancers and delete meshes on indirect - Inline IndirectModel into IndirectInstancer since it was basically just a thin wrapper - Share one meshpool between all culling groups - Always upload draw commands because the mesh may have moved - No longer need to set the base instance in the apply shader --- .../backend/engine/AbstractInstancer.java | 8 +- .../flywheel/backend/engine/MeshPool.java | 249 ++++++++++++++++++ .../engine/indirect/IndirectCullingGroup.java | 96 +++---- .../backend/engine/indirect/IndirectDraw.java | 32 ++- .../engine/indirect/IndirectDrawManager.java | 33 ++- .../engine/indirect/IndirectInstancer.java | 33 ++- .../engine/indirect/IndirectMeshPool.java | 182 ------------- .../engine/indirect/IndirectModel.java | 40 --- .../backend/engine/instancing/DrawCall.java | 9 +- .../instancing/InstancedDrawManager.java | 8 +- .../engine/instancing/InstancedMeshPool.java | 205 -------------- .../flywheel/internal/indirect/apply.glsl | 1 - 12 files changed, 388 insertions(+), 508 deletions(-) create mode 100644 src/main/java/com/jozufozu/flywheel/backend/engine/MeshPool.java delete mode 100644 src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectMeshPool.java delete mode 100644 src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectModel.java delete mode 100644 src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedMeshPool.java 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 37efbcf32..4772766a3 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/AbstractInstancer.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/AbstractInstancer.java @@ -81,19 +81,19 @@ public abstract class AbstractInstancer implements Instancer changed.set(handle.index); } - public int getInstanceCount() { + public int instanceCount() { return instances.size(); } public void notifyDirty(int index) { - if (index < 0 || index >= getInstanceCount()) { + if (index < 0 || index >= instanceCount()) { return; } changed.set(index); } public void notifyRemoval(int index) { - if (index < 0 || index >= getInstanceCount()) { + if (index < 0 || index >= instanceCount()) { return; } deleted.set(index); @@ -159,6 +159,6 @@ public abstract class AbstractInstancer implements Instancer @Override public String toString() { - return "AbstractInstancer[" + getInstanceCount() + ']'; + return "AbstractInstancer[" + instanceCount() + ']'; } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/MeshPool.java b/src/main/java/com/jozufozu/flywheel/backend/engine/MeshPool.java new file mode 100644 index 000000000..1775b89d3 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/MeshPool.java @@ -0,0 +1,249 @@ +package com.jozufozu.flywheel.backend.engine; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jetbrains.annotations.Nullable; +import org.lwjgl.opengl.GL32; + +import com.jozufozu.flywheel.api.model.IndexSequence; +import com.jozufozu.flywheel.api.model.Mesh; +import com.jozufozu.flywheel.api.vertex.VertexView; +import com.jozufozu.flywheel.backend.InternalVertex; +import com.jozufozu.flywheel.backend.gl.GlNumericType; +import com.jozufozu.flywheel.backend.gl.GlPrimitive; +import com.jozufozu.flywheel.backend.gl.array.GlVertexArray; +import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer; +import com.jozufozu.flywheel.lib.memory.MemoryBlock; + +import it.unimi.dsi.fastutil.objects.Reference2IntMap; +import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceArraySet; + +public class MeshPool { + private final VertexView vertexView; + private final Map meshes = new HashMap<>(); + private final List meshList = new ArrayList<>(); + + private final GlBuffer vbo; + private final GlBuffer ebo; + + private boolean dirty; + private boolean anyToRemove; + + /** + * Create a new mesh pool. + */ + public MeshPool() { + vertexView = InternalVertex.createVertexView(); + vbo = new GlBuffer(); + ebo = new GlBuffer(); + } + + /** + * Allocate a model in the arena. + * + * @param mesh The model to allocate. + * @return A handle to the allocated model. + */ + public BufferedMesh alloc(Mesh mesh) { + return meshes.computeIfAbsent(mesh, this::_alloc); + } + + private BufferedMesh _alloc(Mesh m) { + BufferedMesh bufferedModel = new BufferedMesh(m); + meshList.add(bufferedModel); + + dirty = true; + return bufferedModel; + } + + @Nullable + public BufferedMesh get(Mesh mesh) { + return meshes.get(mesh); + } + + public void flush() { + if (!dirty) { + return; + } + + if (anyToRemove) { + anyToRemove = false; + processDeletions(); + } + + uploadAll(); + dirty = false; + } + + private void processDeletions() { + // remove deleted meshes + meshList.removeIf(bufferedMesh -> { + boolean deleted = bufferedMesh.deleted(); + if (deleted) { + meshes.remove(bufferedMesh.mesh); + } + return deleted; + }); + } + + private void uploadAll() { + long neededSize = 0; + Reference2IntMap indexCounts = new Reference2IntOpenHashMap<>(); + indexCounts.defaultReturnValue(0); + + for (BufferedMesh mesh : meshList) { + neededSize += mesh.byteSize(); + + int count = indexCounts.getInt(mesh.mesh.indexSequence()); + int newCount = Math.max(count, mesh.indexCount()); + + if (newCount > count) { + indexCounts.put(mesh.mesh.indexSequence(), newCount); + } + } + + long totalIndexCount = 0; + + for (int count : indexCounts.values()) { + totalIndexCount += count; + } + + final var indexBlock = MemoryBlock.malloc(totalIndexCount * GlNumericType.UINT.byteWidth()); + final long indexPtr = indexBlock.ptr(); + + Reference2IntMap firstIndices = new Reference2IntOpenHashMap<>(); + + int firstIndex = 0; + for (Reference2IntMap.Entry entries : indexCounts.reference2IntEntrySet()) { + var indexSequence = entries.getKey(); + var indexCount = entries.getIntValue(); + + firstIndices.put(indexSequence, firstIndex); + + indexSequence.fill(indexPtr + (long) firstIndex * GlNumericType.UINT.byteWidth(), indexCount); + + firstIndex += indexCount; + } + + final var vertexBlock = MemoryBlock.malloc(neededSize); + final long vertexPtr = vertexBlock.ptr(); + + int byteIndex = 0; + int baseVertex = 0; + for (BufferedMesh mesh : meshList) { + mesh.byteIndex = byteIndex; + mesh.baseVertex = baseVertex; + + mesh.write(vertexPtr, vertexView); + + byteIndex += mesh.byteSize(); + baseVertex += mesh.vertexCount(); + + mesh.firstIndex = firstIndices.getInt(mesh.mesh.indexSequence()); + } + + vbo.upload(vertexBlock); + ebo.upload(indexBlock); + + vertexBlock.free(); + indexBlock.free(); + } + + public void bind(GlVertexArray vertexArray) { + vertexArray.setElementBuffer(ebo.handle()); + vertexArray.bindVertexBuffer(0, vbo.handle(), 0, InternalVertex.STRIDE); + vertexArray.bindAttributes(0, 0, InternalVertex.ATTRIBUTES); + } + + public void delete() { + vbo.delete(); + ebo.delete(); + meshes.clear(); + meshList.clear(); + } + + public class BufferedMesh { + private final Mesh mesh; + private final int vertexCount; + private final int byteSize; + + private long byteIndex; + private int baseVertex; + private int firstIndex; + + private int referenceCount = 0; + private final Set boundTo = new ReferenceArraySet<>(); + + private BufferedMesh(Mesh mesh) { + this.mesh = mesh; + vertexCount = mesh.vertexCount(); + byteSize = vertexCount * InternalVertex.STRIDE; + } + + public int vertexCount() { + return vertexCount; + } + + public int byteSize() { + return byteSize; + } + + public int indexCount() { + return mesh.indexCount(); + } + + public int baseVertex() { + return baseVertex; + } + + public int firstIndex() { + return firstIndex; + } + + public boolean deleted() { + return referenceCount <= 0; + } + + public boolean invalid() { + return mesh.vertexCount() == 0 || deleted() || byteIndex == -1; + } + + private void write(long ptr, VertexView vertexView) { + vertexView.ptr(ptr + byteIndex); + vertexView.vertexCount(vertexCount); + mesh.write(vertexView); + } + + public void draw(int instanceCount) { + if (instanceCount > 1) { + GL32.glDrawElementsInstanced(GlPrimitive.TRIANGLES.glEnum, mesh.indexCount(), GL32.GL_UNSIGNED_INT, firstIndex, instanceCount); + } else { + GL32.glDrawElements(GlPrimitive.TRIANGLES.glEnum, mesh.indexCount(), GL32.GL_UNSIGNED_INT, firstIndex); + } + } + + public void setup(GlVertexArray vao) { + if (boundTo.add(vao)) { + vao.setElementBuffer(MeshPool.this.ebo.handle()); + vao.bindVertexBuffer(0, MeshPool.this.vbo.handle(), byteIndex, InternalVertex.STRIDE); + vao.bindAttributes(0, 0, InternalVertex.ATTRIBUTES); + } + } + + public void acquire() { + referenceCount++; + } + + public void drop() { + if (--referenceCount == 0) { + MeshPool.this.dirty = true; + MeshPool.this.anyToRemove = true; + } + } + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java index 730b43fcd..7dac156c3 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java @@ -25,6 +25,7 @@ import com.jozufozu.flywheel.api.model.Mesh; import com.jozufozu.flywheel.api.model.Model; import com.jozufozu.flywheel.backend.compile.IndirectPrograms; import com.jozufozu.flywheel.backend.engine.MaterialRenderState; +import com.jozufozu.flywheel.backend.engine.MeshPool; import com.jozufozu.flywheel.backend.engine.textures.TextureBinder; import com.jozufozu.flywheel.backend.engine.textures.TextureSourceImpl; import com.jozufozu.flywheel.backend.engine.uniform.Uniforms; @@ -43,8 +44,7 @@ public class IndirectCullingGroup { private final Context context; private final long objectStride; private final IndirectBuffers buffers; - private final IndirectMeshPool meshPool; - private final List indirectModels = new ArrayList<>(); + private final List> instancers = new ArrayList<>(); private final List indirectDraws = new ArrayList<>(); private final Map> multiDraws = new EnumMap<>(RenderStage.class); @@ -54,7 +54,7 @@ public class IndirectCullingGroup { private final GlProgram drawProgram; private boolean needsDrawBarrier; - private boolean hasNewDraws; + private boolean needsDrawSort; private int instanceCountThisFrame; IndirectCullingGroup(InstanceType instanceType, Context context, IndirectPrograms programs) { @@ -63,7 +63,6 @@ public class IndirectCullingGroup { objectStride = instanceType.layout() .byteSize() + IndirectBuffers.INT_SIZE; buffers = new IndirectBuffers(objectStride); - meshPool = new IndirectMeshPool(); this.programs = programs; // TODO: Culling programs need to be context aware. @@ -72,18 +71,40 @@ public class IndirectCullingGroup { drawProgram = programs.getIndirectProgram(instanceType, context.contextShader()); } - public void flush(StagingBuffer stagingBuffer) { - needsDrawBarrier = true; - instanceCountThisFrame = prepareModels(); + public void flushInstancers() { + instanceCountThisFrame = 0; + int modelIndex = 0; + for (var iterator = instancers.iterator(); iterator.hasNext(); ) { + var instancer = iterator.next(); + instancer.update(); + var instanceCount = instancer.instanceCount(); + if (instanceCount == 0) { + iterator.remove(); + for (IndirectDraw draw : instancer.draws()) { + draw.delete(); + } + continue; + } + + instancer.index = modelIndex; + instancer.baseInstance = instanceCountThisFrame; + instanceCountThisFrame += instanceCount; + + modelIndex++; + } + + if (indirectDraws.removeIf(IndirectDraw::deleted)) { + needsDrawSort = true; + } + } + + public void upload(StagingBuffer stagingBuffer) { if (nothingToDo()) { return; } - buffers.updateCounts(instanceCountThisFrame, indirectModels.size(), indirectDraws.size()); - - // Must flush the mesh pool first so everything else has the right baseVertex and baseIndex. - meshPool.flush(); + buffers.updateCounts(instanceCountThisFrame, instancers.size(), indirectDraws.size()); // Upload only objects that have changed. uploadObjects(stagingBuffer); @@ -91,14 +112,14 @@ public class IndirectCullingGroup { // We need to upload the models every frame to reset the instance count. uploadModels(stagingBuffer); - if (hasNewDraws) { + if (needsDrawSort) { sortDraws(); - // Draws, however, only need to be updated when we get new ones. - // The instanceCount and baseInstance will be updated by the applyProgram, - // and all other fields are constant to the lifetime of the draw. - uploadDraws(stagingBuffer); - hasNewDraws = false; + needsDrawSort = false; } + + uploadDraws(stagingBuffer); + + needsDrawBarrier = true; } public void dispatchCull() { @@ -132,18 +153,6 @@ public class IndirectCullingGroup { return nothingToDo() || !multiDraws.containsKey(stage); } - /** - * @return the total instance count - */ - private int prepareModels() { - int baseInstance = 0; - for (var model : indirectModels) { - model.prepare(baseInstance); - baseInstance += model.instancer.getInstanceCount(); - } - return baseInstance; - } - private void sortDraws() { multiDraws.clear(); // sort by stage, then material @@ -169,20 +178,18 @@ public class IndirectCullingGroup { return multiDraws.containsKey(stage); } - public void add(IndirectInstancer instancer, Model model, RenderStage stage) { - int modelIndex = indirectModels.size(); - instancer.setModelIndex(modelIndex); - var indirectModel = new IndirectModel(instancer, modelIndex, model.boundingSphere()); - indirectModels.add(indirectModel); + public void add(IndirectInstancer instancer, Model model, RenderStage stage, MeshPool meshPool) { + instancer.index = instancers.size(); + instancers.add(instancer); for (Map.Entry entry : model.meshes().entrySet()) { - IndirectMeshPool.BufferedMesh bufferedMesh = meshPool.alloc(entry.getValue()); - var draw = new IndirectDraw(indirectModel, entry.getKey(), bufferedMesh, stage); + MeshPool.BufferedMesh bufferedMesh = meshPool.alloc(entry.getValue()); + var draw = new IndirectDraw(instancer, entry.getKey(), bufferedMesh, stage); indirectDraws.add(draw); instancer.addDraw(draw); } - hasNewDraws = true; + needsDrawSort = true; } public void submit(RenderStage stage, TextureSourceImpl textures) { @@ -190,9 +197,7 @@ public class IndirectCullingGroup { return; } - Uniforms.bindForDraw(); drawProgram.bind(); - meshPool.bindForDraw(); buffers.bindForDraw(); drawBarrier(); @@ -215,8 +220,6 @@ public class IndirectCullingGroup { program.bind(); - Uniforms.bindForDraw(); - meshPool.bindForDraw(); buffers.bindForCrumbling(); drawBarrier(); @@ -236,8 +239,8 @@ public class IndirectCullingGroup { private void uploadObjects(StagingBuffer stagingBuffer) { long pos = 0; - for (IndirectModel model : indirectModels) { - var instanceCount = model.instancer.getInstanceCount(); + for (var model : instancers) { + var instanceCount = model.instanceCount(); model.uploadObjects(stagingBuffer, pos, buffers.object.handle()); pos += instanceCount * objectStride; @@ -245,7 +248,7 @@ public class IndirectCullingGroup { } private void uploadModels(StagingBuffer stagingBuffer) { - var totalSize = indirectModels.size() * IndirectBuffers.MODEL_STRIDE; + var totalSize = instancers.size() * IndirectBuffers.MODEL_STRIDE; var handle = buffers.model.handle(); stagingBuffer.enqueueCopy(totalSize, handle, 0, this::writeModels); @@ -259,8 +262,8 @@ public class IndirectCullingGroup { } private void writeModels(long writePtr) { - for (var model : indirectModels) { - model.write(writePtr); + for (var model : instancers) { + model.writeModel(writePtr); writePtr += IndirectBuffers.MODEL_STRIDE; } } @@ -274,7 +277,6 @@ public class IndirectCullingGroup { public void delete() { buffers.delete(); - meshPool.delete(); } private record MultiDraw(Material material, int start, int end) { diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDraw.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDraw.java index ca2ffeb99..f9fdbbd06 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDraw.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectDraw.java @@ -6,35 +6,43 @@ import com.jozufozu.flywheel.api.event.RenderStage; import com.jozufozu.flywheel.api.material.Material; import com.jozufozu.flywheel.backend.ShaderIndices; import com.jozufozu.flywheel.backend.engine.MaterialEncoder; +import com.jozufozu.flywheel.backend.engine.MeshPool; public class IndirectDraw { - private final IndirectModel model; + private final IndirectInstancer model; private final Material material; - private final IndirectMeshPool.BufferedMesh mesh; + private final MeshPool.BufferedMesh mesh; private final RenderStage stage; private final int materialVertexIndex; private final int materialFragmentIndex; private final int packedFogAndCutout; private final int packedMaterialProperties; + private boolean deleted; - public IndirectDraw(IndirectModel model, Material material, IndirectMeshPool.BufferedMesh mesh, RenderStage stage) { + public IndirectDraw(IndirectInstancer model, Material material, MeshPool.BufferedMesh mesh, RenderStage stage) { this.model = model; this.material = material; this.mesh = mesh; this.stage = stage; + mesh.acquire(); + this.materialVertexIndex = ShaderIndices.getVertexShaderIndex(material.shaders()); this.materialFragmentIndex = ShaderIndices.getFragmentShaderIndex(material.shaders()); this.packedFogAndCutout = MaterialEncoder.packFogAndCutout(material); this.packedMaterialProperties = MaterialEncoder.packProperties(material); } + public boolean deleted() { + return deleted; + } + public Material material() { return material; } - public IndirectMeshPool.BufferedMesh mesh() { + public MeshPool.BufferedMesh mesh() { return mesh; } @@ -47,9 +55,9 @@ public class IndirectDraw { MemoryUtil.memPutInt(ptr + 4, 0); // instanceCount - to be set by the apply shader MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex - MemoryUtil.memPutInt(ptr + 16, 0); // baseInstance - to be set by the apply shader + MemoryUtil.memPutInt(ptr + 16, model.baseInstance); // baseInstance - MemoryUtil.memPutInt(ptr + 20, model.index); // modelIndex - never changes + MemoryUtil.memPutInt(ptr + 20, model.index); // modelIndex MemoryUtil.memPutInt(ptr + 24, materialVertexIndex); // materialVertexIndex MemoryUtil.memPutInt(ptr + 28, materialFragmentIndex); // materialFragmentIndex @@ -62,7 +70,7 @@ public class IndirectDraw { MemoryUtil.memPutInt(ptr + 4, 1); // instanceCount - only drawing one instance MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex - MemoryUtil.memPutInt(ptr + 16, model.baseInstance() + instanceIndex); // baseInstance + MemoryUtil.memPutInt(ptr + 16, model.baseInstance + instanceIndex); // baseInstance MemoryUtil.memPutInt(ptr + 20, model.index); // modelIndex @@ -71,4 +79,14 @@ public class IndirectDraw { MemoryUtil.memPutInt(ptr + 32, MaterialEncoder.packFogAndCutout(materialOverride)); // packedFogAndCutout MemoryUtil.memPutInt(ptr + 36, MaterialEncoder.packProperties(materialOverride)); // packedMaterialProperties } + + public void delete() { + if (deleted) { + return; + } + + mesh.drop(); + + deleted = true; + } } 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 0ad8985e0..30c8d9e9c 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 @@ -22,9 +22,12 @@ 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.textures.TextureBinder; import com.jozufozu.flywheel.backend.engine.textures.TextureSourceImpl; +import com.jozufozu.flywheel.backend.engine.uniform.Uniforms; import com.jozufozu.flywheel.backend.gl.GlStateTracker; +import com.jozufozu.flywheel.backend.gl.array.GlVertexArray; import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer; import com.jozufozu.flywheel.backend.gl.buffer.GlBufferType; import com.jozufozu.flywheel.lib.context.ContextShaders; @@ -40,6 +43,8 @@ import net.minecraft.client.resources.model.ModelBakery; public class IndirectDrawManager extends InstancerStorage> { private final IndirectPrograms programs; private final StagingBuffer stagingBuffer; + private final MeshPool meshPool; + private final GlVertexArray vertexArray; private final TextureSourceImpl textures = new TextureSourceImpl(); private final Map, IndirectCullingGroup> cullingGroups = new HashMap<>(); private final GlBuffer crumblingDrawBuffer = new GlBuffer(); @@ -47,11 +52,17 @@ public class IndirectDrawManager extends InstancerStorage> public IndirectDrawManager(IndirectPrograms programs) { this.programs = programs; stagingBuffer = new StagingBuffer(this.programs); + + meshPool = new MeshPool(); + + vertexArray = GlVertexArray.create(); + + meshPool.bind(vertexArray); } @Override protected IndirectInstancer create(InstancerKey key) { - return new IndirectInstancer<>(key.type(), key.context()); + return new IndirectInstancer<>(key.type(), key.context(), key.model()); } @SuppressWarnings("unchecked") @@ -59,7 +70,7 @@ public class IndirectDrawManager extends InstancerStorage> protected void initialize(InstancerKey key, IndirectInstancer instancer) { var groupKey = new GroupKey<>(key.type(), key.context()); var group = (IndirectCullingGroup) cullingGroups.computeIfAbsent(groupKey, t -> new IndirectCullingGroup<>(t.type, t.context, programs)); - group.add((IndirectInstancer) instancer, key.model(), key.stage()); + group.add((IndirectInstancer) instancer, key.model(), key.stage(), meshPool); } public boolean hasStage(RenderStage stage) { @@ -74,6 +85,9 @@ public class IndirectDrawManager extends InstancerStorage> public void renderStage(RenderStage stage) { TextureBinder.bindLightAndOverlay(); + vertexArray.bindForDraw(); + Uniforms.bindForDraw(); + for (var group : cullingGroups.values()) { group.submit(stage, textures); } @@ -86,10 +100,18 @@ public class IndirectDrawManager extends InstancerStorage> public void flush() { super.flush(); + for (var group : cullingGroups.values()) { + group.flushInstancers(); + } + + instancers.values().removeIf(instancer -> instancer.instanceCount() == 0); + + meshPool.flush(); + stagingBuffer.reclaim(); for (var group : cullingGroups.values()) { - group.flush(stagingBuffer); + group.upload(stagingBuffer); } stagingBuffer.flush(); @@ -113,6 +135,8 @@ public class IndirectDrawManager extends InstancerStorage> stagingBuffer.delete(); + meshPool.delete(); + crumblingDrawBuffer.delete(); } @@ -126,6 +150,9 @@ public class IndirectDrawManager extends InstancerStorage> try (var state = GlStateTracker.getRestoreState()) { TextureBinder.bindLightAndOverlay(); + vertexArray.bindForDraw(); + Uniforms.bindForDraw(); + var crumblingMaterial = SimpleMaterial.builder(); // Scratch memory for writing draw commands. diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectInstancer.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectInstancer.java index 411d7e58f..f7f42a0d2 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectInstancer.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectInstancer.java @@ -3,27 +3,34 @@ package com.jozufozu.flywheel.backend.engine.indirect; import java.util.ArrayList; import java.util.List; +import org.joml.Vector4fc; import org.lwjgl.system.MemoryUtil; import com.jozufozu.flywheel.api.context.Context; import com.jozufozu.flywheel.api.instance.Instance; import com.jozufozu.flywheel.api.instance.InstanceType; import com.jozufozu.flywheel.api.instance.InstanceWriter; +import com.jozufozu.flywheel.api.model.Model; import com.jozufozu.flywheel.backend.engine.AbstractInstancer; public class IndirectInstancer extends AbstractInstancer { private final long objectStride; private final InstanceWriter writer; private final List associatedDraws = new ArrayList<>(); - private int modelIndex; + private final Vector4fc boundingSphere; + + public int index; + public int baseInstance = -1; + private int lastModelIndex = -1; private long lastStartPos = -1; - public IndirectInstancer(InstanceType type, Context context) { + public IndirectInstancer(InstanceType type, Context context, Model model) { super(type, context); this.objectStride = type.layout() .byteSize() + IndirectBuffers.INT_SIZE; writer = this.type.writer(); + boundingSphere = model.boundingSphere(); } public void addDraw(IndirectDraw draw) { @@ -38,8 +45,17 @@ public class IndirectInstancer extends AbstractInstancer removeDeletedInstances(); } - public void upload(StagingBuffer stagingBuffer, long startPos, int dstVbo) { - if (shouldUploadAll(startPos)) { + public void writeModel(long ptr) { + MemoryUtil.memPutInt(ptr, 0); // instanceCount - to be incremented by the cull shader + MemoryUtil.memPutInt(ptr + 4, baseInstance); // baseInstance + MemoryUtil.memPutFloat(ptr + 8, boundingSphere.x()); // boundingSphere + MemoryUtil.memPutFloat(ptr + 12, boundingSphere.y()); + MemoryUtil.memPutFloat(ptr + 16, boundingSphere.z()); + MemoryUtil.memPutFloat(ptr + 20, boundingSphere.w()); + } + + public void uploadObjects(StagingBuffer stagingBuffer, long startPos, int dstVbo) { + if (shouldUploadAll(startPos)) { uploadAll(stagingBuffer, startPos, dstVbo); } else { uploadChanged(stagingBuffer, startPos, dstVbo); @@ -47,10 +63,11 @@ public class IndirectInstancer extends AbstractInstancer changed.clear(); lastStartPos = startPos; + lastModelIndex = index; } private boolean shouldUploadAll(long startPos) { - return startPos != lastStartPos; + return startPos != lastStartPos || index != lastModelIndex; } private void uploadChanged(StagingBuffer stagingBuffer, long baseByte, int dstVbo) { @@ -81,11 +98,7 @@ public class IndirectInstancer extends AbstractInstancer } private void writeOne(long ptr, I instance) { - MemoryUtil.memPutInt(ptr, modelIndex); + MemoryUtil.memPutInt(ptr, index); writer.write(ptr + IndirectBuffers.INT_SIZE, instance); } - - public void setModelIndex(int modelIndex) { - this.modelIndex = modelIndex; - } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectMeshPool.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectMeshPool.java deleted file mode 100644 index 9cf4c3167..000000000 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectMeshPool.java +++ /dev/null @@ -1,182 +0,0 @@ -package com.jozufozu.flywheel.backend.engine.indirect; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.jetbrains.annotations.Nullable; - -import com.jozufozu.flywheel.api.model.Mesh; -import com.jozufozu.flywheel.api.vertex.VertexView; -import com.jozufozu.flywheel.backend.InternalVertex; -import com.jozufozu.flywheel.backend.gl.GlNumericType; -import com.jozufozu.flywheel.backend.gl.array.GlVertexArray; -import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer; -import com.jozufozu.flywheel.lib.memory.MemoryBlock; -import com.jozufozu.flywheel.lib.model.QuadIndexSequence; - -public class IndirectMeshPool { - private final VertexView vertexView; - private final Map meshes = new HashMap<>(); - private final List meshList = new ArrayList<>(); - - private final GlVertexArray vertexArray; - private final GlBuffer vbo; - private final GlBuffer ebo; - - private boolean dirty; - - /** - * Create a new mesh pool. - */ - public IndirectMeshPool() { - vertexView = InternalVertex.createVertexView(); - vbo = new GlBuffer(); - ebo = new GlBuffer(); - vertexArray = GlVertexArray.create(); - - vertexArray.setElementBuffer(ebo.handle()); - vertexArray.bindVertexBuffer(0, vbo.handle(), 0, InternalVertex.STRIDE); - vertexArray.bindAttributes(0, 0, InternalVertex.ATTRIBUTES); - } - - /** - * Allocate a model in the arena. - * - * @param mesh The model to allocate. - * @return A handle to the allocated model. - */ - public BufferedMesh alloc(Mesh mesh) { - return meshes.computeIfAbsent(mesh, m -> { - BufferedMesh bufferedModel = new BufferedMesh(m); - meshList.add(bufferedModel); - - dirty = true; - return bufferedModel; - }); - } - - @Nullable - public BufferedMesh get(Mesh mesh) { - return meshes.get(mesh); - } - - public void flush() { - if (dirty) { - uploadAll(); - dirty = false; - } - } - - private void uploadAll() { - long neededSize = 0; - int maxQuadIndexCount = 0; - int nonQuadIndexCount = 0; - for (BufferedMesh mesh : meshList) { - neededSize += mesh.size(); - - if (mesh.mesh.indexSequence() == QuadIndexSequence.INSTANCE) { - maxQuadIndexCount = Math.max(maxQuadIndexCount, mesh.mesh.indexCount()); - } else { - nonQuadIndexCount += mesh.mesh.indexCount(); - } - } - - final long totalIndexCount = maxQuadIndexCount + nonQuadIndexCount; - - final var vertexBlock = MemoryBlock.malloc(neededSize); - final var indexBlock = MemoryBlock.malloc(totalIndexCount * GlNumericType.UINT.byteWidth()); - - final long vertexPtr = vertexBlock.ptr(); - final long indexPtr = indexBlock.ptr(); - - int byteIndex = 0; - int baseVertex = 0; - int firstIndex = maxQuadIndexCount; - for (BufferedMesh mesh : meshList) { - mesh.byteIndex = byteIndex; - mesh.baseVertex = baseVertex; - - mesh.write(vertexPtr, vertexView); - - byteIndex += mesh.size(); - baseVertex += mesh.vertexCount(); - - var indexFiller = mesh.mesh.indexSequence(); - if (indexFiller == QuadIndexSequence.INSTANCE) { - mesh.firstIndex = 0; - } else { - var indexCount = mesh.mesh.indexCount(); - mesh.firstIndex = firstIndex; - indexFiller.fill(indexPtr + (long) firstIndex * GlNumericType.UINT.byteWidth(), indexCount); - - firstIndex += indexCount; - } - } - - if (maxQuadIndexCount > 0) { - QuadIndexSequence.INSTANCE.fill(indexPtr, maxQuadIndexCount); - } - - vbo.upload(vertexBlock); - ebo.upload(indexBlock); - - vertexBlock.free(); - indexBlock.free(); - } - - public void bindForDraw() { - vertexArray.bindForDraw(); - } - - public void delete() { - vertexArray.delete(); - vbo.delete(); - ebo.delete(); - meshes.clear(); - meshList.clear(); - } - - public static class BufferedMesh { - private final Mesh mesh; - private final int vertexCount; - private final int byteSize; - - private long byteIndex; - private int baseVertex; - private int firstIndex; - - private BufferedMesh(Mesh mesh) { - this.mesh = mesh; - vertexCount = mesh.vertexCount(); - byteSize = vertexCount * InternalVertex.STRIDE; - } - - public int vertexCount() { - return vertexCount; - } - - public int size() { - return byteSize; - } - - public int indexCount() { - return mesh.indexCount(); - } - - public int baseVertex() { - return baseVertex; - } - - public int firstIndex() { - return firstIndex; - } - - private void write(long ptr, VertexView vertexView) { - vertexView.ptr(ptr + byteIndex); - vertexView.vertexCount(vertexCount); - mesh.write(vertexView); - } - } -} diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectModel.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectModel.java deleted file mode 100644 index afd722489..000000000 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectModel.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jozufozu.flywheel.backend.engine.indirect; - -import org.joml.Vector4fc; -import org.lwjgl.system.MemoryUtil; - -public class IndirectModel { - public final IndirectInstancer instancer; - public final int index; - private final Vector4fc boundingSphere; - - private int baseInstance = -1; - - public IndirectModel(IndirectInstancer instancer, int index, Vector4fc boundingSphere) { - this.instancer = instancer; - this.index = index; - this.boundingSphere = boundingSphere; - } - - public int baseInstance() { - return baseInstance; - } - - public void prepare(int baseInstance) { - instancer.update(); - this.baseInstance = baseInstance; - } - - public void uploadObjects(StagingBuffer stagingBuffer, long start, int dstVbo) { - instancer.upload(stagingBuffer, start, dstVbo); - } - - public void write(long ptr) { - MemoryUtil.memPutInt(ptr, 0); // instanceCount - to be incremented by the cull shader - MemoryUtil.memPutInt(ptr + 4, baseInstance); // baseInstance - MemoryUtil.memPutFloat(ptr + 8, boundingSphere.x()); // boundingSphere - MemoryUtil.memPutFloat(ptr + 12, boundingSphere.y()); - MemoryUtil.memPutFloat(ptr + 16, boundingSphere.z()); - MemoryUtil.memPutFloat(ptr + 20, boundingSphere.w()); - } -} diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/DrawCall.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/DrawCall.java index a8801fa20..7578b5933 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/DrawCall.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/DrawCall.java @@ -4,19 +4,20 @@ import org.jetbrains.annotations.Nullable; import com.jozufozu.flywheel.backend.InternalVertex; import com.jozufozu.flywheel.backend.engine.InstanceHandleImpl; +import com.jozufozu.flywheel.backend.engine.MeshPool; import com.jozufozu.flywheel.backend.gl.array.GlVertexArray; public class DrawCall { public final ShaderState shaderState; private final InstancedInstancer instancer; - private final InstancedMeshPool.BufferedMesh mesh; + private final MeshPool.BufferedMesh mesh; private final GlVertexArray vao; @Nullable private GlVertexArray vaoScratch; private boolean deleted; - public DrawCall(InstancedInstancer instancer, InstancedMeshPool.BufferedMesh mesh, ShaderState shaderState) { + public DrawCall(InstancedInstancer instancer, MeshPool.BufferedMesh mesh, ShaderState shaderState) { this.instancer = instancer; this.mesh = mesh; this.shaderState = shaderState; @@ -40,7 +41,7 @@ public class DrawCall { vao.bindForDraw(); - mesh.draw(instancer.getInstanceCount()); + mesh.draw(instancer.instanceCount()); } public void renderOne(InstanceHandleImpl impl) { @@ -48,7 +49,7 @@ public class DrawCall { return; } - int instanceCount = instancer.getInstanceCount(); + int instanceCount = instancer.instanceCount(); if (instanceCount <= 0 || impl.index >= instanceCount) { return; } 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 f7950aae9..c88bbafb0 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 @@ -12,17 +12,17 @@ import com.jozufozu.flywheel.api.event.RenderStage; import com.jozufozu.flywheel.api.instance.Instance; import com.jozufozu.flywheel.backend.engine.InstancerKey; import com.jozufozu.flywheel.backend.engine.InstancerStorage; +import com.jozufozu.flywheel.backend.engine.MeshPool; public class InstancedDrawManager extends InstancerStorage> { /** * The set of draw calls to make in each {@link RenderStage}. */ private final Map drawSets = new EnumMap<>(RenderStage.class); - private final EboCache eboCache = new EboCache(); /** * A map of vertex types to their mesh pools. */ - private final InstancedMeshPool meshPool = new InstancedMeshPool(eboCache); + private final MeshPool meshPool = new MeshPool(); public DrawSet get(RenderStage stage) { return drawSets.getOrDefault(stage, DrawSet.EMPTY); @@ -36,7 +36,7 @@ public class InstancedDrawManager extends InstancerStorage // Update the instancers and remove any that are empty. instancer.update(); - if (instancer.getInstanceCount() == 0) { + if (instancer.instanceCount() == 0) { instancer.delete(); return true; } else { @@ -63,8 +63,6 @@ public class InstancedDrawManager extends InstancerStorage drawSets.values() .forEach(DrawSet::delete); drawSets.clear(); - - eboCache.invalidate(); } @Override diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedMeshPool.java b/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedMeshPool.java deleted file mode 100644 index 502797a1c..000000000 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/instancing/InstancedMeshPool.java +++ /dev/null @@ -1,205 +0,0 @@ -package com.jozufozu.flywheel.backend.engine.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 org.jetbrains.annotations.Nullable; -import org.lwjgl.opengl.GL32; - -import com.jozufozu.flywheel.Flywheel; -import com.jozufozu.flywheel.api.model.Mesh; -import com.jozufozu.flywheel.api.vertex.VertexView; -import com.jozufozu.flywheel.backend.InternalVertex; -import com.jozufozu.flywheel.backend.gl.GlPrimitive; -import com.jozufozu.flywheel.backend.gl.array.GlVertexArray; -import com.jozufozu.flywheel.backend.gl.buffer.GlBuffer; -import com.jozufozu.flywheel.backend.gl.buffer.MappedBuffer; - -public class InstancedMeshPool { - private final VertexView vertexView; - private final Map byMesh = new HashMap<>(); - private final List ordered = new ArrayList<>(); - - private final GlBuffer vbo; - private final EboCache eboCache; - private long byteSize; - - private boolean dirty; - private boolean anyToRemove; - - /** - * Create a new mesh pool. - */ - public InstancedMeshPool(EboCache eboCache) { - this.eboCache = eboCache; - vertexView = InternalVertex.createVertexView(); - vbo = new GlBuffer(); - vbo.growthFunction(l -> Math.max(l + InternalVertex.STRIDE * 128L, (long) (l * 1.6))); - } - - /** - * Allocate a mesh in the arena. - * - * @param mesh The mesh to allocate. - * @return A handle to the allocated mesh. - */ - public BufferedMesh alloc(Mesh mesh) { - return byMesh.computeIfAbsent(mesh, this::_alloc); - } - - private BufferedMesh _alloc(Mesh m) { - BufferedMesh bufferedMesh = new BufferedMesh(m, this.eboCache); - ordered.add(bufferedMesh); - - dirty = true; - return bufferedMesh; - } - - @Nullable - public BufferedMesh get(Mesh mesh) { - return byMesh.get(mesh); - } - - public void flush() { - if (!dirty) { - return; - } - - if (anyToRemove) { - anyToRemove = false; - processDeletions(); - } - - var forUpload = calculateByteSizeAndGetMeshesForUpload(); - - if (!forUpload.isEmpty()) { - vbo.ensureCapacity(byteSize); - - upload(forUpload); - } - - dirty = false; - } - - private void processDeletions() { - // remove deleted meshes - ordered.removeIf(bufferedMesh -> { - boolean deleted = bufferedMesh.deleted(); - if (deleted) { - byMesh.remove(bufferedMesh.mesh); - } - return deleted; - }); - } - - private List calculateByteSizeAndGetMeshesForUpload() { - List out = new ArrayList<>(); - - long byteIndex = 0; - for (BufferedMesh mesh : ordered) { - if (mesh.byteIndex != byteIndex) { - out.add(mesh); - } - - mesh.byteIndex = byteIndex; - - byteIndex += mesh.byteSize; - } - - this.byteSize = byteIndex; - - return out; - } - - private void upload(List meshes) { - try (MappedBuffer mapped = vbo.map()) { - long ptr = mapped.ptr(); - - for (BufferedMesh mesh : meshes) { - mesh.write(ptr, vertexView); - mesh.boundTo.clear(); - } - } catch (Exception e) { - Flywheel.LOGGER.error("Error uploading pooled meshes:", e); - } - } - - public void delete() { - vbo.delete(); - byMesh.clear(); - ordered.clear(); - } - - @Override - public String toString() { - return "InstancedMeshPool{" + "byteSize=" + byteSize + ", meshCount=" + byMesh.size() + '}'; - } - - public class BufferedMesh { - private final Mesh mesh; - private final int vertexCount; - private final int byteSize; - private final int ebo; - - private long byteIndex = -1; - private int referenceCount = 0; - - private final Set boundTo = new HashSet<>(); - - private BufferedMesh(Mesh mesh, EboCache eboCache) { - this.mesh = mesh; - vertexCount = mesh.vertexCount(); - byteSize = vertexCount * InternalVertex.STRIDE; - this.ebo = eboCache.get(mesh.indexSequence(), mesh.indexCount()); - } - - public boolean deleted() { - return referenceCount <= 0; - } - - public boolean invalid() { - return mesh.vertexCount() == 0 || deleted() || byteIndex == -1; - } - - private void write(long ptr, VertexView vertexView) { - if (invalid()) { - return; - } - - vertexView.ptr(ptr + byteIndex); - vertexView.vertexCount(vertexCount); - mesh.write(vertexView); - } - - public void setup(GlVertexArray vao) { - if (boundTo.add(vao)) { - vao.bindVertexBuffer(0, InstancedMeshPool.this.vbo.handle(), byteIndex, InternalVertex.STRIDE); - vao.bindAttributes(0, 0, InternalVertex.ATTRIBUTES); - vao.setElementBuffer(ebo); - } - } - - public void draw(int instanceCount) { - if (instanceCount > 1) { - GL32.glDrawElementsInstanced(GlPrimitive.TRIANGLES.glEnum, mesh.indexCount(), GL32.GL_UNSIGNED_INT, 0, instanceCount); - } else { - GL32.glDrawElements(GlPrimitive.TRIANGLES.glEnum, mesh.indexCount(), GL32.GL_UNSIGNED_INT, 0); - } - } - - public void acquire() { - referenceCount++; - } - - public void drop() { - if (--referenceCount == 0) { - InstancedMeshPool.this.dirty = true; - InstancedMeshPool.this.anyToRemove = true; - } - } - } -} diff --git a/src/main/resources/assets/flywheel/flywheel/internal/indirect/apply.glsl b/src/main/resources/assets/flywheel/flywheel/internal/indirect/apply.glsl index 4c23bf5f3..4b9f6e2bc 100644 --- a/src/main/resources/assets/flywheel/flywheel/internal/indirect/apply.glsl +++ b/src/main/resources/assets/flywheel/flywheel/internal/indirect/apply.glsl @@ -22,5 +22,4 @@ void main() { uint modelIndex = drawCommands[drawIndex].modelIndex; drawCommands[drawIndex].instanceCount = models[modelIndex].instanceCount; - drawCommands[drawIndex].baseInstance = models[modelIndex].baseInstance; }