Skip to content

Commit

Permalink
Use diamond spiral iteration to enqueue sections in fallback path (#1972
Browse files Browse the repository at this point in the history
)

OcclusionCuller.initOutsideWorldHeight now uses diamond spiral iteration to avoid collecting and sorting sections before enqueuing them.
  • Loading branch information
PepperCode1 authored Aug 9, 2023
1 parent f9dba62 commit 175939b
Showing 1 changed file with 50 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@
import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
import me.jellysquid.mods.sodium.client.render.chunk.RenderSection;
import me.jellysquid.mods.sodium.client.render.viewport.Viewport;
import me.jellysquid.mods.sodium.client.util.sorting.MergeSort;
import net.minecraft.client.render.Camera;
import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class OcclusionCuller {
Expand All @@ -38,7 +35,7 @@ public void searchChunks(Consumer<RenderSection> visitor,

final ArrayDeque<RenderSection> queue = this.queue;

this.init(queue, viewport, camera, origin, searchDistance, frame);
this.init(queue, viewport, origin, searchDistance, frame);

while ((section = queue.poll()) != null) {
if (origin.getX() != section.getChunkX() || origin.getY() != section.getChunkY() || origin.getZ() != section.getChunkZ()) {
Expand Down Expand Up @@ -127,18 +124,17 @@ private static double getClosestVertexDistanceToCamera(Vec3d camera, ChunkSectio

private void init(ArrayDeque<RenderSection> queue,
Viewport viewport,
Camera camera,
ChunkSectionPos origin,
double searchDistance,
int frame)
{
if (origin.getY() < this.world.getBottomSectionCoord()) {
// above the world
this.initOutsideWorldHeight(queue, viewport, camera, origin, searchDistance, frame,
// below the world
this.initOutsideWorldHeight(queue, viewport, origin, searchDistance, frame,
this.world.getBottomSectionCoord(), GraphDirection.DOWN);
} else if (origin.getY() >= this.world.getTopSectionCoord()) {
// below the world
this.initOutsideWorldHeight(queue, viewport, camera, origin, searchDistance, frame,
// above the world
this.initOutsideWorldHeight(queue, viewport, origin, searchDistance, frame,
this.world.getTopSectionCoord() - 1, GraphDirection.UP);
} else {
var node = this.getRenderSection(origin.getX(), origin.getY(), origin.getZ());
Expand All @@ -150,53 +146,72 @@ private void init(ArrayDeque<RenderSection> queue,
}
}

// Enqueues sections that are inside the viewport using diamond spiral iteration to avoid sorting and ensure a
// consistent order. Innermost layers are enqueued first. Within each layer, iteration starts at the northernmost
// section and proceeds counterclockwise (N->W->S->E).
private void initOutsideWorldHeight(ArrayDeque<RenderSection> queue,
Viewport viewport,
Camera camera,
ChunkSectionPos origin,
double searchDistance,
int frame,
int height,
int direction)
{
List<RenderSection> sections = new ArrayList<>();
int radius = MathHelper.ceil(searchDistance / 16.0D);

for (int x = -radius; x <= radius; x++) {
for (int z = -radius; z <= radius; z++) {
RenderSection section = this.getRenderSection(origin.getX() + x, height, origin.getZ() + z);
int originX = origin.getX();
int originZ = origin.getZ();

if (section == null || section.isOutsideViewport(viewport)) {
continue;
}
// Layer 0
this.enqueue(queue, originX, height, originZ, direction, frame, viewport);

// Complete layers, excluding layer 0
for (int layer = 1; layer <= radius; layer++) {
for (int z = -layer; z < layer; z++) {
int x = Math.abs(z) - layer;
this.enqueue(queue, originX + x, height, originZ + z, direction, frame, viewport);
}

sections.add(section);
for (int z = layer; z > -layer; z--) {
int x = layer - Math.abs(z);
this.enqueue(queue, originX + x, height, originZ + z, direction, frame, viewport);
}
}

if (!sections.isEmpty()) {
enqueueAll(queue, sections, camera, direction, frame);
// Incomplete layers
for (int layer = radius + 1; layer <= 2 * radius; layer++) {
int l = layer - radius;

for (int z = -radius; z <= -l; z++) {
int x = -z - layer;
this.enqueue(queue, originX + x, height, originZ + z, direction, frame, viewport);
}

for (int z = l; z <= radius; z++) {
int x = z - layer;
this.enqueue(queue, originX + x, height, originZ + z, direction, frame, viewport);
}

for (int z = radius; z >= l; z--) {
int x = layer - z;
this.enqueue(queue, originX + x, height, originZ + z, direction, frame, viewport);
}

for (int z = -l; z >= -radius; z--) {
int x = layer + z;
this.enqueue(queue, originX + x, height, originZ + z, direction, frame, viewport);
}
}
}

private static void enqueueAll(ArrayDeque<RenderSection> queue,
List<RenderSection> sections,
Camera camera,
int direction,
int frame)
{
final var distance = new float[sections.size()];
final var origin = camera.getBlockPos();
private void enqueue(ArrayDeque<RenderSection> queue, int x, int y, int z, int direction, int frame, Viewport viewport) {
RenderSection section = this.getRenderSection(x, y, z);

for (int index = 0; index < sections.size(); index++) {
var section = sections.get(index);
distance[index] = -section.getSquaredDistance(origin); // sort by closest to camera
if (section == null || section.isOutsideViewport(viewport)) {
return;
}

// TODO: avoid indirect sort via indices
for (int index : MergeSort.mergeSort(distance)) {
enqueue(queue, sections.get(index), 1 << direction, frame);
}
enqueue(queue, section, 1 << direction, frame);
}

private RenderSection getRenderSection(int x, int y, int z) {
Expand Down

0 comments on commit 175939b

Please sign in to comment.