From 567491ca4d888b83696d29c4891b5bd6eb37877f Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Wed, 6 Dec 2023 23:30:01 -0800 Subject: [PATCH] In an abstract sense, buffered - Abstract indirect buffers to deduplicate code. - Write buffer handles when updating sizes. - StagingBuffer internally handles deferring to a memCopy, accepting a long consumer --- .../engine/indirect/IndirectBuffers.java | 193 ++++-------------- .../engine/indirect/IndirectCullingGroup.java | 42 ++-- .../engine/indirect/IndirectInstancer.java | 53 ++--- .../engine/indirect/IndirectModel.java | 4 +- .../indirect/ResizableStorageArray.java | 66 ++++++ .../indirect/ResizableStorageBuffer.java | 58 ++++++ .../engine/indirect/StagingBuffer.java | 58 +++++- .../jozufozu/flywheel/lib/math/MoreMath.java | 8 + 8 files changed, 256 insertions(+), 226 deletions(-) create mode 100644 src/main/java/com/jozufozu/flywheel/backend/engine/indirect/ResizableStorageArray.java create mode 100644 src/main/java/com/jozufozu/flywheel/backend/engine/indirect/ResizableStorageBuffer.java diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectBuffers.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectBuffers.java index ab77e0503..20cae48c4 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectBuffers.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectBuffers.java @@ -1,22 +1,14 @@ package com.jozufozu.flywheel.backend.engine.indirect; -import static org.lwjgl.opengl.GL15.glDeleteBuffers; -import static org.lwjgl.opengl.GL15.nglDeleteBuffers; import static org.lwjgl.opengl.GL43.GL_SHADER_STORAGE_BUFFER; import static org.lwjgl.opengl.GL44.nglBindBuffersRange; -import static org.lwjgl.opengl.GL45.glCopyNamedBufferSubData; -import static org.lwjgl.opengl.GL45.glCreateBuffers; -import static org.lwjgl.opengl.GL45.glNamedBufferStorage; -import static org.lwjgl.opengl.GL45.nglCreateBuffers; import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.Pointer; import com.jozufozu.flywheel.gl.buffer.GlBufferType; -import com.jozufozu.flywheel.lib.memory.FlwMemoryTracker; import com.jozufozu.flywheel.lib.memory.MemoryBlock; -// TODO: better abstractions public class IndirectBuffers { // Number of vbos created. public static final int BUFFER_COUNT = 4; @@ -30,22 +22,24 @@ public class IndirectBuffers { public static final long MODEL_STRIDE = 24; - // Offsets to the vbos - private static final long VBO_OFFSET = 0; - private static final long OBJECT_OFFSET = VBO_OFFSET; - private static final long TARGET_OFFSET = INT_SIZE; - private static final long MODEL_OFFSET = INT_SIZE * 2; - private static final long DRAW_OFFSET = INT_SIZE * 3; - // Offsets to the 3 segments + private static final long HANDLE_OFFSET = 0; private static final long OFFSET_OFFSET = BUFFER_COUNT * INT_SIZE; private static final long SIZE_OFFSET = OFFSET_OFFSET + BUFFER_COUNT * PTR_SIZE; + // Total size of the buffer. + private static final long BUFFERS_SIZE_BYTES = SIZE_OFFSET + BUFFER_COUNT * PTR_SIZE; + + // Offsets to the vbos + private static final long OBJECT_HANDLE_OFFSET = HANDLE_OFFSET; + private static final long TARGET_HANDLE_OFFSET = INT_SIZE; + private static final long MODEL_HANDLE_OFFSET = INT_SIZE * 2; + private static final long DRAW_HANDLE_OFFSET = INT_SIZE * 3; + + // Offsets to the sizes private static final long OBJECT_SIZE_OFFSET = SIZE_OFFSET; private static final long TARGET_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE; private static final long MODEL_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE * 2; private static final long DRAW_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE * 3; - // Total size of the buffer. - private static final long BUFFERS_SIZE_BYTES = SIZE_OFFSET + BUFFER_COUNT * PTR_SIZE; /** @@ -60,170 +54,61 @@ public class IndirectBuffers { * Each segment stores {@link IndirectBuffers#BUFFER_COUNT} elements, * one for the object buffer, target buffer, model buffer, and draw buffer. */ - private final MemoryBlock buffers; + private final MemoryBlock multiBindBlock; private final long objectStride; - public int object; - public int target; - public int model; - public int draw; - - MemoryBlock modelPtr; - MemoryBlock drawPtr; - - private int maxObjectCount = 0; - private int maxModelCount = 0; - private int maxDrawCount = 0; - - private static final float OBJECT_GROWTH_FACTOR = 1.25f; - private static final float MODEL_GROWTH_FACTOR = 2f; - private static final float DRAW_GROWTH_FACTOR = 2f; + public final ResizableStorageArray object; + public final ResizableStorageArray target; + public final ResizableStorageArray model; + public final ResizableStorageArray draw; IndirectBuffers(long objectStride) { this.objectStride = objectStride; - this.buffers = MemoryBlock.calloc(BUFFERS_SIZE_BYTES, 1); - } + this.multiBindBlock = MemoryBlock.calloc(BUFFERS_SIZE_BYTES, 1); - void createBuffers() { - final long ptr = buffers.ptr(); - nglCreateBuffers(BUFFER_COUNT, ptr); - object = MemoryUtil.memGetInt(ptr + OBJECT_OFFSET); - target = MemoryUtil.memGetInt(ptr + TARGET_OFFSET); - model = MemoryUtil.memGetInt(ptr + MODEL_OFFSET); - draw = MemoryUtil.memGetInt(ptr + DRAW_OFFSET); + object = new ResizableStorageArray(objectStride, 1.75); + target = new ResizableStorageArray(INT_SIZE, 1.75); + model = new ResizableStorageArray(MODEL_STRIDE, 2); + draw = new ResizableStorageArray(DRAW_COMMAND_STRIDE, 2); } void updateCounts(int objectCount, int drawCount, int modelCount) { - if (objectCount > maxObjectCount) { - createObjectStorage((int) (objectCount * OBJECT_GROWTH_FACTOR)); - } - if (modelCount > maxModelCount) { - createModelStorage((int) (modelCount * MODEL_GROWTH_FACTOR)); - } - if (drawCount > maxDrawCount) { - createDrawStorage((int) (drawCount * DRAW_GROWTH_FACTOR)); - } + object.ensureCapacity(objectCount); + target.ensureCapacity(objectCount); + model.ensureCapacity(modelCount); + draw.ensureCapacity(drawCount); + + final long ptr = multiBindBlock.ptr(); + MemoryUtil.memPutInt(ptr + OBJECT_HANDLE_OFFSET, object.handle()); + MemoryUtil.memPutInt(ptr + TARGET_HANDLE_OFFSET, target.handle()); + MemoryUtil.memPutInt(ptr + MODEL_HANDLE_OFFSET, model.handle()); + MemoryUtil.memPutInt(ptr + DRAW_HANDLE_OFFSET, draw.handle()); - final long ptr = buffers.ptr(); MemoryUtil.memPutAddress(ptr + OBJECT_SIZE_OFFSET, objectStride * objectCount); MemoryUtil.memPutAddress(ptr + TARGET_SIZE_OFFSET, INT_SIZE * objectCount); MemoryUtil.memPutAddress(ptr + MODEL_SIZE_OFFSET, MODEL_STRIDE * modelCount); MemoryUtil.memPutAddress(ptr + DRAW_SIZE_OFFSET, DRAW_COMMAND_STRIDE * drawCount); } - void createObjectStorage(int objectCount) { - freeObjectStorage(); - var objectSize = objectStride * objectCount; - var targetSize = INT_SIZE * objectCount; - - if (maxObjectCount > 0) { - final long ptr = buffers.ptr(); - nglCreateBuffers(2, ptr); - - int objectNew = MemoryUtil.memGetInt(ptr + OBJECT_OFFSET); - int targetNew = MemoryUtil.memGetInt(ptr + TARGET_OFFSET); - - glNamedBufferStorage(objectNew, objectSize, 0); - glNamedBufferStorage(targetNew, targetSize, 0); - - glCopyNamedBufferSubData(object, objectNew, 0, 0, objectStride * maxObjectCount); - glCopyNamedBufferSubData(target, targetNew, 0, 0, INT_SIZE * maxObjectCount); - - glDeleteBuffers(object); - glDeleteBuffers(target); - - object = objectNew; - target = targetNew; - } else { - glNamedBufferStorage(object, objectSize, 0); - glNamedBufferStorage(target, targetSize, 0); - } - - maxObjectCount = objectCount; - - FlwMemoryTracker._allocGPUMemory(maxObjectCount * objectStride); - } - - void createModelStorage(int modelCount) { - freeModelStorage(); - - var modelSize = MODEL_STRIDE * modelCount; - if (maxModelCount > 0) { - int modelNew = glCreateBuffers(); - - glNamedBufferStorage(modelNew, modelSize, 0); - - glDeleteBuffers(model); - - MemoryUtil.memPutInt(buffers.ptr() + MODEL_OFFSET, modelNew); - model = modelNew; - modelPtr = modelPtr.realloc(modelSize); - } else { - glNamedBufferStorage(model, modelSize, 0); - modelPtr = MemoryBlock.malloc(modelSize); - } - maxModelCount = modelCount; - FlwMemoryTracker._allocGPUMemory(maxModelCount * MODEL_STRIDE); - } - - void createDrawStorage(int drawCount) { - freeDrawStorage(); - - var drawSize = DRAW_COMMAND_STRIDE * drawCount; - if (maxDrawCount > 0) { - int drawNew = glCreateBuffers(); - - glNamedBufferStorage(drawNew, drawSize, 0); - - glDeleteBuffers(draw); - - MemoryUtil.memPutInt(buffers.ptr() + DRAW_OFFSET, drawNew); - draw = drawNew; - drawPtr = drawPtr.realloc(drawSize); - } else { - glNamedBufferStorage(draw, drawSize, 0); - drawPtr = MemoryBlock.malloc(drawSize); - } - maxDrawCount = drawCount; - FlwMemoryTracker._allocGPUMemory(maxDrawCount * DRAW_COMMAND_STRIDE); - } - - private void freeObjectStorage() { - FlwMemoryTracker._freeGPUMemory(maxObjectCount * objectStride); - } - - private void freeModelStorage() { - FlwMemoryTracker._freeGPUMemory(maxModelCount * MODEL_STRIDE); - } - - private void freeDrawStorage() { - FlwMemoryTracker._freeGPUMemory(maxDrawCount * DRAW_COMMAND_STRIDE); - } - public void bindForCompute() { multiBind(); } public void bindForDraw() { multiBind(); - GlBufferType.DRAW_INDIRECT_BUFFER.bind(draw); + GlBufferType.DRAW_INDIRECT_BUFFER.bind(draw.handle()); } private void multiBind() { - final long ptr = buffers.ptr(); + final long ptr = multiBindBlock.ptr(); nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, IndirectBuffers.BUFFER_COUNT, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET); } public void delete() { - nglDeleteBuffers(BUFFER_COUNT, buffers.ptr()); - buffers.free(); - if (modelPtr != null) { - modelPtr.free(); - } - if (drawPtr != null) { - drawPtr.free(); - } - freeObjectStorage(); - freeModelStorage(); - freeDrawStorage(); + multiBindBlock.free(); + + object.delete(); + target.delete(); + model.delete(); + draw.delete(); } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java index 79f5cd535..b41628938 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectCullingGroup.java @@ -15,8 +15,6 @@ import java.util.EnumMap; import java.util.List; import java.util.Map; -import org.lwjgl.system.MemoryUtil; - import com.jozufozu.flywheel.api.event.RenderStage; import com.jozufozu.flywheel.api.instance.Instance; import com.jozufozu.flywheel.api.instance.InstanceType; @@ -29,7 +27,6 @@ import com.jozufozu.flywheel.backend.engine.UniformBuffer; import com.jozufozu.flywheel.gl.GlCompat; import com.jozufozu.flywheel.gl.shader.GlProgram; import com.jozufozu.flywheel.lib.context.Contexts; -import com.jozufozu.flywheel.lib.memory.MemoryBlock; import com.jozufozu.flywheel.lib.model.ModelUtil; public class IndirectCullingGroup { @@ -53,7 +50,6 @@ public class IndirectCullingGroup { .getStride() + IndirectBuffers.INT_SIZE; buffers = new IndirectBuffers(objectStride); - buffers.createBuffers(); meshPool = new IndirectMeshPool(); @@ -68,8 +64,9 @@ public class IndirectCullingGroup { var boundingSphere = ModelUtil.computeBoundingSphere(meshes.values()); - int modelID = indirectModels.size(); - var indirectModel = new IndirectModel(instancer, modelID, boundingSphere); + int modelId = indirectModels.size(); + instancer.setModelId(modelId); + var indirectModel = new IndirectModel(instancer, modelId, boundingSphere); indirectModels.add(indirectModel); for (Map.Entry materialMeshEntry : meshes.entrySet()) { @@ -180,7 +177,7 @@ public class IndirectCullingGroup { long pos = 0; for (IndirectModel batch : indirectModels) { var instanceCount = batch.instancer.getInstanceCount(); - batch.writeObjects(stagingBuffer, pos, buffers.object); + batch.writeObjects(stagingBuffer, pos, buffers.object.handle()); pos += instanceCount * objectStride; } @@ -188,16 +185,16 @@ public class IndirectCullingGroup { private void uploadModels(StagingBuffer stagingBuffer) { var totalSize = indirectModels.size() * IndirectBuffers.MODEL_STRIDE; - long writePtr = stagingBuffer.reserveForTransferTo(totalSize, buffers.model, 0); + var handle = buffers.model.handle(); - if (writePtr == MemoryUtil.NULL) { - var block = MemoryBlock.malloc(totalSize); - writeModels(block.ptr()); - stagingBuffer.enqueueCopy(block.ptr(), totalSize, buffers.model, 0); - block.free(); - } else { - writeModels(writePtr); - } + stagingBuffer.enqueueCopy(totalSize, handle, 0, this::writeModels); + } + + private void uploadIndirectCommands(StagingBuffer stagingBuffer) { + var totalSize = indirectDraws.size() * IndirectBuffers.DRAW_COMMAND_STRIDE; + var handle = buffers.draw.handle(); + + stagingBuffer.enqueueCopy(totalSize, handle, 0, this::writeCommands); } private void writeModels(long writePtr) { @@ -207,19 +204,6 @@ public class IndirectCullingGroup { } } - private void uploadIndirectCommands(StagingBuffer stagingBuffer) { - var totalSize = indirectDraws.size() * IndirectBuffers.DRAW_COMMAND_STRIDE; - long writePtr = stagingBuffer.reserveForTransferTo(totalSize, buffers.draw, 0); - if (writePtr == MemoryUtil.NULL) { - var block = MemoryBlock.malloc(totalSize); - writeCommands(block.ptr()); - stagingBuffer.enqueueCopy(block.ptr(), totalSize, buffers.draw, 0); - block.free(); - } else { - writeCommands(writePtr); - } - } - private void writeCommands(long writePtr) { for (var batch : indirectDraws) { batch.writeIndirectCommand(writePtr); diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectInstancer.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectInstancer.java index 5c9ceb2a1..0de1270a6 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectInstancer.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectInstancer.java @@ -6,16 +6,15 @@ import com.jozufozu.flywheel.api.instance.Instance; import com.jozufozu.flywheel.api.instance.InstanceType; import com.jozufozu.flywheel.api.instance.InstanceWriter; import com.jozufozu.flywheel.backend.engine.AbstractInstancer; -import com.jozufozu.flywheel.lib.memory.MemoryBlock; public class IndirectInstancer extends AbstractInstancer { - private final long instanceStride; private final long objectStride; private final InstanceWriter writer; + private int modelId; public IndirectInstancer(InstanceType type) { super(type); - this.instanceStride = type.getLayout() + long instanceStride = type.getLayout() .getStride(); this.objectStride = instanceStride + IndirectBuffers.INT_SIZE; writer = this.type.getWriter(); @@ -25,60 +24,38 @@ public class IndirectInstancer extends AbstractInstancer removeDeletedInstances(); } - public void writeSparse(StagingBuffer stagingBuffer, long start, int modelID, int dstVbo) { + public void writeSparse(StagingBuffer stagingBuffer, long start, int dstVbo) { int count = instances.size(); - // Backup buffer for when we can't write to the staging buffer. - MemoryBlock backup = null; for (int i = changed.nextSetBit(0); i >= 0 && i < count; i = changed.nextSetBit(i + 1)) { - long ptr = stagingBuffer.reserveForTransferTo(objectStride, dstVbo, start + i * objectStride); - if (ptr == MemoryUtil.NULL) { - // Staging buffer can't fit this object, so we'll have to write it to a backup buffer. - if (backup == null) { - backup = MemoryBlock.malloc(objectStride); - } - writeOne(backup.ptr(), instances.get(i), modelID); - - stagingBuffer.enqueueCopy(backup.ptr(), objectStride, dstVbo, start + i * objectStride); - } else { - writeOne(ptr, instances.get(i), modelID); - } + var instance = instances.get(i); + stagingBuffer.enqueueCopy(objectStride, dstVbo, start + i * objectStride, ptr -> writeOne(ptr, instance)); } changed.clear(); - - // Free the backup buffer if we allocated one. - if (backup != null) { - backup.free(); - } } - public void writeFull(StagingBuffer stagingBuffer, long start, int modelID, int dstVbo) { + public void writeFull(StagingBuffer stagingBuffer, long start, int dstVbo) { long totalSize = objectStride * instances.size(); - long ptr = stagingBuffer.reserveForTransferTo(totalSize, dstVbo, start); - - if (ptr != MemoryUtil.NULL) { - writeAll(ptr, modelID); - } else { - var block = MemoryBlock.malloc(totalSize); - writeAll(block.ptr(), modelID); - stagingBuffer.enqueueCopy(block.ptr(), totalSize, dstVbo, start); - block.free(); - } + stagingBuffer.enqueueCopy(totalSize, dstVbo, start, this::writeAll); changed.clear(); } - private void writeAll(long ptr, int modelID) { + private void writeAll(long ptr) { for (I instance : instances) { - writeOne(ptr, instance, modelID); + writeOne(ptr, instance); ptr += objectStride; } } - private void writeOne(long ptr, I instance, int modelID) { + private void writeOne(long ptr, I instance) { // write modelID - MemoryUtil.memPutInt(ptr, modelID); + MemoryUtil.memPutInt(ptr, modelId); // write object writer.write(ptr + IndirectBuffers.INT_SIZE, instance); } + + public void setModelId(int modelId) { + this.modelId = modelId; + } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectModel.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectModel.java index b19561531..a33be9d5f 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectModel.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/IndirectModel.java @@ -37,9 +37,9 @@ public class IndirectModel { public void writeObjects(StagingBuffer stagingBuffer, long start, int dstVbo) { if (needsFullWrite) { - instancer.writeFull(stagingBuffer, start, id, dstVbo); + instancer.writeFull(stagingBuffer, start, dstVbo); } else { - instancer.writeSparse(stagingBuffer, start, id, dstVbo); + instancer.writeSparse(stagingBuffer, start, dstVbo); } } } diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/ResizableStorageArray.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/ResizableStorageArray.java new file mode 100644 index 000000000..3b6e44df1 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/ResizableStorageArray.java @@ -0,0 +1,66 @@ +package com.jozufozu.flywheel.backend.engine.indirect; + +import com.jozufozu.flywheel.lib.math.MoreMath; + +/** + * A buffer that is aware of its content's stride with some control over how it grows. + */ +public class ResizableStorageArray { + private static final double DEFAULT_GROWTH_FACTOR = 1.25; + private final ResizableStorageBuffer buffer; + private final long stride; + private final double growthFactor; + + private long capacity; + + public ResizableStorageArray(long stride) { + this(stride, DEFAULT_GROWTH_FACTOR); + } + + public ResizableStorageArray(long stride, double growthFactor) { + this.stride = stride; + this.growthFactor = growthFactor; + + if (stride <= 0) { + throw new IllegalArgumentException("Stride must be positive!"); + } + + if (growthFactor <= 1) { + throw new IllegalArgumentException("Growth factor must be greater than 1!"); + } + + this.buffer = new ResizableStorageBuffer(); + } + + public int handle() { + return buffer.handle(); + } + + public long stride() { + return stride; + } + + public long capacity() { + return capacity; + } + + public long byteCapacity() { + return buffer.capacity(); + } + + public void ensureCapacity(long capacity) { + if (capacity > this.capacity) { + long newCapacity = grow(capacity); + buffer.ensureCapacity(stride * newCapacity); + this.capacity = newCapacity; + } + } + + public void delete() { + buffer.delete(); + } + + private long grow(long capacity) { + return MoreMath.ceilLong(capacity * growthFactor); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/ResizableStorageBuffer.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/ResizableStorageBuffer.java new file mode 100644 index 000000000..3750193f1 --- /dev/null +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/ResizableStorageBuffer.java @@ -0,0 +1,58 @@ +package com.jozufozu.flywheel.backend.engine.indirect; + +import static org.lwjgl.opengl.GL15.glDeleteBuffers; +import static org.lwjgl.opengl.GL45.glCopyNamedBufferSubData; +import static org.lwjgl.opengl.GL45.glCreateBuffers; +import static org.lwjgl.opengl.GL45.glNamedBufferStorage; + +import com.jozufozu.flywheel.gl.GlObject; +import com.jozufozu.flywheel.lib.memory.FlwMemoryTracker; + +/** + * A buffer for storing data on the GPU that can be resized. + *
+ * The only way to get data in and out is to use GPU copies. + */ +public class ResizableStorageBuffer extends GlObject { + private long capacity = 0; + + public ResizableStorageBuffer() { + handle(glCreateBuffers()); + } + + public long capacity() { + return capacity; + } + + public void ensureCapacity(long capacity) { + FlwMemoryTracker._freeGPUMemory(this.capacity); + + if (this.capacity > 0) { + int oldHandle = handle(); + int newHandle = glCreateBuffers(); + + glNamedBufferStorage(newHandle, capacity, 0); + + glCopyNamedBufferSubData(oldHandle, newHandle, 0, 0, this.capacity); + + deleteInternal(oldHandle); + + handle(newHandle); + } else { + glNamedBufferStorage(handle(), capacity, 0); + } + this.capacity = capacity; + FlwMemoryTracker._allocGPUMemory(this.capacity); + } + + @Override + protected void deleteInternal(int handle) { + glDeleteBuffers(handle); + } + + @Override + public void delete() { + super.delete(); + FlwMemoryTracker._freeGPUMemory(capacity); + } +} diff --git a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/StagingBuffer.java b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/StagingBuffer.java index ca7ba8e8d..ec1e4ea9b 100644 --- a/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/StagingBuffer.java +++ b/src/main/java/com/jozufozu/flywheel/backend/engine/indirect/StagingBuffer.java @@ -2,19 +2,21 @@ package com.jozufozu.flywheel.backend.engine.indirect; import java.util.ArrayList; import java.util.List; +import java.util.function.LongConsumer; import org.lwjgl.opengl.GL45C; import org.lwjgl.system.MemoryUtil; import com.jozufozu.flywheel.gl.GlFence; import com.jozufozu.flywheel.lib.memory.FlwMemoryTracker; +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 public class StagingBuffer { - private static final long DEFAULT_CAPACITY = 1024 * 1024 * 8; + 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 MAP_FLAGS = GL45C.GL_MAP_PERSISTENT_BIT | GL45C.GL_MAP_WRITE_BIT | GL45C.GL_MAP_FLUSH_EXPLICIT_BIT | GL45C.GL_MAP_INVALIDATE_BUFFER_BIT; @@ -27,6 +29,8 @@ public class StagingBuffer { private long totalAvailable; + private MemoryBlock scratch; + private final OverflowStagingBuffer overflow = new OverflowStagingBuffer(); private final PriorityQueue transfers = new ObjectArrayFIFOQueue<>(); private final PriorityQueue fencedRegions = new ObjectArrayFIFOQueue<>(); @@ -47,6 +51,52 @@ public class StagingBuffer { FlwMemoryTracker._allocCPUMemory(capacity); } + /** + * Enqueue a copy of a known size to the given VBO. + *
+ * The consumer will receive a pointer to a block of memory of the given size, and is expected to write to the + * complete range. The initial contents of the memory block are undefined. + * + * @param size The size in bytes of the copy. + * @param dstVbo The VBO to copy to. + * @param dstOffset The offset in the destination VBO. + * @param write A consumer that will receive a pointer to the memory block. + */ + public void enqueueCopy(long size, int dstVbo, long dstOffset, LongConsumer write) { + // Try to write directly into the staging buffer if there is enough contiguous space. + var direct = reserveForTransferTo(size, dstVbo, dstOffset); + + if (direct != MemoryUtil.NULL) { + write.accept(direct); + return; + } + + // Otherwise, write to a scratch buffer and enqueue a copy. + var block = getScratch(size); + write.accept(block.ptr()); + enqueueCopy(block.ptr(), size, dstVbo, dstOffset); + } + + private MemoryBlock getScratch(long size) { + if (scratch == null) { + scratch = MemoryBlock.malloc(size); + } else if (scratch.size() < size) { + scratch = scratch.realloc(size); + } + return scratch; + } + + /** + * Enqueue a copy from the given pointer to the given VBO. + * + * @param block The block to copy from. + * @param dstVbo The VBO to copy to. + * @param dstOffset The offset in the destination VBO. + */ + public void enqueueCopy(MemoryBlock block, int dstVbo, long dstOffset) { + enqueueCopy(block.ptr(), block.size(), dstVbo, dstOffset); + } + /** * Enqueue a copy from the given pointer to the given VBO. * @@ -92,12 +142,12 @@ public class StagingBuffer { *
* This will generally be a more efficient way to transfer data as it avoids a copy, however, * this method does not allow for non-contiguous writes, so you should fall back to - * {@link #enqueueCopy} if this returns {@link MemoryUtil#NULL}. + * {@link #enqueueCopy} if this returns {@code null}. * * @param size The size of the transfer you wish to make. * @param dstVbo The VBO you wish to transfer to. * @param dstOffset The offset in the destination VBO. - * @return A pointer to the reserved space, or {@link MemoryUtil#NULL} if there is not enough contiguous space. + * @return A pointer to the reserved space, or {@code null} if there is not enough contiguous space. */ public long reserveForTransferTo(long size, int dstVbo, long dstOffset) { // Don't need to check totalAvailable here because that's a looser constraint than the bytes remaining. @@ -187,6 +237,8 @@ public class StagingBuffer { GL45C.glDeleteBuffers(vbo); overflow.delete(); + scratch.free(); + FlwMemoryTracker._freeCPUMemory(capacity); } diff --git a/src/main/java/com/jozufozu/flywheel/lib/math/MoreMath.java b/src/main/java/com/jozufozu/flywheel/lib/math/MoreMath.java index 2e7ed44a2..169a449a1 100644 --- a/src/main/java/com/jozufozu/flywheel/lib/math/MoreMath.java +++ b/src/main/java/com/jozufozu/flywheel/lib/math/MoreMath.java @@ -57,4 +57,12 @@ public final class MoreMath { } } } + + public static long ceilLong(double d) { + return (long) Math.ceil(d); + } + + public static long ceilLong(float f) { + return (long) Math.ceil(f); + } }