mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-01-08 13:26:39 +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
c0d1e736e4
commit
c2a4ac2e83
8 changed files with 256 additions and 226 deletions
|
@ -1,22 +1,14 @@
|
||||||
package com.jozufozu.flywheel.backend.engine.indirect;
|
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.GL43.GL_SHADER_STORAGE_BUFFER;
|
||||||
import static org.lwjgl.opengl.GL44.nglBindBuffersRange;
|
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.MemoryUtil;
|
||||||
import org.lwjgl.system.Pointer;
|
import org.lwjgl.system.Pointer;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.gl.buffer.GlBufferType;
|
import com.jozufozu.flywheel.gl.buffer.GlBufferType;
|
||||||
import com.jozufozu.flywheel.lib.memory.FlwMemoryTracker;
|
|
||||||
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
|
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
|
||||||
|
|
||||||
// TODO: better abstractions
|
|
||||||
public class IndirectBuffers {
|
public class IndirectBuffers {
|
||||||
// Number of vbos created.
|
// Number of vbos created.
|
||||||
public static final int BUFFER_COUNT = 4;
|
public static final int BUFFER_COUNT = 4;
|
||||||
|
@ -30,22 +22,24 @@ public class IndirectBuffers {
|
||||||
|
|
||||||
public static final long MODEL_STRIDE = 24;
|
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
|
// 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 OFFSET_OFFSET = BUFFER_COUNT * INT_SIZE;
|
||||||
private static final long SIZE_OFFSET = OFFSET_OFFSET + BUFFER_COUNT * PTR_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 OBJECT_SIZE_OFFSET = SIZE_OFFSET;
|
||||||
private static final long TARGET_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE;
|
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 MODEL_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE * 2;
|
||||||
private static final long DRAW_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE * 3;
|
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,
|
* Each segment stores {@link IndirectBuffers#BUFFER_COUNT} elements,
|
||||||
* one for the object buffer, target buffer, model buffer, and draw buffer.
|
* one for the object buffer, target buffer, model buffer, and draw buffer.
|
||||||
*/
|
*/
|
||||||
private final MemoryBlock buffers;
|
private final MemoryBlock multiBindBlock;
|
||||||
private final long objectStride;
|
private final long objectStride;
|
||||||
public int object;
|
public final ResizableStorageArray object;
|
||||||
public int target;
|
public final ResizableStorageArray target;
|
||||||
public int model;
|
public final ResizableStorageArray model;
|
||||||
public int draw;
|
public final ResizableStorageArray 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;
|
|
||||||
|
|
||||||
IndirectBuffers(long objectStride) {
|
IndirectBuffers(long objectStride) {
|
||||||
this.objectStride = objectStride;
|
this.objectStride = objectStride;
|
||||||
this.buffers = MemoryBlock.calloc(BUFFERS_SIZE_BYTES, 1);
|
this.multiBindBlock = MemoryBlock.calloc(BUFFERS_SIZE_BYTES, 1);
|
||||||
}
|
|
||||||
|
|
||||||
void createBuffers() {
|
object = new ResizableStorageArray(objectStride, 1.75);
|
||||||
final long ptr = buffers.ptr();
|
target = new ResizableStorageArray(INT_SIZE, 1.75);
|
||||||
nglCreateBuffers(BUFFER_COUNT, ptr);
|
model = new ResizableStorageArray(MODEL_STRIDE, 2);
|
||||||
object = MemoryUtil.memGetInt(ptr + OBJECT_OFFSET);
|
draw = new ResizableStorageArray(DRAW_COMMAND_STRIDE, 2);
|
||||||
target = MemoryUtil.memGetInt(ptr + TARGET_OFFSET);
|
|
||||||
model = MemoryUtil.memGetInt(ptr + MODEL_OFFSET);
|
|
||||||
draw = MemoryUtil.memGetInt(ptr + DRAW_OFFSET);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateCounts(int objectCount, int drawCount, int modelCount) {
|
void updateCounts(int objectCount, int drawCount, int modelCount) {
|
||||||
if (objectCount > maxObjectCount) {
|
object.ensureCapacity(objectCount);
|
||||||
createObjectStorage((int) (objectCount * OBJECT_GROWTH_FACTOR));
|
target.ensureCapacity(objectCount);
|
||||||
}
|
model.ensureCapacity(modelCount);
|
||||||
if (modelCount > maxModelCount) {
|
draw.ensureCapacity(drawCount);
|
||||||
createModelStorage((int) (modelCount * MODEL_GROWTH_FACTOR));
|
|
||||||
}
|
final long ptr = multiBindBlock.ptr();
|
||||||
if (drawCount > maxDrawCount) {
|
MemoryUtil.memPutInt(ptr + OBJECT_HANDLE_OFFSET, object.handle());
|
||||||
createDrawStorage((int) (drawCount * DRAW_GROWTH_FACTOR));
|
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 + OBJECT_SIZE_OFFSET, objectStride * objectCount);
|
||||||
MemoryUtil.memPutAddress(ptr + TARGET_SIZE_OFFSET, INT_SIZE * objectCount);
|
MemoryUtil.memPutAddress(ptr + TARGET_SIZE_OFFSET, INT_SIZE * objectCount);
|
||||||
MemoryUtil.memPutAddress(ptr + MODEL_SIZE_OFFSET, MODEL_STRIDE * modelCount);
|
MemoryUtil.memPutAddress(ptr + MODEL_SIZE_OFFSET, MODEL_STRIDE * modelCount);
|
||||||
MemoryUtil.memPutAddress(ptr + DRAW_SIZE_OFFSET, DRAW_COMMAND_STRIDE * drawCount);
|
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() {
|
public void bindForCompute() {
|
||||||
multiBind();
|
multiBind();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void bindForDraw() {
|
public void bindForDraw() {
|
||||||
multiBind();
|
multiBind();
|
||||||
GlBufferType.DRAW_INDIRECT_BUFFER.bind(draw);
|
GlBufferType.DRAW_INDIRECT_BUFFER.bind(draw.handle());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void multiBind() {
|
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);
|
nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, IndirectBuffers.BUFFER_COUNT, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete() {
|
public void delete() {
|
||||||
nglDeleteBuffers(BUFFER_COUNT, buffers.ptr());
|
multiBindBlock.free();
|
||||||
buffers.free();
|
|
||||||
if (modelPtr != null) {
|
object.delete();
|
||||||
modelPtr.free();
|
target.delete();
|
||||||
}
|
model.delete();
|
||||||
if (drawPtr != null) {
|
draw.delete();
|
||||||
drawPtr.free();
|
|
||||||
}
|
|
||||||
freeObjectStorage();
|
|
||||||
freeModelStorage();
|
|
||||||
freeDrawStorage();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,6 @@ import java.util.EnumMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.lwjgl.system.MemoryUtil;
|
|
||||||
|
|
||||||
import com.jozufozu.flywheel.api.event.RenderStage;
|
import com.jozufozu.flywheel.api.event.RenderStage;
|
||||||
import com.jozufozu.flywheel.api.instance.Instance;
|
import com.jozufozu.flywheel.api.instance.Instance;
|
||||||
import com.jozufozu.flywheel.api.instance.InstanceType;
|
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.GlCompat;
|
||||||
import com.jozufozu.flywheel.gl.shader.GlProgram;
|
import com.jozufozu.flywheel.gl.shader.GlProgram;
|
||||||
import com.jozufozu.flywheel.lib.context.Contexts;
|
import com.jozufozu.flywheel.lib.context.Contexts;
|
||||||
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
|
|
||||||
import com.jozufozu.flywheel.lib.model.ModelUtil;
|
import com.jozufozu.flywheel.lib.model.ModelUtil;
|
||||||
|
|
||||||
public class IndirectCullingGroup<I extends Instance> {
|
public class IndirectCullingGroup<I extends Instance> {
|
||||||
|
@ -53,7 +50,6 @@ public class IndirectCullingGroup<I extends Instance> {
|
||||||
.getStride() + IndirectBuffers.INT_SIZE;
|
.getStride() + IndirectBuffers.INT_SIZE;
|
||||||
|
|
||||||
buffers = new IndirectBuffers(objectStride);
|
buffers = new IndirectBuffers(objectStride);
|
||||||
buffers.createBuffers();
|
|
||||||
|
|
||||||
meshPool = new IndirectMeshPool();
|
meshPool = new IndirectMeshPool();
|
||||||
|
|
||||||
|
@ -68,8 +64,9 @@ public class IndirectCullingGroup<I extends Instance> {
|
||||||
|
|
||||||
var boundingSphere = ModelUtil.computeBoundingSphere(meshes.values());
|
var boundingSphere = ModelUtil.computeBoundingSphere(meshes.values());
|
||||||
|
|
||||||
int modelID = indirectModels.size();
|
int modelId = indirectModels.size();
|
||||||
var indirectModel = new IndirectModel(instancer, modelID, boundingSphere);
|
instancer.setModelId(modelId);
|
||||||
|
var indirectModel = new IndirectModel(instancer, modelId, boundingSphere);
|
||||||
indirectModels.add(indirectModel);
|
indirectModels.add(indirectModel);
|
||||||
|
|
||||||
for (Map.Entry<Material, Mesh> materialMeshEntry : meshes.entrySet()) {
|
for (Map.Entry<Material, Mesh> materialMeshEntry : meshes.entrySet()) {
|
||||||
|
@ -180,7 +177,7 @@ public class IndirectCullingGroup<I extends Instance> {
|
||||||
long pos = 0;
|
long pos = 0;
|
||||||
for (IndirectModel batch : indirectModels) {
|
for (IndirectModel batch : indirectModels) {
|
||||||
var instanceCount = batch.instancer.getInstanceCount();
|
var instanceCount = batch.instancer.getInstanceCount();
|
||||||
batch.writeObjects(stagingBuffer, pos, buffers.object);
|
batch.writeObjects(stagingBuffer, pos, buffers.object.handle());
|
||||||
|
|
||||||
pos += instanceCount * objectStride;
|
pos += instanceCount * objectStride;
|
||||||
}
|
}
|
||||||
|
@ -188,16 +185,16 @@ public class IndirectCullingGroup<I extends Instance> {
|
||||||
|
|
||||||
private void uploadModels(StagingBuffer stagingBuffer) {
|
private void uploadModels(StagingBuffer stagingBuffer) {
|
||||||
var totalSize = indirectModels.size() * IndirectBuffers.MODEL_STRIDE;
|
var totalSize = indirectModels.size() * IndirectBuffers.MODEL_STRIDE;
|
||||||
long writePtr = stagingBuffer.reserveForTransferTo(totalSize, buffers.model, 0);
|
var handle = buffers.model.handle();
|
||||||
|
|
||||||
if (writePtr == MemoryUtil.NULL) {
|
stagingBuffer.enqueueCopy(totalSize, handle, 0, this::writeModels);
|
||||||
var block = MemoryBlock.malloc(totalSize);
|
}
|
||||||
writeModels(block.ptr());
|
|
||||||
stagingBuffer.enqueueCopy(block.ptr(), totalSize, buffers.model, 0);
|
private void uploadIndirectCommands(StagingBuffer stagingBuffer) {
|
||||||
block.free();
|
var totalSize = indirectDraws.size() * IndirectBuffers.DRAW_COMMAND_STRIDE;
|
||||||
} else {
|
var handle = buffers.draw.handle();
|
||||||
writeModels(writePtr);
|
|
||||||
}
|
stagingBuffer.enqueueCopy(totalSize, handle, 0, this::writeCommands);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeModels(long writePtr) {
|
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) {
|
private void writeCommands(long writePtr) {
|
||||||
for (var batch : indirectDraws) {
|
for (var batch : indirectDraws) {
|
||||||
batch.writeIndirectCommand(writePtr);
|
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.InstanceType;
|
||||||
import com.jozufozu.flywheel.api.instance.InstanceWriter;
|
import com.jozufozu.flywheel.api.instance.InstanceWriter;
|
||||||
import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
|
import com.jozufozu.flywheel.backend.engine.AbstractInstancer;
|
||||||
import com.jozufozu.flywheel.lib.memory.MemoryBlock;
|
|
||||||
|
|
||||||
public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I> {
|
public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I> {
|
||||||
private final long instanceStride;
|
|
||||||
private final long objectStride;
|
private final long objectStride;
|
||||||
private final InstanceWriter<I> writer;
|
private final InstanceWriter<I> writer;
|
||||||
|
private int modelId;
|
||||||
|
|
||||||
public IndirectInstancer(InstanceType<I> type) {
|
public IndirectInstancer(InstanceType<I> type) {
|
||||||
super(type);
|
super(type);
|
||||||
this.instanceStride = type.getLayout()
|
long instanceStride = type.getLayout()
|
||||||
.getStride();
|
.getStride();
|
||||||
this.objectStride = instanceStride + IndirectBuffers.INT_SIZE;
|
this.objectStride = instanceStride + IndirectBuffers.INT_SIZE;
|
||||||
writer = this.type.getWriter();
|
writer = this.type.getWriter();
|
||||||
|
@ -25,60 +24,38 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
|
||||||
removeDeletedInstances();
|
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();
|
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)) {
|
for (int i = changed.nextSetBit(0); i >= 0 && i < count; i = changed.nextSetBit(i + 1)) {
|
||||||
long ptr = stagingBuffer.reserveForTransferTo(objectStride, dstVbo, start + i * objectStride);
|
var instance = instances.get(i);
|
||||||
if (ptr == MemoryUtil.NULL) {
|
stagingBuffer.enqueueCopy(objectStride, dstVbo, start + i * objectStride, ptr -> writeOne(ptr, instance));
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
changed.clear();
|
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 totalSize = objectStride * instances.size();
|
||||||
|
|
||||||
long ptr = stagingBuffer.reserveForTransferTo(totalSize, dstVbo, start);
|
stagingBuffer.enqueueCopy(totalSize, dstVbo, start, this::writeAll);
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
changed.clear();
|
changed.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeAll(long ptr, int modelID) {
|
private void writeAll(long ptr) {
|
||||||
for (I instance : instances) {
|
for (I instance : instances) {
|
||||||
writeOne(ptr, instance, modelID);
|
writeOne(ptr, instance);
|
||||||
ptr += objectStride;
|
ptr += objectStride;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeOne(long ptr, I instance, int modelID) {
|
private void writeOne(long ptr, I instance) {
|
||||||
// write modelID
|
// write modelID
|
||||||
MemoryUtil.memPutInt(ptr, modelID);
|
MemoryUtil.memPutInt(ptr, modelId);
|
||||||
// write object
|
// write object
|
||||||
writer.write(ptr + IndirectBuffers.INT_SIZE, instance);
|
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) {
|
public void writeObjects(StagingBuffer stagingBuffer, long start, int dstVbo) {
|
||||||
if (needsFullWrite) {
|
if (needsFullWrite) {
|
||||||
instancer.writeFull(stagingBuffer, start, id, dstVbo);
|
instancer.writeFull(stagingBuffer, start, dstVbo);
|
||||||
} else {
|
} 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.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.LongConsumer;
|
||||||
|
|
||||||
import org.lwjgl.opengl.GL45C;
|
import org.lwjgl.opengl.GL45C;
|
||||||
import org.lwjgl.system.MemoryUtil;
|
import org.lwjgl.system.MemoryUtil;
|
||||||
|
|
||||||
import com.jozufozu.flywheel.gl.GlFence;
|
import com.jozufozu.flywheel.gl.GlFence;
|
||||||
import com.jozufozu.flywheel.lib.memory.FlwMemoryTracker;
|
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.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
|
// https://github.com/CaffeineMC/sodium-fabric/blob/dev/src/main/java/me/jellysquid/mods/sodium/client/gl/arena/staging/MappedStagingBuffer.java
|
||||||
public class StagingBuffer {
|
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 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;
|
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 long totalAvailable;
|
||||||
|
|
||||||
|
private MemoryBlock scratch;
|
||||||
|
|
||||||
private final OverflowStagingBuffer overflow = new OverflowStagingBuffer();
|
private final OverflowStagingBuffer overflow = new OverflowStagingBuffer();
|
||||||
private final PriorityQueue<Transfer> transfers = new ObjectArrayFIFOQueue<>();
|
private final PriorityQueue<Transfer> transfers = new ObjectArrayFIFOQueue<>();
|
||||||
private final PriorityQueue<FencedRegion> fencedRegions = new ObjectArrayFIFOQueue<>();
|
private final PriorityQueue<FencedRegion> fencedRegions = new ObjectArrayFIFOQueue<>();
|
||||||
|
@ -47,6 +51,52 @@ public class StagingBuffer {
|
||||||
FlwMemoryTracker._allocCPUMemory(capacity);
|
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.
|
* Enqueue a copy from the given pointer to the given VBO.
|
||||||
*
|
*
|
||||||
|
@ -92,12 +142,12 @@ public class StagingBuffer {
|
||||||
* <br>
|
* <br>
|
||||||
* This will generally be a more efficient way to transfer data as it avoids a copy, however,
|
* 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
|
* 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 size The size of the transfer you wish to make.
|
||||||
* @param dstVbo The VBO you wish to transfer to.
|
* @param dstVbo The VBO you wish to transfer to.
|
||||||
* @param dstOffset The offset in the destination VBO.
|
* @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) {
|
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.
|
// 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);
|
GL45C.glDeleteBuffers(vbo);
|
||||||
overflow.delete();
|
overflow.delete();
|
||||||
|
|
||||||
|
scratch.free();
|
||||||
|
|
||||||
FlwMemoryTracker._freeCPUMemory(capacity);
|
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