From 1a9c166a2505d71f10fb6a3d6dcd8e1c76fd8cb5 Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 23 Sep 2024 02:49:09 +0200 Subject: [PATCH 1/2] cache multi draw batches on regions --- .../client/gl/device/GLRenderDevice.java | 2 +- .../client/gl/device/MultiDrawBatch.java | 14 +------ .../render/chunk/DefaultChunkRenderer.java | 17 ++++---- .../render/chunk/lists/ChunkRenderList.java | 18 ++++++++- .../render/chunk/lists/SortedRenderLists.java | 40 ------------------- .../render/chunk/region/RenderRegion.java | 36 +++++++++++++++++ .../chunk/region/RenderRegionManager.java | 4 ++ 7 files changed, 67 insertions(+), 64 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java index 98af1f3ba3..237d49cc6e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java @@ -274,7 +274,7 @@ public void multiDrawElementsBaseVertex(MultiDrawBatch batch, GlIndexType indexT batch.pElementCount, indexType.getFormatId(), batch.pElementPointer, - batch.size(), + batch.size, batch.pBaseVertex); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/MultiDrawBatch.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/MultiDrawBatch.java index ceda21721a..7169f4bf26 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/MultiDrawBatch.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/MultiDrawBatch.java @@ -14,9 +14,8 @@ public final class MultiDrawBatch { public final long pElementCount; public final long pBaseVertex; - private final int capacity; - public int size; + public boolean isFilled; public MultiDrawBatch(int capacity) { this.pElementPointer = MemoryUtil.nmemAlignedAlloc(32, (long) capacity * Pointer.POINTER_SIZE); @@ -24,20 +23,11 @@ public MultiDrawBatch(int capacity) { this.pElementCount = MemoryUtil.nmemAlignedAlloc(32, (long) capacity * Integer.BYTES); this.pBaseVertex = MemoryUtil.nmemAlignedAlloc(32, (long) capacity * Integer.BYTES); - - this.capacity = capacity; - } - - public int size() { - return this.size; - } - - public int capacity() { - return this.capacity; } public void clear() { this.size = 0; + this.isFilled = false; } public void delete() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java index e273e61744..910500c9f7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java @@ -29,14 +29,11 @@ import java.util.Iterator; public class DefaultChunkRenderer extends ShaderChunkRenderer { - private final MultiDrawBatch batch; - private final SharedQuadIndexBuffer sharedIndexBuffer; public DefaultChunkRenderer(RenderDevice device, ChunkVertexType vertexType) { super(device, vertexType); - this.batch = new MultiDrawBatch((ModelQuadFacing.COUNT * RenderRegion.REGION_SIZE) + 1); this.sharedIndexBuffer = new SharedQuadIndexBuffer(device.createCommandList(), SharedQuadIndexBuffer.IndexType.INTEGER); } @@ -72,16 +69,19 @@ public void render(ChunkRenderMatrices matrices, continue; } - fillCommandBuffer(this.batch, region, storage, renderList, camera, renderPass, useBlockFaceCulling); + var batch = region.getCachedBatch(renderPass); + if (!batch.isFilled) { + fillCommandBuffer(batch, region, storage, renderList, camera, renderPass, useBlockFaceCulling); + } - if (this.batch.isEmpty()) { + if (batch.isEmpty()) { continue; } // When the shared index buffer is being used, we must ensure the storage has been allocated *before* // the tessellation is prepared. if (!useIndexedTessellation) { - this.sharedIndexBuffer.ensureCapacity(commandList, this.batch.getIndexBufferSize()); + this.sharedIndexBuffer.ensureCapacity(commandList, batch.getIndexBufferSize()); } GlTessellation tessellation; @@ -93,7 +93,7 @@ public void render(ChunkRenderMatrices matrices, } setModelMatrixUniforms(shader, region, camera); - executeDrawBatch(commandList, tessellation, this.batch); + executeDrawBatch(commandList, tessellation, batch); } super.end(renderPass); @@ -110,7 +110,7 @@ private static void fillCommandBuffer(MultiDrawBatch batch, CameraTransform camera, TerrainRenderPass pass, boolean useBlockFaceCulling) { - batch.clear(); + batch.isFilled = true; var iterator = renderList.sectionsWithGeometryIterator(pass.isTranslucent()); @@ -321,6 +321,5 @@ public void delete(CommandList commandList) { super.delete(commandList); this.sharedIndexBuffer.delete(commandList); - this.batch.delete(); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java index 75e6757df8..2a0e022e9a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java @@ -13,6 +13,7 @@ public class ChunkRenderList { private final byte[] sectionsWithGeometry = new byte[RenderRegion.REGION_SIZE]; private int sectionsWithGeometryCount = 0; + private int prevSectionsWithGeometryCount = 0; private final byte[] sectionsWithSprites = new byte[RenderRegion.REGION_SIZE]; private int sectionsWithSpritesCount = 0; @@ -29,6 +30,9 @@ public ChunkRenderList(RenderRegion region) { } public void reset(int frame) { + // TODO: benchmark overall improvement and test if partial batch writing is worth it + this.prevSectionsWithGeometryCount = this.sectionsWithGeometryCount; + this.sectionsWithGeometryCount = 0; this.sectionsWithSpritesCount = 0; this.sectionsWithEntitiesCount = 0; @@ -47,8 +51,14 @@ public void add(RenderSection render) { int index = render.getSectionIndex(); int flags = render.getFlags(); - this.sectionsWithGeometry[this.sectionsWithGeometryCount] = (byte) index; - this.sectionsWithGeometryCount += (flags >>> RenderSectionFlags.HAS_BLOCK_GEOMETRY) & 1; + if (((flags >>> RenderSectionFlags.HAS_BLOCK_GEOMETRY) & 1) != 0) { + var byteIndex = (byte) index; + if (this.sectionsWithGeometry[this.sectionsWithGeometryCount] != byteIndex) { + this.sectionsWithGeometry[this.sectionsWithGeometryCount] = byteIndex; + this.prevSectionsWithGeometryCount = -1; + } + this.sectionsWithGeometryCount++; + } this.sectionsWithSprites[this.sectionsWithSpritesCount] = (byte) index; this.sectionsWithSpritesCount += (flags >>> RenderSectionFlags.HAS_ANIMATED_SPRITES) & 1; @@ -57,6 +67,10 @@ public void add(RenderSection render) { this.sectionsWithEntitiesCount += (flags >>> RenderSectionFlags.HAS_BLOCK_ENTITIES) & 1; } + public boolean isCacheInvalidated() { + return this.prevSectionsWithGeometryCount != this.sectionsWithGeometryCount; + } + public @Nullable ByteIterator sectionsWithGeometryIterator(boolean reverse) { if (this.sectionsWithGeometryCount == 0) { return null; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortedRenderLists.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortedRenderLists.java index 1837e0ae9d..7e6104526d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortedRenderLists.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortedRenderLists.java @@ -27,44 +27,4 @@ public ReversibleObjectArrayIterator iterator(boolean reverse) public static SortedRenderLists empty() { return EMPTY; } - - public static class Builder { - private final ObjectArrayList lists = new ObjectArrayList<>(); - private final int frame; - - public Builder(int frame) { - this.frame = frame; - } - - public void add(RenderSection section) { - RenderRegion region = section.getRegion(); - ChunkRenderList list = region.getRenderList(); - - // Even if a section does not have render objects, we must ensure the render list is initialized and put - // into the sorted queue of lists, so that we maintain the correct order of draw calls. - if (list.getLastVisibleFrame() != this.frame) { - list.reset(this.frame); - - this.lists.add(list); - } - - // Only add the section to the render list if it actually contains render objects - if (section.getFlags() != 0) { - list.add(section); - } - } - - public SortedRenderLists build() { - var filtered = new ObjectArrayList(this.lists.size()); - - // Filter any empty render lists - for (var list : this.lists) { - if (list.size() > 0) { - filtered.add(list); - } - } - - return new SortedRenderLists(filtered); - } - } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java index df6df676d4..4e101986b7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java @@ -5,7 +5,9 @@ import net.caffeinemc.mods.sodium.client.gl.arena.staging.StagingBuffer; import net.caffeinemc.mods.sodium.client.gl.buffer.GlBuffer; import net.caffeinemc.mods.sodium.client.gl.device.CommandList; +import net.caffeinemc.mods.sodium.client.gl.device.MultiDrawBatch; import net.caffeinemc.mods.sodium.client.gl.tessellation.GlTessellation; +import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.data.SectionRenderDataStorage; import net.caffeinemc.mods.sodium.client.render.chunk.lists.ChunkRenderList; @@ -51,6 +53,8 @@ public class RenderRegion { private final Map sectionRenderData = new Reference2ReferenceOpenHashMap<>(); private DeviceResources resources; + private final Map cachedBatches = new Reference2ReferenceOpenHashMap<>(); + public RenderRegion(int x, int y, int z, StagingBuffer stagingBuffer) { this.x = x; this.y = y; @@ -101,6 +105,38 @@ public void delete(CommandList commandList) { } Arrays.fill(this.sections, null); + + for (var batch : this.cachedBatches.values()) { + batch.delete(); + } + this.cachedBatches.clear(); + } + + public void clearAllCachedBatches() { + for (var batch : this.cachedBatches.values()) { + batch.clear(); + } + } + + public void clearCachedBatchFor(TerrainRenderPass pass) { + var batch = this.cachedBatches.remove(pass); + if (batch != null) { + batch.delete(); + } + } + + public MultiDrawBatch getCachedBatch(TerrainRenderPass pass) { + MultiDrawBatch batch = this.cachedBatches.get(pass); + if (batch != null) { + if (this.renderList.isCacheInvalidated()) { + batch.clear(); + } + return batch; + } + + batch = new MultiDrawBatch((ModelQuadFacing.COUNT * RenderRegion.REGION_SIZE) + 1); + this.cachedBatches.put(pass, batch); + return batch; } public boolean isEmpty() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java index 71665e93a2..cda409b745 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java @@ -74,6 +74,7 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect if (storage != null) { storage.removeVertexData(renderSectionIndex); + region.clearCachedBatchFor(pass); } BuiltSectionMeshParts mesh = chunkBuildOutput.getMesh(pass); @@ -98,6 +99,7 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect var storage = region.getStorage(DefaultTerrainRenderPasses.TRANSLUCENT); if (storage != null) { storage.removeIndexData(renderSectionIndex); + region.clearCachedBatchFor(DefaultTerrainRenderPasses.TRANSLUCENT); } } } @@ -118,6 +120,7 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect // Once invalidated the tessellation will be re-created on the next attempted use if (bufferChanged) { region.refreshTesselation(commandList); + region.clearAllCachedBatches(); } // Collect the upload results @@ -135,6 +138,7 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect if (bufferChanged) { region.refreshIndexedTesselation(commandList); + region.clearCachedBatchFor(DefaultTerrainRenderPasses.TRANSLUCENT); } for (PendingSectionIndexBufferUpload upload : indexUploads) { From 3cbdaf1fff69f6e9d332b0cc5f04f25c4dd933d5 Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 23 Sep 2024 03:12:05 +0200 Subject: [PATCH 2/2] remove todo --- .../mods/sodium/client/render/chunk/lists/ChunkRenderList.java | 1 - 1 file changed, 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java index 2a0e022e9a..207e80b078 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java @@ -30,7 +30,6 @@ public ChunkRenderList(RenderRegion region) { } public void reset(int frame) { - // TODO: benchmark overall improvement and test if partial batch writing is worth it this.prevSectionsWithGeometryCount = this.sectionsWithGeometryCount; this.sectionsWithGeometryCount = 0;