From 27e531f36479c4de63c36afdc5b8707d96487c16 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Thu, 18 Jan 2024 01:21:41 -0600 Subject: [PATCH] Ensure strict ordering of regions during occlusion culling --- .../chunk/lists/VisibleChunkCollector.java | 38 ++++++++++++----- .../chunk/occlusion/OcclusionCuller.java | 41 ++++++++++--------- 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java index 94d7abed69..72800176e5 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java @@ -1,20 +1,23 @@ package me.jellysquid.mods.sodium.client.render.chunk.lists; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import me.jellysquid.mods.sodium.client.render.chunk.ChunkUpdateType; import me.jellysquid.mods.sodium.client.render.chunk.RenderSection; +import me.jellysquid.mods.sodium.client.render.chunk.occlusion.OcclusionCuller; +import me.jellysquid.mods.sodium.client.render.chunk.region.RenderRegion; -import java.util.ArrayDeque; -import java.util.EnumMap; -import java.util.Map; -import java.util.Queue; -import java.util.function.Consumer; +import java.util.*; -public class VisibleChunkCollector implements Consumer { - private final SortedRenderLists.Builder sortedRenderLists; +public class VisibleChunkCollector implements OcclusionCuller.Visitor { + private final ObjectArrayList sortedRenderLists; private final EnumMap> sortedRebuildLists; + private final int frame; + public VisibleChunkCollector(int frame) { - this.sortedRenderLists = new SortedRenderLists.Builder(frame); + this.frame = frame; + + this.sortedRenderLists = new ObjectArrayList<>(); this.sortedRebuildLists = new EnumMap<>(ChunkUpdateType.class); for (var type : ChunkUpdateType.values()) { @@ -23,8 +26,21 @@ public VisibleChunkCollector(int frame) { } @Override - public void accept(RenderSection section) { - this.sortedRenderLists.add(section); + public void visit(RenderSection section, boolean visible) { + 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); + + this.sortedRenderLists.add(renderList); + } + + if (visible && section.getFlags() != 0) { + renderList.add(section); + } this.addToRebuildLists(section); } @@ -42,7 +58,7 @@ private void addToRebuildLists(RenderSection section) { } public SortedRenderLists createRenderLists() { - return this.sortedRenderLists.build(); + return new SortedRenderLists(this.sortedRenderLists); } public Map> getRebuildLists() { diff --git a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java index 4c832c0e32..27f5803fcd 100644 --- a/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java +++ b/src/main/java/me/jellysquid/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java @@ -3,17 +3,15 @@ import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; import me.jellysquid.mods.sodium.client.render.chunk.RenderSection; import me.jellysquid.mods.sodium.client.render.viewport.CameraTransform; +import me.jellysquid.mods.sodium.client.render.viewport.Viewport; import me.jellysquid.mods.sodium.client.util.collections.DoubleBufferedQueue; import me.jellysquid.mods.sodium.client.util.collections.ReadQueue; import me.jellysquid.mods.sodium.client.util.collections.WriteQueue; -import me.jellysquid.mods.sodium.client.render.viewport.Viewport; import net.minecraft.util.math.ChunkSectionPos; import net.minecraft.util.math.MathHelper; import net.minecraft.world.World; import org.jetbrains.annotations.NotNull; -import java.util.function.Consumer; - public class OcclusionCuller { private final Long2ReferenceMap sections; private final World world; @@ -25,7 +23,7 @@ public OcclusionCuller(Long2ReferenceMap sections, World world) { this.world = world; } - public void findVisible(Consumer visitor, + public void findVisible(Visitor visitor, Viewport viewport, float searchDistance, boolean useOcclusionCulling, @@ -41,7 +39,7 @@ public void findVisible(Consumer visitor, } } - private static void processQueue(Consumer visitor, + private static void processQueue(Visitor visitor, Viewport viewport, float searchDistance, boolean useOcclusionCulling, @@ -52,16 +50,13 @@ private static void processQueue(Consumer visitor, RenderSection section; while ((section = readQueue.dequeue()) != null) { - if (isOutsideRenderDistance(viewport.getTransform(), section, searchDistance)) { - continue; - } + boolean visible = isSectionVisible(section, viewport, searchDistance); + visitor.visit(section, visible); - if (isOutsideFrustum(viewport, section)) { + if (!visible) { continue; } - visitor.accept(section); - int connections; { @@ -84,6 +79,10 @@ private static void processQueue(Consumer visitor, } } + private static boolean isSectionVisible(RenderSection section, Viewport viewport, float maxDistance) { + return isWithinRenderDistance(viewport.getTransform(), section, maxDistance) && isWithinFrustum(viewport, section); + } + private static void visitNeighbors(final WriteQueue queue, RenderSection section, int outgoing, int frame) { // Only traverse into neighbors which are actually present. // This avoids a null-check on each invocation to enqueue, and since the compiler will see that a null @@ -151,7 +150,7 @@ private static int getOutwardDirections(ChunkSectionPos origin, RenderSection se return planes; } - private static boolean isOutsideRenderDistance(CameraTransform camera, RenderSection section, float maxDistance) { + private static boolean isWithinRenderDistance(CameraTransform camera, RenderSection section, float maxDistance) { // origin point of the chunk's bounding box (in view space) int ox = section.getOriginX() - camera.intX; int oy = section.getOriginY() - camera.intY; @@ -165,7 +164,7 @@ private static boolean isOutsideRenderDistance(CameraTransform camera, RenderSec // vanilla's "cylindrical fog" algorithm // max(length(distance.xz), abs(distance.y)) - return (((dx * dx) + (dz * dz)) > (maxDistance * maxDistance)) || (Math.abs(dy) > maxDistance); + return (((dx * dx) + (dz * dz)) < (maxDistance * maxDistance)) || (Math.abs(dy) < maxDistance); } @SuppressWarnings("ManualMinMaxCalculation") // we know what we are doing. @@ -182,11 +181,11 @@ private static int nearestToZero(int min, int max) { // to deal with floating point imprecision during a frustum check (see GH#2132). private static final float CHUNK_SECTION_SIZE = 8.0f /* chunk bounds */ + 1.0f /* maximum model extent */ + 0.125f /* epsilon */; - public static boolean isOutsideFrustum(Viewport viewport, RenderSection section) { - return !viewport.isBoxVisible(section.getCenterX(), section.getCenterY(), section.getCenterZ(), CHUNK_SECTION_SIZE); + public static boolean isWithinFrustum(Viewport viewport, RenderSection section) { + return viewport.isBoxVisible(section.getCenterX(), section.getCenterY(), section.getCenterZ(), CHUNK_SECTION_SIZE); } - private void init(Consumer visitor, + private void init(Visitor visitor, WriteQueue queue, Viewport viewport, float searchDistance, @@ -208,7 +207,7 @@ private void init(Consumer visitor, } } - private void initWithinWorld(Consumer visitor, WriteQueue queue, Viewport viewport, boolean useOcclusionCulling, int frame) { + private void initWithinWorld(Visitor visitor, WriteQueue queue, Viewport viewport, boolean useOcclusionCulling, int frame) { var origin = viewport.getChunkCoord(); var section = this.getRenderSection(origin.getX(), origin.getY(), origin.getZ()); @@ -219,7 +218,7 @@ private void initWithinWorld(Consumer visitor, WriteQueue queue, private void tryVisitNode(WriteQueue queue, int x, int y, int z, int direction, int frame, Viewport viewport) { RenderSection section = this.getRenderSection(x, y, z); - if (section == null || isOutsideFrustum(viewport, section)) { + if (section == null || isWithinFrustum(viewport, section)) { return; } @@ -303,4 +302,8 @@ private void tryVisitNode(WriteQueue queue, int x, int y, int z, private RenderSection getRenderSection(int x, int y, int z) { return this.sections.get(ChunkSectionPos.asLong(x, y, z)); } + + public interface Visitor { + void visit(RenderSection section, boolean visible); + } }