More smarter less upload

- Only write draw calls when they change.
- Update baseInstance in the apply shader.
- Simplify object upload logic.
This commit is contained in:
Jozufozu 2023-12-07 13:43:48 -08:00
parent d4a9b51c8b
commit abca3a2389
7 changed files with 55 additions and 39 deletions

View File

@ -15,7 +15,6 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
protected final ArrayList<I> instances = new ArrayList<>();
protected final ArrayList<InstanceHandleImpl> handles = new ArrayList<>();
// TODO: atomic bitset?
protected final BitSet changed = new BitSet();
protected final BitSet deleted = new BitSet();
@ -45,6 +44,7 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
if (index < 0 || index >= getInstanceCount()) {
return;
}
// TODO: Atomic bitset. Synchronizing here blocks the task executor and causes massive overhead.
synchronized (lock) {
changed.set(index);
}

View File

@ -45,7 +45,7 @@ public class IndirectCullingGroup<I extends Instance> {
private final List<IndirectDraw> indirectDraws = new ArrayList<>();
private final Map<RenderStage, List<MultiDraw>> multiDraws = new EnumMap<>(RenderStage.class);
private boolean needsDrawBarrier;
private boolean needsSortDraws;
private boolean hasNewDraws;
private int instanceCountThisFrame;
IndirectCullingGroup(InstanceType<I> instanceType) {
@ -72,15 +72,23 @@ public class IndirectCullingGroup<I extends Instance> {
buffers.updateCounts(instanceCountThisFrame, indirectModels.size(), indirectDraws.size());
if (needsSortDraws) {
sortDraws();
needsSortDraws = false;
}
// Must flush the mesh pool first so everything else has the right baseVertex and baseIndex.
meshPool.flush(stagingBuffer);
// Upload only objects that have changed.
uploadObjects(stagingBuffer);
// We need to upload the models every frame to reset the instance count.
uploadModels(stagingBuffer);
uploadDraws(stagingBuffer);
if (hasNewDraws) {
sortDraws();
// Draws, however, only need to be updated when we get new ones.
// The instanceCount and baseInstance will be updated by the applyProgram,
// and all other fields are constant to the lifetime of the draw.
uploadDraws(stagingBuffer);
hasNewDraws = false;
}
}
public void dispatchCull() {
@ -161,7 +169,7 @@ public class IndirectCullingGroup<I extends Instance> {
indirectDraws.add(new IndirectDraw(indirectModel, entry.getKey(), bufferedMesh, stage));
}
needsSortDraws = true;
hasNewDraws = true;
}
public void submit(RenderStage stage) {
@ -193,9 +201,9 @@ public class IndirectCullingGroup<I extends Instance> {
private void uploadObjects(StagingBuffer stagingBuffer) {
long pos = 0;
for (IndirectModel batch : indirectModels) {
var instanceCount = batch.instancer.getInstanceCount();
batch.writeObjects(stagingBuffer, pos, buffers.object.handle());
for (IndirectModel model : indirectModels) {
var instanceCount = model.instancer.getInstanceCount();
model.writeObjects(stagingBuffer, pos, buffers.object.handle());
pos += instanceCount * objectStride;
}
@ -216,15 +224,15 @@ public class IndirectCullingGroup<I extends Instance> {
}
private void writeModels(long writePtr) {
for (var batch : indirectModels) {
batch.write(writePtr);
for (var model : indirectModels) {
model.write(writePtr);
writePtr += IndirectBuffers.MODEL_STRIDE;
}
}
private void writeCommands(long writePtr) {
for (var batch : indirectDraws) {
batch.write(writePtr);
for (var draw : indirectDraws) {
draw.write(writePtr);
writePtr += IndirectBuffers.DRAW_COMMAND_STRIDE;
}
}

View File

@ -47,9 +47,9 @@ public class IndirectDraw {
MemoryUtil.memPutInt(ptr + 4, 0); // instanceCount - to be set by the apply shader
MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex
MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex
MemoryUtil.memPutInt(ptr + 16, model.baseInstance()); // baseInstance
MemoryUtil.memPutInt(ptr + 16, 0); // baseInstance - to be set by the apply shader
MemoryUtil.memPutInt(ptr + 20, model.index); // modelIndex
MemoryUtil.memPutInt(ptr + 20, model.index); // modelIndex - never changes
MemoryUtil.memPutInt(ptr + 24, materialVertexIndex); // materialVertexIndex
MemoryUtil.memPutInt(ptr + 28, materialFragmentIndex); // materialFragmentIndex

View File

@ -12,6 +12,8 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
private final InstanceWriter<I> writer;
private int modelIndex;
private long lastStartPos = -1;
public IndirectInstancer(InstanceType<I> type) {
super(type);
long instanceStride = type.getLayout()
@ -24,21 +26,38 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
removeDeletedInstances();
}
public void writeChanged(StagingBuffer stagingBuffer, long start, int dstVbo) {
public void write(StagingBuffer stagingBuffer, long startPos, int dstVbo) {
if (shouldWriteAll(startPos)) {
writeAll(stagingBuffer, startPos, dstVbo);
} else {
writeChanged(stagingBuffer, startPos, dstVbo);
}
changed.clear();
lastStartPos = startPos;
}
private boolean shouldWriteAll(long startPos) {
// If enough of the buffer has changed, write the whole thing to avoid the overhead of a bunch of small writes.
return startPos != lastStartPos || moreThanTwoThirdsChanged();
}
private boolean moreThanTwoThirdsChanged() {
return (changed.cardinality() * 3) > (instances.size() * 2);
}
private void writeChanged(StagingBuffer stagingBuffer, long start, int dstVbo) {
int count = instances.size();
for (int i = changed.nextSetBit(0); i >= 0 && i < count; i = changed.nextSetBit(i + 1)) {
var instance = instances.get(i);
stagingBuffer.enqueueCopy(objectStride, dstVbo, start + i * objectStride, ptr -> writeOne(ptr, instance));
}
changed.clear();
}
public void writeAll(StagingBuffer stagingBuffer, long start, int dstVbo) {
private void writeAll(StagingBuffer stagingBuffer, long start, int dstVbo) {
long totalSize = objectStride * instances.size();
stagingBuffer.enqueueCopy(totalSize, dstVbo, start, this::writeAll);
changed.clear();
}
private void writeAll(long ptr) {
@ -49,9 +68,7 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
}
private void writeOne(long ptr, I instance) {
// write modelID
MemoryUtil.memPutInt(ptr, modelIndex);
// write object
writer.write(ptr + IndirectBuffers.INT_SIZE, instance);
}

View File

@ -9,7 +9,6 @@ public class IndirectModel {
private final Vector4fc boundingSphere;
private int baseInstance = -1;
private boolean needsFullWrite = true;
public IndirectModel(IndirectInstancer<?> instancer, int index, Vector4fc boundingSphere) {
this.instancer = instancer;
@ -23,20 +22,11 @@ public class IndirectModel {
public void prepare(int baseInstance) {
instancer.update();
if (baseInstance == this.baseInstance) {
needsFullWrite = false;
return;
}
this.baseInstance = baseInstance;
needsFullWrite = true;
}
public void writeObjects(StagingBuffer stagingBuffer, long start, int dstVbo) {
if (needsFullWrite) {
instancer.writeAll(stagingBuffer, start, dstVbo);
} else {
instancer.writeChanged(stagingBuffer, start, dstVbo);
}
instancer.write(stagingBuffer, start, dstVbo);
}
public void write(long ptr) {

View File

@ -14,7 +14,8 @@ import com.jozufozu.flywheel.lib.memory.MemoryBlock;
import it.unimi.dsi.fastutil.PriorityQueue;
import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue;
// https://github.com/CaffeineMC/sodium-fabric/blob/dev/src/main/java/me/jellysquid/mods/sodium/client/gl/arena/staging/MappedStagingBuffer.java
// Used https://github.com/CaffeineMC/sodium-fabric/blob/dev/src/main/java/me/jellysquid/mods/sodium/client/gl/arena/staging/MappedStagingBuffer.java
// as a reference for implementation. Modified to be less safe and to allow for writing directly into the staging buffer.
public class StagingBuffer {
private static final long DEFAULT_CAPACITY = 1024 * 1024 * 16;
private static final int STORAGE_FLAGS = GL45C.GL_MAP_PERSISTENT_BIT | GL45C.GL_MAP_WRITE_BIT | GL45C.GL_CLIENT_STORAGE_BIT;

View File

@ -21,6 +21,6 @@ void main() {
}
uint modelIndex = drawCommands[drawIndex].modelIndex;
uint instanceCount = models[modelIndex].instanceCount;
drawCommands[drawIndex].instanceCount = instanceCount;
drawCommands[drawIndex].instanceCount = models[modelIndex].instanceCount;
drawCommands[drawIndex].baseInstance = models[modelIndex].baseInstance;
}