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 3bec5e4f7..8ee084136 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 @@ -168,7 +168,7 @@ public int takeFrom(InstancePage other) { int otherValid = other.valid.get(); // If the other page is empty, or we're full, we're done. - if (otherValid == 0 || valid == 0xFFFFFFFF) { + if (isEmpty(otherValid) || isFull(valid)) { return valid; } @@ -223,15 +223,27 @@ public List draws() { public void update(int modelIndex, int baseInstance) { this.baseInstance = baseInstance; - if (this.modelIndex == modelIndex && changedPages.isEmpty()) { + var sameModelIndex = this.modelIndex == modelIndex; + if (sameModelIndex && changedPages.isEmpty()) { + // Nothing to do! return; } + this.modelIndex = modelIndex; + var pages = this.pages.get(); mapping.updateCount(pages.length); - for (int i = 0; i < pages.length; i++) { - mapping.updatePage(i, modelIndex, pages[i].valid.get()); + if (sameModelIndex) { + // Only need to update the changed pages. + for (int page = changedPages.nextSetBit(0); page >= 0 && page < pages.length; page = changedPages.nextSetBit(page + 1)) { + mapping.updatePage(page, modelIndex, pages[page].valid.get()); + } + } else { + // Need to update all pages since the model index changed. + for (int i = 0; i < pages.length; i++) { + mapping.updatePage(i, modelIndex, pages[i].valid.get()); + } } } @@ -291,46 +303,17 @@ public void uploadInstances(StagingBuffer stagingBuffer, int instanceVbo) { } public void parallelUpdate() { - if (true) { - // FIXME: infinite loop when the page in readpos doesn't have enough to fill the page in writepos - return; - } - - var pages = this.pages.get(); - - // If there are at least 2 pages with space, we can consolidate. - if (fullPages.cardinality() > (pages.length - 2)) { - return; - } - - // Note this runs after visuals are updated so we don't really have to take care for thread safety. - - int writePos = 0; - - while (true) { - writePos = fullPages.nextClearBit(writePos); - int readPos = fullPages.nextClearBit(writePos + 1); - - if (writePos >= pages.length || readPos >= pages.length) { - break; - } - - InstancePage writeTo = pages[writePos]; - InstancePage readFrom = pages[readPos]; - - int validNow = writeTo.takeFrom(readFrom); - - if (isFull(validNow)) { - fullPages.set(writePos); - writePos = readPos; - } - } + // TODO: Merge pages when they're less than half full. } private static boolean isFull(int valid) { return valid == 0xFFFFFFFF; } + private static boolean isEmpty(int otherValid) { + return otherValid == 0; + } + @Override public void delete() { for (IndirectDraw draw : draws()) { diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/ObjectStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/ObjectStorage.java index 3f43b8374..20b6ee87a 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/ObjectStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/ObjectStorage.java @@ -13,6 +13,8 @@ public class ObjectStorage extends AbstractArena { public static final int PAGE_SIZE = 1 << LOG_2_PAGE_SIZE; public static final int PAGE_MASK = PAGE_SIZE - 1; + public static final int INVALID_PAGE = -1; + public static final int INITIAL_PAGES_ALLOCATED = 4; public static final int DESCRIPTOR_SIZE_BYTES = Integer.BYTES * 2; @@ -53,8 +55,13 @@ public long byteCapacity() { @Override public void free(int i) { + if (i == INVALID_PAGE) { + return; + } super.free(i); - MemoryUtil.memPutInt(ptrForPage(i), 0); + var ptr = ptrForPage(i); + MemoryUtil.memPutInt(ptr, 0); + MemoryUtil.memPutInt(ptr + 4, 0); } @Override @@ -98,14 +105,49 @@ public class Mapping { private static final int[] EMPTY_ALLOCATION = new int[0]; private int[] pages = EMPTY_ALLOCATION; - public void updatePage(int i, int modelIndex, int i1) { - var ptr = ptrForPage(pages[i]); + public void updatePage(int index, int modelIndex, int validBits) { + if (validBits == 0) { + holePunch(index); + return; + } + var page = pages[index]; + + if (page == INVALID_PAGE) { + // Un-holed punch. + page = unHolePunch(index); + } + + var ptr = ptrForPage(page); MemoryUtil.memPutInt(ptr, modelIndex); - MemoryUtil.memPutInt(ptr + 4, i1); + MemoryUtil.memPutInt(ptr + 4, validBits); ObjectStorage.this.needsUpload = true; } + /** + * Free a page on the inside of the mapping, maintaining the same virtual mapping size. + * + * @param index The index of the page to free. + */ + public void holePunch(int index) { + ObjectStorage.this.free(pages[index]); + pages[index] = INVALID_PAGE; + + ObjectStorage.this.needsUpload = true; + } + + /** + * Allocate a new page on the inside of the mapping, maintaining the same virtual mapping size. + * + * @param index The index of the page to allocate. + * @return The allocated page. + */ + private int unHolePunch(int index) { + int page = ObjectStorage.this.alloc(); + pages[index] = page; + return page; + } + public void updateCount(int newLength) { var oldLength = pages.length; if (oldLength > newLength) { @@ -122,8 +164,8 @@ public int pageCount() { return pages.length; } - public long page2ByteOffset(int page) { - return ObjectStorage.this.byteOffsetOf(pages[page]); + public long page2ByteOffset(int index) { + return ObjectStorage.this.byteOffsetOf(pages[index]); } public void delete() {