diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java index 0103850155..3ffa67f27e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java @@ -69,9 +69,9 @@ public RenderSection(RenderRegion region, int chunkX, int chunkY, int chunkZ) { this.chunkY = chunkY; this.chunkZ = chunkZ; - int rX = this.getChunkX() & (RenderRegion.REGION_WIDTH - 1); - int rY = this.getChunkY() & (RenderRegion.REGION_HEIGHT - 1); - int rZ = this.getChunkZ() & (RenderRegion.REGION_LENGTH - 1); + int rX = this.getChunkX() & RenderRegion.REGION_WIDTH_M; + int rY = this.getChunkY() & RenderRegion.REGION_HEIGHT_M; + int rZ = this.getChunkZ() & RenderRegion.REGION_LENGTH_M; this.sectionIndex = LocalSectionIndex.pack(rX, rY, rZ); 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 516255ecac..f6afc5f05a 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 @@ -146,7 +146,7 @@ private void createTerrainRenderList(Camera camera, Viewport viewport, int frame this.occlusionCuller.findVisible(visitor, viewport, searchDistance, useOcclusionCulling, frame); - this.renderLists = visitor.createRenderLists(); + this.renderLists = visitor.createRenderLists(viewport); this.taskLists = visitor.getRebuildLists(); } 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..6212573773 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 @@ -1,11 +1,14 @@ package net.caffeinemc.mods.sodium.client.render.chunk.lists; +import net.caffeinemc.mods.sodium.client.render.chunk.LocalSectionIndex; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionFlags; +import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; +import net.caffeinemc.mods.sodium.client.render.viewport.CameraTransform; +import net.caffeinemc.mods.sodium.client.util.iterator.ByteArrayIterator; import net.caffeinemc.mods.sodium.client.util.iterator.ByteIterator; import net.caffeinemc.mods.sodium.client.util.iterator.ReversibleByteArrayIterator; -import net.caffeinemc.mods.sodium.client.util.iterator.ByteArrayIterator; -import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; +import net.minecraft.util.Mth; import org.jetbrains.annotations.Nullable; public class ChunkRenderList { @@ -37,6 +40,39 @@ public void reset(int frame) { this.lastVisibleFrame = frame; } + // clamping the relative camera position to the region bounds means there can only be very few different distances + private static final int SORTING_HISTOGRAM_SIZE = RenderRegion.REGION_WIDTH + RenderRegion.REGION_HEIGHT + RenderRegion.REGION_LENGTH - 2; + + public void sortSections(CameraTransform transform, int[] sortItems) { + var cameraX = Mth.clamp((transform.intX >> 4) - this.region.getChunkX(), 0, RenderRegion.REGION_WIDTH - 1); + var cameraY = Mth.clamp((transform.intY >> 4) - this.region.getChunkY(), 0, RenderRegion.REGION_HEIGHT - 1); + var cameraZ = Mth.clamp((transform.intZ >> 4) - this.region.getChunkZ(), 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; + } + + // prefix sum to calculate indexes + for (int i = 1; i < SORTING_HISTOGRAM_SIZE; i++) { + histogram[i] += histogram[i - 1]; + } + + for (int i = 0; i < this.sectionsWithGeometryCount; i++) { + var item = sortItems[i]; + var distance = item >>> 8; + this.sectionsWithGeometry[--histogram[distance]] = (byte) item; + } + } + public void add(RenderSection render) { if (this.size >= RenderRegion.REGION_SIZE) { throw new ArrayIndexOutOfBoundsException("Render list is full"); 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..080530bf11 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 @@ -1,9 +1,7 @@ package net.caffeinemc.mods.sodium.client.render.chunk.lists; import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.util.iterator.ReversibleObjectArrayIterator; -import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; /** * Stores one render list of sections per region, sorted by the order in which @@ -27,44 +25,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/lists/VisibleChunkCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java index 5ed657795f..1eda00204c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java @@ -1,10 +1,12 @@ package net.caffeinemc.mods.sodium.client.render.chunk.lists; +import it.unimi.dsi.fastutil.ints.IntArrays; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.caffeinemc.mods.sodium.client.render.chunk.ChunkUpdateType; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.OcclusionCuller; import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; +import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; import java.util.*; @@ -30,22 +32,22 @@ public VisibleChunkCollector(int frame) { } @Override - public void visit(RenderSection section, boolean visible) { - RenderRegion region = section.getRegion(); - ChunkRenderList renderList = region.getRenderList(); + public void visit(RenderSection section) { + // only process section (and associated render list) if it has content that needs rendering + if (section.getFlags() != 0) { + RenderRegion region = section.getRegion(); + ChunkRenderList renderList = 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 (renderList.getLastVisibleFrame() != this.frame) { - renderList.reset(this.frame); + if (renderList.getLastVisibleFrame() != this.frame) { + renderList.reset(this.frame); - this.sortedRenderLists.add(renderList); - } + this.sortedRenderLists.add(renderList); + } - if (visible && section.getFlags() != 0) { renderList.add(section); } + // always add to rebuild lists though, because it might just not be built yet this.addToRebuildLists(section); } @@ -61,8 +63,42 @@ private void addToRebuildLists(RenderSection section) { } } - public SortedRenderLists createRenderLists() { - return new SortedRenderLists(this.sortedRenderLists); + private static int[] sortItems = new int[RenderRegion.REGION_SIZE]; + + public SortedRenderLists createRenderLists(Viewport viewport) { + // sort the regions by distance to fix rare region ordering bugs + var transform = viewport.getTransform(); + var cameraX = transform.intX >> (4 + RenderRegion.REGION_WIDTH_SH); + var cameraY = transform.intY >> (4 + RenderRegion.REGION_HEIGHT_SH); + var cameraZ = transform.intZ >> (4 + RenderRegion.REGION_LENGTH_SH); + var size = this.sortedRenderLists.size(); + + if (sortItems.length < size) { + sortItems = new int[size]; + } + + for (var i = 0; i < size; i++) { + var region = this.sortedRenderLists.get(i).getRegion(); + var x = Math.abs(region.getX() - cameraX); + var y = Math.abs(region.getY() - cameraY); + var z = Math.abs(region.getZ() - cameraZ); + sortItems[i] = (x + y + z) << 16 | i; + } + + IntArrays.unstableSort(sortItems, 0, size); + + var sorted = new ObjectArrayList(size); + for (var i = 0; i < size; i++) { + var key = sortItems[i]; + var renderList = this.sortedRenderLists.get(key & 0xFFFF); + sorted.add(renderList); + } + + for (var list : sorted) { + list.sortSections(transform, sortItems); + } + + return new SortedRenderLists(sorted); } public Map> getRebuildLists() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java index 6b6f6c264a..41b125d200 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java @@ -50,13 +50,12 @@ private static void processQueue(Visitor visitor, RenderSection section; while ((section = readQueue.dequeue()) != null) { - boolean visible = isSectionVisible(section, viewport, searchDistance); - visitor.visit(section, visible); - - if (!visible) { + if (!isSectionVisible(section, viewport, searchDistance)) { continue; } + visitor.visit(section); + int connections; { @@ -249,7 +248,7 @@ private void initWithinWorld(Visitor visitor, WriteQueue queue, V section.setLastVisibleFrame(frame); section.setIncomingDirections(GraphDirectionSet.NONE); - visitor.visit(section, true); + visitor.visit(section); int outgoing; @@ -335,6 +334,6 @@ private RenderSection getRenderSection(int x, int y, int z) { } public interface Visitor { - void visit(RenderSection section, boolean visible); + void visit(RenderSection section); } } 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..29ca64b226 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 @@ -24,13 +24,13 @@ public class RenderRegion { public static final int REGION_HEIGHT = 4; public static final int REGION_LENGTH = 8; - private static final int REGION_WIDTH_M = RenderRegion.REGION_WIDTH - 1; - private static final int REGION_HEIGHT_M = RenderRegion.REGION_HEIGHT - 1; - private static final int REGION_LENGTH_M = RenderRegion.REGION_LENGTH - 1; + public static final int REGION_WIDTH_M = RenderRegion.REGION_WIDTH - 1; + public static final int REGION_HEIGHT_M = RenderRegion.REGION_HEIGHT - 1; + public static final int REGION_LENGTH_M = RenderRegion.REGION_LENGTH - 1; - protected static final int REGION_WIDTH_SH = Integer.bitCount(REGION_WIDTH_M); - protected static final int REGION_HEIGHT_SH = Integer.bitCount(REGION_HEIGHT_M); - protected static final int REGION_LENGTH_SH = Integer.bitCount(REGION_LENGTH_M); + public static final int REGION_WIDTH_SH = Integer.bitCount(REGION_WIDTH_M); + public static final int REGION_HEIGHT_SH = Integer.bitCount(REGION_HEIGHT_M); + public static final int REGION_LENGTH_SH = Integer.bitCount(REGION_LENGTH_M); public static final int REGION_SIZE = REGION_WIDTH * REGION_HEIGHT * REGION_LENGTH; @@ -64,6 +64,18 @@ public static long key(int x, int y, int z) { return SectionPos.asLong(x, y, z); } + public int getX() { + return this.x; + } + + public int getY() { + return this.y; + } + + public int getZ() { + return this.z; + } + public int getChunkX() { return this.x << REGION_WIDTH_SH; }