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:
Jozufozu 2023-12-06 23:30:01 -08:00
parent d3e7789f13
commit 567491ca4d
8 changed files with 256 additions and 226 deletions

View file

@ -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();
}
}

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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);
}
}