Skip to content

Commit

Permalink
Implement optional CPU-based translucency sorting on 1.16
Browse files Browse the repository at this point in the history
The distance sort implementation is derived from https://github.com/spoorn/sodium-forge,
but most of the scheduling, uploading, and buffer handling logic has
been completely rewritten for simplicity and improved performance
  • Loading branch information
embeddedt committed Oct 12, 2023
1 parent dd4a93f commit bfadbed
Show file tree
Hide file tree
Showing 20 changed files with 569 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,15 @@ public static OptionPage advanced() {
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build()
)
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(new TranslatableText("sodium.options.translucency_sorting.name"))
.setTooltip(new TranslatableText("sodium.options.translucency_sorting.tooltip"))
.setControl(TickBoxControl::new)
.setBinding((opts, value) -> opts.advanced.translucencySorting = value, opts -> opts.advanced.translucencySorting)
.setImpact(OptionImpact.MEDIUM)
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
.build()
)
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
.setName(new TranslatableText("sodium.options.use_entity_culling.name"))
.setTooltip(new TranslatableText("sodium.options.use_entity_culling.tooltip"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public static class AdvancedSettings {
public boolean useBlockFaceCulling = true;
public boolean allowDirectMemoryAccess = true;
public boolean ignoreDriverBlacklist = false;
public boolean translucencySorting = false;
}

public static class PerformanceSettings {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ public void updateChunks(Camera camera, Frustum frustum, boolean hasForcedFrustu
}

Vec3d pos = camera.getPos();

this.chunkRenderManager.setCameraPosition(pos.x, pos.y, pos.z);

float pitch = camera.getPitch();
float yaw = camera.getYaw();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

import me.jellysquid.mods.sodium.client.gl.device.CommandList;

import java.nio.ByteBuffer;

public abstract class ChunkGraphicsState {
private final int x, y, z;

private ByteBuffer translucencyData;

protected ChunkGraphicsState(ChunkRenderContainer<?> container) {
this.x = container.getRenderX();
this.y = container.getRenderY();
Expand All @@ -24,4 +28,12 @@ public int getY() {
public int getZ() {
return this.z;
}

public ByteBuffer getTranslucencyData() {
return this.translucencyData;
}

public void setTranslucencyData(ByteBuffer data) {
this.translucencyData = data;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ public class ChunkRenderContainer<T extends ChunkGraphicsState> {
private boolean needsRebuild;
private boolean needsImportantRebuild;

private boolean needsSort;

private boolean tickable;
private int id;

private boolean rebuildableForTranslucents;

public ChunkRenderContainer(ChunkRenderBackend<T> backend, SodiumWorldRenderer worldRenderer, int chunkX, int chunkY, int chunkZ, ChunkRenderColumn<T> column) {
this.worldRenderer = worldRenderer;

Expand All @@ -43,6 +47,7 @@ public ChunkRenderContainer(ChunkRenderBackend<T> backend, SodiumWorldRenderer w

//noinspection unchecked
this.graphicsStates = (T[]) Array.newInstance(backend.getGraphicsStateType(), BlockRenderPass.COUNT);
this.rebuildableForTranslucents = false;
this.column = column;
}

Expand All @@ -53,6 +58,7 @@ public ChunkRenderContainer(ChunkRenderBackend<T> backend, SodiumWorldRenderer w
public void cancelRebuildTask() {
this.needsRebuild = false;
this.needsImportantRebuild = false;
this.needsSort = false;

if (this.rebuildTask != null) {
this.rebuildTask.cancel(false);
Expand All @@ -78,6 +84,10 @@ public boolean needsImportantRebuild() {
return this.needsImportantRebuild;
}

public boolean needsSort() {
return this.needsSort;
}

/**
* Deletes all data attached to this render and drops any pending tasks. This should be used when the render falls
* out of view or otherwise needs to be destroyed. After the render has been destroyed, the object can no longer
Expand All @@ -102,6 +112,15 @@ private void deleteGraphicsState() {
}
}

public boolean shouldRebuildForTranslucents() {
return this.rebuildableForTranslucents;
}

public void setRebuildForTranslucents(boolean flag) {
this.rebuildableForTranslucents = flag;
}


public void setData(ChunkRenderData info) {
if (info == null) {
throw new NullPointerException("Mesh information must not be null");
Expand All @@ -123,6 +142,17 @@ public boolean scheduleRebuild(boolean important) {

this.needsImportantRebuild = important;
this.needsRebuild = true;
this.needsSort = false;

return changed;
}

public boolean scheduleSort(boolean important) {
if (this.needsRebuild)
return false;

boolean changed = !this.needsSort;
this.needsSort = true;

return changed;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import me.jellysquid.mods.sodium.client.gl.device.RenderDevice;
import me.jellysquid.mods.sodium.client.gl.compat.FogHelper;
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer;
import me.jellysquid.mods.sodium.client.render.chunk.backends.multidraw.MultidrawChunkRenderBackend;
import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuildResult;
import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuilder;
import me.jellysquid.mods.sodium.client.render.chunk.cull.ChunkCuller;
Expand Down Expand Up @@ -72,6 +73,7 @@ public class ChunkRenderManager<T extends ChunkGraphicsState> implements ChunkSt

private final ObjectArrayFIFOQueue<ChunkRenderContainer<T>> importantRebuildQueue = new ObjectArrayFIFOQueue<>();
private final ObjectArrayFIFOQueue<ChunkRenderContainer<T>> rebuildQueue = new ObjectArrayFIFOQueue<>();
private final ObjectArrayFIFOQueue<ChunkRenderContainer<T>> sortQueue = new ObjectArrayFIFOQueue<>();
private final ObjectArrayFIFOQueue<ChunkRenderContainer<T>> unloadQueue = new ObjectArrayFIFOQueue<>();

@SuppressWarnings("unchecked")
Expand All @@ -89,10 +91,14 @@ public class ChunkRenderManager<T extends ChunkGraphicsState> implements ChunkSt
private float cameraX, cameraY, cameraZ;
private boolean dirty;

private final boolean translucencySorting;

private int visibleChunkCount;

private boolean useFogCulling;
private double fogRenderCutoff;

private final int translucencyBlockRenderDistance;

private boolean alwaysDeferChunkUpdates;

Expand All @@ -111,6 +117,9 @@ public ChunkRenderManager(SodiumWorldRenderer renderer, ChunkRenderBackend<T> ba
}

this.culler = new ChunkGraphCuller(world, renderDistance);
this.translucencySorting = SodiumClientMod.options().advanced.translucencySorting;
this.translucencyBlockRenderDistance = Math.min(9216, (renderDistance << 4) * (renderDistance << 4));

this.useBlockFaceCulling = SodiumClientMod.options().advanced.useBlockFaceCulling;
}

Expand Down Expand Up @@ -145,6 +154,24 @@ private void setup(Camera camera) {
}

private void iterateChunks(Camera camera, FrustumExtended frustum, int frame, boolean spectator) {
// Schedule new translucency sorting tasks if the camera has moved
if(this.translucencySorting) {
this.checkTranslucencyCameraMoved();
if(this.hasCameraMovedTranslucent) {
for(Object o : this.renders.getElements()) {
if(o == null)
continue;
ChunkRenderContainer<T> render = (ChunkRenderContainer<T>)o;
if(render.getData().isEmpty())
continue;
if(!render.needsRebuild() && render.canRebuild() && render.shouldRebuildForTranslucents() && render.getSquaredDistance(cameraX, cameraY, cameraZ) < translucencyBlockRenderDistance) {
// put it at the end of the queue, after any "real" rebuild tasks
render.scheduleSort(false);
}
}
}
}

IntList list = this.culler.computeVisible(camera, frustum, frame, spectator);
IntIterator it = list.iterator();

Expand All @@ -155,13 +182,31 @@ private void iterateChunks(Camera camera, FrustumExtended frustum, int frame, bo
}
}

private float lastCameraTranslucentX, lastCameraTranslucentY, lastCameraTranslucentZ;
private boolean hasCameraMovedTranslucent;

private void checkTranslucencyCameraMoved() {
float dx = (cameraX - lastCameraTranslucentX);
float dy = (cameraY - lastCameraTranslucentY);
float dz = (cameraZ - lastCameraTranslucentZ);
if((dx * dx + dy * dy + dz * dz) > 1.0) {
lastCameraTranslucentX = cameraX;
lastCameraTranslucentY = cameraY;
lastCameraTranslucentZ = cameraZ;
hasCameraMovedTranslucent = true;
} else
hasCameraMovedTranslucent = false;
}

private void addChunk(ChunkRenderContainer<T> render) {
if (render.needsRebuild() && render.canRebuild()) {
if (!this.alwaysDeferChunkUpdates && render.needsImportantRebuild()) {
this.importantRebuildQueue.enqueue(render);
} else {
this.rebuildQueue.enqueue(render);
}
} else if (render.canRebuild() && !render.getData().isEmpty() && render.needsSort()) {
this.sortQueue.enqueue(render);
}

if (this.useFogCulling && render.getSquaredDistanceXZ(this.cameraX, this.cameraZ) >= this.fogRenderCutoff) {
Expand Down Expand Up @@ -189,7 +234,7 @@ private void addChunkToRenderLists(ChunkRenderContainer<T> render) {

if (state != null) {
ChunkRenderList<T> list = this.chunkRenderLists[i];
list.add(state, visibleFaces);
list.add(state, (this.translucencySorting && BlockRenderPass.VALUES[i].isTranslucent()) ? (ChunkFaceFlags.ALL & render.getFacesWithData()) : visibleFaces);

added = true;
}
Expand Down Expand Up @@ -264,6 +309,7 @@ public ChunkRenderContainer<T> getRender(int x, int y, int z) {
private void reset() {
this.rebuildQueue.clear();
this.importantRebuildQueue.clear();
this.sortQueue.clear();

this.visibleBlockEntities.clear();

Expand Down Expand Up @@ -406,6 +452,10 @@ public void renderLayer(MatrixStack matrixStack, BlockRenderPass pass, double x,
CommandList commandList = device.createCommandList();

this.backend.begin(matrixStack);
// Ensure multidraw regions are ordered appropriately
if(this.backend instanceof MultidrawChunkRenderBackend) {
((MultidrawChunkRenderBackend) this.backend).setReverseRegions(pass.isTranslucent());
}
this.backend.render(commandList, iterator, new ChunkCameraContext(x, y, z));
this.backend.end(matrixStack);

Expand Down Expand Up @@ -456,6 +506,16 @@ public void updateChunks() {
submitted++;
}

// always do at least one sort
boolean sortedAnything = false;
while ((!sortedAnything || submitted < budget) && !this.sortQueue.isEmpty()) {
ChunkRenderContainer<T> render = this.sortQueue.dequeue();

this.builder.deferSort(render);
sortedAnything = true;
submitted++;
}

this.dirty |= submitted > 0;

// Try to complete some other work on the main thread while we wait for rebuilds to complete
Expand Down Expand Up @@ -488,6 +548,10 @@ public boolean isBuildComplete() {
return this.builder.isBuildQueueEmpty();
}

public void setCameraPosition(double x, double y, double z) {
this.builder.setCameraPosition(x, y, z);
}

public void destroy() {
this.reset();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@

import com.mojang.blaze3d.platform.GlStateManager;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand Down Expand Up @@ -129,7 +126,7 @@ public void upload(CommandList commandList, Iterator<ChunkBuildResult<MultidrawG
ChunkRenderContainer<MultidrawGraphicsState> render = result.render;
ChunkRenderData data = result.data;

for (BlockRenderPass pass : BlockRenderPass.VALUES) {
for (BlockRenderPass pass : result.passesToUpload) {
MultidrawGraphicsState graphics = render.getGraphicsState(pass);

// De-allocate the existing buffer arena for this render
Expand All @@ -147,7 +144,14 @@ public void upload(CommandList commandList, Iterator<ChunkBuildResult<MultidrawG

GlBufferSegment segment = arena.uploadBuffer(commandList, this.uploadBuffer, 0, upload.buffer.capacity());

render.setGraphicsState(pass, new MultidrawGraphicsState(render, region, segment, meshData, this.vertexFormat));
MultidrawGraphicsState graphicsState = new MultidrawGraphicsState(render, region, segment, meshData, this.vertexFormat);
if(pass.isTranslucent()) {
upload.buffer.limit(upload.buffer.capacity());
upload.buffer.position(0);

graphicsState.setTranslucencyData(upload.buffer);
}
render.setGraphicsState(pass, graphicsState);
} else {
render.setGraphicsState(pass, null);
}
Expand Down Expand Up @@ -191,6 +195,16 @@ private GlTessellation createRegionTessellation(CommandList commandList, GlBuffe
});
}

private boolean reverseRegions = false;
private ChunkCameraContext regionCamera;

/**
* Sets whether to reverse the order in which regions are drawn (fixes
*/
public void setReverseRegions(boolean flag) {
this.reverseRegions = flag;
}

@Override
public void render(CommandList commandList, ChunkRenderListIterator<MultidrawGraphicsState> renders, ChunkCameraContext camera) {
this.bufferManager.cleanup();
Expand Down Expand Up @@ -220,9 +234,23 @@ public void render(CommandList commandList, ChunkRenderListIterator<MultidrawGra
this.pendingBatches.clear();
}

private static final Comparator<ChunkRegion<?>> REGION_REVERSER = Comparator.<ChunkRegion<?>>comparingDouble(r -> r.camDistance).reversed();

private void buildCommandBuffer() {
this.commandClientBufferBuilder.begin();

if(this.reverseRegions) {
ChunkCameraContext camera = this.regionCamera;
for (ChunkRegion<?> region : this.pendingBatches) {
float x = camera.getChunkModelOffset(region.x * 16, camera.blockOriginX, camera.originX);
float y = camera.getChunkModelOffset(region.y * 16, camera.blockOriginY, camera.originY);
float z = camera.getChunkModelOffset(region.z * 16, camera.blockOriginZ, camera.originZ);
region.camDistance = x * x + y * y + z * z;
}

this.pendingBatches.sort(REGION_REVERSER);
}

for (ChunkRegion<?> region : this.pendingBatches) {
ChunkDrawCallBatcher batcher = region.getDrawBatcher();
batcher.end();
Expand Down Expand Up @@ -266,6 +294,7 @@ private void setupUploadBatches(Iterator<ChunkBuildResult<MultidrawGraphicsState

private void setupDrawBatches(CommandList commandList, ChunkRenderListIterator<MultidrawGraphicsState> it, ChunkCameraContext camera) {
this.uniformBufferBuilder.reset();
this.regionCamera = camera;

int drawCount = 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,9 @@ public void upload(CommandList commandList, ChunkMeshData meshData) {
});

this.setupModelParts(meshData, vertexData.format);

vertexData.buffer.limit(vertexData.buffer.capacity());
vertexData.buffer.position(0);
this.setTranslucencyData(vertexData.buffer);
}
}
Loading

0 comments on commit bfadbed

Please sign in to comment.