mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2025-01-27 05:17:56 +01:00
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:
parent
637f0538fc
commit
b5680a0fd6
5 changed files with 113 additions and 82 deletions
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue