From 446dddcf1bc1f35c89d20a996129d21f9c4f038f Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 16 Feb 2025 05:18:54 +0100 Subject: [PATCH] Handle camera movement in draw batch cache invalidation (#3028) Fixes draw batch cache invalidation by also taking the effect of relative camera position on which facings are rendered into account. --- .../render/chunk/lists/ChunkRenderList.java | 69 +++++++++++++------ 1 file changed, 49 insertions(+), 20 deletions(-) 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 b50b9c2026..4d6779c576 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 @@ -17,9 +17,13 @@ public class ChunkRenderList { private final RenderRegion region; private final byte[] sectionsWithGeometry = new byte[RenderRegion.REGION_SIZE]; - private final byte[] prevSectionsWithGeometry = new byte[RenderRegion.REGION_SIZE]; + private final long[] sectionsWithGeometryMap = new long[RenderRegion.REGION_SIZE / Long.SIZE]; + private final long[] prevSectionsWithGeometryMap = new long[RenderRegion.REGION_SIZE / Long.SIZE]; private int sectionsWithGeometryCount = 0; private int prevSectionsWithGeometryCount = 0; + private int lastRelativeCameraSectionX; + private int lastRelativeCameraSectionY; + private int lastRelativeCameraSectionZ; private final byte[] sectionsWithSprites = new byte[RenderRegion.REGION_SIZE]; private int sectionsWithSpritesCount = 0; @@ -37,6 +41,7 @@ public ChunkRenderList(RenderRegion region) { public void reset(int frame) { this.prevSectionsWithGeometryCount = this.sectionsWithGeometryCount; + Arrays.fill(this.sectionsWithGeometryMap, 0L); this.sectionsWithGeometryCount = 0; this.sectionsWithSpritesCount = 0; @@ -50,35 +55,57 @@ public void reset(int frame) { private static final int SORTING_HISTOGRAM_SIZE = RenderRegion.REGION_WIDTH + RenderRegion.REGION_HEIGHT + RenderRegion.REGION_LENGTH - 2; public void prepareForRender(SectionPos cameraPos, int[] sortItems) { - this.sortSections(cameraPos, sortItems); + // The relative coordinates are clamped to one section larger than the region bounds to also capture cache invalidation that happens + // when the camera moves from outside the region to inside the region (when seen on all axes independently). + // This type of cache invalidation stems from different facings of sections being rendered if the camera is aligned with them on an axis. + // For sorting only the position clamped to inside the region is used. + int relativeCameraSectionX = Mth.clamp(cameraPos.getX() - this.region.getChunkX(), -1, RenderRegion.REGION_WIDTH); + int relativeCameraSectionY = Mth.clamp(cameraPos.getY() - this.region.getChunkY(), -1, RenderRegion.REGION_HEIGHT); + int relativeCameraSectionZ = Mth.clamp(cameraPos.getZ() - this.region.getChunkZ(), -1, RenderRegion.REGION_LENGTH); // invalidate batch cache if the render list changed if (this.prevSectionsWithGeometryCount != this.sectionsWithGeometryCount || - !Arrays.equals(this.sectionsWithGeometry, this.prevSectionsWithGeometry)) { + relativeCameraSectionX != this.lastRelativeCameraSectionX || + relativeCameraSectionY != this.lastRelativeCameraSectionY || + relativeCameraSectionZ != this.lastRelativeCameraSectionZ || + !Arrays.equals(this.sectionsWithGeometryMap, this.prevSectionsWithGeometryMap)) { + // reset cache invalidation, the newly built batches will remain valid until the next change this.region.clearAllCachedBatches(); - // reset cache invalidation, the newly built batch will remain valid until the next change this.prevSectionsWithGeometryCount = this.sectionsWithGeometryCount; - System.arraycopy(this.sectionsWithGeometry, 0, this.prevSectionsWithGeometry, 0, RenderRegion.REGION_SIZE); + System.arraycopy(this.sectionsWithGeometryMap, 0, this.prevSectionsWithGeometryMap, 0, this.sectionsWithGeometryMap.length); + this.lastRelativeCameraSectionX = relativeCameraSectionX; + this.lastRelativeCameraSectionY = relativeCameraSectionY; + this.lastRelativeCameraSectionZ = relativeCameraSectionZ; + + this.sortSections(relativeCameraSectionX, relativeCameraSectionY, relativeCameraSectionZ, sortItems); } } - public void sortSections(SectionPos cameraPos, int[] sortItems) { - var cameraX = Mth.clamp(cameraPos.getX() - this.region.getChunkX(), 0, RenderRegion.REGION_WIDTH - 1); - var cameraY = Mth.clamp(cameraPos.getY() - this.region.getChunkY(), 0, RenderRegion.REGION_HEIGHT - 1); - var cameraZ = Mth.clamp(cameraPos.getZ() - this.region.getChunkZ(), 0, RenderRegion.REGION_LENGTH - 1); + public void sortSections(int relativeCameraSectionX, int relativeCameraSectionY, int relativeCameraSectionZ, int[] sortItems) { + relativeCameraSectionX = Mth.clamp(relativeCameraSectionX, 0, RenderRegion.REGION_WIDTH - 1); + relativeCameraSectionY = Mth.clamp(relativeCameraSectionY, 0, RenderRegion.REGION_HEIGHT - 1); + relativeCameraSectionZ = Mth.clamp(relativeCameraSectionZ, 0, RenderRegion.REGION_LENGTH - 1); int[] histogram = new int[SORTING_HISTOGRAM_SIZE]; - for (int i = 0; i < this.sectionsWithGeometryCount; i++) { - var index = this.sectionsWithGeometry[i] & 0xFF; // makes sure the byte -> int conversion is unsigned - var x = Math.abs(LocalSectionIndex.unpackX(index) - cameraX); - var y = Math.abs(LocalSectionIndex.unpackY(index) - cameraY); - var z = Math.abs(LocalSectionIndex.unpackZ(index) - cameraZ); - - var distance = x + y + z; - histogram[distance]++; - sortItems[i] = distance << 8 | index; + this.sectionsWithGeometryCount = 0; + for (int mapIndex = 0; mapIndex < this.sectionsWithGeometryMap.length; mapIndex++) { + var map = this.sectionsWithGeometryMap[mapIndex]; + var mapOffset = mapIndex << 6; + + while (map != 0) { + var index = Long.numberOfTrailingZeros(map) + mapOffset; + map &= map - 1; + + var x = Math.abs(LocalSectionIndex.unpackX(index) - relativeCameraSectionX); + var y = Math.abs(LocalSectionIndex.unpackY(index) - relativeCameraSectionY); + var z = Math.abs(LocalSectionIndex.unpackZ(index) - relativeCameraSectionZ); + + var distance = x + y + z; + histogram[distance]++; + sortItems[this.sectionsWithGeometryCount++] = distance << 8 | index; + } } // prefix sum to calculate indexes @@ -103,8 +130,10 @@ 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) == 1) { + this.sectionsWithGeometryMap[index >> 6] |= 1L << (index & 0b111111); + this.sectionsWithGeometryCount++; + } this.sectionsWithSprites[this.sectionsWithSpritesCount] = (byte) index; this.sectionsWithSpritesCount += (flags >>> RenderSectionFlags.HAS_ANIMATED_SPRITES) & 1;