Skip to content

Commit

Permalink
refactor job effort estimation, add category-based meshing task size …
Browse files Browse the repository at this point in the history
…estimation, limit upload size based on previous mesh task result size or an estimate of it,

the limit behavior changes depending on which type of upload buffer is used
  • Loading branch information
douira committed Nov 25, 2024
1 parent eca45dc commit 158cd78
Show file tree
Hide file tree
Showing 23 changed files with 310 additions and 132 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import java.nio.ByteBuffer;

public class FallbackStagingBuffer implements StagingBuffer {
private static final float BYTES_PER_NANO_LIMIT = 8_000_000.0f / (1_000_000_000.0f / 60.0f); // MB per frame at 60fps

private final GlMutableBuffer fallbackBufferObject;

public FallbackStagingBuffer(CommandList commandList) {
Expand Down Expand Up @@ -39,4 +41,9 @@ public void flip() {
public String toString() {
return "Fallback";
}

@Override
public long getUploadSizeLimit(long frameDuration) {
return (long) (frameDuration * BYTES_PER_NANO_LIMIT);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import java.util.List;

public class MappedStagingBuffer implements StagingBuffer {
private static final float UPLOAD_LIMIT_MARGIN = 0.8f;

private static final EnumBitField<GlBufferStorageFlags> STORAGE_FLAGS =
EnumBitField.of(GlBufferStorageFlags.PERSISTENT, GlBufferStorageFlags.CLIENT_STORAGE, GlBufferStorageFlags.MAP_WRITE);

Expand Down Expand Up @@ -156,6 +158,11 @@ public void flip() {
}
}

@Override
public long getUploadSizeLimit(long frameDuration) {
return (long) (this.capacity * UPLOAD_LIMIT_MARGIN);
}

private static final class CopyCommand {
private final GlBuffer buffer;
private final long readOffset;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ public interface StagingBuffer {
void delete(CommandList commandList);

void flip();

long getUploadSizeLimit(long frameDuration);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.caffeinemc.mods.sodium.client.render.chunk;

import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshResultSize;
import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionInfo;
import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.GraphDirection;
import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.GraphDirectionSet;
Expand Down Expand Up @@ -46,7 +47,7 @@ public class RenderSection {
// Pending Update State
@Nullable
private CancellationToken taskCancellationToken = null;
private long lastMeshingTaskEffort = 1;
private long lastMeshResultSize = MeshResultSize.NO_DATA;

@Nullable
private ChunkUpdateType pendingUpdateType;
Expand Down Expand Up @@ -150,12 +151,12 @@ private void clearRenderState() {
this.visibilityData = VisibilityEncoding.NULL;
}

public void setLastMeshingTaskEffort(long effort) {
this.lastMeshingTaskEffort = effort;
public void setLastMeshResultSize(long size) {
this.lastMeshResultSize = size;
}

public long getLastMeshingTaskEffort() {
return this.lastMeshingTaskEffort;
public long getLastMeshResultSize() {
return this.lastMeshResultSize;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
import net.caffeinemc.mods.sodium.client.render.chunk.compile.BuilderTaskOutput;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildOutput;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkSortOutput;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkBuilder;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkJobCollector;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkJobResult;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.JobEffortEstimator;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.JobDurationEstimator;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshResultSize;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshTaskSizeEstimator;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.*;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderMeshingTask;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderSortingTask;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderTask;
Expand Down Expand Up @@ -71,7 +71,8 @@ public class RenderSectionManager {
private final Long2ReferenceMap<RenderSection> sectionByPosition = new Long2ReferenceOpenHashMap<>();

private final ConcurrentLinkedDeque<ChunkJobResult<? extends BuilderTaskOutput>> buildResults = new ConcurrentLinkedDeque<>();
private final JobEffortEstimator jobEffortEstimator = new JobEffortEstimator();
private final JobDurationEstimator jobDurationEstimator = new JobDurationEstimator();
private final MeshTaskSizeEstimator meshTaskSizeEstimator = new MeshTaskSizeEstimator();
private ChunkJobCollector lastBlockingCollector;
private long thisFrameBlockingTasks;
private long nextFrameBlockingTasks;
Expand Down Expand Up @@ -573,7 +574,10 @@ private boolean processChunkBuildResults(ArrayList<BuilderTaskOutput> results) {
TranslucentData oldData = result.render.getTranslucentData();
if (result instanceof ChunkBuildOutput chunkBuildOutput) {
this.updateSectionInfo(result.render, chunkBuildOutput.info);
result.render.setLastMeshingTaskEffort(chunkBuildOutput.getEffort());

var resultSize = chunkBuildOutput.getResultSize();
result.render.setLastMeshResultSize(resultSize);
this.meshTaskSizeEstimator.addBatchEntry(MeshResultSize.forSection(result.render, resultSize));

touchedSectionInfo = true;

Expand All @@ -600,6 +604,8 @@ private boolean processChunkBuildResults(ArrayList<BuilderTaskOutput> results) {
result.render.setLastUploadFrame(result.submitTime);
}

this.meshTaskSizeEstimator.flushNewData();

return touchedSectionInfo;
}

Expand Down Expand Up @@ -642,11 +648,11 @@ private ArrayList<BuilderTaskOutput> collectChunkBuildResults() {
results.add(result.unwrap());
var jobEffort = result.getJobEffort();
if (jobEffort != null) {
this.jobEffortEstimator.addJobEffort(jobEffort);
this.jobDurationEstimator.addBatchEntry(jobEffort);
}
}

this.jobEffortEstimator.flushNewData();
this.jobDurationEstimator.flushNewData();

return results;
}
Expand All @@ -670,21 +676,22 @@ public void updateChunks(boolean updateImmediately) {
if (updateImmediately) {
// for a perfect frame where everything is finished use the last frame's blocking collector
// and add all tasks to it so that they're waited on
this.submitSectionTasks(thisFrameBlockingCollector, thisFrameBlockingCollector, thisFrameBlockingCollector);
this.submitSectionTasks(Long.MAX_VALUE, thisFrameBlockingCollector, thisFrameBlockingCollector, thisFrameBlockingCollector);

this.thisFrameBlockingTasks = thisFrameBlockingCollector.getSubmittedTaskCount();
thisFrameBlockingCollector.awaitCompletion(this.builder);
} else {
var nextFrameBlockingCollector = new ChunkJobCollector(this.buildResults::add);
var remainingDuration = this.builder.getTotalRemainingDuration(this.averageFrameDuration);
var remainingUploadSize = this.regions.getStagingBuffer().getUploadSizeLimit(this.averageFrameDuration);
var deferredCollector = new ChunkJobCollector(remainingDuration, this.buildResults::add);

// if zero frame delay is allowed, submit important sorts with the current frame blocking collector.
// otherwise submit with the collector that the next frame is blocking on.
if (SodiumClientMod.options().performance.getSortBehavior().getDeferMode() == DeferMode.ZERO_FRAMES) {
this.submitSectionTasks(thisFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector);
this.submitSectionTasks(remainingUploadSize, thisFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector);
} else {
this.submitSectionTasks(nextFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector);
this.submitSectionTasks(remainingUploadSize, nextFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector);
}

this.thisFrameBlockingTasks = thisFrameBlockingCollector.getSubmittedTaskCount();
Expand All @@ -701,6 +708,7 @@ public void updateChunks(boolean updateImmediately) {
}

private void submitSectionTasks(
long remainingUploadSize,
ChunkJobCollector importantCollector,
ChunkJobCollector semiImportantCollector,
ChunkJobCollector deferredCollector) {
Expand All @@ -711,11 +719,15 @@ private void submitSectionTasks(
case ALWAYS -> deferredCollector;
};

submitSectionTasks(collector, deferMode);
// don't limit on size for zero frame defer (needs to be done, no matter the limit)
remainingUploadSize = submitSectionTasks(remainingUploadSize, deferMode != DeferMode.ZERO_FRAMES, collector, deferMode);
if (remainingUploadSize <= 0) {
break;
}
}
}

private void submitSectionTasks(ChunkJobCollector collector, DeferMode deferMode) {
private long submitSectionTasks(long remainingUploadSize, boolean limitOnSize, ChunkJobCollector collector, DeferMode deferMode) {
LongHeapPriorityQueue frustumQueue = null;
LongHeapPriorityQueue globalQueue = null;
float frustumPriorityBias = 0;
Expand Down Expand Up @@ -743,7 +755,8 @@ private void submitSectionTasks(ChunkJobCollector collector, DeferMode deferMode
long frustumItem = 0;
long globalItem = 0;

while ((!frustumQueue.isEmpty() || !globalQueue.isEmpty()) && collector.hasBudgetRemaining()) {
while ((!frustumQueue.isEmpty() || !globalQueue.isEmpty()) &&
collector.hasBudgetRemaining() && (!limitOnSize || remainingUploadSize > 0)) {
// get the first item from the non-empty queues and see which one has higher priority.
// if the priority is not infinity, then the item priority was fetched the last iteration and doesn't need updating.
if (!frustumQueue.isEmpty() && Float.isInfinite(frustumPriority)) {
Expand Down Expand Up @@ -780,18 +793,17 @@ private void submitSectionTasks(ChunkJobCollector collector, DeferMode deferMode
continue;
}

int frame = this.frame;
ChunkBuilderTask<? extends BuilderTaskOutput> task;
if (type == ChunkUpdateType.SORT || type == ChunkUpdateType.IMPORTANT_SORT) {
task = this.createSortTask(section, frame);
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.
continue;
}
} else {
task = this.createRebuildTask(section, frame);
task = this.createRebuildTask(section, this.frame);

if (task == null) {
// if the section is empty or doesn't exist submit this null-task to set the
Expand All @@ -804,7 +816,7 @@ private void submitSectionTasks(ChunkJobCollector collector, DeferMode deferMode
// rebuild that must have happened in the meantime includes new non-dynamic
// index data.
var result = ChunkJobResult.successfully(new ChunkBuildOutput(
section, frame, NoData.forEmptySection(section.getPosition()),
section, this.frame, NoData.forEmptySection(section.getPosition()),
BuiltSectionInfo.EMPTY, Collections.emptyMap()));
this.buildResults.add(result);

Expand All @@ -815,13 +827,16 @@ private void submitSectionTasks(ChunkJobCollector collector, DeferMode deferMode
if (task != null) {
var job = this.builder.scheduleTask(task, type.isImportant(), collector::onJobFinished);
collector.addSubmittedJob(job);
remainingUploadSize -= job.getEstimatedSize();

section.setTaskCancellationToken(job);
}

section.setLastSubmittedFrame(frame);
section.setLastSubmittedFrame(this.frame);
section.clearPendingUpdate();
}

return remainingUploadSize;
}

public @Nullable ChunkBuilderMeshingTask createRebuildTask(RenderSection render, int frame) {
Expand All @@ -832,14 +847,14 @@ private void submitSectionTasks(ChunkJobCollector collector, DeferMode deferMode
}

var task = new ChunkBuilderMeshingTask(render, frame, this.cameraPosition, context);
task.estimateDurationWith(this.jobEffortEstimator);
task.calculateEstimations(this.jobDurationEstimator, this.meshTaskSizeEstimator);
return task;
}

public ChunkBuilderSortingTask createSortTask(RenderSection render, int frame) {
var task = ChunkBuilderSortingTask.createTask(render, frame, this.cameraPosition);
if (task != null) {
task.estimateDurationWith(this.jobEffortEstimator);
task.calculateEstimations(this.jobDurationEstimator, this.meshTaskSizeEstimator);
}
return task;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package net.caffeinemc.mods.sodium.client.render.chunk.compile;

import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection;
import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshResultSize;

public abstract class BuilderTaskOutput {
public final RenderSection render;
public final int submitTime;
private long resultSize = MeshResultSize.NO_DATA;

public BuilderTaskOutput(RenderSection render, int buildTime) {
this.render = render;
Expand All @@ -13,4 +15,13 @@ public BuilderTaskOutput(RenderSection render, int buildTime) {

public void destroy() {
}

protected abstract long calculateResultSize();

public long getResultSize() {
if (this.resultSize == MeshResultSize.NO_DATA) {
this.resultSize = this.calculateResultSize();
}
return this.resultSize;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,6 @@ public BuiltSectionMeshParts getMesh(TerrainRenderPass pass) {
return this.meshes.get(pass);
}

public long getEffort() {
long size = 0;
for (var data : this.meshes.values()) {
size += data.getVertexData().getLength();
}
return 1 + (size >> 8); // make sure the number isn't huge
}

@Override
public void destroy() {
super.destroy();
Expand All @@ -48,4 +40,17 @@ public void destroy() {
data.getVertexData().free();
}
}

private long getMeshSize() {
long size = 0;
for (var data : this.meshes.values()) {
size += data.getVertexData().getLength();
}
return size;
}

@Override
public long calculateResultSize() {
return super.calculateResultSize() + this.getMeshSize();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,9 @@ public void destroy() {
this.indexBuffer.free();
}
}

@Override
protected long calculateResultSize() {
return this.indexBuffer == null ? 0 : this.indexBuffer.getLength();
}
}
Loading

0 comments on commit 158cd78

Please sign in to comment.