diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/buffer/IndexedVertexData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/buffer/IndexedVertexData.java deleted file mode 100644 index 12b879ba99..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/buffer/IndexedVertexData.java +++ /dev/null @@ -1,16 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gl.buffer; - -import net.caffeinemc.mods.sodium.client.gl.attribute.GlVertexFormat; -import net.caffeinemc.mods.sodium.client.util.NativeBuffer; - -/** - * Helper type for tagging the vertex format alongside the raw buffer data. - */ -public record IndexedVertexData(GlVertexFormat vertexFormat, - NativeBuffer vertexBuffer, - NativeBuffer indexBuffer) { - public void delete() { - this.vertexBuffer.free(); - this.indexBuffer.free(); - } -} 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 1d1165c705..5ece7e2a7d 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 @@ -1,7 +1,6 @@ package net.caffeinemc.mods.sodium.client.render.chunk; import net.caffeinemc.mods.sodium.client.SodiumClientMod; -import net.caffeinemc.mods.sodium.client.gl.attribute.GlVertexAttributeBinding; import net.caffeinemc.mods.sodium.client.gl.device.CommandList; import net.caffeinemc.mods.sodium.client.gl.device.DrawCommandList; import net.caffeinemc.mods.sodium.client.gl.device.MultiDrawBatch; @@ -16,7 +15,6 @@ import net.caffeinemc.mods.sodium.client.render.chunk.lists.ChunkRenderList; import net.caffeinemc.mods.sodium.client.render.chunk.lists.ChunkRenderListIterable; import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; -import net.caffeinemc.mods.sodium.client.render.chunk.shader.ChunkShaderBindingPoints; import net.caffeinemc.mods.sodium.client.render.chunk.shader.ChunkShaderInterface; import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior; @@ -73,7 +71,7 @@ public void render(ChunkRenderMatrices matrices, continue; } - fillCommandBuffer(this.batch, region, storage, renderList, camera, renderPass, useBlockFaceCulling); + fillCommandBuffer(this.batch, region, storage, renderList, camera, renderPass, useBlockFaceCulling, useIndexedTessellation); if (this.batch.isEmpty()) { continue; @@ -110,7 +108,8 @@ private static void fillCommandBuffer(MultiDrawBatch batch, ChunkRenderList renderList, CameraTransform camera, TerrainRenderPass pass, - boolean useBlockFaceCulling) { + boolean useBlockFaceCulling, + boolean useIndexedTessellation) { batch.clear(); var iterator = renderList.sectionsWithGeometryIterator(pass.isTranslucent()); @@ -150,30 +149,48 @@ private static void fillCommandBuffer(MultiDrawBatch batch, continue; } - if (pass.isTranslucent()) { - addIndexedDrawCommands(batch, pMeshData, slices); + // it's necessary to sometimes not the locally-indexed command generator even for indexed tessellations since + // sometimes the index buffer is shared, but not globally shared. This means that translucent sections that + // are sharing an index buffer amongst them need to use the shared index command generator since it sets the + // same element offset for each draw command and doesn't increment it. Recall that in each draw command the indexing + // of the elements needs to start at 0 and thus starting somewhere further into the shared index buffer is invalid. + // there's also the optimization that draw commands can be combined when using a shared index buffer, be it + // globally shared or just shared within the region, which isn't possible with the locally-indexed command generator. + if (useIndexedTessellation && SectionRenderDataUnsafe.isLocalIndex(pMeshData)) { + addLocalIndexedDrawCommands(batch, pMeshData, slices); } else { - addNonIndexedDrawCommands(batch, pMeshData, slices); + addSharedIndexedDrawCommands(batch, pMeshData, slices); } } } /** - * Generates the draw commands for a chunk's meshes using the shared index buffer. + * Generates the draw commands for a chunk's meshes, where each mesh has a separate index buffer. This is used + * when rendering translucent geometry, as each geometry set needs a sorted index buffer. */ @SuppressWarnings("IntegerMultiplicationImplicitCastToLong") - private static void addNonIndexedDrawCommands(MultiDrawBatch batch, long pMeshData, int mask) { + private static void addLocalIndexedDrawCommands(MultiDrawBatch batch, long pMeshData, int mask) { final var pElementPointer = batch.pElementPointer; final var pBaseVertex = batch.pBaseVertex; final var pElementCount = batch.pElementCount; int size = batch.size; + long elementOffset = SectionRenderDataUnsafe.getBaseElement(pMeshData); + long baseVertex = SectionRenderDataUnsafe.getBaseVertex(pMeshData); + for (int facing = 0; facing < ModelQuadFacing.COUNT; facing++) { - // Uint32 -> Int32 cast is always safe and should be optimized away - MemoryUtil.memPutInt(pBaseVertex + (size << 2), (int) SectionRenderDataUnsafe.getVertexOffset(pMeshData, facing)); - MemoryUtil.memPutInt(pElementCount + (size << 2), (int) SectionRenderDataUnsafe.getElementCount(pMeshData, facing)); - MemoryUtil.memPutAddress(pElementPointer + (size << Pointer.POINTER_SHIFT), 0 /* using a shared index buffer */); + final long vertexCount = SectionRenderDataUnsafe.getVertexCount(pMeshData, facing); + final long elementCount = (vertexCount >> 2) * 6; + + MemoryUtil.memPutInt(pElementCount + (size << 2), UInt32.uncheckedDowncast(elementCount)); + MemoryUtil.memPutInt(pBaseVertex + (size << 2), UInt32.uncheckedDowncast(baseVertex)); + + // * 4 to convert to bytes (the index buffer contains integers) + MemoryUtil.memPutAddress(pElementPointer + (size << Pointer.POINTER_SHIFT), elementOffset << 2); + + baseVertex += vertexCount; + elementOffset += elementCount; size += (mask >> facing) & 1; } @@ -182,34 +199,57 @@ private static void addNonIndexedDrawCommands(MultiDrawBatch batch, long pMeshDa } /** - * Generates the draw commands for a chunk's meshes, where each mesh has a separate index buffer. This is used - * when rendering translucent geometry, as each geometry set needs a sorted index buffer. + * Generates the draw commands for a chunk's meshes using the shared index buffer. */ @SuppressWarnings("IntegerMultiplicationImplicitCastToLong") - private static void addIndexedDrawCommands(MultiDrawBatch batch, long pMeshData, int mask) { + private static void addSharedIndexedDrawCommands(MultiDrawBatch batch, long pMeshData, int mask) { final var pElementPointer = batch.pElementPointer; final var pBaseVertex = batch.pBaseVertex; final var pElementCount = batch.pElementCount; - int size = batch.size; - - long elementOffset = SectionRenderDataUnsafe.getBaseElement(pMeshData); + // this is either zero (global shared index buffer) or the offset to the location of the shared element buffer (region shared index buffer) + final var elementOffsetBytes = SectionRenderDataUnsafe.getBaseElement(pMeshData) << 2; + final var facingList = SectionRenderDataUnsafe.getFacingList(pMeshData); - for (int facing = 0; facing < ModelQuadFacing.COUNT; facing++) { - final long vertexOffset = SectionRenderDataUnsafe.getVertexOffset(pMeshData, facing); - final long elementCount = SectionRenderDataUnsafe.getElementCount(pMeshData, facing); - - // Uint32 -> Int32 cast is always safe and should be optimized away - MemoryUtil.memPutInt(pBaseVertex + (size << 2), UInt32.uncheckedDowncast(vertexOffset)); - MemoryUtil.memPutInt(pElementCount + (size << 2), UInt32.uncheckedDowncast(elementCount)); + int size = batch.size; + long groupVertexCount = 0; + long baseVertex = SectionRenderDataUnsafe.getBaseVertex(pMeshData); + int lastMaskBit = 0; + + for (int i = 0; i <= ModelQuadFacing.COUNT; i++) { + var maskBit = 0; + long vertexCount = 0; + if (i < ModelQuadFacing.COUNT) { + vertexCount = SectionRenderDataUnsafe.getVertexCount(pMeshData, i); + + // if there's no vertexes, the mask bit is just 0 + if (vertexCount != 0) { + var facing = (facingList >>> (i * 8)) & 0xFF; + maskBit = (mask >>> facing) & 1; + } + } - // * 4 to convert to bytes (the index buffer contains integers) - // the section render data storage for the indices stores the offset in indices (also called elements) - MemoryUtil.memPutAddress(pElementPointer + (size << Pointer.POINTER_SHIFT), elementOffset << 2); + if (maskBit == 0) { + if (lastMaskBit == 1) { + // delay writing out draw command if there's a zero-size group + if (i < ModelQuadFacing.COUNT && vertexCount == 0) { + continue; + } + + MemoryUtil.memPutInt(pElementCount + (size << 2), UInt32.uncheckedDowncast((groupVertexCount >> 2) * 6)); + MemoryUtil.memPutInt(pBaseVertex + (size << 2), UInt32.uncheckedDowncast(baseVertex)); + MemoryUtil.memPutAddress(pElementPointer + (size << Pointer.POINTER_SHIFT), elementOffsetBytes); + size++; + baseVertex += groupVertexCount; + groupVertexCount = 0; + } + + baseVertex += vertexCount; + } else { + groupVertexCount += vertexCount; + } - // adding the number of elements works because the index data has one index per element (which are the indices) - elementOffset += elementCount; - size += (mask >> facing) & 1; + lastMaskBit = maskBit; } batch.size = size; @@ -224,7 +264,7 @@ private static void addIndexedDrawCommands(MultiDrawBatch batch, long pMeshData, private static final int MODEL_NEG_Y = ModelQuadFacing.NEG_Y.ordinal(); private static final int MODEL_NEG_Z = ModelQuadFacing.NEG_Z.ordinal(); - private static int getVisibleFaces(int originX, int originY, int originZ, int chunkX, int chunkY, int chunkZ) { + public static int getVisibleFaces(int originX, int originY, int originZ, int chunkX, int chunkY, int chunkZ) { // This is carefully written so that we can keep everything branch-less. // // Normally, this would be a ridiculous way to handle the problem. But the Hotspot VM's diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index d91c5496ab..0a59c25df6 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -328,9 +328,9 @@ private boolean processChunkBuildResults(ArrayList results) { result.render.setTranslucentData(chunkBuildOutput.translucentData); } } else if (result instanceof ChunkSortOutput sortOutput - && sortOutput.getTopoSorter() != null + && sortOutput.getDynamicSorter() != null && result.render.getTranslucentData() instanceof DynamicTopoData data) { - this.sortTriggering.applyTriggerChanges(data, sortOutput.getTopoSorter(), result.render.getPosition(), this.cameraPosition); + this.sortTriggering.applyTriggerChanges(data, sortOutput.getDynamicSorter(), result.render.getPosition(), this.cameraPosition); } var job = result.render.getTaskCancellationToken(); @@ -678,8 +678,10 @@ public Collection getDebugStrings() { int count = 0; - long deviceUsed = 0; - long deviceAllocated = 0; + long geometryDeviceUsed = 0; + long geometryDeviceAllocated = 0; + long indexDeviceUsed = 0; + long indexDeviceAllocated = 0; for (var region : this.regions.getLoadedRegions()) { var resources = region.getResources(); @@ -688,15 +690,20 @@ public Collection getDebugStrings() { continue; } - var buffer = resources.getGeometryArena(); + var geometryArena = resources.getGeometryArena(); + geometryDeviceUsed += geometryArena.getDeviceUsedMemory(); + geometryDeviceAllocated += geometryArena.getDeviceAllocatedMemory(); - deviceUsed += buffer.getDeviceUsedMemory(); - deviceAllocated += buffer.getDeviceAllocatedMemory(); + var indexArena = resources.getIndexArena(); + indexDeviceUsed += indexArena.getDeviceUsedMemory(); + indexDeviceAllocated += indexArena.getDeviceAllocatedMemory(); count++; } - list.add(String.format("Geometry Pool: %d/%d MiB (%d buffers)", MathUtil.toMib(deviceUsed), MathUtil.toMib(deviceAllocated), count)); + list.add(String.format("Pools: Geometry %d/%d MiB, Index %d/%d MiB (%d buffers)", + MathUtil.toMib(geometryDeviceUsed), MathUtil.toMib(geometryDeviceAllocated), + MathUtil.toMib(indexDeviceUsed), MathUtil.toMib(indexDeviceAllocated), count)); list.add(String.format("Transfer Queue: %s", this.regions.getStagingBuffer().toString())); list.add(String.format("Chunk Builder: Permits=%02d (E %03d) | Busy=%02d | Total=%02d", diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java index e21dc96476..8985b13244 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java @@ -7,6 +7,7 @@ import net.caffeinemc.mods.sodium.client.gl.device.CommandList; import net.caffeinemc.mods.sodium.client.gl.tessellation.GlIndexType; import net.caffeinemc.mods.sodium.client.gl.util.EnumBitField; +import net.caffeinemc.mods.sodium.client.util.NativeBuffer; import java.nio.ByteBuffer; import java.nio.IntBuffer; @@ -55,6 +56,14 @@ private void grow(CommandList commandList, int primitiveCount) { this.maxPrimitives = primitiveCount; } + public static NativeBuffer createIndexBuffer(IndexType indexType, int primitiveCount) { + var bufferSize = primitiveCount * indexType.getBytesPerElement() * ELEMENTS_PER_PRIMITIVE; + var buffer = new NativeBuffer(bufferSize); + + indexType.createIndexBuffer(buffer.getDirectBuffer(), primitiveCount); + + return buffer; + } public GlBuffer getBufferObject() { return this.buffer; @@ -64,14 +73,6 @@ public void delete(CommandList commandList) { commandList.deleteBuffer(this.buffer); } - public GlIndexType getIndexFormat() { - return this.indexType.getFormat(); - } - - public IndexType getIndexType() { - return this.indexType; - } - public enum IndexType { SHORT(GlIndexType.UNSIGNED_SHORT, 64 * 1024) { @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers.java index eae5389deb..f76fe115d0 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers.java @@ -13,10 +13,6 @@ import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexType; import net.caffeinemc.mods.sodium.client.util.NativeBuffer; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - /** * A collection of temporary buffers for each worker thread which will be used to build chunk meshes for given render * passes. This makes a best-effort attempt to pick a suitable size for each scratch buffer, but will never try to @@ -60,47 +56,79 @@ public ChunkModelBuilder get(TerrainRenderPass pass) { * have been rendered to pass the finished meshes over to the graphics card. This function can be called multiple * times to return multiple copies. */ - public BuiltSectionMeshParts createMesh(TerrainRenderPass pass, boolean forceUnassigned) { + public BuiltSectionMeshParts createMesh(TerrainRenderPass pass, int visibleSlices, boolean forceUnassigned, boolean sliceReordering) { var builder = this.builders.get(pass); + int[] vertexSegments = new int[ModelQuadFacing.COUNT << 1]; + int vertexTotal = 0; - List vertexBuffers = new ArrayList<>(); - int[] vertexCounts = new int[ModelQuadFacing.COUNT]; + // get the total vertex count to initialize the buffer + for (ModelQuadFacing facing : ModelQuadFacing.VALUES) { + vertexTotal += builder.getVertexBuffer(facing).count(); + } - int vertexSum = 0; + if (vertexTotal == 0) { + return null; + } - for (ModelQuadFacing facing : ModelQuadFacing.VALUES) { - var ordinal = facing.ordinal(); - var buffer = builder.getVertexBuffer(facing); + var mergedBuffer = new NativeBuffer(vertexTotal * this.vertexType.getVertexFormat().getStride()); + var mergedBufferBuilder = mergedBuffer.getDirectBuffer(); - if (buffer.isEmpty()) { - continue; + if (sliceReordering) { + // sliceReordering implies !forceUnassigned + + // write all currently visible slices first, and then the rest. + // start with unassigned as it will never become invisible + var unassignedBuffer = builder.getVertexBuffer(ModelQuadFacing.UNASSIGNED); + int vertexSegmentCount = 0; + vertexSegments[vertexSegmentCount++] = unassignedBuffer.count(); + vertexSegments[vertexSegmentCount++] = ModelQuadFacing.UNASSIGNED.ordinal(); + if (!unassignedBuffer.isEmpty()) { + mergedBufferBuilder.put(unassignedBuffer.slice()); } - vertexBuffers.add(buffer.slice()); - var bufferCount = buffer.count(); - if (!forceUnassigned) { - vertexCounts[ordinal] = bufferCount; - } + // write all visible and then invisible slices + for (var step = 0; step < 2; step++) { + for (ModelQuadFacing facing : ModelQuadFacing.VALUES) { + var facingIndex = facing.ordinal(); + if (facing == ModelQuadFacing.UNASSIGNED || ((visibleSlices >> facingIndex) & 1) == step) { + continue; + } - vertexSum += bufferCount; - } + var buffer = builder.getVertexBuffer(facing); - if (vertexSum == 0) { - return null; - } + // generate empty ranges to prevent SectionRenderData storage from making up indexes for null ranges + vertexSegments[vertexSegmentCount++] = buffer.count(); + vertexSegments[vertexSegmentCount++] = facingIndex; - if (forceUnassigned) { - vertexCounts[ModelQuadFacing.UNASSIGNED.ordinal()] = vertexSum; - } + if (!buffer.isEmpty()) { + mergedBufferBuilder.put(buffer.slice()); + } + } + } + } else { + // forceUnassigned implies !sliceReordering - var mergedBuffer = new NativeBuffer(vertexSum * this.vertexType.getVertexFormat().getStride()); - var mergedBufferBuilder = mergedBuffer.getDirectBuffer(); + if (forceUnassigned) { + var segmentIndex = ModelQuadFacing.UNASSIGNED.ordinal() << 1; + vertexSegments[segmentIndex] = vertexTotal; + vertexSegments[segmentIndex + 1] = ModelQuadFacing.UNASSIGNED.ordinal(); + } - for (var buffer : vertexBuffers) { - mergedBufferBuilder.put(buffer); + for (ModelQuadFacing facing : ModelQuadFacing.VALUES) { + var buffer = builder.getVertexBuffer(facing); + if (!buffer.isEmpty()) { + if (!forceUnassigned) { + var facingIndex = facing.ordinal(); + var segmentIndex = facingIndex << 1; + vertexSegments[segmentIndex] = buffer.count(); + vertexSegments[segmentIndex + 1] = facingIndex; + } + mergedBufferBuilder.put(buffer.slice()); + } + } } - return new BuiltSectionMeshParts(mergedBuffer, vertexCounts); + return new BuiltSectionMeshParts(mergedBuffer, vertexSegments); } public void destroy() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java index 52236a161e..1e17585537 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java @@ -2,14 +2,11 @@ import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.DynamicTopoData; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.SortData; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.Sorter; -import net.caffeinemc.mods.sodium.client.util.NativeBuffer; -public class ChunkSortOutput extends BuilderTaskOutput implements SortData { - private NativeBuffer indexBuffer; +public class ChunkSortOutput extends BuilderTaskOutput { + private Sorter sorter; private boolean reuseUploadedIndexData; - private DynamicTopoData.DynamicTopoSorter topoSorter; public ChunkSortOutput(RenderSection render, int buildTime) { super(render, buildTime); @@ -17,41 +14,34 @@ public ChunkSortOutput(RenderSection render, int buildTime) { public ChunkSortOutput(RenderSection render, int buildTime, Sorter data) { this(render, buildTime); - this.copyResultFrom(data); + this.setSorter(data); } - public void copyResultFrom(Sorter sorter) { - this.indexBuffer = sorter.getIndexBuffer(); + public void setSorter(Sorter sorter) { + this.sorter = sorter; this.reuseUploadedIndexData = false; - if (sorter instanceof DynamicTopoData.DynamicTopoSorter topoSorterInstance) { - this.topoSorter = topoSorterInstance; - } } - public void markAsReusingUploadedData() { - this.reuseUploadedIndexData = true; + public Sorter getSorter() { + return this.sorter; } - @Override - public NativeBuffer getIndexBuffer() { - return this.indexBuffer; + public void markAsReusingUploadedData() { + this.reuseUploadedIndexData = true; } - @Override public boolean isReusingUploadedIndexData() { return this.reuseUploadedIndexData; } - public DynamicTopoData.DynamicTopoSorter getTopoSorter() { - return this.topoSorter; + public DynamicTopoData.DynamicTopoSorter getDynamicSorter() { + return this.sorter instanceof DynamicTopoData.DynamicTopoSorter dynamicSorter ? dynamicSorter : null; } - @Override public void destroy() { super.destroy(); - - if (this.indexBuffer != null) { - this.indexBuffer.free(); + if (this.sorter != null) { + this.sorter.destroy(); } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java index 75b7f7064c..a328e552c6 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java @@ -3,6 +3,7 @@ import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; import net.caffeinemc.mods.sodium.client.SodiumClientMod; import net.caffeinemc.mods.sodium.client.render.chunk.ExtendedBlockEntityType; +import net.caffeinemc.mods.sodium.client.render.chunk.DefaultChunkRenderer; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildBuffers; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildContext; @@ -85,7 +86,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke TranslucentGeometryCollector collector; if (SodiumClientMod.options().performance.getSortBehavior() != SortBehavior.OFF) { - collector = new TranslucentGeometryCollector(render.getPosition()); + collector = new TranslucentGeometryCollector(this.render.getPosition()); } else { collector = null; } @@ -149,7 +150,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke } profiler.popPush("mesh appenders"); - PlatformLevelRenderHooks.INSTANCE.runChunkMeshAppenders(renderContext.getRenderers(), type -> buffers.get(DefaultMaterials.forRenderLayer(type)).asFallbackVertexConsumer(DefaultMaterials.forRenderLayer(type), collector), + PlatformLevelRenderHooks.INSTANCE.runChunkMeshAppenders(this.renderContext.getRenderers(), type -> buffers.get(DefaultMaterials.forRenderLayer(type)).asFallbackVertexConsumer(DefaultMaterials.forRenderLayer(type), collector), slice); blockRenderer.release(); @@ -160,12 +161,18 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke } Map meshes = new Reference2ReferenceOpenHashMap<>(); + var visibleSlices = DefaultChunkRenderer.getVisibleFaces( + (int) this.absoluteCameraPos.x(), (int) this.absoluteCameraPos.y(), (int) this.absoluteCameraPos.z(), + this.render.getChunkX(), this.render.getChunkY(), this.render.getChunkZ()); profiler.popPush("meshing"); for (TerrainRenderPass pass : DefaultTerrainRenderPasses.ALL) { - // consolidate all translucent geometry into UNASSIGNED so that it's rendered - // all together if it needs to share an index buffer between the directions - BuiltSectionMeshParts mesh = buffers.createMesh(pass, pass.isTranslucent() && sortType.needsDirectionMixing); + // if the translucent geometry needs to share an index buffer between the directions, + // consolidate all translucent geometry into UNASSIGNED + boolean translucentBehavior = collector != null && pass.isTranslucent(); + boolean forceUnassigned = translucentBehavior && sortType.needsDirectionMixing; + boolean sliceReordering = !translucentBehavior || sortType.allowSliceReordering; + BuiltSectionMeshParts mesh = buffers.createMesh(pass, visibleSlices, forceUnassigned, sliceReordering); if (mesh != null) { meshes.put(pass, mesh); @@ -201,7 +208,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke } else if (translucentData instanceof PresentTranslucentData present) { var sorter = present.getSorter(); sorter.writeIndexBuffer(this, true); - output.copyResultFrom(sorter); + output.setSorter(sorter); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java index ec250afb86..21a1736347 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java @@ -1,13 +1,14 @@ package net.caffeinemc.mods.sodium.client.render.chunk.data; +import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; import net.caffeinemc.mods.sodium.client.util.NativeBuffer; public class BuiltSectionMeshParts { - private final int[] vertexCounts; + private final int[] vertexSegments; private final NativeBuffer buffer; public BuiltSectionMeshParts(NativeBuffer buffer, int[] vertexCounts) { - this.vertexCounts = vertexCounts; + this.vertexSegments = vertexCounts; this.buffer = buffer; } @@ -15,7 +16,17 @@ public NativeBuffer getVertexData() { return this.buffer; } - public int[] getVertexCounts() { - return this.vertexCounts; + public int[] getVertexSegments() { + return this.vertexSegments; + } + + public int[] computeVertexCounts() { + var vertexCounts = new int[ModelQuadFacing.COUNT]; + + for (int i = 0; i < this.vertexSegments.length; i += 2) { + vertexCounts[this.vertexSegments[i + 1]] = this.vertexSegments[i]; + } + + return vertexCounts; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java index bcc184f714..99fbe9e69a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java @@ -1,13 +1,18 @@ package net.caffeinemc.mods.sodium.client.render.chunk.data; +import net.caffeinemc.mods.sodium.client.gl.arena.GlBufferArena; import net.caffeinemc.mods.sodium.client.gl.arena.GlBufferSegment; +import net.caffeinemc.mods.sodium.client.gl.arena.PendingUpload; +import net.caffeinemc.mods.sodium.client.gl.device.CommandList; import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; +import net.caffeinemc.mods.sodium.client.render.chunk.SharedQuadIndexBuffer; import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; import net.caffeinemc.mods.sodium.client.util.UInt32; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Arrays; +import java.util.stream.Stream; /** * The section render data storage stores the gl buffer segments of uploaded @@ -17,20 +22,24 @@ * buffer segments is stored in a natively allocated piece of memory referenced * by {@code pMeshDataArray} and accessed through * {@link SectionRenderDataUnsafe}. - * + *

* When the backing buffer (from the gl buffer arena) is resized, the storage * object is notified and then it updates the changed offsets of the buffer * segments. Since the index data's size and alignment directly corresponds to * that of the vertex data except for the vertex/index scaling of two thirds, * only an offset to the index data within the index data buffer arena is * stored. - * + *

* Index and vertex data storage can be managed separately since they may be * updated independently of each other (in both directions). */ public class SectionRenderDataStorage { private final @Nullable GlBufferSegment[] vertexAllocations; - private final @Nullable GlBufferSegment @Nullable[] elementAllocations; + private final @Nullable GlBufferSegment @Nullable [] elementAllocations; + private @Nullable GlBufferSegment sharedIndexAllocation; + private int sharedIndexCapacity = 0; + private boolean needsSharedIndexUpdate = false; + private final int[] sharedIndexUsage = new int[RenderRegion.REGION_SIZE]; private final long pMeshDataArray; @@ -46,8 +55,7 @@ public SectionRenderDataStorage(boolean storesIndices) { this.pMeshDataArray = SectionRenderDataUnsafe.allocateHeap(RenderRegion.REGION_SIZE); } - public void setVertexData(int localSectionIndex, - GlBufferSegment allocation, int[] vertexCounts) { + public void setVertexData(int localSectionIndex, GlBufferSegment allocation, int[] vertexSegments) { GlBufferSegment prev = this.vertexAllocations[localSectionIndex]; if (prev != null) { @@ -59,25 +67,25 @@ public void setVertexData(int localSectionIndex, var pMeshData = this.getDataPointer(localSectionIndex); int sliceMask = 0; + long facingList = 0; - long vertexOffset = allocation.getOffset(); + for (int i = 0; i < ModelQuadFacing.COUNT; i++) { + var segmentIndex = i << 1; - for (int facingIndex = 0; facingIndex < ModelQuadFacing.COUNT; facingIndex++) { - long vertexCount = vertexCounts[facingIndex]; + int facing = vertexSegments[segmentIndex + 1]; + facingList |= (long) facing << (i * 8); - SectionRenderDataUnsafe.setVertexOffset(pMeshData, facingIndex, - UInt32.downcast(vertexOffset)); - SectionRenderDataUnsafe.setElementCount(pMeshData, facingIndex, - UInt32.downcast((vertexCount >> 2) * 6)); + long vertexCount = UInt32.upcast(vertexSegments[segmentIndex]); + SectionRenderDataUnsafe.setVertexCount(pMeshData, i, vertexCount); if (vertexCount > 0) { - sliceMask |= 1 << facingIndex; + sliceMask |= 1 << facing; } - - vertexOffset += vertexCount; } + SectionRenderDataUnsafe.setBaseVertex(pMeshData, allocation.getOffset()); SectionRenderDataUnsafe.setSliceMask(pMeshData, sliceMask); + SectionRenderDataUnsafe.setFacingList(pMeshData, facingList); } public void setIndexData(int localSectionIndex, GlBufferSegment allocation) { @@ -95,7 +103,88 @@ public void setIndexData(int localSectionIndex, GlBufferSegment allocation) { var pMeshData = this.getDataPointer(localSectionIndex); - SectionRenderDataUnsafe.setBaseElement(pMeshData, allocation.getOffset()); + SectionRenderDataUnsafe.setLocalBaseElement(pMeshData, allocation.getOffset()); + } + + public void setSharedIndexUsage(int localSectionIndex, int newUsage) { + var previousUsage = this.sharedIndexUsage[localSectionIndex]; + if (previousUsage == newUsage) { + return; + } + + // mark for update if usage is down from max (may need to shrink buffer) + // or if usage increased beyond the max (need to grow buffer) + if (newUsage < previousUsage && previousUsage == this.sharedIndexCapacity || + newUsage > this.sharedIndexCapacity || + newUsage > 0 && this.sharedIndexAllocation == null) { + this.needsSharedIndexUpdate = true; + } else { + // just set the base element since no update is happening + var sharedBaseElement = this.sharedIndexAllocation.getOffset(); + var pMeshData = this.getDataPointer(localSectionIndex); + SectionRenderDataUnsafe.setSharedBaseElement(pMeshData, sharedBaseElement); + } + + this.sharedIndexUsage[localSectionIndex] = newUsage; + } + + public boolean needsSharedIndexUpdate() { + return this.needsSharedIndexUpdate; + } + + /** + * Updates the shared index data buffer to match the current usage. + * + * @param arena The buffer arena to allocate the new buffer from + * @return true if the arena resized itself + */ + public boolean updateSharedIndexData(CommandList commandList, GlBufferArena arena) { + // assumes this.needsSharedIndexUpdate is true when this is called + this.needsSharedIndexUpdate = false; + + // determine the new required capacity + int newCapacity = 0; + for (int i = 0; i < RenderRegion.REGION_SIZE; i++) { + newCapacity = Math.max(newCapacity, this.sharedIndexUsage[i]); + } + if (newCapacity == this.sharedIndexCapacity) { + return false; + } + + this.sharedIndexCapacity = newCapacity; + + // remove the existing allocation and exit if we don't need to create a new one + if (this.sharedIndexAllocation != null) { + this.sharedIndexAllocation.delete(); + this.sharedIndexAllocation = null; + } + if (this.sharedIndexCapacity == 0) { + return false; + } + + // add some base-level capacity to avoid resizing the buffer too often + if (this.sharedIndexCapacity < 128) { + this.sharedIndexCapacity += 32; + } + + // create and upload a new shared index buffer + var buffer = SharedQuadIndexBuffer.createIndexBuffer(SharedQuadIndexBuffer.IndexType.INTEGER, this.sharedIndexCapacity); + var pendingUpload = new PendingUpload(buffer); + var bufferChanged = arena.upload(commandList, Stream.of(pendingUpload)); + this.sharedIndexAllocation = pendingUpload.getResult(); + buffer.free(); + + // only write the base elements now if we're not going to do so again later because of the buffer resize + if (!bufferChanged) { + var sharedBaseElement = this.sharedIndexAllocation.getOffset(); + for (int i = 0; i < RenderRegion.REGION_SIZE; i++) { + if (this.sharedIndexUsage[i] > 0) { + SectionRenderDataUnsafe.setSharedBaseElement(this.getDataPointer(i), sharedBaseElement); + } + } + } + + return bufferChanged; } public void removeData(int localSectionIndex) { @@ -104,6 +193,8 @@ public void removeData(int localSectionIndex) { if (this.elementAllocations != null) { this.removeIndexData(localSectionIndex); } + + this.setSharedIndexUsage(localSectionIndex, 0); } public void removeVertexData(int localSectionIndex) { @@ -127,7 +218,7 @@ private void removeVertexData(int localSectionIndex, boolean retainIndexData) { SectionRenderDataUnsafe.clear(pMeshData); if (retainIndexData) { - SectionRenderDataUnsafe.setBaseElement(pMeshData, baseElement); + SectionRenderDataUnsafe.setLocalBaseElement(pMeshData, baseElement); } } @@ -160,27 +251,27 @@ private void updateMeshes(int sectionIndex) { return; } - long offset = allocation.getOffset(); var data = this.getDataPointer(sectionIndex); - - for (int facing = 0; facing < ModelQuadFacing.COUNT; facing++) { - SectionRenderDataUnsafe.setVertexOffset(data, facing, offset); - - var count = SectionRenderDataUnsafe.getElementCount(data, facing); - offset += (count / 6) * 4; // convert elements back into vertices - } + long offset = allocation.getOffset(); + SectionRenderDataUnsafe.setBaseVertex(data, offset); } public void onIndexBufferResized() { - if (this.elementAllocations == null) { - return; + long sharedBaseElement = 0; + if (this.sharedIndexAllocation != null) { + sharedBaseElement = this.sharedIndexAllocation.getOffset(); } - for (int sectionIndex = 0; sectionIndex < RenderRegion.REGION_SIZE; sectionIndex++) { - var allocation = this.elementAllocations[sectionIndex]; + for (int i = 0; i < RenderRegion.REGION_SIZE; i++) { + if (this.sharedIndexUsage[i] > 0) { + // update index sharing sections to use the new shared index buffer's offset + SectionRenderDataUnsafe.setSharedBaseElement(this.getDataPointer(i), sharedBaseElement); + } else if (this.elementAllocations != null) { + var allocation = this.elementAllocations[i]; - if (allocation != null) { - SectionRenderDataUnsafe.setBaseElement(this.getDataPointer(sectionIndex), allocation.getOffset()); + if (allocation != null) { + SectionRenderDataUnsafe.setLocalBaseElement(this.getDataPointer(i), allocation.getOffset()); + } } } } @@ -196,6 +287,10 @@ public void delete() { deleteAllocations(this.elementAllocations); } + if (this.sharedIndexAllocation != null) { + this.sharedIndexAllocation.delete(); + } + SectionRenderDataUnsafe.freeHeap(this.pMeshDataArray); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java index 665973beff..63176b9ec2 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java @@ -16,15 +16,13 @@ // Please never try to write performance critical code in Java. This is what it will do to you. And you will still be // three times slower than the most naive solution in literally any other language that LLVM can compile. -// struct SectionRenderData { // 64 bytes -// base_element: u32 -// mask: u32, -// ranges: [VertexRange; 7] -// } -// -// struct VertexRange { // 8 bytes -// offset: u32, -// count: u32 +// struct SectionRenderData { // 48 bytes +// base_element: u32, +// base_vertex: u32, +// is_local_index: u8, +// facing_list: u56, +// slice_mask: u32, +// vertex_count: [u32; 7] // } public class SectionRenderDataUnsafe { @@ -35,12 +33,14 @@ public class SectionRenderDataUnsafe { * Otherwise, indices should be sourced from the index buffer for the render region using the specified offset. */ private static final long OFFSET_BASE_ELEMENT = 0; + private static final long OFFSET_BASE_VERTEX = 4; + private static final long OFFSET_FACING_LIST = 8; + private static final long OFFSET_IS_LOCAL_INDEX = 15; + private static final long OFFSET_SLICE_MASK = 16; + private static final long OFFSET_ELEMENT_COUNTS = 20; - private static final long OFFSET_SLICE_MASK = 4; - private static final long OFFSET_SLICE_RANGES = 8; - - private static final long ALIGNMENT = 64; - private static final long STRIDE = 64; // cache-line friendly! :) + private static final long ALIGNMENT = 8; // 8 byte (64 bit) alignment for reading longs + private static final long STRIDE = 48; public static long allocateHeap(int count) { final var bytes = STRIDE * count; @@ -63,6 +63,20 @@ public static long heapPointer(long ptr, int index) { return ptr + (index * STRIDE); } + public static void setLocalBaseElement(long ptr, long value /* Uint32 */) { + MemoryUtil.memPutInt(ptr + OFFSET_BASE_ELEMENT, UInt32.downcast(value)); + MemoryUtil.memPutByte(ptr + OFFSET_IS_LOCAL_INDEX, (byte) 1); + } + + public static void setSharedBaseElement(long ptr, long value /* Uint32 */) { + MemoryUtil.memPutInt(ptr + OFFSET_BASE_ELEMENT, UInt32.downcast(value)); + MemoryUtil.memPutByte(ptr + OFFSET_IS_LOCAL_INDEX, (byte) 0); + } + + public static long getBaseElement(long ptr) { + return Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + OFFSET_BASE_ELEMENT)); + } + public static void setSliceMask(long ptr, int value) { MemoryUtil.memPutInt(ptr + OFFSET_SLICE_MASK, value); } @@ -71,27 +85,31 @@ public static int getSliceMask(long ptr) { return MemoryUtil.memGetInt(ptr + OFFSET_SLICE_MASK); } - public static void setBaseElement(long ptr, long value) { - MemoryUtil.memPutInt(ptr + OFFSET_BASE_ELEMENT, UInt32.downcast(value)); + public static void setFacingList(long ptr, long facingList) { + MemoryUtil.memPutLong(ptr + OFFSET_FACING_LIST, facingList); } - public static long getBaseElement(long ptr) { - return Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + OFFSET_BASE_ELEMENT)); + public static long getFacingList(long ptr) { + return MemoryUtil.memGetLong(ptr + OFFSET_FACING_LIST); + } + + public static boolean isLocalIndex(long ptr) { + return MemoryUtil.memGetByte(ptr + OFFSET_IS_LOCAL_INDEX) != 0; } - public static void setVertexOffset(long ptr, int facing, long value /* Uint32 */) { - MemoryUtil.memPutInt(ptr + OFFSET_SLICE_RANGES + (facing * 8L) + 0L, UInt32.downcast(value)); + public static void setBaseVertex(long ptr, long value /* Uint32 */) { + MemoryUtil.memPutInt(ptr + OFFSET_BASE_VERTEX, UInt32.downcast(value)); } - public static long /* Uint32 */ getVertexOffset(long ptr, int facing) { - return UInt32.upcast(MemoryUtil.memGetInt(ptr + OFFSET_SLICE_RANGES + (facing * 8L) + 0L)); + public static long /* Uint32 */ getBaseVertex(long ptr) { + return UInt32.upcast(MemoryUtil.memGetInt(ptr + OFFSET_BASE_VERTEX)); } - public static void setElementCount(long ptr, int facing, long value /* Uint32 */) { - MemoryUtil.memPutInt(ptr + OFFSET_SLICE_RANGES + (facing * 8L) + 4L, UInt32.downcast(value)); + public static void setVertexCount(long ptr, int index, long count /* Uint32 */) { + MemoryUtil.memPutInt(ptr + OFFSET_ELEMENT_COUNTS + (index * 4), UInt32.downcast(count)); } - public static long /* Uint32 */ getElementCount(long ptr, int facing) { - return UInt32.upcast(MemoryUtil.memGetInt(ptr + OFFSET_SLICE_RANGES + (facing * 8L) + 4L)); + public static long /* Uint32 */ getVertexCount(long ptr, int index) { + return UInt32.upcast(MemoryUtil.memGetInt(ptr + OFFSET_ELEMENT_COUNTS + (index * 4))); } } \ No newline at end of file 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 2b4c704256..cdd6df0d72 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 @@ -20,6 +20,7 @@ import net.minecraft.util.profiling.Profiler; import net.minecraft.util.profiling.ProfilerFiller; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.SharedIndexSorter; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -82,24 +83,34 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect if (mesh != null) { uploads.add(new PendingSectionMeshUpload(result.render, mesh, pass, - new PendingUpload(mesh.getVertexData()))); + new PendingUpload(mesh.getVertexData()))); } } } if (result instanceof ChunkSortOutput indexDataOutput && !indexDataOutput.isReusingUploadedIndexData()) { - var buffer = indexDataOutput.getIndexBuffer(); - - // when a non-present TranslucentData is used like NoData, the indexBuffer is null - if (buffer == null) { - continue; - } + var sorter = indexDataOutput.getSorter(); + if (sorter instanceof SharedIndexSorter sharedIndexSorter) { + var storage = region.createStorage(DefaultTerrainRenderPasses.TRANSLUCENT); + storage.removeIndexData(renderSectionIndex); + storage.setSharedIndexUsage(renderSectionIndex, sharedIndexSorter.quadCount()); + } else { + var storage = region.getStorage(DefaultTerrainRenderPasses.TRANSLUCENT); + if (storage != null) { + storage.removeIndexData(renderSectionIndex); + storage.setSharedIndexUsage(renderSectionIndex, 0); + } - indexUploads.add(new PendingSectionIndexBufferUpload(result.render, new PendingUpload(buffer))); + if (sorter == null) { + continue; + } + // when a non-present TranslucentData is used like NoData, the indexBuffer is null + var buffer = sorter.getIndexBuffer(); + if (buffer == null) { + continue; + } - var storage = region.getStorage(DefaultTerrainRenderPasses.TRANSLUCENT); - if (storage != null) { - storage.removeIndexData(renderSectionIndex); + indexUploads.add(new PendingSectionIndexBufferUpload(result.render, new PendingUpload(buffer))); } } } @@ -107,7 +118,9 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect ProfilerFiller profiler = Profiler.get(); // If we have nothing to upload, abort! - if (uploads.isEmpty() && indexUploads.isEmpty()) { + var translucentStorage = region.getStorage(DefaultTerrainRenderPasses.TRANSLUCENT); + var needsSharedIndexUpdate = translucentStorage != null && translucentStorage.needsSharedIndexUpdate(); + if (uploads.isEmpty() && indexUploads.isEmpty() && !needsSharedIndexUpdate) { return; } @@ -130,27 +143,32 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect for (PendingSectionMeshUpload upload : uploads) { var storage = region.createStorage(upload.pass); storage.setVertexData(upload.section.getSectionIndex(), - upload.vertexUpload.getResult(), upload.meshData.getVertexCounts()); + upload.vertexUpload.getResult(), upload.meshData.getVertexSegments()); } } profiler.popPush("upload_indices"); + var indexBufferChanged = false; if (!indexUploads.isEmpty()) { var arena = resources.getIndexArena(); - boolean bufferChanged = arena.upload(commandList, indexUploads.stream() + indexBufferChanged = arena.upload(commandList, indexUploads.stream() .map(upload -> upload.indexBufferUpload)); - if (bufferChanged) { - region.refreshIndexedTesselation(commandList); - } - for (PendingSectionIndexBufferUpload upload : indexUploads) { var storage = region.createStorage(DefaultTerrainRenderPasses.TRANSLUCENT); storage.setIndexData(upload.section.getSectionIndex(), upload.indexBufferUpload.getResult()); } } + if (needsSharedIndexUpdate) { + indexBufferChanged |= translucentStorage.updateSharedIndexData(commandList, resources.getIndexArena()); + } + + if (indexBufferChanged) { + region.refreshIndexedTesselation(commandList); + } + profiler.pop(); } @@ -206,7 +224,6 @@ private record PendingSectionMeshUpload(RenderSection section, BuiltSectionMeshP private record PendingSectionIndexBufferUpload(RenderSection section, PendingUpload indexBufferUpload) { } - private static StagingBuffer createStagingBuffer(CommandList commandList) { if (SodiumClientMod.options().advanced.useAdvancedStagingBuffers && MappedStagingBuffer.isSupported(RenderDevice.INSTANCE)) { return new MappedStagingBuffer(commandList); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java index d00713259a..2980f99365 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java @@ -8,17 +8,17 @@ public enum SortType { /** * The section is fully empty, no index buffer is needed. */ - EMPTY_SECTION(false), + EMPTY_SECTION(false, true), /** * The section has no translucent geometry, no index buffer is needed. */ - NO_TRANSLUCENT(false), + NO_TRANSLUCENT(false, true), /** * No sorting is required and the sort order doesn't matter. */ - NONE(false), + NONE(false, true), /** * There is only one sort order. No active sorting is required, but an initial @@ -45,8 +45,15 @@ public enum SortType { DYNAMIC(true); public final boolean needsDirectionMixing; + public final boolean allowSliceReordering; SortType(boolean needsDirectionMixing) { this.needsDirectionMixing = needsDirectionMixing; + this.allowSliceReordering = false; + } + + SortType(boolean needsDirectionMixing, boolean allowSliceReordering) { + this.needsDirectionMixing = needsDirectionMixing; + this.allowSliceReordering = allowSliceReordering; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java index 2125b23b45..8ba267a3e6 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java @@ -561,7 +561,7 @@ public TranslucentData getTranslucentData( return NoData.forNoTranslucent(this.sectionPos); } - var vertexCounts = translucentMesh.getVertexCounts(); + var vertexCounts = translucentMesh.computeVertexCounts(); // re-use the original translucent data if it's the same. This reduces the // amount of generated and uploaded index data when sections are rebuilt without diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java index 34f923ff02..a8fff490e0 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java @@ -46,21 +46,7 @@ public Sorter getSorter() { public static AnyOrderData fromMesh(int[] vertexCounts, TQuad[] quads, SectionPos sectionPos) { var anyOrderData = new AnyOrderData(sectionPos, vertexCounts, quads.length); - var sorter = new StaticSorter(quads.length); - anyOrderData.sorterOnce = sorter; - var indexBuffer = sorter.getIntBuffer(); - - for (var vertexCount : vertexCounts) { - if (vertexCount <= 0) { - continue; - } - - int count = TranslucentData.vertexCountToQuadCount(vertexCount); - for (int i = 0; i < count; i++) { - TranslucentData.writeQuadVertexIndexes(indexBuffer, i); - } - } - + anyOrderData.sorterOnce = new SharedIndexSorter(quads.length); return anyOrderData; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java index 87539c346d..d184fb4ad3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; -abstract class DynamicSorter extends Sorter { +abstract class DynamicSorter extends PresentSorter { private final int quadCount; DynamicSorter(int quadCount) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/PresentSorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/PresentSorter.java new file mode 100644 index 0000000000..81358a07a4 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/PresentSorter.java @@ -0,0 +1,23 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; + +import net.caffeinemc.mods.sodium.client.util.NativeBuffer; + +public abstract class PresentSorter implements Sorter { + private NativeBuffer indexBuffer; + + @Override + public NativeBuffer getIndexBuffer() { + return this.indexBuffer; + } + + void initBufferWithQuadLength(int quadCount) { + this.indexBuffer = new NativeBuffer(TranslucentData.quadCountToIndexBytes(quadCount)); + } + + @Override + public void destroy() { + if (this.indexBuffer != null) { + this.indexBuffer.free(); + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SharedIndexSorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SharedIndexSorter.java new file mode 100644 index 0000000000..9dc709a761 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SharedIndexSorter.java @@ -0,0 +1,27 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; + +import net.caffeinemc.mods.sodium.client.util.NativeBuffer; + +import java.nio.IntBuffer; + +public record SharedIndexSorter(int quadCount) implements Sorter { + @Override + public NativeBuffer getIndexBuffer() { + return null; + } + + @Override + public IntBuffer getIntBuffer() { + return null; + } + + @Override + public void writeIndexBuffer(CombinedCameraPos cameraPos, boolean initial) { + // no-op + } + + @Override + public void destroy() { + // no-op + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SortData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SortData.java deleted file mode 100644 index c543a82335..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SortData.java +++ /dev/null @@ -1,5 +0,0 @@ -package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; - -public interface SortData extends PresentSortData { - boolean isReusingUploadedIndexData(); -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/Sorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/Sorter.java index 1103773793..545e58a2b9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/Sorter.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/Sorter.java @@ -1,18 +1,7 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; -import net.caffeinemc.mods.sodium.client.util.NativeBuffer; +public interface Sorter extends PresentSortData { + void writeIndexBuffer(CombinedCameraPos cameraPos, boolean initial); -public abstract class Sorter implements PresentSortData { - private NativeBuffer indexBuffer; - - public abstract void writeIndexBuffer(CombinedCameraPos cameraPos, boolean initial); - - @Override - public NativeBuffer getIndexBuffer() { - return this.indexBuffer; - } - - void initBufferWithQuadLength(int quadCount) { - this.indexBuffer = new NativeBuffer(TranslucentData.quadCountToIndexBytes(quadCount)); - } + void destroy(); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticSorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticSorter.java index fe6bc4d133..cbc09e19ed 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticSorter.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticSorter.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; -class StaticSorter extends Sorter { +class StaticSorter extends PresentSorter { StaticSorter(int quadCount) { this.initBufferWithQuadLength(quadCount); }