Skip to content

Commit 158cd78

Browse files
committed
refactor job effort estimation, add category-based meshing task size 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
1 parent eca45dc commit 158cd78

23 files changed

+310
-132
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

@@ -156,6 +158,11 @@ public void flip() {
156158
}
157159
}
158160

161+
@Override
162+
public long getUploadSizeLimit(long frameDuration) {
163+
return (long) (this.capacity * UPLOAD_LIMIT_MARGIN);
164+
}
165+
159166
private static final class CopyCommand {
160167
private final GlBuffer buffer;
161168
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/render/chunk/RenderSection.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package net.caffeinemc.mods.sodium.client.render.chunk;
22

3+
import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshResultSize;
34
import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionInfo;
45
import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.GraphDirection;
56
import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.GraphDirectionSet;
@@ -46,7 +47,7 @@ public class RenderSection {
4647
// Pending Update State
4748
@Nullable
4849
private CancellationToken taskCancellationToken = null;
49-
private long lastMeshingTaskEffort = 1;
50+
private long lastMeshResultSize = MeshResultSize.NO_DATA;
5051

5152
@Nullable
5253
private ChunkUpdateType pendingUpdateType;
@@ -150,12 +151,12 @@ private void clearRenderState() {
150151
this.visibilityData = VisibilityEncoding.NULL;
151152
}
152153

153-
public void setLastMeshingTaskEffort(long effort) {
154-
this.lastMeshingTaskEffort = effort;
154+
public void setLastMeshResultSize(long size) {
155+
this.lastMeshResultSize = size;
155156
}
156157

157-
public long getLastMeshingTaskEffort() {
158-
return this.lastMeshingTaskEffort;
158+
public long getLastMeshResultSize() {
159+
return this.lastMeshResultSize;
159160
}
160161

161162
/**

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

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
import net.caffeinemc.mods.sodium.client.render.chunk.compile.BuilderTaskOutput;
1111
import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildOutput;
1212
import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkSortOutput;
13-
import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkBuilder;
14-
import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkJobCollector;
15-
import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkJobResult;
16-
import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.JobEffortEstimator;
13+
import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.JobDurationEstimator;
14+
import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshResultSize;
15+
import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshTaskSizeEstimator;
16+
import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.*;
1717
import net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderMeshingTask;
1818
import net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderSortingTask;
1919
import net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderTask;
@@ -71,7 +71,8 @@ public class RenderSectionManager {
7171
private final Long2ReferenceMap<RenderSection> sectionByPosition = new Long2ReferenceOpenHashMap<>();
7272

7373
private final ConcurrentLinkedDeque<ChunkJobResult<? extends BuilderTaskOutput>> buildResults = new ConcurrentLinkedDeque<>();
74-
private final JobEffortEstimator jobEffortEstimator = new JobEffortEstimator();
74+
private final JobDurationEstimator jobDurationEstimator = new JobDurationEstimator();
75+
private final MeshTaskSizeEstimator meshTaskSizeEstimator = new MeshTaskSizeEstimator();
7576
private ChunkJobCollector lastBlockingCollector;
7677
private long thisFrameBlockingTasks;
7778
private long nextFrameBlockingTasks;
@@ -573,7 +574,10 @@ private boolean processChunkBuildResults(ArrayList<BuilderTaskOutput> results) {
573574
TranslucentData oldData = result.render.getTranslucentData();
574575
if (result instanceof ChunkBuildOutput chunkBuildOutput) {
575576
this.updateSectionInfo(result.render, chunkBuildOutput.info);
576-
result.render.setLastMeshingTaskEffort(chunkBuildOutput.getEffort());
577+
578+
var resultSize = chunkBuildOutput.getResultSize();
579+
result.render.setLastMeshResultSize(resultSize);
580+
this.meshTaskSizeEstimator.addBatchEntry(MeshResultSize.forSection(result.render, resultSize));
577581

578582
touchedSectionInfo = true;
579583

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

607+
this.meshTaskSizeEstimator.flushNewData();
608+
603609
return touchedSectionInfo;
604610
}
605611

@@ -642,11 +648,11 @@ private ArrayList<BuilderTaskOutput> collectChunkBuildResults() {
642648
results.add(result.unwrap());
643649
var jobEffort = result.getJobEffort();
644650
if (jobEffort != null) {
645-
this.jobEffortEstimator.addJobEffort(jobEffort);
651+
this.jobDurationEstimator.addBatchEntry(jobEffort);
646652
}
647653
}
648654

649-
this.jobEffortEstimator.flushNewData();
655+
this.jobDurationEstimator.flushNewData();
650656

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

675681
this.thisFrameBlockingTasks = thisFrameBlockingCollector.getSubmittedTaskCount();
676682
thisFrameBlockingCollector.awaitCompletion(this.builder);
677683
} else {
678684
var nextFrameBlockingCollector = new ChunkJobCollector(this.buildResults::add);
679685
var remainingDuration = this.builder.getTotalRemainingDuration(this.averageFrameDuration);
686+
var remainingUploadSize = this.regions.getStagingBuffer().getUploadSizeLimit(this.averageFrameDuration);
680687
var deferredCollector = new ChunkJobCollector(remainingDuration, this.buildResults::add);
681688

682689
// if zero frame delay is allowed, submit important sorts with the current frame blocking collector.
683690
// otherwise submit with the collector that the next frame is blocking on.
684691
if (SodiumClientMod.options().performance.getSortBehavior().getDeferMode() == DeferMode.ZERO_FRAMES) {
685-
this.submitSectionTasks(thisFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector);
692+
this.submitSectionTasks(remainingUploadSize, thisFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector);
686693
} else {
687-
this.submitSectionTasks(nextFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector);
694+
this.submitSectionTasks(remainingUploadSize, nextFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector);
688695
}
689696

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

703710
private void submitSectionTasks(
711+
long remainingUploadSize,
704712
ChunkJobCollector importantCollector,
705713
ChunkJobCollector semiImportantCollector,
706714
ChunkJobCollector deferredCollector) {
@@ -711,11 +719,15 @@ private void submitSectionTasks(
711719
case ALWAYS -> deferredCollector;
712720
};
713721

714-
submitSectionTasks(collector, deferMode);
722+
// don't limit on size for zero frame defer (needs to be done, no matter the limit)
723+
remainingUploadSize = submitSectionTasks(remainingUploadSize, deferMode != DeferMode.ZERO_FRAMES, collector, deferMode);
724+
if (remainingUploadSize <= 0) {
725+
break;
726+
}
715727
}
716728
}
717729

718-
private void submitSectionTasks(ChunkJobCollector collector, DeferMode deferMode) {
730+
private long submitSectionTasks(long remainingUploadSize, boolean limitOnSize, ChunkJobCollector collector, DeferMode deferMode) {
719731
LongHeapPriorityQueue frustumQueue = null;
720732
LongHeapPriorityQueue globalQueue = null;
721733
float frustumPriorityBias = 0;
@@ -743,7 +755,8 @@ private void submitSectionTasks(ChunkJobCollector collector, DeferMode deferMode
743755
long frustumItem = 0;
744756
long globalItem = 0;
745757

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

783-
int frame = this.frame;
784796
ChunkBuilderTask<? extends BuilderTaskOutput> task;
785797
if (type == ChunkUpdateType.SORT || type == ChunkUpdateType.IMPORTANT_SORT) {
786-
task = this.createSortTask(section, frame);
798+
task = this.createSortTask(section, this.frame);
787799

788800
if (task == null) {
789801
// when a sort task is null it means the render section has no dynamic data and
790802
// doesn't need to be sorted. Nothing needs to be done.
791803
continue;
792804
}
793805
} else {
794-
task = this.createRebuildTask(section, frame);
806+
task = this.createRebuildTask(section, this.frame);
795807

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

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

819832
section.setTaskCancellationToken(job);
820833
}
821834

822-
section.setLastSubmittedFrame(frame);
835+
section.setLastSubmittedFrame(this.frame);
823836
section.clearPendingUpdate();
824837
}
838+
839+
return remainingUploadSize;
825840
}
826841

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

834849
var task = new ChunkBuilderMeshingTask(render, frame, this.cameraPosition, context);
835-
task.estimateDurationWith(this.jobEffortEstimator);
850+
task.calculateEstimations(this.jobDurationEstimator, this.meshTaskSizeEstimator);
836851
return task;
837852
}
838853

839854
public ChunkBuilderSortingTask createSortTask(RenderSection render, int frame) {
840855
var task = ChunkBuilderSortingTask.createTask(render, frame, this.cameraPosition);
841856
if (task != null) {
842-
task.estimateDurationWith(this.jobEffortEstimator);
857+
task.calculateEstimations(this.jobDurationEstimator, this.meshTaskSizeEstimator);
843858
}
844859
return task;
845860
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package net.caffeinemc.mods.sodium.client.render.chunk.compile;
22

33
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection;
4+
import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshResultSize;
45

56
public abstract class BuilderTaskOutput {
67
public final RenderSection render;
78
public final int submitTime;
9+
private long resultSize = MeshResultSize.NO_DATA;
810

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

1416
public void destroy() {
1517
}
18+
19+
protected abstract long calculateResultSize();
20+
21+
public long getResultSize() {
22+
if (this.resultSize == MeshResultSize.NO_DATA) {
23+
this.resultSize = this.calculateResultSize();
24+
}
25+
return this.resultSize;
26+
}
1627
}

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

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,6 @@ public BuiltSectionMeshParts getMesh(TerrainRenderPass pass) {
3232
return this.meshes.get(pass);
3333
}
3434

35-
public long getEffort() {
36-
long size = 0;
37-
for (var data : this.meshes.values()) {
38-
size += data.getVertexData().getLength();
39-
}
40-
return 1 + (size >> 8); // make sure the number isn't huge
41-
}
42-
4335
@Override
4436
public void destroy() {
4537
super.destroy();
@@ -48,4 +40,17 @@ public void destroy() {
4840
data.getVertexData().free();
4941
}
5042
}
43+
44+
private long getMeshSize() {
45+
long size = 0;
46+
for (var data : this.meshes.values()) {
47+
size += data.getVertexData().getLength();
48+
}
49+
return size;
50+
}
51+
52+
@Override
53+
public long calculateResultSize() {
54+
return super.calculateResultSize() + this.getMeshSize();
55+
}
5156
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,9 @@ public void destroy() {
5454
this.indexBuffer.free();
5555
}
5656
}
57+
58+
@Override
59+
protected long calculateResultSize() {
60+
return this.indexBuffer == null ? 0 : this.indexBuffer.getLength();
61+
}
5762
}

0 commit comments

Comments
 (0)