Skip to content

Commit

Permalink
Ensure strict ordering of regions during occlusion culling
Browse files Browse the repository at this point in the history
  • Loading branch information
jellysquid3 committed Jan 18, 2024
1 parent 53a673d commit 27e531f
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -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<RenderSection> {
private final SortedRenderLists.Builder sortedRenderLists;
public class VisibleChunkCollector implements OcclusionCuller.Visitor {
private final ObjectArrayList<ChunkRenderList> sortedRenderLists;
private final EnumMap<ChunkUpdateType, ArrayDeque<RenderSection>> 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()) {
Expand All @@ -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);
}
Expand All @@ -42,7 +58,7 @@ private void addToRebuildLists(RenderSection section) {
}

public SortedRenderLists createRenderLists() {
return this.sortedRenderLists.build();
return new SortedRenderLists(this.sortedRenderLists);
}

public Map<ChunkUpdateType, ArrayDeque<RenderSection>> getRebuildLists() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<RenderSection> sections;
private final World world;
Expand All @@ -25,7 +23,7 @@ public OcclusionCuller(Long2ReferenceMap<RenderSection> sections, World world) {
this.world = world;
}

public void findVisible(Consumer<RenderSection> visitor,
public void findVisible(Visitor visitor,
Viewport viewport,
float searchDistance,
boolean useOcclusionCulling,
Expand All @@ -41,7 +39,7 @@ public void findVisible(Consumer<RenderSection> visitor,
}
}

private static void processQueue(Consumer<RenderSection> visitor,
private static void processQueue(Visitor visitor,
Viewport viewport,
float searchDistance,
boolean useOcclusionCulling,
Expand All @@ -52,16 +50,13 @@ private static void processQueue(Consumer<RenderSection> 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;

{
Expand All @@ -84,6 +79,10 @@ private static void processQueue(Consumer<RenderSection> 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<RenderSection> 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
Expand Down Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -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<RenderSection> visitor,
private void init(Visitor visitor,
WriteQueue<RenderSection> queue,
Viewport viewport,
float searchDistance,
Expand All @@ -208,7 +207,7 @@ private void init(Consumer<RenderSection> visitor,
}
}

private void initWithinWorld(Consumer<RenderSection> visitor, WriteQueue<RenderSection> queue, Viewport viewport, boolean useOcclusionCulling, int frame) {
private void initWithinWorld(Visitor visitor, WriteQueue<RenderSection> queue, Viewport viewport, boolean useOcclusionCulling, int frame) {
var origin = viewport.getChunkCoord();
var section = this.getRenderSection(origin.getX(), origin.getY(), origin.getZ());

Expand All @@ -219,7 +218,7 @@ private void initWithinWorld(Consumer<RenderSection> visitor, WriteQueue<RenderS
section.setLastVisibleFrame(frame);
section.setIncomingDirections(GraphDirectionSet.NONE);

visitor.accept(section);
visitor.visit(section, true);

int outgoing;

Expand Down Expand Up @@ -293,7 +292,7 @@ private void initOutsideWorldHeight(WriteQueue<RenderSection> queue,
private void tryVisitNode(WriteQueue<RenderSection> 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;
}

Expand All @@ -303,4 +302,8 @@ private void tryVisitNode(WriteQueue<RenderSection> 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);
}
}

0 comments on commit 27e531f

Please sign in to comment.