On-call paging

- Only update the page table when an allocation is resized
- Only upload the page table after it's uploaded
- Combine various setters for InstancePager.Allocation and
  IndirectInstancer
- Free pages when an allocation is deleted
This commit is contained in:
Jozufozu 2024-09-03 10:56:46 -05:00
parent 637f0538fc
commit b5680a0fd6
5 changed files with 113 additions and 82 deletions

View file

@ -127,7 +127,7 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
// We definitely shouldn't consider the deleted instances as changed though, // We definitely shouldn't consider the deleted instances as changed though,
// else we might try some out of bounds accesses later. // else we might try some out of bounds accesses later.
clearChangedRange(newSize, oldSize); changed.clear(newSize, oldSize);
// Punch out the deleted instances, shifting over surviving instances to fill their place. // Punch out the deleted instances, shifting over surviving instances to fill their place.
for (int scanPos = writePos; (scanPos < oldSize) && (writePos < newSize); scanPos++, writePos++) { for (int scanPos = writePos; (scanPos < oldSize) && (writePos < newSize); scanPos++, writePos++) {
@ -155,10 +155,6 @@ public abstract class AbstractInstancer<I extends Instance> implements Instancer
.clear(); .clear();
} }
protected void clearChangedRange(int start, int end) {
changed.clear(start, end);
}
protected void setRangeChanged(int start, int end) { protected void setRangeChanged(int start, int end) {
changed.set(start, end); changed.set(start, end);
} }

View file

@ -83,13 +83,13 @@ public class IndirectBuffers {
draw.ensureCapacity(drawCount); draw.ensureCapacity(drawCount);
final long ptr = multiBindBlock.ptr(); final long ptr = multiBindBlock.ptr();
MemoryUtil.memPutInt(ptr + INSTANCE_HANDLE_OFFSET, pageFile.storage.handle()); MemoryUtil.memPutInt(ptr + INSTANCE_HANDLE_OFFSET, pageFile.objects.handle());
MemoryUtil.memPutInt(ptr + TARGET_HANDLE_OFFSET, target.handle()); MemoryUtil.memPutInt(ptr + TARGET_HANDLE_OFFSET, target.handle());
MemoryUtil.memPutInt(ptr + MODEL_INDEX_HANDLE_OFFSET, pageFile.pageTable.handle()); MemoryUtil.memPutInt(ptr + MODEL_INDEX_HANDLE_OFFSET, pageFile.pageTable.handle());
MemoryUtil.memPutInt(ptr + MODEL_HANDLE_OFFSET, model.handle()); MemoryUtil.memPutInt(ptr + MODEL_HANDLE_OFFSET, model.handle());
MemoryUtil.memPutInt(ptr + DRAW_HANDLE_OFFSET, draw.handle()); MemoryUtil.memPutInt(ptr + DRAW_HANDLE_OFFSET, draw.handle());
MemoryUtil.memPutAddress(ptr + INSTANCE_SIZE_OFFSET, pageFile.storage.capacity()); MemoryUtil.memPutAddress(ptr + INSTANCE_SIZE_OFFSET, pageFile.objects.capacity());
MemoryUtil.memPutAddress(ptr + TARGET_SIZE_OFFSET, INT_SIZE * instanceCount); MemoryUtil.memPutAddress(ptr + TARGET_SIZE_OFFSET, INT_SIZE * instanceCount);
MemoryUtil.memPutAddress(ptr + MODEL_INDEX_SIZE_OFFSET, pageFile.pageTable.capacity()); MemoryUtil.memPutAddress(ptr + MODEL_INDEX_SIZE_OFFSET, pageFile.pageTable.capacity());
MemoryUtil.memPutAddress(ptr + MODEL_SIZE_OFFSET, MODEL_STRIDE * modelCount); MemoryUtil.memPutAddress(ptr + MODEL_SIZE_OFFSET, MODEL_STRIDE * modelCount);

View file

@ -74,8 +74,7 @@ public class IndirectCullingGroup<I extends Instance> {
continue; continue;
} }
instancer.modelIndex(modelIndex); instancer.postUpdate(modelIndex, instanceCountThisFrame);
instancer.baseInstance(instanceCountThisFrame);
instanceCountThisFrame += instanceCount; instanceCountThisFrame += instanceCount;
modelIndex++; modelIndex++;
@ -173,8 +172,8 @@ public class IndirectCullingGroup<I extends Instance> {
} }
public void add(IndirectInstancer<I> instancer, InstancerKey<I> key, MeshPool meshPool) { public void add(IndirectInstancer<I> instancer, InstancerKey<I> key, MeshPool meshPool) {
instancer.pageFile = buffers.pageFile.createPage(); instancer.pageFile = buffers.pageFile.createAllocation();
instancer.modelIndex(instancers.size()); instancer.postUpdate(instancers.size(), -1);
instancers.add(instancer); instancers.add(instancer);
@ -246,7 +245,7 @@ public class IndirectCullingGroup<I extends Instance> {
private void uploadInstances(StagingBuffer stagingBuffer) { private void uploadInstances(StagingBuffer stagingBuffer) {
for (var instancer : instancers) { for (var instancer : instancers) {
instancer.uploadInstances(stagingBuffer, buffers.pageFile.storage.handle()); instancer.uploadInstances(stagingBuffer, buffers.pageFile.objects.handle());
} }
} }

View file

@ -52,13 +52,6 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
changedPages.set(InstancePager.object2Page(start), InstancePager.object2Page(end) + 1); changedPages.set(InstancePager.object2Page(start), InstancePager.object2Page(end) + 1);
} }
@Override
protected void clearChangedRange(int start, int end) {
super.clearChangedRange(start, end);
// changedPages.clear(pageFile.object2Page(start), pageFile);
}
public void addDraw(IndirectDraw draw) { public void addDraw(IndirectDraw draw) {
associatedDraws.add(draw); associatedDraws.add(draw);
} }
@ -69,8 +62,12 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
public void update() { public void update() {
removeDeletedInstances(); removeDeletedInstances();
}
pageFile.activeCount(instanceCount()); public void postUpdate(int modelIndex, int baseInstance) {
this.modelIndex = modelIndex;
this.baseInstance = baseInstance;
pageFile.update(modelIndex, instanceCount());
} }
public void writeModel(long ptr) { public void writeModel(long ptr) {
@ -117,21 +114,14 @@ public class IndirectInstancer<I extends Instance> extends AbstractInstancer<I>
for (IndirectDraw draw : draws()) { for (IndirectDraw draw : draws()) {
draw.delete(); draw.delete();
} }
}
public void modelIndex(int modelIndex) { pageFile.delete();
this.modelIndex = modelIndex;
pageFile.modelIndex(modelIndex);
} }
public int modelIndex() { public int modelIndex() {
return modelIndex; return modelIndex;
} }
public void baseInstance(int baseInstance) {
this.baseInstance = baseInstance;
}
public int baseInstance() { public int baseInstance() {
return baseInstance; return baseInstance;
} }

View file

@ -1,8 +1,6 @@
package dev.engine_room.flywheel.backend.engine.indirect; package dev.engine_room.flywheel.backend.engine.indirect;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
@ -17,24 +15,20 @@ public class InstancePager extends AbstractArena {
public static final int INITIAL_PAGES_ALLOCATED = 4; public static final int INITIAL_PAGES_ALLOCATED = 4;
private final long objectSizeBytes; private MemoryBlock pageTableData;
public final ResizableStorageBuffer objects;
private MemoryBlock pageData;
public final ResizableStorageBuffer storage;
public final ResizableStorageBuffer pageTable; public final ResizableStorageBuffer pageTable;
private final List<Allocation> allocations = new ArrayList<>(); private boolean needsUpload = false;
public InstancePager(long objectSizeBytes) { public InstancePager(long objectSizeBytes) {
super(PAGE_SIZE * objectSizeBytes); super(PAGE_SIZE * objectSizeBytes);
this.objectSizeBytes = objectSizeBytes;
this.storage = new ResizableStorageBuffer(); this.objects = new ResizableStorageBuffer();
this.pageTable = new ResizableStorageBuffer(); this.pageTable = new ResizableStorageBuffer();
pageData = MemoryBlock.malloc(INITIAL_PAGES_ALLOCATED * Integer.BYTES); pageTableData = MemoryBlock.malloc(INITIAL_PAGES_ALLOCATED * Integer.BYTES);
storage.ensureCapacity(INITIAL_PAGES_ALLOCATED * elementSizeBytes); objects.ensureCapacity(INITIAL_PAGES_ALLOCATED * elementSizeBytes);
pageTable.ensureCapacity(INITIAL_PAGES_ALLOCATED * Integer.BYTES); pageTable.ensureCapacity(INITIAL_PAGES_ALLOCATED * Integer.BYTES);
} }
@ -46,79 +40,122 @@ public class InstancePager extends AbstractArena {
return pageIndex << LOG_2_PAGE_SIZE; return pageIndex << LOG_2_PAGE_SIZE;
} }
public Allocation createPage() { public Allocation createAllocation() {
var out = new Allocation(); return new Allocation();
allocations.add(out);
return out;
} }
@Override @Override
public long byteCapacity() { public long byteCapacity() {
return storage.capacity(); return objects.capacity();
}
@Override
public void free(int i) {
super.free(i);
MemoryUtil.memPutInt(ptrForPage(i), 0);
} }
@Override @Override
protected void grow() { protected void grow() {
pageData = pageData.realloc(pageData.size() * 2); pageTableData = pageTableData.realloc(pageTableData.size() * 2);
storage.ensureCapacity(storage.capacity() * 2); objects.ensureCapacity(objects.capacity() * 2);
pageTable.ensureCapacity(pageTable.capacity() * 2); pageTable.ensureCapacity(pageTable.capacity() * 2);
} }
public void uploadTable(StagingBuffer stagingBuffer) { public void uploadTable(StagingBuffer stagingBuffer) {
for (Allocation allocation : allocations) { if (!needsUpload) {
allocation.updatePageTable(); return;
} }
stagingBuffer.enqueueCopy(pageData.ptr(), pageData.size(), pageTable.handle(), 0); // 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);
needsUpload = false;
} }
public void delete() { public void delete() {
storage.delete(); objects.delete();
pageTable.delete(); pageTable.delete();
pageData.free(); pageTableData.free();
}
private long ptrForPage(int page) {
return pageTableData.ptr() + (long) page * Integer.BYTES;
} }
public class Allocation { public class Allocation {
public int[] pages = new int[0]; public static final int[] EMPTY_ALLOCATION = new int[0];
public int[] pages = EMPTY_ALLOCATION;
private int modelIndex = -1; private int modelIndex = -1;
private int activeCount = 0; private int objectCount = 0;
public void modelIndex(int modelIndex) { /**
if (this.modelIndex != modelIndex) { * Calculates the page descriptor for the given page index.
this.modelIndex = modelIndex; * 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 updatePageTable() { public void update(int modelIndex, int objectCount) {
if (pages.length == 0) { boolean incremental = this.modelIndex == modelIndex;
if (incremental && objectCount == this.objectCount) {
// Nothing will change.
return; return;
} }
var ptr = pageData.ptr(); InstancePager.this.needsUpload = true;
int fullPage = (modelIndex & 0x3FFFFF) | (32 << 26); this.modelIndex = modelIndex;
this.objectCount = objectCount;
int remainder = activeCount;
for (int i = 0; i < pages.length - 1; i++) {
int page = pages[i];
MemoryUtil.memPutInt(ptr + page * Integer.BYTES, fullPage);
remainder -= PAGE_SIZE;
}
MemoryUtil.memPutInt(ptr + pages[pages.length - 1] * Integer.BYTES, (modelIndex & 0x3FFFFF) | (remainder << 26));
}
public void activeCount(int objectCount) {
var neededPages = object2Page((objectCount + PAGE_MASK));
activeCount = objectCount;
var oldLength = pages.length; var oldLength = pages.length;
var newLength = object2Page((objectCount + PAGE_MASK));
if (oldLength > neededPages) { if (oldLength > newLength) {
shrink(oldLength, neededPages); // Eagerly free the now unnecessary pages.
} else if (oldLength < neededPages) { // shrink will zero out the pageTable entries for the freed pages.
grow(neededPages, oldLength); shrink(oldLength, newLength);
if (incremental) {
// Only update the last page, everything else is unchanged.
updateRange(newLength - 1, newLength);
}
} else if (oldLength < newLength) {
// Allocate new pages to fit the new object count.
grow(newLength, oldLength);
if (incremental) {
// Update the old last page + all new pages
updateRange(oldLength - 1, newLength);
}
} else {
if (incremental) {
// Only update the last page.
updateRange(oldLength - 1, oldLength);
}
}
if (!incremental) {
// Update all pages.
updateRange(0, newLength);
}
}
private void updateRange(int start, int oldLength) {
for (int i = start; i < oldLength; i++) {
MemoryUtil.memPutInt(ptrForPage(pages[i]), calculatePageDescriptor(i));
} }
} }
@ -126,7 +163,8 @@ public class InstancePager extends AbstractArena {
pages = Arrays.copyOf(pages, neededPages); pages = Arrays.copyOf(pages, neededPages);
for (int i = oldLength; i < neededPages; i++) { for (int i = oldLength; i < neededPages; i++) {
pages[i] = InstancePager.this.alloc(); var page = InstancePager.this.alloc();
pages[i] = page;
} }
} }
@ -134,7 +172,6 @@ public class InstancePager extends AbstractArena {
for (int i = oldLength - 1; i >= neededPages; i--) { for (int i = oldLength - 1; i >= neededPages; i--) {
var page = pages[i]; var page = pages[i];
InstancePager.this.free(page); InstancePager.this.free(page);
MemoryUtil.memPutInt(pageData.ptr() + page * Integer.BYTES, 0);
} }
pages = Arrays.copyOf(pages, neededPages); pages = Arrays.copyOf(pages, neededPages);
@ -151,5 +188,14 @@ public class InstancePager extends AbstractArena {
public long page2ByteOffset(int page) { public long page2ByteOffset(int page) {
return InstancePager.this.byteOffsetOf(pages[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;
}
} }
} }