Skip to content

Commit 913fd7a

Browse files
committed
Implementation of asynchronous culling and tree-based render list generation (CaffeineMC#2887)
1 parent f474452 commit 913fd7a

File tree

85 files changed

+3820
-707
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+3820
-707
lines changed

common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/FallbackStagingBuffer.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import java.nio.ByteBuffer;
99

1010
public class FallbackStagingBuffer implements StagingBuffer {
11+
private static final float BYTES_PER_NANO_LIMIT = 8_000_000.0f / (1_000_000_000.0f / 60.0f); // MB per frame at 60fps
12+
1113
private final GlMutableBuffer fallbackBufferObject;
1214

1315
public FallbackStagingBuffer(CommandList commandList) {
@@ -39,4 +41,9 @@ public void flip() {
3941
public String toString() {
4042
return "Fallback";
4143
}
44+
45+
@Override
46+
public long getUploadSizeLimit(long frameDuration) {
47+
return (long) (frameDuration * BYTES_PER_NANO_LIMIT);
48+
}
4249
}

common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/MappedStagingBuffer.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import java.util.List;
1616

1717
public class MappedStagingBuffer implements StagingBuffer {
18+
private static final float UPLOAD_LIMIT_MARGIN = 0.8f;
19+
1820
private static final EnumBitField<GlBufferStorageFlags> STORAGE_FLAGS =
1921
EnumBitField.of(GlBufferStorageFlags.PERSISTENT, GlBufferStorageFlags.CLIENT_STORAGE, GlBufferStorageFlags.MAP_WRITE);
2022

@@ -163,6 +165,11 @@ public void flip() {
163165
}
164166
}
165167

168+
@Override
169+
public long getUploadSizeLimit(long frameDuration) {
170+
return (long) (this.capacity * UPLOAD_LIMIT_MARGIN);
171+
}
172+
166173
private static final class CopyCommand {
167174
private final GlBuffer buffer;
168175
private final long readOffset;

common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/StagingBuffer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ public interface StagingBuffer {
1313
void delete(CommandList commandList);
1414

1515
void flip();
16+
17+
long getUploadSizeLimit(long frameDuration);
1618
}

common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import net.caffeinemc.mods.sodium.client.gui.options.control.*;
1616
import net.caffeinemc.mods.sodium.client.gui.options.storage.MinecraftOptionsStorage;
1717
import net.caffeinemc.mods.sodium.client.gui.options.storage.SodiumOptionsStorage;
18+
import net.caffeinemc.mods.sodium.client.render.chunk.DeferMode;
1819
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.QuadSplittingMode;
1920
import net.minecraft.client.*;
2021
import net.minecraft.network.chat.Component;
@@ -282,12 +283,12 @@ public static OptionPage performance() {
282283
.setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD)
283284
.build()
284285
)
285-
.add(OptionImpl.createBuilder(boolean.class, sodiumOpts)
286-
.setName(Component.translatable("sodium.options.always_defer_chunk_updates.name"))
287-
.setTooltip(Component.translatable("sodium.options.always_defer_chunk_updates.tooltip"))
288-
.setControl(TickBoxControl::new)
286+
.add(OptionImpl.createBuilder(DeferMode.class, sodiumOpts)
287+
.setName(Component.translatable("sodium.options.defer_chunk_updates.name"))
288+
.setTooltip(Component.translatable("sodium.options.defer_chunk_updates.tooltip"))
289+
.setControl(option -> new CyclingControl<>(option, DeferMode.class))
289290
.setImpact(OptionImpact.HIGH)
290-
.setBinding((opts, value) -> opts.performance.alwaysDeferChunkUpdates = value, opts -> opts.performance.alwaysDeferChunkUpdates)
291+
.setBinding((opts, value) -> opts.performance.chunkBuildDeferMode = value, opts -> opts.performance.chunkBuildDeferMode)
291292
.setFlags(OptionFlag.REQUIRES_RENDERER_UPDATE)
292293
.build())
293294
.build()

common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import com.google.gson.FieldNamingPolicy;
44
import com.google.gson.Gson;
55
import com.google.gson.GsonBuilder;
6-
import com.google.gson.annotations.SerializedName;
76
import net.caffeinemc.mods.sodium.client.gui.options.TextProvider;
7+
import net.caffeinemc.mods.sodium.client.render.chunk.DeferMode;
88
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.QuadSplittingMode;
99
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior;
1010
import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation;
@@ -41,8 +41,7 @@ public static SodiumGameOptions defaults() {
4141

4242
public static class PerformanceSettings {
4343
public int chunkBuilderThreads = 0;
44-
@SerializedName("always_defer_chunk_updates_v2") // this will reset the option in older configs
45-
public boolean alwaysDeferChunkUpdates = true;
44+
public DeferMode chunkBuildDeferMode = DeferMode.ALWAYS;
4645

4746
public boolean animateOnlyVisibleTextures = true;
4847
public boolean useEntityCulling = true;

common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java

Lines changed: 26 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import net.minecraft.client.renderer.entity.state.EntityRenderState;
3434
import net.minecraft.client.resources.model.ModelBakery;
3535
import net.minecraft.core.BlockPos;
36-
import net.minecraft.core.SectionPos;
3736
import net.minecraft.server.level.BlockDestructionProgress;
3837
import net.minecraft.util.Mth;
3938
import net.minecraft.util.profiling.Profiler;
@@ -191,48 +190,45 @@ public void setupTerrain(Camera camera,
191190
float fogDistance = RenderSystem.getShaderFog().end();
192191

193192
if (this.lastCameraPos == null) {
194-
this.lastCameraPos = new Vector3d(pos);
193+
this.lastCameraPos = pos;
195194
}
196195
if (this.lastProjectionMatrix == null) {
197196
this.lastProjectionMatrix = new Matrix4f(projectionMatrix);
198197
}
199198
boolean cameraLocationChanged = !pos.equals(this.lastCameraPos);
200199
boolean cameraAngleChanged = pitch != this.lastCameraPitch || yaw != this.lastCameraYaw || fogDistance != this.lastFogDistance;
201-
boolean cameraProjectionChanged = !projectionMatrix.equals(this.lastProjectionMatrix);
200+
boolean cameraProjectionChanged = !projectionMatrix.equals(this.lastProjectionMatrix, 0.0001f);
202201

203202
this.lastProjectionMatrix = projectionMatrix;
204203

205204
this.lastCameraPitch = pitch;
206205
this.lastCameraYaw = yaw;
207206

208207
if (cameraLocationChanged || cameraAngleChanged || cameraProjectionChanged) {
209-
this.renderSectionManager.markGraphDirty();
208+
this.renderSectionManager.notifyChangedCamera();
210209
}
211210

212211
this.lastFogDistance = fogDistance;
213212

214-
this.renderSectionManager.updateCameraState(pos, camera);
213+
this.renderSectionManager.prepareFrame(pos);
215214

216215
if (cameraLocationChanged) {
217216
profiler.popPush("translucent_triggering");
218217

219218
this.renderSectionManager.processGFNIMovement(new CameraMovement(this.lastCameraPos, pos));
220-
this.lastCameraPos = new Vector3d(pos);
219+
this.lastCameraPos = pos;
221220
}
222221

223222
int maxChunkUpdates = updateChunksImmediately ? this.renderDistance : 1;
224-
225223
for (int i = 0; i < maxChunkUpdates; i++) {
226-
if (this.renderSectionManager.needsUpdate()) {
227-
profiler.popPush("chunk_render_lists");
224+
profiler.popPush("chunk_render_lists");
228225

229-
this.renderSectionManager.update(camera, viewport, fogParameters, spectator);
230-
}
226+
this.renderSectionManager.updateRenderLists(camera, viewport, fogParameters, spectator, updateChunksImmediately);
231227

232228
profiler.popPush("chunk_update");
233229

234230
this.renderSectionManager.cleanupAndFlip();
235-
this.renderSectionManager.updateChunks(updateChunksImmediately);
231+
this.renderSectionManager.updateChunks(viewport, updateChunksImmediately);
236232

237233
profiler.popPush("chunk_upload");
238234

@@ -253,6 +249,7 @@ public void setupTerrain(Camera camera,
253249
}
254250

255251
private void processChunkEvents() {
252+
this.renderSectionManager.beforeSectionUpdates();
256253
var tracker = ChunkTrackerHolder.get(this.level);
257254
tracker.forEachEvent(this.renderSectionManager::onChunkAdded, this.renderSectionManager::onChunkRemoved);
258255
}
@@ -343,9 +340,8 @@ private void renderBlockEntities(PoseStack matrices,
343340

344341
while (renderSectionIterator.hasNext()) {
345342
var renderSectionId = renderSectionIterator.nextByteAsInt();
346-
var renderSection = renderRegion.getSection(renderSectionId);
347343

348-
var blockEntities = renderSection.getCulledBlockEntities();
344+
var blockEntities = renderRegion.getCulledBlockEntities(renderSectionId);
349345

350346
if (blockEntities == null) {
351347
continue;
@@ -370,7 +366,7 @@ private void renderGlobalBlockEntities(PoseStack matrices,
370366
LocalPlayer player,
371367
LocalBooleanRef isGlowing) {
372368
for (var renderSection : this.renderSectionManager.getSectionsWithGlobalEntities()) {
373-
var blockEntities = renderSection.getGlobalBlockEntities();
369+
var blockEntities = renderSection.getRegion().getGlobalBlockEntities(renderSection.getSectionIndex());
374370

375371
if (blockEntities == null) {
376372
continue;
@@ -444,9 +440,7 @@ public void iterateVisibleBlockEntities(Consumer<BlockEntity> blockEntityConsume
444440

445441
while (renderSectionIterator.hasNext()) {
446442
var renderSectionId = renderSectionIterator.nextByteAsInt();
447-
var renderSection = renderRegion.getSection(renderSectionId);
448-
449-
var blockEntities = renderSection.getCulledBlockEntities();
443+
var blockEntities = renderRegion.getCulledBlockEntities(renderSectionId);
450444

451445
if (blockEntities == null) {
452446
continue;
@@ -459,7 +453,7 @@ public void iterateVisibleBlockEntities(Consumer<BlockEntity> blockEntityConsume
459453
}
460454

461455
for (var renderSection : this.renderSectionManager.getSectionsWithGlobalEntities()) {
462-
var blockEntities = renderSection.getGlobalBlockEntities();
456+
var blockEntities = renderSection.getRegion().getGlobalBlockEntities(renderSection.getSectionIndex());
463457

464458
if (blockEntities == null) {
465459
continue;
@@ -472,10 +466,13 @@ public void iterateVisibleBlockEntities(Consumer<BlockEntity> blockEntityConsume
472466
}
473467

474468
// the volume of a section multiplied by the number of sections to be checked at most
475-
private static final double MAX_ENTITY_CHECK_VOLUME = 16 * 16 * 16 * 15;
469+
private static final double MAX_ENTITY_CHECK_VOLUME = 16 * 16 * 16 * 50;
476470

477471
/**
478472
* Returns whether the entity intersects with any visible chunks in the graph.
473+
*
474+
* Note that this method assumes the entity is within the frustum. It does not perform a frustum check.
475+
*
479476
* @return True if the entity is visible, otherwise false
480477
*/
481478
public <T extends Entity, S extends EntityRenderState> boolean isEntityVisible(EntityRenderer<T, S> renderer, T entity) {
@@ -493,7 +490,7 @@ public <T extends Entity, S extends EntityRenderState> boolean isEntityVisible(E
493490
// bail on very large entities to avoid checking many sections
494491
double entityVolume = (bb.maxX - bb.minX) * (bb.maxY - bb.minY) * (bb.maxZ - bb.minZ);
495492
if (entityVolume > MAX_ENTITY_CHECK_VOLUME) {
496-
// TODO: do a frustum check instead, even large entities aren't visible if they're outside the frustum
493+
// large entities are only frustum tested, their sections are not checked for visibility
497494
return true;
498495
}
499496

@@ -507,48 +504,28 @@ public boolean isBoxVisible(double x1, double y1, double z1, double x2, double y
507504
return true;
508505
}
509506

510-
int minX = SectionPos.posToSectionCoord(x1 - 0.5D);
511-
int minY = SectionPos.posToSectionCoord(y1 - 0.5D);
512-
int minZ = SectionPos.posToSectionCoord(z1 - 0.5D);
513-
514-
int maxX = SectionPos.posToSectionCoord(x2 + 0.5D);
515-
int maxY = SectionPos.posToSectionCoord(y2 + 0.5D);
516-
int maxZ = SectionPos.posToSectionCoord(z2 + 0.5D);
517-
518-
for (int x = minX; x <= maxX; x++) {
519-
for (int z = minZ; z <= maxZ; z++) {
520-
for (int y = minY; y <= maxY; y++) {
521-
if (this.renderSectionManager.isSectionVisible(x, y, z)) {
522-
return true;
523-
}
524-
}
525-
}
526-
}
527-
528-
return false;
507+
return this.renderSectionManager.isBoxVisible(x1, y1, z1, x2, y2, z2);
529508
}
530509

531510
public String getChunksDebugString() {
532-
// C: visible/total D: distance
533-
// TODO: add dirty and queued counts
534-
return String.format("C: %d/%d D: %d", this.renderSectionManager.getVisibleChunkCount(), this.renderSectionManager.getTotalSections(), this.renderDistance);
511+
return this.renderSectionManager.getChunksDebugString();
535512
}
536513

537514
/**
538515
* Schedules chunk rebuilds for all chunks in the specified block region.
539516
*/
540-
public void scheduleRebuildForBlockArea(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, boolean important) {
541-
this.scheduleRebuildForChunks(minX >> 4, minY >> 4, minZ >> 4, maxX >> 4, maxY >> 4, maxZ >> 4, important);
517+
public void scheduleRebuildForBlockArea(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, boolean playerChanged) {
518+
this.scheduleRebuildForChunks(minX >> 4, minY >> 4, minZ >> 4, maxX >> 4, maxY >> 4, maxZ >> 4, playerChanged);
542519
}
543520

544521
/**
545522
* Schedules chunk rebuilds for all chunks in the specified chunk region.
546523
*/
547-
public void scheduleRebuildForChunks(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, boolean important) {
524+
public void scheduleRebuildForChunks(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, boolean playerChanged) {
548525
for (int chunkX = minX; chunkX <= maxX; chunkX++) {
549526
for (int chunkY = minY; chunkY <= maxY; chunkY++) {
550527
for (int chunkZ = minZ; chunkZ <= maxZ; chunkZ++) {
551-
this.scheduleRebuildForChunk(chunkX, chunkY, chunkZ, important);
528+
this.scheduleRebuildForChunk(chunkX, chunkY, chunkZ, playerChanged);
552529
}
553530
}
554531
}
@@ -557,8 +534,8 @@ public void scheduleRebuildForChunks(int minX, int minY, int minZ, int maxX, int
557534
/**
558535
* Schedules a chunk rebuild for the render belonging to the given chunk section position.
559536
*/
560-
public void scheduleRebuildForChunk(int x, int y, int z, boolean important) {
561-
this.renderSectionManager.scheduleRebuild(x, y, z, important);
537+
public void scheduleRebuildForChunk(int x, int y, int z, boolean playerChanged) {
538+
this.renderSectionManager.scheduleRebuild(x, y, z, playerChanged);
562539
}
563540

564541
public Collection<String> getDebugStrings() {

common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkUpdateType.java

Lines changed: 0 additions & 43 deletions
This file was deleted.

0 commit comments

Comments
 (0)