diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/BufferBindings.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/BufferBindings.java index 479eaed74..658096695 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/BufferBindings.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/BufferBindings.java @@ -3,7 +3,7 @@ package dev.engine_room.flywheel.backend.engine.indirect; public final class BufferBindings { public static final int INSTANCE = 0; public static final int TARGET = 1; - public static final int MODEL_INDEX = 2; + public static final int PAGE_FRAME_DESCRIPTOR = 2; public static final int MODEL = 3; public static final int DRAW = 4; public static final int LIGHT_LUT = 5; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java index 193efd004..498030fe7 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java @@ -32,14 +32,14 @@ public class IndirectBuffers { // Offsets to the vbos private static final long INSTANCE_HANDLE_OFFSET = HANDLE_OFFSET; private static final long TARGET_HANDLE_OFFSET = INT_SIZE; - private static final long MODEL_INDEX_HANDLE_OFFSET = INT_SIZE * 2; + private static final long PAGE_FRAME_DESCRIPTOR_HANDLE_OFFSET = INT_SIZE * 2; private static final long MODEL_HANDLE_OFFSET = INT_SIZE * 3; private static final long DRAW_HANDLE_OFFSET = INT_SIZE * 4; // Offsets to the sizes private static final long INSTANCE_SIZE_OFFSET = SIZE_OFFSET; private static final long TARGET_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE; - private static final long MODEL_INDEX_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE * 2; + private static final long PAGE_FRAME_DESCRIPTOR_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE * 2; private static final long MODEL_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE * 3; private static final long DRAW_SIZE_OFFSET = SIZE_OFFSET + PTR_SIZE * 4; @@ -62,7 +62,7 @@ public class IndirectBuffers { private final MemoryBlock multiBindBlock; private final long instanceStride; - public final InstancePager pageFile; + public final ObjectStorage objectStorage; public final ResizableStorageArray target; public final ResizableStorageArray model; public final ResizableStorageArray draw; @@ -71,7 +71,7 @@ public class IndirectBuffers { this.instanceStride = instanceStride; this.multiBindBlock = MemoryBlock.calloc(BUFFERS_SIZE_BYTES, 1); - pageFile = new InstancePager(instanceStride); + objectStorage = new ObjectStorage(instanceStride); target = new ResizableStorageArray(INT_SIZE, INSTANCE_GROWTH_FACTOR); model = new ResizableStorageArray(MODEL_STRIDE, MODEL_GROWTH_FACTOR); draw = new ResizableStorageArray(DRAW_COMMAND_STRIDE, DRAW_GROWTH_FACTOR); @@ -83,15 +83,15 @@ public class IndirectBuffers { draw.ensureCapacity(drawCount); final long ptr = multiBindBlock.ptr(); - MemoryUtil.memPutInt(ptr + INSTANCE_HANDLE_OFFSET, pageFile.objects.handle()); + MemoryUtil.memPutInt(ptr + INSTANCE_HANDLE_OFFSET, objectStorage.objectBuffer.handle()); MemoryUtil.memPutInt(ptr + TARGET_HANDLE_OFFSET, target.handle()); - MemoryUtil.memPutInt(ptr + MODEL_INDEX_HANDLE_OFFSET, pageFile.pageTable.handle()); + MemoryUtil.memPutInt(ptr + PAGE_FRAME_DESCRIPTOR_HANDLE_OFFSET, objectStorage.frameDescriptorBuffer.handle()); MemoryUtil.memPutInt(ptr + MODEL_HANDLE_OFFSET, model.handle()); MemoryUtil.memPutInt(ptr + DRAW_HANDLE_OFFSET, draw.handle()); - MemoryUtil.memPutAddress(ptr + INSTANCE_SIZE_OFFSET, pageFile.objects.capacity()); + MemoryUtil.memPutAddress(ptr + INSTANCE_SIZE_OFFSET, objectStorage.objectBuffer.capacity()); MemoryUtil.memPutAddress(ptr + TARGET_SIZE_OFFSET, INT_SIZE * instanceCount); - MemoryUtil.memPutAddress(ptr + MODEL_INDEX_SIZE_OFFSET, pageFile.pageTable.capacity()); + MemoryUtil.memPutAddress(ptr + PAGE_FRAME_DESCRIPTOR_SIZE_OFFSET, objectStorage.frameDescriptorBuffer.capacity()); MemoryUtil.memPutAddress(ptr + MODEL_SIZE_OFFSET, MODEL_STRIDE * modelCount); MemoryUtil.memPutAddress(ptr + DRAW_SIZE_OFFSET, DRAW_COMMAND_STRIDE * drawCount); } @@ -121,7 +121,7 @@ public class IndirectBuffers { public void delete() { multiBindBlock.free(); - pageFile.delete(); + objectStorage.delete(); target.delete(); model.delete(); draw.delete(); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java index da4f8cfe8..961d5b2ce 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java @@ -95,7 +95,7 @@ public class IndirectCullingGroup { // Upload only instances that have changed. uploadInstances(stagingBuffer); - buffers.pageFile.uploadTable(stagingBuffer); + buffers.objectStorage.uploadDescriptors(stagingBuffer); // We need to upload the models every frame to reset the instance count. uploadModels(stagingBuffer); @@ -119,7 +119,7 @@ public class IndirectCullingGroup { cullProgram.bind(); buffers.bindForCompute(); - glDispatchCompute(buffers.pageFile.capacity(), 1, 1); + glDispatchCompute(buffers.objectStorage.capacity(), 1, 1); } public void dispatchApply() { @@ -172,7 +172,7 @@ public class IndirectCullingGroup { } public void add(IndirectInstancer instancer, InstancerKey key, MeshPool meshPool) { - instancer.pageFile = buffers.pageFile.createAllocation(); + instancer.mapping = buffers.objectStorage.createMapping(); instancer.postUpdate(instancers.size(), -1); instancers.add(instancer); @@ -245,7 +245,7 @@ public class IndirectCullingGroup { private void uploadInstances(StagingBuffer stagingBuffer) { for (var instancer : instancers) { - instancer.uploadInstances(stagingBuffer, buffers.pageFile.objects.handle()); + instancer.uploadInstances(stagingBuffer, buffers.objectStorage.objectBuffer.handle()); } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectInstancer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectInstancer.java index da825ed63..fc8e1361e 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectInstancer.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectInstancer.java @@ -3,6 +3,7 @@ package dev.engine_room.flywheel.backend.engine.indirect; import java.util.ArrayList; import java.util.List; +import org.jetbrains.annotations.UnknownNullability; import org.joml.Vector4fc; import org.lwjgl.system.MemoryUtil; @@ -23,7 +24,7 @@ public class IndirectInstancer extends AbstractInstancer private final AtomicBitSet changedPages = new AtomicBitSet(); - public InstancePager.Allocation pageFile; + public ObjectStorage.@UnknownNullability Mapping mapping; private int modelIndex = -1; private int baseInstance = -1; @@ -42,14 +43,14 @@ public class IndirectInstancer extends AbstractInstancer return; } changed.set(index); - changedPages.set(InstancePager.object2Page(index)); + changedPages.set(ObjectStorage.objectIndex2PageIndex(index)); } @Override protected void setRangeChanged(int start, int end) { super.setRangeChanged(start, end); - changedPages.set(InstancePager.object2Page(start), InstancePager.object2Page(end) + 1); + changedPages.set(ObjectStorage.objectIndex2PageIndex(start), ObjectStorage.objectIndex2PageIndex(end) + 1); } public void addDraw(IndirectDraw draw) { @@ -67,7 +68,7 @@ public class IndirectInstancer extends AbstractInstancer public void postUpdate(int modelIndex, int baseInstance) { this.modelIndex = modelIndex; this.baseInstance = baseInstance; - pageFile.update(modelIndex, instanceCount()); + mapping.update(modelIndex, instanceCount()); } public void writeModel(long ptr) { @@ -81,20 +82,20 @@ public class IndirectInstancer extends AbstractInstancer } public void uploadInstances(StagingBuffer stagingBuffer, int instanceVbo) { - int numPages = pageFile.pageCount(); + int numPages = mapping.pageCount(); var instanceCount = instances.size(); for (int page = changedPages.nextSetBit(0); page >= 0 && page < numPages; page = changedPages.nextSetBit(page + 1)) { - int startObject = InstancePager.page2Object(page); + int startObject = ObjectStorage.pageIndex2ObjectIndex(page); if (startObject >= instanceCount) { break; } - int endObject = Math.min(instanceCount, InstancePager.page2Object(page + 1)); + int endObject = Math.min(instanceCount, ObjectStorage.pageIndex2ObjectIndex(page + 1)); - long baseByte = pageFile.page2ByteOffset(page); + long baseByte = mapping.page2ByteOffset(page); long size = (endObject - startObject) * instanceStride; stagingBuffer.enqueueCopy(size, instanceVbo, baseByte, ptr -> { @@ -115,7 +116,7 @@ public class IndirectInstancer extends AbstractInstancer draw.delete(); } - pageFile.delete(); + mapping.delete(); } public int modelIndex() { diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/InstancePager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/ObjectStorage.java similarity index 59% rename from common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/InstancePager.java rename to common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/ObjectStorage.java index a70b98321..69b0ddef4 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/InstancePager.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/ObjectStorage.java @@ -7,7 +7,7 @@ import org.lwjgl.system.MemoryUtil; import dev.engine_room.flywheel.backend.engine.AbstractArena; import dev.engine_room.flywheel.lib.memory.MemoryBlock; -public class InstancePager extends AbstractArena { +public class ObjectStorage extends AbstractArena { // 32 objects per page. Allows for convenient bitsets on the gpu. public static final int LOG_2_PAGE_SIZE = 5; public static final int PAGE_SIZE = 1 << LOG_2_PAGE_SIZE; @@ -15,38 +15,39 @@ public class InstancePager extends AbstractArena { public static final int INITIAL_PAGES_ALLOCATED = 4; - private MemoryBlock pageTableData; - public final ResizableStorageBuffer objects; - public final ResizableStorageBuffer pageTable; + /** + * The GPU side buffer containing all the objects, logically divided into page frames. + */ + public final ResizableStorageBuffer objectBuffer; + /** + * The GPU side buffer containing 32 bit descriptors for each page frame. + */ + public final ResizableStorageBuffer frameDescriptorBuffer; + /** + * The CPU side memory block containing the page descriptors. + */ + private MemoryBlock frameDescriptors; private boolean needsUpload = false; - public InstancePager(long objectSizeBytes) { + public ObjectStorage(long objectSizeBytes) { super(PAGE_SIZE * objectSizeBytes); - this.objects = new ResizableStorageBuffer(); - this.pageTable = new ResizableStorageBuffer(); + this.objectBuffer = new ResizableStorageBuffer(); + this.frameDescriptorBuffer = new ResizableStorageBuffer(); - pageTableData = MemoryBlock.malloc(INITIAL_PAGES_ALLOCATED * Integer.BYTES); - objects.ensureCapacity(INITIAL_PAGES_ALLOCATED * elementSizeBytes); - pageTable.ensureCapacity(INITIAL_PAGES_ALLOCATED * Integer.BYTES); + objectBuffer.ensureCapacity(INITIAL_PAGES_ALLOCATED * elementSizeBytes); + frameDescriptorBuffer.ensureCapacity(INITIAL_PAGES_ALLOCATED * Integer.BYTES); + frameDescriptors = MemoryBlock.malloc(INITIAL_PAGES_ALLOCATED * Integer.BYTES); } - public static int object2Page(int objectIndex) { - return objectIndex >> LOG_2_PAGE_SIZE; - } - - public static int page2Object(int pageIndex) { - return pageIndex << LOG_2_PAGE_SIZE; - } - - public Allocation createAllocation() { - return new Allocation(); + public Mapping createMapping() { + return new Mapping(); } @Override public long byteCapacity() { - return objects.capacity(); + return objectBuffer.capacity(); } @Override @@ -57,56 +58,57 @@ public class InstancePager extends AbstractArena { @Override protected void grow() { - pageTableData = pageTableData.realloc(pageTableData.size() * 2); - objects.ensureCapacity(objects.capacity() * 2); - pageTable.ensureCapacity(pageTable.capacity() * 2); + objectBuffer.ensureCapacity(objectBuffer.capacity() * 2); + frameDescriptorBuffer.ensureCapacity(frameDescriptorBuffer.capacity() * 2); + frameDescriptors = frameDescriptors.realloc(frameDescriptors.size() * 2); } - public void uploadTable(StagingBuffer stagingBuffer) { + public void uploadDescriptors(StagingBuffer stagingBuffer) { if (!needsUpload) { return; } // We could be smarter about which spans are uploaded but this thing is so small it's probably not worth it. - stagingBuffer.enqueueCopy(pageTableData.ptr(), pageTableData.size(), pageTable.handle(), 0); + stagingBuffer.enqueueCopy(frameDescriptors.ptr(), frameDescriptors.size(), frameDescriptorBuffer.handle(), 0); needsUpload = false; } public void delete() { - objects.delete(); - pageTable.delete(); - pageTableData.free(); + objectBuffer.delete(); + frameDescriptorBuffer.delete(); + frameDescriptors.free(); } private long ptrForPage(int page) { - return pageTableData.ptr() + (long) page * Integer.BYTES; + return frameDescriptors.ptr() + (long) page * Integer.BYTES; } - public class Allocation { - public static final int[] EMPTY_ALLOCATION = new int[0]; - public int[] pages = EMPTY_ALLOCATION; + public static int objectIndex2PageIndex(int objectIndex) { + return objectIndex >> LOG_2_PAGE_SIZE; + } + + public static int pageIndex2ObjectIndex(int pageIndex) { + return pageIndex << LOG_2_PAGE_SIZE; + } + + /** + * Maps serial object indices to pages, and manages the allocation of pages. + */ + public class Mapping { + private static final int[] EMPTY_ALLOCATION = new int[0]; + private int[] pages = EMPTY_ALLOCATION; private int modelIndex = -1; private int objectCount = 0; /** - * Calculates the page descriptor for the given page index. - * Runs under the assumption than all pages are full except maybe the last one. + * Adjust this allocation to the given model index and object count. + * + *

This method triggers eager resizing of the allocation to fit the new object count. + * If the model index is different from the current one, all frame descriptors will be updated. + * + * @param modelIndex The model index the objects in this allocation are associated with. + * @param objectCount The number of objects in this allocation. */ - private int calculatePageDescriptor(int pageIndex) { - int countInPage; - if (objectCount % PAGE_SIZE != 0 && pageIndex == pages.length - 1) { - // Last page && it isn't full -> use the remainder. - countInPage = objectCount & PAGE_MASK; - } else if (objectCount > 0) { - // Full page. - countInPage = PAGE_SIZE; - } else { - // Empty page, this shouldn't be reachable because we eagerly free empty pages. - countInPage = 0; - } - return (modelIndex & 0x3FFFFF) | (countInPage << 26); - } - public void update(int modelIndex, int objectCount) { boolean incremental = this.modelIndex == modelIndex; @@ -115,13 +117,13 @@ public class InstancePager extends AbstractArena { return; } - InstancePager.this.needsUpload = true; + ObjectStorage.this.needsUpload = true; this.modelIndex = modelIndex; this.objectCount = objectCount; var oldLength = pages.length; - var newLength = object2Page((objectCount + PAGE_MASK)); + var newLength = objectIndex2PageIndex((objectCount + PAGE_MASK)); if (oldLength > newLength) { // Eagerly free the now unnecessary pages. @@ -153,6 +155,42 @@ public class InstancePager extends AbstractArena { } } + public int pageCount() { + return pages.length; + } + + public long page2ByteOffset(int page) { + return ObjectStorage.this.byteOffsetOf(pages[page]); + } + + public void delete() { + for (int page : pages) { + ObjectStorage.this.free(page); + } + pages = EMPTY_ALLOCATION; + modelIndex = -1; + objectCount = 0; + } + + /** + * Calculates the page descriptor for the given page index. + * Runs under the assumption than all pages are full except maybe the last one. + */ + private int calculatePageDescriptor(int pageIndex) { + int countInPage; + if (objectCount % PAGE_SIZE != 0 && pageIndex == pages.length - 1) { + // Last page && it isn't full -> use the remainder. + countInPage = objectCount & PAGE_MASK; + } else if (objectCount > 0) { + // Full page. + countInPage = PAGE_SIZE; + } else { + // Empty page, this shouldn't be reachable because we eagerly free empty pages. + countInPage = 0; + } + return (modelIndex & 0x3FFFFF) | (countInPage << 26); + } + private void updateRange(int start, int oldLength) { for (int i = start; i < oldLength; i++) { MemoryUtil.memPutInt(ptrForPage(pages[i]), calculatePageDescriptor(i)); @@ -163,7 +201,7 @@ public class InstancePager extends AbstractArena { pages = Arrays.copyOf(pages, neededPages); for (int i = oldLength; i < neededPages; i++) { - var page = InstancePager.this.alloc(); + var page = ObjectStorage.this.alloc(); pages[i] = page; } } @@ -171,31 +209,10 @@ public class InstancePager extends AbstractArena { private void shrink(int oldLength, int neededPages) { for (int i = oldLength - 1; i >= neededPages; i--) { var page = pages[i]; - InstancePager.this.free(page); + ObjectStorage.this.free(page); } pages = Arrays.copyOf(pages, neededPages); } - - public int capacity() { - return pages.length << LOG_2_PAGE_SIZE; - } - - public int pageCount() { - return pages.length; - } - - public long page2ByteOffset(int page) { - return InstancePager.this.byteOffsetOf(pages[page]); - } - - public void delete() { - for (int page : pages) { - InstancePager.this.free(page); - } - pages = EMPTY_ALLOCATION; - modelIndex = -1; - objectCount = 0; - } } } diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/buffer_bindings.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/buffer_bindings.glsl index 346adfa93..449836630 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/buffer_bindings.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/buffer_bindings.glsl @@ -1,6 +1,6 @@ #define _FLW_INSTANCE_BUFFER_BINDING 0 #define _FLW_TARGET_BUFFER_BINDING 1 -#define _FLW_MODEL_INDEX_BUFFER_BINDING 2 +#define _FLW_PAGE_FRAME_DESCRIPTOR_BUFFER_BINDING 2 #define _FLW_MODEL_BUFFER_BINDING 3 #define _FLW_DRAW_BUFFER_BINDING 4 #define _FLW_LIGHT_LUT_BUFFER_BINDING 5 diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/cull.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/cull.glsl index 4186f470d..e128b0daf 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/cull.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/cull.glsl @@ -15,8 +15,8 @@ const uint _FLW_PAGE_COUNT_OFFSET = 26u; // Bottom 26 bits for the model index. const uint _FLW_MODEL_INDEX_MASK = 0x3FFFFFF; -layout(std430, binding = _FLW_MODEL_INDEX_BUFFER_BINDING) restrict readonly buffer ModelIndexBuffer { - uint _flw_pageTable[]; +layout(std430, binding = _FLW_PAGE_FRAME_DESCRIPTOR_BUFFER_BINDING) restrict readonly buffer PageFrameDescriptorBuffer { + uint _flw_pageFrameDescriptors[]; }; layout(std430, binding = _FLW_MODEL_BUFFER_BINDING) restrict buffer ModelBuffer { @@ -62,11 +62,11 @@ bool _flw_isVisible(uint instanceIndex, uint modelIndex) { void main() { uint pageIndex = gl_WorkGroupID.x; - if (pageIndex >= _flw_pageTable.length()) { + if (pageIndex >= _flw_pageFrameDescriptors.length()) { return; } - uint packedModelIndexAndCount = _flw_pageTable[pageIndex]; + uint packedModelIndexAndCount = _flw_pageFrameDescriptors[pageIndex]; uint pageInstanceCount = packedModelIndexAndCount >> _FLW_PAGE_COUNT_OFFSET;