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<I> instances = new ArrayList<>();
protected final ArrayList<InstanceHandleImpl> handles = new ArrayList<>(); protected final ArrayList<InstanceHandleImpl> handles = new ArrayList<>();
// TODO: atomic bitset?
protected final BitSet changed = new BitSet(); protected final BitSet changed = new BitSet();
protected final BitSet deleted = 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()) { if (index < 0 || index >= getInstanceCount()) {
return; return;
} }
// TODO: Atomic bitset. Synchronizing here blocks the task executor and causes massive overhead.
synchronized (lock) { synchronized (lock) {
changed.set(index); changed.set(index);
} }

View File

@ -45,7 +45,7 @@ public class IndirectCullingGroup<I extends Instance> {
private final List<IndirectDraw> indirectDraws = new ArrayList<>(); private final List<IndirectDraw> indirectDraws = new ArrayList<>();
private final Map<RenderStage, List<MultiDraw>> multiDraws = new EnumMap<>(RenderStage.class); private final Map<RenderStage, List<MultiDraw>> multiDraws = new EnumMap<>(RenderStage.class);
private boolean needsDrawBarrier; private boolean needsDrawBarrier;
private boolean needsSortDraws; private boolean hasNewDraws;
private int instanceCountThisFrame; private int instanceCountThisFrame;
IndirectCullingGroup(InstanceType<I> instanceType) { IndirectCullingGroup(InstanceType<I> instanceType) {
@ -72,15 +72,23 @@ public class IndirectCullingGroup<I extends Instance> {
buffers.updateCounts(instanceCountThisFrame, indirectModels.size(), indirectDraws.size()); buffers.updateCounts(instanceCountThisFrame, indirectModels.size(), indirectDraws.size());
if (needsSortDraws) { // Must flush the mesh pool first so everything else has the right baseVertex and baseIndex.
sortDraws();
needsSortDraws = false;
}
meshPool.flush(stagingBuffer); meshPool.flush(stagingBuffer);
// Upload only objects that have changed.
uploadObjects(stagingBuffer); uploadObjects(stagingBuffer);
// We need to upload the models every frame to reset the instance count.
uploadModels(stagingBuffer); 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() { public void dispatchCull() {
@ -161,7 +169,7 @@ public class IndirectCullingGroup<I extends Instance> {
indirectDraws.add(new IndirectDraw(indirectModel, entry.getKey(), bufferedMesh, stage)); indirectDraws.add(new IndirectDraw(indirectModel, entry.getKey(), bufferedMesh, stage));
} }
needsSortDraws = true; hasNewDraws = true;
} }
public void submit(RenderStage stage) { public void submit(RenderStage stage) {
@ -193,9 +201,9 @@ public class IndirectCullingGroup<I extends Instance> {
private void uploadObjects(StagingBuffer stagingBuffer) { private void uploadObjects(StagingBuffer stagingBuffer) {
long pos = 0; long pos = 0;
for (IndirectModel batch : indirectModels) { for (IndirectModel model : indirectModels) {
var instanceCount = batch.instancer.getInstanceCount(); var instanceCount = model.instancer.getInstanceCount();
batch.writeObjects(stagingBuffer, pos, buffers.object.handle()); model.writeObjects(stagingBuffer, pos, buffers.object.handle());
pos += instanceCount * objectStride; pos += instanceCount * objectStride;
} }
@ -216,15 +224,15 @@ public class IndirectCullingGroup<I extends Instance> {
} }
private void writeModels(long writePtr) { private void writeModels(long writePtr) {
for (var batch : indirectModels) { for (var model : indirectModels) {
batch.write(writePtr); model.write(writePtr);
writePtr += IndirectBuffers.MODEL_STRIDE; writePtr += IndirectBuffers.MODEL_STRIDE;
} }
} }
private void writeCommands(long writePtr) { private void writeCommands(long writePtr) {
for (var batch : indirectDraws) { for (var draw : indirectDraws) {
batch.write(writePtr); draw.write(writePtr);
writePtr += IndirectBuffers.DRAW_COMMAND_STRIDE; 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 + 4, 0); // instanceCount - to be set by the apply shader
MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex
MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex 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 + 24, materialVertexIndex); // materialVertexIndex
MemoryUtil.memPutInt(ptr + 28, materialFragmentIndex); // materialFragmentIndex 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 final InstanceWriter<I> writer;
private int modelIndex; private int modelIndex;
private long lastStartPos = -1;
public IndirectInstancer(InstanceType<I> type) { public IndirectInstancer(InstanceType<I> type) {
super(type); super(type);
long instanceStride = type.getLayout() long instanceStride = type.getLayout()
@ -24,21 +26,38 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
removeDeletedInstances(); 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(); int count = instances.size();
for (int i = changed.nextSetBit(0); i >= 0 && i < count; i = changed.nextSetBit(i + 1)) { for (int i = changed.nextSetBit(0); i >= 0 && i < count; i = changed.nextSetBit(i + 1)) {
var instance = instances.get(i); var instance = instances.get(i);
stagingBuffer.enqueueCopy(objectStride, dstVbo, start + i * objectStride, ptr -> writeOne(ptr, instance)); 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(); long totalSize = objectStride * instances.size();
stagingBuffer.enqueueCopy(totalSize, dstVbo, start, this::writeAll); stagingBuffer.enqueueCopy(totalSize, dstVbo, start, this::writeAll);
changed.clear();
} }
private void writeAll(long ptr) { 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) { private void writeOne(long ptr, I instance) {
// write modelID
MemoryUtil.memPutInt(ptr, modelIndex); MemoryUtil.memPutInt(ptr, modelIndex);
// write object
writer.write(ptr + IndirectBuffers.INT_SIZE, instance); writer.write(ptr + IndirectBuffers.INT_SIZE, instance);
} }

View File

@ -9,7 +9,6 @@ public class IndirectModel {
private final Vector4fc boundingSphere; private final Vector4fc boundingSphere;
private int baseInstance = -1; private int baseInstance = -1;
private boolean needsFullWrite = true;
public IndirectModel(IndirectInstancer<?> instancer, int index, Vector4fc boundingSphere) { public IndirectModel(IndirectInstancer<?> instancer, int index, Vector4fc boundingSphere) {
this.instancer = instancer; this.instancer = instancer;
@ -23,20 +22,11 @@ public class IndirectModel {
public void prepare(int baseInstance) { public void prepare(int baseInstance) {
instancer.update(); instancer.update();
if (baseInstance == this.baseInstance) {
needsFullWrite = false;
return;
}
this.baseInstance = baseInstance; this.baseInstance = baseInstance;
needsFullWrite = true;
} }
public void writeObjects(StagingBuffer stagingBuffer, long start, int dstVbo) { public void writeObjects(StagingBuffer stagingBuffer, long start, int dstVbo) {
if (needsFullWrite) { instancer.write(stagingBuffer, start, dstVbo);
instancer.writeAll(stagingBuffer, start, dstVbo);
} else {
instancer.writeChanged(stagingBuffer, start, dstVbo);
}
} }
public void write(long ptr) { 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.PriorityQueue;
import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue; 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 { public class StagingBuffer {
private static final long DEFAULT_CAPACITY = 1024 * 1024 * 16; 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; 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 modelIndex = drawCommands[drawIndex].modelIndex;
uint instanceCount = models[modelIndex].instanceCount; drawCommands[drawIndex].instanceCount = models[modelIndex].instanceCount;
drawCommands[drawIndex].instanceCount = instanceCount; drawCommands[drawIndex].baseInstance = models[modelIndex].baseInstance;
} }