Skip to content

Commit bfadbed

Browse files
committed
Implement optional CPU-based translucency sorting on 1.16
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
1 parent dd4a93f commit bfadbed

20 files changed

+569
-13
lines changed

src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptionPages.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,15 @@ public static OptionPage advanced() {
283283
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
284284
.build()
285285
)
286+
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
287+
.setName(new TranslatableText("sodium.options.translucency_sorting.name"))
288+
.setTooltip(new TranslatableText("sodium.options.translucency_sorting.tooltip"))
289+
.setControl(TickBoxControl::new)
290+
.setBinding((opts, value) -> opts.advanced.translucencySorting = value, opts -> opts.advanced.translucencySorting)
291+
.setImpact(OptionImpact.MEDIUM)
292+
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
293+
.build()
294+
)
286295
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
287296
.setName(new TranslatableText("sodium.options.use_entity_culling.name"))
288297
.setTooltip(new TranslatableText("sodium.options.use_entity_culling.tooltip"))

src/main/java/me/jellysquid/mods/sodium/client/gui/SodiumGameOptions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public static class AdvancedSettings {
3838
public boolean useBlockFaceCulling = true;
3939
public boolean allowDirectMemoryAccess = true;
4040
public boolean ignoreDriverBlacklist = false;
41+
public boolean translucencySorting = false;
4142
}
4243

4344
public static class PerformanceSettings {

src/main/java/me/jellysquid/mods/sodium/client/render/SodiumWorldRenderer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,9 @@ public void updateChunks(Camera camera, Frustum frustum, boolean hasForcedFrustu
180180
}
181181

182182
Vec3d pos = camera.getPos();
183+
184+
this.chunkRenderManager.setCameraPosition(pos.x, pos.y, pos.z);
185+
183186
float pitch = camera.getPitch();
184187
float yaw = camera.getYaw();
185188

src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkGraphicsState.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22

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

5+
import java.nio.ByteBuffer;
6+
57
public abstract class ChunkGraphicsState {
68
private final int x, y, z;
79

10+
private ByteBuffer translucencyData;
11+
812
protected ChunkGraphicsState(ChunkRenderContainer<?> container) {
913
this.x = container.getRenderX();
1014
this.y = container.getRenderY();
@@ -24,4 +28,12 @@ public int getY() {
2428
public int getZ() {
2529
return this.z;
2630
}
31+
32+
public ByteBuffer getTranslucencyData() {
33+
return this.translucencyData;
34+
}
35+
36+
public void setTranslucencyData(ByteBuffer data) {
37+
this.translucencyData = data;
38+
}
2739
}

src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderContainer.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,13 @@ public class ChunkRenderContainer<T extends ChunkGraphicsState> {
3131
private boolean needsRebuild;
3232
private boolean needsImportantRebuild;
3333

34+
private boolean needsSort;
35+
3436
private boolean tickable;
3537
private int id;
3638

39+
private boolean rebuildableForTranslucents;
40+
3741
public ChunkRenderContainer(ChunkRenderBackend<T> backend, SodiumWorldRenderer worldRenderer, int chunkX, int chunkY, int chunkZ, ChunkRenderColumn<T> column) {
3842
this.worldRenderer = worldRenderer;
3943

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

4448
//noinspection unchecked
4549
this.graphicsStates = (T[]) Array.newInstance(backend.getGraphicsStateType(), BlockRenderPass.COUNT);
50+
this.rebuildableForTranslucents = false;
4651
this.column = column;
4752
}
4853

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

5763
if (this.rebuildTask != null) {
5864
this.rebuildTask.cancel(false);
@@ -78,6 +84,10 @@ public boolean needsImportantRebuild() {
7884
return this.needsImportantRebuild;
7985
}
8086

87+
public boolean needsSort() {
88+
return this.needsSort;
89+
}
90+
8191
/**
8292
* Deletes all data attached to this render and drops any pending tasks. This should be used when the render falls
8393
* out of view or otherwise needs to be destroyed. After the render has been destroyed, the object can no longer
@@ -102,6 +112,15 @@ private void deleteGraphicsState() {
102112
}
103113
}
104114

115+
public boolean shouldRebuildForTranslucents() {
116+
return this.rebuildableForTranslucents;
117+
}
118+
119+
public void setRebuildForTranslucents(boolean flag) {
120+
this.rebuildableForTranslucents = flag;
121+
}
122+
123+
105124
public void setData(ChunkRenderData info) {
106125
if (info == null) {
107126
throw new NullPointerException("Mesh information must not be null");
@@ -123,6 +142,17 @@ public boolean scheduleRebuild(boolean important) {
123142

124143
this.needsImportantRebuild = important;
125144
this.needsRebuild = true;
145+
this.needsSort = false;
146+
147+
return changed;
148+
}
149+
150+
public boolean scheduleSort(boolean important) {
151+
if (this.needsRebuild)
152+
return false;
153+
154+
boolean changed = !this.needsSort;
155+
this.needsSort = true;
126156

127157
return changed;
128158
}

src/main/java/me/jellysquid/mods/sodium/client/render/chunk/ChunkRenderManager.java

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import me.jellysquid.mods.sodium.client.gl.device.RenderDevice;
1515
import me.jellysquid.mods.sodium.client.gl.compat.FogHelper;
1616
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer;
17+
import me.jellysquid.mods.sodium.client.render.chunk.backends.multidraw.MultidrawChunkRenderBackend;
1718
import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuildResult;
1819
import me.jellysquid.mods.sodium.client.render.chunk.compile.ChunkBuilder;
1920
import me.jellysquid.mods.sodium.client.render.chunk.cull.ChunkCuller;
@@ -72,6 +73,7 @@ public class ChunkRenderManager<T extends ChunkGraphicsState> implements ChunkSt
7273

7374
private final ObjectArrayFIFOQueue<ChunkRenderContainer<T>> importantRebuildQueue = new ObjectArrayFIFOQueue<>();
7475
private final ObjectArrayFIFOQueue<ChunkRenderContainer<T>> rebuildQueue = new ObjectArrayFIFOQueue<>();
76+
private final ObjectArrayFIFOQueue<ChunkRenderContainer<T>> sortQueue = new ObjectArrayFIFOQueue<>();
7577
private final ObjectArrayFIFOQueue<ChunkRenderContainer<T>> unloadQueue = new ObjectArrayFIFOQueue<>();
7678

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

94+
private final boolean translucencySorting;
95+
9296
private int visibleChunkCount;
9397

9498
private boolean useFogCulling;
9599
private double fogRenderCutoff;
100+
101+
private final int translucencyBlockRenderDistance;
96102

97103
private boolean alwaysDeferChunkUpdates;
98104

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

113119
this.culler = new ChunkGraphCuller(world, renderDistance);
120+
this.translucencySorting = SodiumClientMod.options().advanced.translucencySorting;
121+
this.translucencyBlockRenderDistance = Math.min(9216, (renderDistance << 4) * (renderDistance << 4));
122+
114123
this.useBlockFaceCulling = SodiumClientMod.options().advanced.useBlockFaceCulling;
115124
}
116125

@@ -145,6 +154,24 @@ private void setup(Camera camera) {
145154
}
146155

147156
private void iterateChunks(Camera camera, FrustumExtended frustum, int frame, boolean spectator) {
157+
// Schedule new translucency sorting tasks if the camera has moved
158+
if(this.translucencySorting) {
159+
this.checkTranslucencyCameraMoved();
160+
if(this.hasCameraMovedTranslucent) {
161+
for(Object o : this.renders.getElements()) {
162+
if(o == null)
163+
continue;
164+
ChunkRenderContainer<T> render = (ChunkRenderContainer<T>)o;
165+
if(render.getData().isEmpty())
166+
continue;
167+
if(!render.needsRebuild() && render.canRebuild() && render.shouldRebuildForTranslucents() && render.getSquaredDistance(cameraX, cameraY, cameraZ) < translucencyBlockRenderDistance) {
168+
// put it at the end of the queue, after any "real" rebuild tasks
169+
render.scheduleSort(false);
170+
}
171+
}
172+
}
173+
}
174+
148175
IntList list = this.culler.computeVisible(camera, frustum, frame, spectator);
149176
IntIterator it = list.iterator();
150177

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

185+
private float lastCameraTranslucentX, lastCameraTranslucentY, lastCameraTranslucentZ;
186+
private boolean hasCameraMovedTranslucent;
187+
188+
private void checkTranslucencyCameraMoved() {
189+
float dx = (cameraX - lastCameraTranslucentX);
190+
float dy = (cameraY - lastCameraTranslucentY);
191+
float dz = (cameraZ - lastCameraTranslucentZ);
192+
if((dx * dx + dy * dy + dz * dz) > 1.0) {
193+
lastCameraTranslucentX = cameraX;
194+
lastCameraTranslucentY = cameraY;
195+
lastCameraTranslucentZ = cameraZ;
196+
hasCameraMovedTranslucent = true;
197+
} else
198+
hasCameraMovedTranslucent = false;
199+
}
200+
158201
private void addChunk(ChunkRenderContainer<T> render) {
159202
if (render.needsRebuild() && render.canRebuild()) {
160203
if (!this.alwaysDeferChunkUpdates && render.needsImportantRebuild()) {
161204
this.importantRebuildQueue.enqueue(render);
162205
} else {
163206
this.rebuildQueue.enqueue(render);
164207
}
208+
} else if (render.canRebuild() && !render.getData().isEmpty() && render.needsSort()) {
209+
this.sortQueue.enqueue(render);
165210
}
166211

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

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

194239
added = true;
195240
}
@@ -264,6 +309,7 @@ public ChunkRenderContainer<T> getRender(int x, int y, int z) {
264309
private void reset() {
265310
this.rebuildQueue.clear();
266311
this.importantRebuildQueue.clear();
312+
this.sortQueue.clear();
267313

268314
this.visibleBlockEntities.clear();
269315

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

408454
this.backend.begin(matrixStack);
455+
// Ensure multidraw regions are ordered appropriately
456+
if(this.backend instanceof MultidrawChunkRenderBackend) {
457+
((MultidrawChunkRenderBackend) this.backend).setReverseRegions(pass.isTranslucent());
458+
}
409459
this.backend.render(commandList, iterator, new ChunkCameraContext(x, y, z));
410460
this.backend.end(matrixStack);
411461

@@ -456,6 +506,16 @@ public void updateChunks() {
456506
submitted++;
457507
}
458508

509+
// always do at least one sort
510+
boolean sortedAnything = false;
511+
while ((!sortedAnything || submitted < budget) && !this.sortQueue.isEmpty()) {
512+
ChunkRenderContainer<T> render = this.sortQueue.dequeue();
513+
514+
this.builder.deferSort(render);
515+
sortedAnything = true;
516+
submitted++;
517+
}
518+
459519
this.dirty |= submitted > 0;
460520

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

551+
public void setCameraPosition(double x, double y, double z) {
552+
this.builder.setCameraPosition(x, y, z);
553+
}
554+
491555
public void destroy() {
492556
this.reset();
493557

src/main/java/me/jellysquid/mods/sodium/client/render/chunk/backends/multidraw/MultidrawChunkRenderBackend.java

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,7 @@
3636

3737
import com.mojang.blaze3d.platform.GlStateManager;
3838

39-
import java.util.ArrayList;
40-
import java.util.Iterator;
41-
import java.util.List;
42-
import java.util.Objects;
39+
import java.util.*;
4340
import java.util.regex.Matcher;
4441
import java.util.regex.Pattern;
4542

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

132-
for (BlockRenderPass pass : BlockRenderPass.VALUES) {
129+
for (BlockRenderPass pass : result.passesToUpload) {
133130
MultidrawGraphicsState graphics = render.getGraphicsState(pass);
134131

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

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

150-
render.setGraphicsState(pass, new MultidrawGraphicsState(render, region, segment, meshData, this.vertexFormat));
147+
MultidrawGraphicsState graphicsState = new MultidrawGraphicsState(render, region, segment, meshData, this.vertexFormat);
148+
if(pass.isTranslucent()) {
149+
upload.buffer.limit(upload.buffer.capacity());
150+
upload.buffer.position(0);
151+
152+
graphicsState.setTranslucencyData(upload.buffer);
153+
}
154+
render.setGraphicsState(pass, graphicsState);
151155
} else {
152156
render.setGraphicsState(pass, null);
153157
}
@@ -191,6 +195,16 @@ private GlTessellation createRegionTessellation(CommandList commandList, GlBuffe
191195
});
192196
}
193197

198+
private boolean reverseRegions = false;
199+
private ChunkCameraContext regionCamera;
200+
201+
/**
202+
* Sets whether to reverse the order in which regions are drawn (fixes
203+
*/
204+
public void setReverseRegions(boolean flag) {
205+
this.reverseRegions = flag;
206+
}
207+
194208
@Override
195209
public void render(CommandList commandList, ChunkRenderListIterator<MultidrawGraphicsState> renders, ChunkCameraContext camera) {
196210
this.bufferManager.cleanup();
@@ -220,9 +234,23 @@ public void render(CommandList commandList, ChunkRenderListIterator<MultidrawGra
220234
this.pendingBatches.clear();
221235
}
222236

237+
private static final Comparator<ChunkRegion<?>> REGION_REVERSER = Comparator.<ChunkRegion<?>>comparingDouble(r -> r.camDistance).reversed();
238+
223239
private void buildCommandBuffer() {
224240
this.commandClientBufferBuilder.begin();
225241

242+
if(this.reverseRegions) {
243+
ChunkCameraContext camera = this.regionCamera;
244+
for (ChunkRegion<?> region : this.pendingBatches) {
245+
float x = camera.getChunkModelOffset(region.x * 16, camera.blockOriginX, camera.originX);
246+
float y = camera.getChunkModelOffset(region.y * 16, camera.blockOriginY, camera.originY);
247+
float z = camera.getChunkModelOffset(region.z * 16, camera.blockOriginZ, camera.originZ);
248+
region.camDistance = x * x + y * y + z * z;
249+
}
250+
251+
this.pendingBatches.sort(REGION_REVERSER);
252+
}
253+
226254
for (ChunkRegion<?> region : this.pendingBatches) {
227255
ChunkDrawCallBatcher batcher = region.getDrawBatcher();
228256
batcher.end();
@@ -266,6 +294,7 @@ private void setupUploadBatches(Iterator<ChunkBuildResult<MultidrawGraphicsState
266294

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

270299
int drawCount = 0;
271300

src/main/java/me/jellysquid/mods/sodium/client/render/chunk/backends/oneshot/ChunkOneshotGraphicsState.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,9 @@ public void upload(CommandList commandList, ChunkMeshData meshData) {
8181
});
8282

8383
this.setupModelParts(meshData, vertexData.format);
84+
85+
vertexData.buffer.limit(vertexData.buffer.capacity());
86+
vertexData.buffer.position(0);
87+
this.setTranslucencyData(vertexData.buffer);
8488
}
8589
}

0 commit comments

Comments
 (0)