mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-12-27 07:26:48 +01:00
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
This commit is contained in:
parent
d3e7789f13
commit
567491ca4d
8 changed files with 256 additions and 226 deletions
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<I extends Instance> {
|
||||
|
@ -53,7 +50,6 @@ public class IndirectCullingGroup<I extends Instance> {
|
|||
.getStride() + IndirectBuffers.INT_SIZE;
|
||||
|
||||
buffers = new IndirectBuffers(objectStride);
|
||||
buffers.createBuffers();
|
||||
|
||||
meshPool = new IndirectMeshPool();
|
||||
|
||||
|
@ -68,8 +64,9 @@ public class IndirectCullingGroup<I extends Instance> {
|
|||
|
||||
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<Material, Mesh> materialMeshEntry : meshes.entrySet()) {
|
||||
|
@ -180,7 +177,7 @@ public class IndirectCullingGroup<I extends Instance> {
|
|||
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<I extends Instance> {
|
|||
|
||||
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<I extends Instance> {
|
|||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -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<I extends Instance> extends AbstractInstancer<I> {
|
||||
private final long instanceStride;
|
||||
private final long objectStride;
|
||||
private final InstanceWriter<I> writer;
|
||||
private int modelId;
|
||||
|
||||
public IndirectInstancer(InstanceType<I> 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<I extends Instance> extends AbstractInstancer<I>
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
* <br>
|
||||
* 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);
|
||||
}
|
||||
}
|
|
@ -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<Transfer> transfers = new ObjectArrayFIFOQueue<>();
|
||||
private final PriorityQueue<FencedRegion> fencedRegions = new ObjectArrayFIFOQueue<>();
|
||||
|
@ -47,6 +51,52 @@ public class StagingBuffer {
|
|||
FlwMemoryTracker._allocCPUMemory(capacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue a copy of a known size to the given VBO.
|
||||
* <br>
|
||||
* 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 {
|
|||
* <br>
|
||||
* 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue