Skip to content

Commit

Permalink
Refactor chunk update type to be a bitmap instead of an enum, and fix…
Browse files Browse the repository at this point in the history
… bug where sort tasks upgraded to rebuild tasks would not update the index data if the translucent geometry didn't otherwise require an upload of the index data
  • Loading branch information
douira committed Feb 20, 2025
1 parent 14dbe4c commit 5367e75
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 114 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package net.caffeinemc.mods.sodium.client.render.chunk;

/**
* Important: Whether the task is scheduled immediately after its creation. Otherwise, they're scheduled through
* asynchronous culling that collects non-important tasks. Defer mode: For important tasks, how fast they are going to
* be executed. One or zero frame deferral only allows one or zero frames to pass before the frame blocks on the task.
* Always deferral allows the task to be deferred indefinitely, but if it's important it will still be put to the front
* of the queue.
*/
public class ChunkUpdateTypes {
public static final int SORT = 0b001;
public static final int REBUILD = 0b010;
public static final int IMPORTANT = 0b100;
public static final int INITIAL_BUILD = 0b1000;

public static int join(int from, int to) {
return from | to;
}

public static boolean isSort(int type) {
return (type & SORT) != 0;
}

public static boolean isRebuild(int type) {
return (type & REBUILD) != 0;
}

public static boolean isImportant(int type) {
return (type & IMPORTANT) != 0;
}

public static boolean isInitialBuild(int type) {
return (type & INITIAL_BUILD) != 0;
}

public static boolean isRebuildWithSort(int type) {
return (isRebuild(type) || isInitialBuild(type)) && isSort(type);
}

public static DeferMode getDeferMode(int type, DeferMode importantRebuildDeferMode) {
if (isImportant(type)) {
if (isRebuild(type)) {
return importantRebuildDeferMode;
} else { // implies important sort task
return DeferMode.ZERO_FRAMES;
}
} else {
return DeferMode.ALWAYS;
}
}

public static int getPriorityValue(int type) {
if (isInitialBuild(type)) {
return 0;
}
if (isRebuild(type)) {
return 1;
}
return 2; // sort
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion;
import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.TranslucentData;
import net.caffeinemc.mods.sodium.client.util.task.CancellationToken;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -49,8 +48,7 @@ public class RenderSection {
private CancellationToken taskCancellationToken = null;
private long lastMeshResultSize = MeshResultSize.NO_DATA;

@Nullable
private ChunkUpdateType pendingUpdateType;
private int pendingUpdateType;
private long pendingUpdateSince;

private int lastUploadFrame = -1;
Expand Down Expand Up @@ -306,21 +304,21 @@ public void setTaskCancellationToken(@Nullable CancellationToken token) {
this.taskCancellationToken = token;
}

public @Nullable ChunkUpdateType getPendingUpdate() {
public int getPendingUpdate() {
return this.pendingUpdateType;
}

public long getPendingUpdateSince() {
return this.pendingUpdateSince;
}

public void setPendingUpdate(ChunkUpdateType type, long now) {
public void setPendingUpdate(int type, long now) {
this.pendingUpdateType = type;
this.pendingUpdateSince = now;
}

public void clearPendingUpdate() {
this.pendingUpdateType = null;
this.pendingUpdateType = 0;
}

public void prepareTrigger(boolean isDirectTrigger) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ public void onSectionAdded(int x, int y, int z) {
this.updateSectionInfo(renderSection, BuiltSectionInfo.EMPTY);
} else {
this.renderableSectionTree.add(renderSection);
renderSection.setPendingUpdate(ChunkUpdateType.INITIAL_BUILD, this.lastFrameAtTime);
renderSection.setPendingUpdate(ChunkUpdateTypes.INITIAL_BUILD, this.lastFrameAtTime);
}

this.connectNeighborNodes(renderSection);
Expand Down Expand Up @@ -933,7 +933,7 @@ private long submitImportantSectionTasks(ChunkJobCollector collector, long remai
while (it.hasNext() && collector.hasBudgetRemaining() && (deferMode.allowsUnlimitedUploadSize() || remainingUploadSize > 0)) {
var section = it.next();
var pendingUpdate = section.getPendingUpdate();
if (pendingUpdate != null && pendingUpdate.getDeferMode(importantRebuildDeferMode) == deferMode && this.shouldPrioritizeTask(section, NEARBY_SORT_DISTANCE)) {
if (pendingUpdate != 0 && ChunkUpdateTypes.getDeferMode(pendingUpdate, importantRebuildDeferMode) == deferMode && this.shouldPrioritizeTask(section, NEARBY_SORT_DISTANCE)) {
// isSectionVisible includes a special case for not testing empty sections against the tree as they won't be in it
if (this.renderTree == null || this.renderTree.isSectionVisible(viewport, section)) {
remainingUploadSize -= submitSectionTask(collector, section, pendingUpdate);
Expand All @@ -953,28 +953,20 @@ private long submitSectionTask(ChunkJobCollector collector, @NotNull RenderSecti
// since the pending update it cleared when a task is started, this includes
// sections for which there's a currently running task.
var type = section.getPendingUpdate();
if (type == null) {
if (type == 0) {
return 0;
}

return submitSectionTask(collector, section, type);
}

private long submitSectionTask(ChunkJobCollector collector, @NotNull RenderSection section, ChunkUpdateType type) {
private long submitSectionTask(ChunkJobCollector collector, @NotNull RenderSection section, int type) {
if (section.isDisposed()) {
return 0;
}

ChunkBuilderTask<? extends BuilderTaskOutput> task;
if (type == ChunkUpdateType.SORT || type == ChunkUpdateType.IMPORTANT_SORT) {
task = this.createSortTask(section, this.frame);

if (task == null) {
// when a sort task is null it means the render section has no dynamic data and
// doesn't need to be sorted. Nothing needs to be done.
return 0;
}
} else {
if (ChunkUpdateTypes.isInitialBuild(type) || ChunkUpdateTypes.isRebuild(type)) {
task = this.createRebuildTask(section, this.frame);

if (task == null) {
Expand All @@ -994,11 +986,19 @@ private long submitSectionTask(ChunkJobCollector collector, @NotNull RenderSecti

section.setTaskCancellationToken(null);
}
} else { // implies it's a type of sort task
task = this.createSortTask(section, this.frame);

if (task == null) {
// when a sort task is null it means the render section has no dynamic data and
// doesn't need to be sorted. Nothing needs to be done.
return 0;
}
}

var estimatedTaskSize = 0L;
if (task != null) {
var job = this.builder.scheduleTask(task, type.isImportant(), collector::onJobFinished);
var job = this.builder.scheduleTask(task, ChunkUpdateTypes.isImportant(type), collector::onJobFinished);
collector.addSubmittedJob(job);
estimatedTaskSize = job.getEstimatedSize();

Expand All @@ -1017,7 +1017,7 @@ private long submitSectionTask(ChunkJobCollector collector, @NotNull RenderSecti
return null;
}

var task = new ChunkBuilderMeshingTask(render, frame, this.cameraPosition, context);
var task = new ChunkBuilderMeshingTask(render, frame, this.cameraPosition, context, ChunkUpdateTypes.isRebuildWithSort(render.getPendingUpdate()));
task.calculateEstimations(this.jobDurationEstimator, this.meshTaskSizeEstimator);
return task;
}
Expand Down Expand Up @@ -1077,23 +1077,26 @@ public int getVisibleChunkCount() {
return sections;
}

private ChunkUpdateType upgradePendingUpdate(RenderSection section, ChunkUpdateType type) {
private boolean upgradePendingUpdate(RenderSection section, int updateType) {
if (updateType == 0) {
return false;
}

var current = section.getPendingUpdate();
type = ChunkUpdateType.getPromotedTypeChange(current, type);
var joined = ChunkUpdateTypes.join(current, updateType);

// if there was no change the upgraded type is null
if (type == null) {
return null;
if (joined == current) {
return false;
}

section.setPendingUpdate(type, this.lastFrameAtTime);
section.setPendingUpdate(joined, this.lastFrameAtTime);

// when the pending task type changes, and it's important, add it to the list of important tasks
if (type.isImportant()) {
this.importantTasks.get(type.getDeferMode(SodiumClientMod.options().performance.chunkBuildDeferMode)).add(section);
if (ChunkUpdateTypes.isImportant(joined)) {
this.importantTasks.get(ChunkUpdateTypes.getDeferMode(joined, SodiumClientMod.options().performance.chunkBuildDeferMode)).add(section);
} else {
// if the section received a new task, mark in the task tree so an update can happen before a global cull task runs
if (this.globalTaskTree != null && current == null) {
if (this.globalTaskTree != null && current == 0) {
this.globalTaskTree.markSectionTask(section);
this.needsFrustumTaskListUpdate = true;

Expand All @@ -1108,20 +1111,20 @@ private ChunkUpdateType upgradePendingUpdate(RenderSection section, ChunkUpdateT
}
}

return type;
return true;
}

public void scheduleSort(long sectionPos, boolean isDirectTrigger) {
RenderSection section = this.sectionByPosition.get(sectionPos);

if (section != null) {
var pendingUpdate = ChunkUpdateType.SORT;
int pendingUpdate = ChunkUpdateTypes.SORT;
var priorityMode = SodiumClientMod.options().performance.getSortBehavior().getPriorityMode();
if (priorityMode == PriorityMode.NEARBY && this.shouldPrioritizeTask(section, NEARBY_SORT_DISTANCE) || priorityMode == PriorityMode.ALL) {
pendingUpdate = ChunkUpdateType.IMPORTANT_SORT;
pendingUpdate = ChunkUpdateTypes.join(pendingUpdate, ChunkUpdateTypes.IMPORTANT);
}

if (this.upgradePendingUpdate(section, pendingUpdate) != null) {
if (this.upgradePendingUpdate(section, pendingUpdate)) {
section.prepareTrigger(isDirectTrigger);
}
}
Expand All @@ -1135,12 +1138,12 @@ public void scheduleRebuild(int x, int y, int z, boolean playerChanged) {
RenderSection section = this.sectionByPosition.get(SectionPos.asLong(x, y, z));

if (section != null && section.isBuilt()) {
ChunkUpdateType pendingUpdate;
int pendingUpdate;

if (playerChanged && this.shouldPrioritizeTask(section, NEARBY_REBUILD_DISTANCE)) {
pendingUpdate = ChunkUpdateType.IMPORTANT_REBUILD;
pendingUpdate = ChunkUpdateTypes.join(ChunkUpdateTypes.REBUILD, ChunkUpdateTypes.IMPORTANT);
} else {
pendingUpdate = ChunkUpdateType.REBUILD;
pendingUpdate = ChunkUpdateTypes.REBUILD;
}

this.upgradePendingUpdate(section, pendingUpdate);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,12 @@
*/
public class ChunkBuilderMeshingTask extends ChunkBuilderTask<ChunkBuildOutput> {
private final ChunkRenderContext renderContext;
private final boolean forceSort;

public ChunkBuilderMeshingTask(RenderSection render, int buildTime, Vector3dc absoluteCameraPos, ChunkRenderContext renderContext) {
public ChunkBuilderMeshingTask(RenderSection render, int buildTime, Vector3dc absoluteCameraPos, ChunkRenderContext renderContext, boolean forceSort) {
super(render, buildTime, absoluteCameraPos);
this.renderContext = renderContext;
this.forceSort = forceSort;
}

@Override
Expand Down Expand Up @@ -197,7 +199,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke
var oldData = this.render.getTranslucentData();
translucentData = collector.getTranslucentData(
oldData, meshes.get(DefaultTerrainRenderPasses.TRANSLUCENT), this);
reuseUploadedData = translucentData == oldData;
reuseUploadedData = !this.forceSort && translucentData == oldData;
}

var output = new ChunkBuildOutput(this.render, this.submitTime, translucentData, renderData.build(), meshes);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package net.caffeinemc.mods.sodium.client.render.chunk.lists;

import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongHeapPriorityQueue;
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkUpdateType;
import net.caffeinemc.mods.sodium.client.render.chunk.DeferMode;
import net.caffeinemc.mods.sodium.client.render.chunk.ChunkUpdateTypes;
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection;
import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.OcclusionCuller;
import net.caffeinemc.mods.sodium.client.render.viewport.Viewport;
Expand Down Expand Up @@ -65,15 +63,15 @@ public void visit(RenderSection section) {
}

protected void checkForTask(RenderSection section) {
ChunkUpdateType type = section.getPendingUpdate();
int type = section.getPendingUpdate();

// collect tasks even if they're important, whether they're actually important is decided later
if (type != null && section.getTaskCancellationToken() == null) {
if (type != 0 && section.getTaskCancellationToken() == null) {
this.addPendingSection(section, type);
}
}

protected void addPendingSection(RenderSection section, ChunkUpdateType type) {
protected void addPendingSection(RenderSection section, int type) {
// start with a base priority value, lowest priority of task gets processed first
float priority = getSectionPriority(section, type);

Expand All @@ -87,8 +85,8 @@ protected void addPendingSection(RenderSection section, ChunkUpdateType type) {
this.pendingTasks.add((long) MathUtil.floatToComparableInt(priority) << 32 | taskCoordinate);
}

private float getSectionPriority(RenderSection section, ChunkUpdateType type) {
float priority = type.getPriorityValue();
private float getSectionPriority(RenderSection section, int type) {
float priority = ChunkUpdateTypes.getPriorityValue(type);

// calculate the relative distance to the camera
// alternatively: var distance = deltaX + deltaY + deltaZ;
Expand Down
Loading

0 comments on commit 5367e75

Please sign in to comment.