Skip to content

Commit bab8281

Browse files
committed
Implementation of asynchronous culling and tree-based render list generation (#2887)
This commit includes the remaining merge items needed for full async culling and render list generation. It also takes advantage of the separately introduced upload time limit for task scheduling.
1 parent 60d40b3 commit bab8281

37 files changed

+1925
-527
lines changed

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

Lines changed: 21 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import net.minecraft.client.renderer.entity.state.EntityRenderState;
3838
import net.minecraft.client.resources.model.ModelBakery;
3939
import net.minecraft.core.BlockPos;
40-
import net.minecraft.core.SectionPos;
4140
import net.minecraft.server.level.BlockDestructionProgress;
4241
import net.minecraft.util.Mth;
4342
import net.minecraft.util.profiling.Profiler;
@@ -210,7 +209,7 @@ public void setupTerrain(Camera camera,
210209
this.lastCameraYaw = yaw;
211210

212211
if (cameraLocationChanged || fogDistanceChanged || cameraAngleChanged || cameraProjectionChanged) {
213-
this.renderSectionManager.markGraphDirty();
212+
this.renderSectionManager.notifyChangedCamera();
214213
}
215214

216215
this.lastFogParameters = fogParameters;
@@ -227,16 +226,14 @@ public void setupTerrain(Camera camera,
227226
int maxChunkUpdates = updateChunksImmediately ? this.renderDistance : 1;
228227

229228
for (int i = 0; i < maxChunkUpdates; i++) {
230-
if (this.renderSectionManager.needsUpdate()) {
231-
profiler.popPush("chunk_render_lists");
229+
profiler.popPush("chunk_render_lists");
232230

233-
this.renderSectionManager.update(camera, viewport, fogParameters, spectator);
234-
}
231+
this.renderSectionManager.updateRenderLists(camera, viewport, fogParameters, spectator, updateChunksImmediately);
235232

236233
profiler.popPush("chunk_update");
237234

238235
this.renderSectionManager.cleanupAndFlip();
239-
this.renderSectionManager.updateChunks(updateChunksImmediately);
236+
this.renderSectionManager.updateChunks(viewport, updateChunksImmediately);
240237

241238
profiler.popPush("chunk_upload");
242239

@@ -356,9 +353,8 @@ private void renderBlockEntities(PoseStack matrices,
356353

357354
while (renderSectionIterator.hasNext()) {
358355
var renderSectionId = renderSectionIterator.nextByteAsInt();
359-
var renderSection = renderRegion.getSection(renderSectionId);
360356

361-
var blockEntities = renderSection.getCulledBlockEntities();
357+
var blockEntities = renderRegion.getCulledBlockEntities(renderSectionId);
362358

363359
if (blockEntities == null) {
364360
continue;
@@ -383,7 +379,7 @@ private void renderGlobalBlockEntities(PoseStack matrices,
383379
LocalPlayer player,
384380
LocalBooleanRef isGlowing) {
385381
for (var renderSection : this.renderSectionManager.getSectionsWithGlobalEntities()) {
386-
var blockEntities = renderSection.getGlobalBlockEntities();
382+
var blockEntities = renderSection.getRegion().getGlobalBlockEntities(renderSection.getSectionIndex());
387383

388384
if (blockEntities == null) {
389385
continue;
@@ -457,9 +453,7 @@ public void iterateVisibleBlockEntities(Consumer<BlockEntity> blockEntityConsume
457453

458454
while (renderSectionIterator.hasNext()) {
459455
var renderSectionId = renderSectionIterator.nextByteAsInt();
460-
var renderSection = renderRegion.getSection(renderSectionId);
461-
462-
var blockEntities = renderSection.getCulledBlockEntities();
456+
var blockEntities = renderRegion.getCulledBlockEntities(renderSectionId);
463457

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

474468
for (var renderSection : this.renderSectionManager.getSectionsWithGlobalEntities()) {
475-
var blockEntities = renderSection.getGlobalBlockEntities();
469+
var blockEntities = renderSection.getRegion().getGlobalBlockEntities(renderSection.getSectionIndex());
476470

477471
if (blockEntities == null) {
478472
continue;
@@ -485,10 +479,13 @@ public void iterateVisibleBlockEntities(Consumer<BlockEntity> blockEntityConsume
485479
}
486480

487481
// the volume of a section multiplied by the number of sections to be checked at most
488-
private static final double MAX_ENTITY_CHECK_VOLUME = 16 * 16 * 16 * 15;
482+
private static final double MAX_ENTITY_CHECK_VOLUME = 16 * 16 * 16 * 50;
489483

490484
/**
491485
* Returns whether the entity intersects with any visible chunks in the graph.
486+
*
487+
* Note that this method assumes the entity is within the frustum. It does not perform a frustum check.
488+
*
492489
* @return True if the entity is visible, otherwise false
493490
*/
494491
public <T extends Entity, S extends EntityRenderState> boolean isEntityVisible(EntityRenderer<T, S> renderer, T entity) {
@@ -506,7 +503,7 @@ public <T extends Entity, S extends EntityRenderState> boolean isEntityVisible(E
506503
// bail on very large entities to avoid checking many sections
507504
double entityVolume = (bb.maxX - bb.minX) * (bb.maxY - bb.minY) * (bb.maxZ - bb.minZ);
508505
if (entityVolume > MAX_ENTITY_CHECK_VOLUME) {
509-
// TODO: do a frustum check instead, even large entities aren't visible if they're outside the frustum
506+
// large entities are only frustum tested, their sections are not checked for visibility
510507
return true;
511508
}
512509

@@ -520,48 +517,28 @@ public boolean isBoxVisible(double x1, double y1, double z1, double x2, double y
520517
return true;
521518
}
522519

523-
int minX = SectionPos.posToSectionCoord(x1 - 0.5D);
524-
int minY = SectionPos.posToSectionCoord(y1 - 0.5D);
525-
int minZ = SectionPos.posToSectionCoord(z1 - 0.5D);
526-
527-
int maxX = SectionPos.posToSectionCoord(x2 + 0.5D);
528-
int maxY = SectionPos.posToSectionCoord(y2 + 0.5D);
529-
int maxZ = SectionPos.posToSectionCoord(z2 + 0.5D);
530-
531-
for (int x = minX; x <= maxX; x++) {
532-
for (int z = minZ; z <= maxZ; z++) {
533-
for (int y = minY; y <= maxY; y++) {
534-
if (this.renderSectionManager.isSectionVisible(x, y, z)) {
535-
return true;
536-
}
537-
}
538-
}
539-
}
540-
541-
return false;
520+
return this.renderSectionManager.isBoxVisible(x1, y1, z1, x2, y2, z2);
542521
}
543522

544523
public String getChunksDebugString() {
545-
// C: visible/total D: distance
546-
// TODO: add dirty and queued counts
547-
return String.format("C: %d/%d D: %d", this.renderSectionManager.getVisibleChunkCount(), this.renderSectionManager.getTotalSections(), this.renderDistance);
524+
return this.renderSectionManager.getChunksDebugString();
548525
}
549526

550527
/**
551528
* Schedules chunk rebuilds for all chunks in the specified block region.
552529
*/
553-
public void scheduleRebuildForBlockArea(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, boolean important) {
554-
this.scheduleRebuildForChunks(minX >> 4, minY >> 4, minZ >> 4, maxX >> 4, maxY >> 4, maxZ >> 4, important);
530+
public void scheduleRebuildForBlockArea(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, boolean playerChanged) {
531+
this.scheduleRebuildForChunks(minX >> 4, minY >> 4, minZ >> 4, maxX >> 4, maxY >> 4, maxZ >> 4, playerChanged);
555532
}
556533

557534
/**
558535
* Schedules chunk rebuilds for all chunks in the specified chunk region.
559536
*/
560-
public void scheduleRebuildForChunks(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, boolean important) {
537+
public void scheduleRebuildForChunks(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, boolean playerChanged) {
561538
for (int chunkX = minX; chunkX <= maxX; chunkX++) {
562539
for (int chunkY = minY; chunkY <= maxY; chunkY++) {
563540
for (int chunkZ = minZ; chunkZ <= maxZ; chunkZ++) {
564-
this.scheduleRebuildForChunk(chunkX, chunkY, chunkZ, important);
541+
this.scheduleRebuildForChunk(chunkX, chunkY, chunkZ, playerChanged);
565542
}
566543
}
567544
}
@@ -570,8 +547,8 @@ public void scheduleRebuildForChunks(int minX, int minY, int minZ, int maxX, int
570547
/**
571548
* Schedules a chunk rebuild for the render belonging to the given chunk section position.
572549
*/
573-
public void scheduleRebuildForChunk(int x, int y, int z, boolean important) {
574-
this.renderSectionManager.scheduleRebuild(x, y, z, important);
550+
public void scheduleRebuildForChunk(int x, int y, int z, boolean playerChanged) {
551+
this.renderSectionManager.scheduleRebuild(x, y, z, playerChanged);
575552
}
576553

577554
public Collection<String> getDebugStrings() {

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,25 @@ public static boolean isRebuildWithSort(int type) {
3737
return (isRebuild(type) || isInitialBuild(type)) && isSort(type);
3838
}
3939

40-
public static TaskQueueType getQueueType(int type, TaskQueueType importantRebuildQueueType) {
41-
if (isInitialBuild(type)) {
42-
return TaskQueueType.INITIAL_BUILD;
43-
}
40+
public static DeferMode getDeferMode(int type, DeferMode importantRebuildDeferMode) {
4441
if (isImportant(type)) {
4542
if (isRebuild(type)) {
46-
return importantRebuildQueueType;
43+
return importantRebuildDeferMode;
4744
} else { // implies important sort task
48-
return TaskQueueType.ZERO_FRAME_DEFER;
45+
return DeferMode.ZERO_FRAMES;
4946
}
5047
} else {
51-
return TaskQueueType.ALWAYS_DEFER;
48+
return DeferMode.ALWAYS;
49+
}
50+
}
51+
52+
public static int getPriorityValue(int type) {
53+
if (isInitialBuild(type)) {
54+
return 0;
55+
}
56+
if (isRebuild(type)) {
57+
return 1;
5258
}
59+
return 2; // sort
5360
}
5461
}

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

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,22 @@
44
import net.minecraft.network.chat.Component;
55

66
public enum DeferMode implements TextProvider {
7-
ALWAYS("sodium.options.defer_chunk_updates.always", TaskQueueType.ALWAYS_DEFER),
8-
ONE_FRAME("sodium.options.defer_chunk_updates.one_frame", TaskQueueType.ONE_FRAME_DEFER),
9-
ZERO_FRAMES("sodium.options.defer_chunk_updates.zero_frames", TaskQueueType.ZERO_FRAME_DEFER);
7+
ALWAYS("sodium.options.defer_chunk_updates.always"),
8+
ONE_FRAME("sodium.options.defer_chunk_updates.one_frame"),
9+
ZERO_FRAMES("sodium.options.defer_chunk_updates.zero_frames");
1010

1111
private final Component name;
12-
private final TaskQueueType importantRebuildQueueType;
1312

14-
DeferMode(String name, TaskQueueType importantRebuildQueueType) {
13+
DeferMode(String name) {
1514
this.name = Component.translatable(name);
16-
this.importantRebuildQueueType = importantRebuildQueueType;
1715
}
1816

1917
@Override
2018
public Component getLocalizedName() {
2119
return this.name;
2220
}
2321

24-
public TaskQueueType getImportantRebuildQueueType() {
25-
return this.importantRebuildQueueType;
22+
public boolean allowsUnlimitedUploadDuration() {
23+
return this == ZERO_FRAMES;
2624
}
2725
}

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

Lines changed: 15 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@
88
import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion;
99
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.TranslucentData;
1010
import net.caffeinemc.mods.sodium.client.util.task.CancellationToken;
11-
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
12-
import net.minecraft.core.BlockPos;
1311
import net.minecraft.core.SectionPos;
14-
import net.minecraft.world.level.block.entity.BlockEntity;
1512
import org.jetbrains.annotations.NotNull;
1613
import org.jetbrains.annotations.Nullable;
1714

@@ -31,7 +28,7 @@ public class RenderSection {
3128
private long visibilityData = VisibilityEncoding.NULL;
3229

3330
private int incomingDirections;
34-
private int lastVisibleFrame = -1;
31+
private int lastVisibleSearchToken = -1;
3532

3633
private int adjacentMask;
3734
public RenderSection
@@ -42,13 +39,7 @@ public class RenderSection {
4239
adjacentWest,
4340
adjacentEast;
4441

45-
4642
// Rendering State
47-
private boolean built = false; // merge with the flags?
48-
private int flags = RenderSectionFlags.NONE;
49-
private BlockEntity @Nullable[] globalBlockEntities;
50-
private BlockEntity @Nullable[] culledBlockEntities;
51-
private TextureAtlasSprite @Nullable[] animatedSprites;
5243
@Nullable
5344
private TranslucentData translucentData;
5445

@@ -150,32 +141,22 @@ public boolean setInfo(@Nullable BuiltSectionInfo info) {
150141
}
151142

152143
private boolean setRenderState(@NotNull BuiltSectionInfo info) {
153-
var prevBuilt = this.built;
154-
var prevFlags = this.flags;
144+
var prevFlags = this.region.getSectionFlags(this.sectionIndex);
155145
var prevVisibilityData = this.visibilityData;
156146

157-
this.built = true;
158-
this.flags = info.flags;
147+
this.region.setSectionRenderState(this.sectionIndex, info);
159148
this.visibilityData = info.visibilityData;
160149

161-
this.globalBlockEntities = info.globalBlockEntities;
162-
this.culledBlockEntities = info.culledBlockEntities;
163-
this.animatedSprites = info.animatedSprites;
164-
165150
// the section is marked as having received graph-relevant changes if it's build state, flags, or connectedness has changed.
166151
// the entities and sprites don't need to be checked since whether they exist is encoded in the flags.
167-
return !prevBuilt || prevFlags != this.flags || prevVisibilityData != this.visibilityData;
152+
return prevFlags != this.region.getSectionFlags(this.sectionIndex) || prevVisibilityData != this.visibilityData;
168153
}
169154

170155
private boolean clearRenderState() {
171-
var wasBuilt = this.built;
156+
var wasBuilt = this.isBuilt();
172157

173-
this.built = false;
174-
this.flags = RenderSectionFlags.NONE;
158+
this.region.clearSectionRenderState(this.sectionIndex);
175159
this.visibilityData = VisibilityEncoding.NULL;
176-
this.globalBlockEntities = null;
177-
this.culledBlockEntities = null;
178-
this.animatedSprites = null;
179160

180161
// changes to data if it moves from built to not built don't matter, so only build state changes matter
181162
return wasBuilt;
@@ -217,14 +198,6 @@ public int getOriginZ() {
217198
return this.chunkZ << 4;
218199
}
219200

220-
/**
221-
* @return The squared distance from the center of this chunk in the level to the center of the block position
222-
* given by {@param pos}
223-
*/
224-
public float getSquaredDistance(BlockPos pos) {
225-
return this.getSquaredDistance(pos.getX() + 0.5f, pos.getY() + 0.5f, pos.getZ() + 0.5f);
226-
}
227-
228201
/**
229202
* @return The squared distance from the center of this chunk to the given block position
230203
*/
@@ -282,7 +255,7 @@ public String toString() {
282255
}
283256

284257
public boolean isBuilt() {
285-
return this.built;
258+
return (this.region.getSectionFlags(this.sectionIndex) & RenderSectionFlags.MASK_IS_BUILT) != 0;
286259
}
287260

288261
public int getSectionIndex() {
@@ -293,12 +266,16 @@ public RenderRegion getRegion() {
293266
return this.region;
294267
}
295268

296-
public void setLastVisibleFrame(int frame) {
297-
this.lastVisibleFrame = frame;
269+
public boolean needsRender() {
270+
return this.region.sectionNeedsRender(this.sectionIndex);
298271
}
299272

300-
public int getLastVisibleFrame() {
301-
return this.lastVisibleFrame;
273+
public void setLastVisibleSearchToken(int frame) {
274+
this.lastVisibleSearchToken = frame;
275+
}
276+
277+
public int getLastVisibleSearchToken() {
278+
return this.lastVisibleSearchToken;
302279
}
303280

304281
public int getIncomingDirections() {
@@ -313,42 +290,13 @@ public void setIncomingDirections(int directions) {
313290
this.incomingDirections = directions;
314291
}
315292

316-
/**
317-
* Returns a bitfield containing the {@link RenderSectionFlags} for this built section.
318-
*/
319-
public int getFlags() {
320-
return this.flags;
321-
}
322-
323293
/**
324294
* Returns the occlusion culling data which determines this chunk's connectedness on the visibility graph.
325295
*/
326296
public long getVisibilityData() {
327297
return this.visibilityData;
328298
}
329299

330-
/**
331-
* Returns the collection of animated sprites contained by this rendered chunk section.
332-
*/
333-
public TextureAtlasSprite @Nullable[] getAnimatedSprites() {
334-
return this.animatedSprites;
335-
}
336-
337-
/**
338-
* Returns the collection of block entities contained by this rendered chunk.
339-
*/
340-
public BlockEntity @Nullable[] getCulledBlockEntities() {
341-
return this.culledBlockEntities;
342-
}
343-
344-
/**
345-
* Returns the collection of block entities contained by this rendered chunk, which are not part of its culling
346-
* volume. These entities should always be rendered regardless of the render being visible in the frustum.
347-
*/
348-
public BlockEntity @Nullable[] getGlobalBlockEntities() {
349-
return this.globalBlockEntities;
350-
}
351-
352300
public @Nullable CancellationToken getTaskCancellationToken() {
353301
return this.taskCancellationToken;
354302
}

0 commit comments

Comments
 (0)