From 0af1127745559ed7b3d35b2a642f80dbf2fae6d3 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 31 Aug 2024 17:57:49 -0500 Subject: [PATCH 01/26] Paging Dr. Instancer - Goal: avoid needing to re-upload everything when instance count for one instancer changes - Solution: store instances in pages of 32 - Allocate pages in a GPU arena - Store one uint per page to indicate which model the instances in the page belong to, and how many instances are actually stored in the page - Instancers eagerly allocate and free pages as their instance count changes - Instancers will not necessarily store instances contiguously anymore, but that's okay because any given cull workgroup will only reference a single page - Culling threads *will* write instances contiguously however, and so we still need to keep track of a base instance per instancer, and the target buffer logic does not change --- .../engine/{Arena.java => AbstractArena.java} | 34 ++-- .../backend/engine/AbstractInstancer.java | 12 +- .../flywheel/backend/engine/CpuArena.java | 30 ++++ .../flywheel/backend/engine/LightStorage.java | 4 +- .../engine/embed/EnvironmentStorage.java | 4 +- .../engine/indirect/IndirectBuffers.java | 20 +-- .../engine/indirect/IndirectCullingGroup.java | 19 +-- .../backend/engine/indirect/IndirectDraw.java | 8 +- .../engine/indirect/IndirectInstancer.java | 127 +++++++------- .../engine/indirect/InstancePager.java | 158 ++++++++++++++++++ .../flywheel/internal/indirect/cull.glsl | 25 ++- 11 files changed, 324 insertions(+), 117 deletions(-) rename common/src/backend/java/dev/engine_room/flywheel/backend/engine/{Arena.java => AbstractArena.java} (55%) create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/CpuArena.java create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/InstancePager.java diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/Arena.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractArena.java similarity index 55% rename from common/src/backend/java/dev/engine_room/flywheel/backend/engine/Arena.java rename to common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractArena.java index e7aa67071..23d023006 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/Arena.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractArena.java @@ -1,23 +1,17 @@ package dev.engine_room.flywheel.backend.engine; -import dev.engine_room.flywheel.lib.memory.MemoryBlock; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; -public class Arena { - private final long elementSizeBytes; - - private MemoryBlock memoryBlock; - - // Monotonic index, generally represents the size of the arena. - private int top = 0; +public abstract class AbstractArena { + protected final long elementSizeBytes; // List of free indices. private final IntList freeStack = new IntArrayList(); + // Monotonic index, generally represents the size of the arena. + private int top = 0; - public Arena(long elementSizeBytes, int initialCapacity) { + public AbstractArena(long elementSizeBytes) { this.elementSizeBytes = elementSizeBytes; - - memoryBlock = MemoryBlock.malloc(elementSizeBytes * initialCapacity); } public int alloc() { @@ -27,8 +21,8 @@ public int alloc() { } // Make sure there's room to increment top. - if (top * elementSizeBytes >= memoryBlock.size()) { - memoryBlock = memoryBlock.realloc(memoryBlock.size() * 2); + if (top * elementSizeBytes >= byteCapacity()) { + resize(); } // Return the top index and increment. @@ -40,19 +34,15 @@ public void free(int i) { freeStack.add(i); } - public long indexToPointer(int i) { - return memoryBlock.ptr() + i * elementSizeBytes; - } - - public void delete() { - memoryBlock.free(); + public long byteOffsetOf(int i) { + return i * elementSizeBytes; } public int capacity() { return top; } - public long byteCapacity() { - return memoryBlock.size(); - } + public abstract long byteCapacity(); + + protected abstract void resize(); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java index 73f8c1714..744bb12a0 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java @@ -122,12 +122,12 @@ protected void removeDeletedInstances() { if (writePos < newSize) { // Since we'll be shifting everything into this space we can consider it all changed. - changed.set(writePos, newSize); + setRangeChanged(writePos, newSize); } // We definitely shouldn't consider the deleted instances as changed though, // else we might try some out of bounds accesses later. - changed.clear(newSize, oldSize); + clearChangedRange(newSize, oldSize); // Punch out the deleted instances, shifting over surviving instances to fill their place. for (int scanPos = writePos; (scanPos < oldSize) && (writePos < newSize); scanPos++, writePos++) { @@ -155,6 +155,14 @@ protected void removeDeletedInstances() { .clear(); } + protected void clearChangedRange(int start, int end) { + changed.clear(start, end); + } + + protected void setRangeChanged(int start, int end) { + changed.set(start, end); + } + /** * Clear all instances without freeing resources. */ diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/CpuArena.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/CpuArena.java new file mode 100644 index 000000000..c1c7843d7 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/CpuArena.java @@ -0,0 +1,30 @@ +package dev.engine_room.flywheel.backend.engine; + +import dev.engine_room.flywheel.lib.memory.MemoryBlock; + +public class CpuArena extends AbstractArena { + + private MemoryBlock memoryBlock; + + public CpuArena(long elementSizeBytes, int initialCapacity) { + super(elementSizeBytes); + + memoryBlock = MemoryBlock.malloc(elementSizeBytes * initialCapacity); + } + + public long indexToPointer(int i) { + return memoryBlock.ptr() + i * elementSizeBytes; + } + + public void delete() { + memoryBlock.free(); + } + + public long byteCapacity() { + return memoryBlock.size(); + } + + protected void resize() { + memoryBlock = memoryBlock.realloc(memoryBlock.size() * 2); + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java index 214a56795..47e884a52 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java @@ -46,7 +46,7 @@ public class LightStorage { private final LevelAccessor level; - private final Arena arena; + private final CpuArena arena; private final Long2IntMap section2ArenaIndex = new Long2IntOpenHashMap(); { section2ArenaIndex.defaultReturnValue(INVALID_SECTION); @@ -62,7 +62,7 @@ public class LightStorage { public LightStorage(LevelAccessor level) { this.level = level; - arena = new Arena(SECTION_SIZE_BYTES, DEFAULT_ARENA_CAPACITY_SECTIONS); + arena = new CpuArena(SECTION_SIZE_BYTES, DEFAULT_ARENA_CAPACITY_SECTIONS); } /** diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EnvironmentStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EnvironmentStorage.java index 2b707a3b8..85ad55387 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EnvironmentStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EnvironmentStorage.java @@ -1,6 +1,6 @@ package dev.engine_room.flywheel.backend.engine.embed; -import dev.engine_room.flywheel.backend.engine.Arena; +import dev.engine_room.flywheel.backend.engine.CpuArena; import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; import it.unimi.dsi.fastutil.objects.ReferenceSet; @@ -13,7 +13,7 @@ public class EnvironmentStorage { // Note than the arena starts indexing at zero, but we reserve zero for the identity matrix. // Any time an ID from the arena is written we want to add one to it. - public final Arena arena = new Arena(MATRIX_SIZE_BYTES, 32); + public final CpuArena arena = new CpuArena(MATRIX_SIZE_BYTES, 32); { // Reserve the identity matrix. Burns a few bytes but oh well. 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 b0766e171..f82d32c10 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 @@ -61,9 +61,9 @@ public class IndirectBuffers { */ private final MemoryBlock multiBindBlock; private final long instanceStride; - public final ResizableStorageArray instance; + + public final InstancePager pageFile; public final ResizableStorageArray target; - public final ResizableStorageArray modelIndex; public final ResizableStorageArray model; public final ResizableStorageArray draw; @@ -71,30 +71,27 @@ public class IndirectBuffers { this.instanceStride = instanceStride; this.multiBindBlock = MemoryBlock.calloc(BUFFERS_SIZE_BYTES, 1); - instance = new ResizableStorageArray(instanceStride, INSTANCE_GROWTH_FACTOR); + pageFile = new InstancePager(instanceStride); target = new ResizableStorageArray(INT_SIZE, INSTANCE_GROWTH_FACTOR); - modelIndex = new ResizableStorageArray(INT_SIZE, INSTANCE_GROWTH_FACTOR); model = new ResizableStorageArray(MODEL_STRIDE, MODEL_GROWTH_FACTOR); draw = new ResizableStorageArray(DRAW_COMMAND_STRIDE, DRAW_GROWTH_FACTOR); } void updateCounts(int instanceCount, int modelCount, int drawCount) { - instance.ensureCapacity(instanceCount); target.ensureCapacity(instanceCount); - modelIndex.ensureCapacity(instanceCount); model.ensureCapacity(modelCount); draw.ensureCapacity(drawCount); final long ptr = multiBindBlock.ptr(); - MemoryUtil.memPutInt(ptr + INSTANCE_HANDLE_OFFSET, instance.handle()); + MemoryUtil.memPutInt(ptr + INSTANCE_HANDLE_OFFSET, pageFile.storage.handle()); MemoryUtil.memPutInt(ptr + TARGET_HANDLE_OFFSET, target.handle()); - MemoryUtil.memPutInt(ptr + MODEL_INDEX_HANDLE_OFFSET, modelIndex.handle()); + MemoryUtil.memPutInt(ptr + MODEL_INDEX_HANDLE_OFFSET, pageFile.pageTable.handle()); MemoryUtil.memPutInt(ptr + MODEL_HANDLE_OFFSET, model.handle()); MemoryUtil.memPutInt(ptr + DRAW_HANDLE_OFFSET, draw.handle()); - MemoryUtil.memPutAddress(ptr + INSTANCE_SIZE_OFFSET, instanceStride * instanceCount); + MemoryUtil.memPutAddress(ptr + INSTANCE_SIZE_OFFSET, pageFile.storage.byteCapacity()); MemoryUtil.memPutAddress(ptr + TARGET_SIZE_OFFSET, INT_SIZE * instanceCount); - MemoryUtil.memPutAddress(ptr + MODEL_INDEX_SIZE_OFFSET, INT_SIZE * instanceCount); + MemoryUtil.memPutAddress(ptr + MODEL_INDEX_SIZE_OFFSET, pageFile.pageTable.byteCapacity()); MemoryUtil.memPutAddress(ptr + MODEL_SIZE_OFFSET, MODEL_STRIDE * modelCount); MemoryUtil.memPutAddress(ptr + DRAW_SIZE_OFFSET, DRAW_COMMAND_STRIDE * drawCount); } @@ -124,9 +121,8 @@ public void bindForCrumbling() { public void delete() { multiBindBlock.free(); - instance.delete(); + pageFile.delete(); target.delete(); - modelIndex.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 9a376ab14..8a25d5df5 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 @@ -74,8 +74,8 @@ public void flushInstancers() { continue; } - instancer.modelIndex = modelIndex; - instancer.baseInstance = instanceCountThisFrame; + instancer.modelIndex(modelIndex); + instancer.baseInstance(instanceCountThisFrame); instanceCountThisFrame += instanceCount; modelIndex++; @@ -96,6 +96,8 @@ public void upload(StagingBuffer stagingBuffer) { // Upload only instances that have changed. uploadInstances(stagingBuffer); + buffers.pageFile.uploadTable(stagingBuffer); + // We need to upload the models every frame to reset the instance count. uploadModels(stagingBuffer); @@ -118,7 +120,7 @@ public void dispatchCull() { cullProgram.bind(); buffers.bindForCompute(); - glDispatchCompute(GlCompat.getComputeGroupCount(instanceCountThisFrame), 1, 1); + glDispatchCompute(buffers.pageFile.capacity(), 1, 1); } public void dispatchApply() { @@ -171,7 +173,9 @@ public boolean hasVisualType(VisualType visualType) { } public void add(IndirectInstancer instancer, InstancerKey key, MeshPool meshPool) { - instancer.modelIndex = instancers.size(); + instancer.pageFile = buffers.pageFile.createPage(); + instancer.modelIndex(instancers.size()); + instancers.add(instancer); List meshes = key.model() @@ -242,12 +246,7 @@ private void drawBarrier() { private void uploadInstances(StagingBuffer stagingBuffer) { for (var instancer : instancers) { - instancer.uploadInstances(stagingBuffer, buffers.instance.handle()); - } - - for (var instancer : instancers) { - instancer.uploadModelIndices(stagingBuffer, buffers.modelIndex.handle()); - instancer.resetChanged(); + instancer.uploadInstances(stagingBuffer, buffers.pageFile.storage.handle()); } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDraw.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDraw.java index fb763d006..48517d1a2 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDraw.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDraw.java @@ -72,9 +72,9 @@ public void write(long ptr) { MemoryUtil.memPutInt(ptr + 4, 0); // instanceCount - to be set by the apply shader MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex - MemoryUtil.memPutInt(ptr + 16, instancer.baseInstance); // baseInstance + MemoryUtil.memPutInt(ptr + 16, instancer.baseInstance()); // baseInstance - MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex); // modelIndex + MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex()); // modelIndex MemoryUtil.memPutInt(ptr + 24, instancer.environment.matrixIndex()); // matrixIndex @@ -89,9 +89,9 @@ public void writeWithOverrides(long ptr, int instanceIndex, Material materialOve MemoryUtil.memPutInt(ptr + 4, 1); // instanceCount - only drawing one instance MemoryUtil.memPutInt(ptr + 8, mesh.firstIndex()); // firstIndex MemoryUtil.memPutInt(ptr + 12, mesh.baseVertex()); // baseVertex - MemoryUtil.memPutInt(ptr + 16, instancer.baseInstance + instanceIndex); // baseInstance + MemoryUtil.memPutInt(ptr + 16, instancer.baseInstance() + instanceIndex); // baseInstance - MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex); // modelIndex + MemoryUtil.memPutInt(ptr + 20, instancer.modelIndex()); // modelIndex MemoryUtil.memPutInt(ptr + 24, instancer.environment.matrixIndex()); // matrixIndex 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 75dc2b8e3..541765870 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 @@ -12,6 +12,7 @@ import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.backend.engine.AbstractInstancer; import dev.engine_room.flywheel.backend.engine.embed.Environment; +import dev.engine_room.flywheel.backend.util.AtomicBitSet; import dev.engine_room.flywheel.lib.math.MoreMath; public class IndirectInstancer extends AbstractInstancer { @@ -20,11 +21,12 @@ public class IndirectInstancer extends AbstractInstancer private final List associatedDraws = new ArrayList<>(); private final Vector4fc boundingSphere; - public int modelIndex = -1; - public int baseInstance = -1; - private int lastModelIndex = -1; - private int lastBaseInstance = -1; - private int lastInstanceCount = -1; + private final AtomicBitSet changedPages = new AtomicBitSet(); + + public InstancePager.Allocation pageFile; + + private int modelIndex = -1; + private int baseInstance = -1; public IndirectInstancer(InstanceType type, Environment environment, Model model) { super(type, environment); @@ -34,6 +36,29 @@ public IndirectInstancer(InstanceType type, Environment environment, Model mo boundingSphere = model.boundingSphere(); } + @Override + public void notifyDirty(int index) { + if (index < 0 || index >= instanceCount()) { + return; + } + changed.set(index); + changedPages.set(pageFile.object2Page(index)); + } + + @Override + protected void setRangeChanged(int start, int end) { + super.setRangeChanged(start, end); + + changedPages.set(pageFile.object2Page(start), pageFile.object2Page(end)); + } + + @Override + protected void clearChangedRange(int start, int end) { + super.clearChangedRange(start, end); + + // changedPages.clear(pageFile.object2Page(start), pageFile); + } + public void addDraw(IndirectDraw draw) { associatedDraws.add(draw); } @@ -44,6 +69,8 @@ public List draws() { public void update() { removeDeletedInstances(); + + pageFile.activeCount(instanceCount()); } public void writeModel(long ptr) { @@ -57,71 +84,38 @@ public void writeModel(long ptr) { } public void uploadInstances(StagingBuffer stagingBuffer, int instanceVbo) { - long baseByte = baseInstance * instanceStride; + int numPages = pageFile.pageCount(); - if (baseInstance != lastBaseInstance) { - uploadAllInstances(stagingBuffer, baseByte, instanceVbo); - } else { - uploadChangedInstances(stagingBuffer, baseByte, instanceVbo); - } - } + var instanceCount = instances.size(); - public void uploadModelIndices(StagingBuffer stagingBuffer, int modelIndexVbo) { - long modelIndexBaseByte = baseInstance * IndirectBuffers.INT_SIZE; + for (int page = 0; page < numPages; page++) { + page = changedPages.nextSetBit(0); - if (baseInstance != lastBaseInstance || modelIndex != lastModelIndex || instances.size() > lastInstanceCount) { - uploadAllModelIndices(stagingBuffer, modelIndexBaseByte, modelIndexVbo); - } - } + if (page == -1) { + break; + } - public void resetChanged() { - lastModelIndex = modelIndex; - lastBaseInstance = baseInstance; - lastInstanceCount = instances.size(); - changed.clear(); - } + int startObject = pageFile.page2Object(page); - private void uploadChangedInstances(StagingBuffer stagingBuffer, long baseByte, int instanceVbo) { - changed.forEachSetSpan((startInclusive, endInclusive) -> { - // Generally we're good about ensuring we don't have changed bits set out of bounds, but check just in case - if (startInclusive >= instances.size()) { - return; + if (startObject >= instanceCount) { + break; } - int actualEnd = Math.min(endInclusive, instances.size() - 1); - int instanceCount = actualEnd - startInclusive + 1; - long totalSize = instanceCount * instanceStride; + int endObject = Math.min(instanceCount, pageFile.page2Object(page + 1) - 1); + + long baseByte = pageFile.page2ByteOffset(page); + long size = (endObject - startObject) * instanceStride; - stagingBuffer.enqueueCopy(totalSize, instanceVbo, baseByte + startInclusive * instanceStride, ptr -> { - for (int i = startInclusive; i <= actualEnd; i++) { - var instance = instances.get(i); - writer.write(ptr, instance); + stagingBuffer.enqueueCopy(size, instanceVbo, baseByte, ptr -> { + for (int i = startObject; i < endObject; i++) { + writer.write(ptr, instances.get(i)); ptr += instanceStride; } }); - }); - } - - private void uploadAllInstances(StagingBuffer stagingBuffer, long baseByte, int instanceVbo) { - long totalSize = instances.size() * instanceStride; - - stagingBuffer.enqueueCopy(totalSize, instanceVbo, baseByte, ptr -> { - for (I instance : instances) { - writer.write(ptr, instance); - ptr += instanceStride; - } - }); - } - - private void uploadAllModelIndices(StagingBuffer stagingBuffer, long modelIndexBaseByte, int modelIndexVbo) { - long modelIndexTotalSize = instances.size() * IndirectBuffers.INT_SIZE; + } - stagingBuffer.enqueueCopy(modelIndexTotalSize, modelIndexVbo, modelIndexBaseByte, ptr -> { - for (int i = 0; i < instances.size(); i++) { - MemoryUtil.memPutInt(ptr, modelIndex); - ptr += IndirectBuffers.INT_SIZE; - } - }); + changed.clear(); + changedPages.clear(); } @Override @@ -130,4 +124,21 @@ public void delete() { draw.delete(); } } + + public void modelIndex(int modelIndex) { + this.modelIndex = modelIndex; + pageFile.modelIndex(modelIndex); + } + + public int modelIndex() { + return modelIndex; + } + + public void baseInstance(int baseInstance) { + this.baseInstance = baseInstance; + } + + public int baseInstance() { + return baseInstance; + } } 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/InstancePager.java new file mode 100644 index 000000000..018a640a7 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/InstancePager.java @@ -0,0 +1,158 @@ +package dev.engine_room.flywheel.backend.engine.indirect; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.jetbrains.annotations.UnknownNullability; +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 { + // 32 objects per page. Allows for convenient bitsets on the gpu. + public static final int DEFAULT_PAGE_SIZE_OBJECTS = 5; + public static final int INITIAL_PAGES_ALLOCATED = 4; + + private final int log2PageSize; + /** + * The number of objects in a page. + */ + private final int pageSize; + + private final long objectSizeBytes; + + @UnknownNullability + private MemoryBlock pageData; + + private final int pageMask; + public final ResizableStorageArray storage; + public final ResizableStorageArray pageTable; + + private final List allocations = new ArrayList<>(); + + public InstancePager(long objectSizeBytes) { + this(DEFAULT_PAGE_SIZE_OBJECTS, objectSizeBytes); + } + + public InstancePager(int log2PageSize, long objectSizeBytes) { + super((1L << log2PageSize) * objectSizeBytes); + this.log2PageSize = log2PageSize; + this.pageSize = 1 << log2PageSize; + this.pageMask = pageSize - 1; + this.objectSizeBytes = objectSizeBytes; + + this.storage = new ResizableStorageArray(this.elementSizeBytes); + this.pageTable = new ResizableStorageArray(Integer.BYTES); + } + + public Allocation createPage() { + var out = new Allocation(); + allocations.add(out); + return out; + } + + @Override + public long byteCapacity() { + return storage.byteCapacity(); + } + + @Override + protected void resize() { + if (pageData == null) { + pageData = MemoryBlock.malloc(INITIAL_PAGES_ALLOCATED * Integer.BYTES); + storage.ensureCapacity(INITIAL_PAGES_ALLOCATED); + pageTable.ensureCapacity(INITIAL_PAGES_ALLOCATED); + } else { + pageData = pageData.realloc(pageData.size() * 2); + storage.ensureCapacity(storage.capacity() * 2); + pageTable.ensureCapacity(pageTable.capacity() * 2); + } + } + + public void uploadTable(StagingBuffer stagingBuffer) { + for (Allocation allocation : allocations) { + allocation.updatePageTable(); + } + stagingBuffer.enqueueCopy(pageData.ptr(), pageData.size(), pageTable.handle(), 0); + } + + public void delete() { + storage.delete(); + pageTable.delete(); + pageData.free(); + } + + public class Allocation { + public int[] pages = new int[0]; + + private int modelIndex = -1; + + public void modelIndex(int modelIndex) { + if (this.modelIndex != modelIndex) { + this.modelIndex = modelIndex; + } + } + + private void updatePageTable() { + var ptr = pageData.ptr(); + + int fullPage = (modelIndex & 0x3FFFFF) | 0x8000000; + + for (int page : pages) { + MemoryUtil.memPutInt(ptr + page * Integer.BYTES, fullPage); + } + } + + public void activeCount(int objectCount) { + var neededPages = object2Page((objectCount + pageMask)); + + var oldLength = pages.length; + + if (oldLength > neededPages) { + shrink(oldLength, neededPages); + } else if (oldLength < neededPages) { + grow(neededPages, oldLength); + } + } + + private void grow(int neededPages, int oldLength) { + pages = Arrays.copyOf(pages, neededPages); + + for (int i = oldLength; i < neededPages; i++) { + pages[i] = InstancePager.this.alloc(); + } + } + + private void shrink(int oldLength, int neededPages) { + for (int i = oldLength - 1; i > neededPages; i--) { + var page = pages[i]; + InstancePager.this.free(page); + MemoryUtil.memPutInt(pageData.ptr() + page * Integer.BYTES, 0); + } + + pages = Arrays.copyOf(pages, neededPages); + } + + public int capacity() { + return pages.length << log2PageSize; + } + + public int pageCount() { + return pages.length; + } + + public int object2Page(int objectIndex) { + return objectIndex >> log2PageSize; + } + + public int page2Object(int pageIndex) { + return pageIndex << log2PageSize; + } + + public long page2ByteOffset(int page) { + return InstancePager.this.byteOffsetOf(pages[page]); + } + } +} 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 65d5baae0..e45f4ec3d 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 @@ -4,14 +4,19 @@ #include "flywheel:util/matrix.glsl" #include "flywheel:internal/indirect/matrices.glsl" -layout(local_size_x = _FLW_SUBGROUP_SIZE) in; +layout(local_size_x = 32) in; layout(std430, binding = _FLW_TARGET_BUFFER_BINDING) restrict writeonly buffer TargetBuffer { uint _flw_instanceIndices[]; }; +// High 6 bits for the number of instances in the page. +const uint _FLW_PAGE_COUNT_OFFSET = 25u; +// Bottom 24 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_modelIndices[]; + uint _flw_pageTable[]; }; layout(std430, binding = _FLW_MODEL_BUFFER_BINDING) restrict buffer ModelBuffer { @@ -55,13 +60,23 @@ bool _flw_isVisible(uint instanceIndex, uint modelIndex) { } void main() { - uint instanceIndex = gl_GlobalInvocationID.x; + uint pageIndex = gl_WorkGroupID.x; - if (instanceIndex >= _flw_modelIndices.length()) { + if (pageIndex >= _flw_pageTable.length()) { return; } - uint modelIndex = _flw_modelIndices[instanceIndex]; + uint packedModelIndexAndCount = _flw_pageTable[pageIndex]; + + uint pageInstanceCount = packedModelIndexAndCount >> _FLW_PAGE_COUNT_OFFSET; + + if (gl_LocalInvocationID.x >= pageInstanceCount) { + return; + } + + uint instanceIndex = gl_GlobalInvocationID.x; + + uint modelIndex = packedModelIndexAndCount & _FLW_MODEL_INDEX_MASK; if (_flw_isVisible(instanceIndex, modelIndex)) { uint localIndex = atomicAdd(_flw_models[modelIndex].instanceCount, 1); From 637f0538fc603d4f4055e8eadd541d9de85480c8 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 1 Sep 2024 12:34:38 -0500 Subject: [PATCH 02/26] Growing pains - Fix bit logic on the GPU - Manually manage the size of the storage and pageTable buffers - Make object2Page and page2Object static - Fix instance writing loop - Fix page table always having full pages - Fix allocations not shrinking --- .../backend/engine/AbstractArena.java | 4 +- .../flywheel/backend/engine/CpuArena.java | 2 +- .../engine/indirect/IndirectBuffers.java | 4 +- .../engine/indirect/IndirectInstancer.java | 16 ++-- .../engine/indirect/InstancePager.java | 87 +++++++++---------- .../flywheel/internal/indirect/cull.glsl | 4 +- 6 files changed, 54 insertions(+), 63 deletions(-) diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractArena.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractArena.java index 23d023006..2493a339b 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractArena.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractArena.java @@ -22,7 +22,7 @@ public int alloc() { // Make sure there's room to increment top. if (top * elementSizeBytes >= byteCapacity()) { - resize(); + grow(); } // Return the top index and increment. @@ -44,5 +44,5 @@ public int capacity() { public abstract long byteCapacity(); - protected abstract void resize(); + protected abstract void grow(); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/CpuArena.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/CpuArena.java index c1c7843d7..33dfa3812 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/CpuArena.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/CpuArena.java @@ -24,7 +24,7 @@ public long byteCapacity() { return memoryBlock.size(); } - protected void resize() { + protected void grow() { memoryBlock = memoryBlock.realloc(memoryBlock.size() * 2); } } 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 f82d32c10..1ce962eec 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 @@ -89,9 +89,9 @@ void updateCounts(int instanceCount, int modelCount, int drawCount) { MemoryUtil.memPutInt(ptr + MODEL_HANDLE_OFFSET, model.handle()); MemoryUtil.memPutInt(ptr + DRAW_HANDLE_OFFSET, draw.handle()); - MemoryUtil.memPutAddress(ptr + INSTANCE_SIZE_OFFSET, pageFile.storage.byteCapacity()); + MemoryUtil.memPutAddress(ptr + INSTANCE_SIZE_OFFSET, pageFile.storage.capacity()); MemoryUtil.memPutAddress(ptr + TARGET_SIZE_OFFSET, INT_SIZE * instanceCount); - MemoryUtil.memPutAddress(ptr + MODEL_INDEX_SIZE_OFFSET, pageFile.pageTable.byteCapacity()); + MemoryUtil.memPutAddress(ptr + MODEL_INDEX_SIZE_OFFSET, pageFile.pageTable.capacity()); MemoryUtil.memPutAddress(ptr + MODEL_SIZE_OFFSET, MODEL_STRIDE * modelCount); MemoryUtil.memPutAddress(ptr + DRAW_SIZE_OFFSET, DRAW_COMMAND_STRIDE * drawCount); } 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 541765870..bbd78d88e 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 @@ -42,14 +42,14 @@ public void notifyDirty(int index) { return; } changed.set(index); - changedPages.set(pageFile.object2Page(index)); + changedPages.set(InstancePager.object2Page(index)); } @Override protected void setRangeChanged(int start, int end) { super.setRangeChanged(start, end); - changedPages.set(pageFile.object2Page(start), pageFile.object2Page(end)); + changedPages.set(InstancePager.object2Page(start), InstancePager.object2Page(end) + 1); } @Override @@ -88,20 +88,14 @@ public void uploadInstances(StagingBuffer stagingBuffer, int instanceVbo) { var instanceCount = instances.size(); - for (int page = 0; page < numPages; page++) { - page = changedPages.nextSetBit(0); - - if (page == -1) { - break; - } - - int startObject = pageFile.page2Object(page); + for (int page = changedPages.nextSetBit(0); page >= 0 && page < numPages; page = changedPages.nextSetBit(page + 1)) { + int startObject = InstancePager.page2Object(page); if (startObject >= instanceCount) { break; } - int endObject = Math.min(instanceCount, pageFile.page2Object(page + 1) - 1); + int endObject = Math.min(instanceCount, InstancePager.page2Object(page + 1)); long baseByte = pageFile.page2ByteOffset(page); long size = (endObject - startObject) * instanceStride; 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/InstancePager.java index 018a640a7..9c746be3d 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/InstancePager.java @@ -4,7 +4,6 @@ import java.util.Arrays; import java.util.List; -import org.jetbrains.annotations.UnknownNullability; import org.lwjgl.system.MemoryUtil; import dev.engine_room.flywheel.backend.engine.AbstractArena; @@ -12,39 +11,39 @@ public class InstancePager extends AbstractArena { // 32 objects per page. Allows for convenient bitsets on the gpu. - public static final int DEFAULT_PAGE_SIZE_OBJECTS = 5; - public static final int INITIAL_PAGES_ALLOCATED = 4; + public static final int LOG_2_PAGE_SIZE = 5; + public static final int PAGE_SIZE = 1 << LOG_2_PAGE_SIZE; + public static final int PAGE_MASK = PAGE_SIZE - 1; - private final int log2PageSize; - /** - * The number of objects in a page. - */ - private final int pageSize; + public static final int INITIAL_PAGES_ALLOCATED = 4; private final long objectSizeBytes; - @UnknownNullability private MemoryBlock pageData; - private final int pageMask; - public final ResizableStorageArray storage; - public final ResizableStorageArray pageTable; + public final ResizableStorageBuffer storage; + public final ResizableStorageBuffer pageTable; private final List allocations = new ArrayList<>(); public InstancePager(long objectSizeBytes) { - this(DEFAULT_PAGE_SIZE_OBJECTS, objectSizeBytes); + super(PAGE_SIZE * objectSizeBytes); + this.objectSizeBytes = objectSizeBytes; + + this.storage = new ResizableStorageBuffer(); + this.pageTable = new ResizableStorageBuffer(); + + pageData = MemoryBlock.malloc(INITIAL_PAGES_ALLOCATED * Integer.BYTES); + storage.ensureCapacity(INITIAL_PAGES_ALLOCATED * elementSizeBytes); + pageTable.ensureCapacity(INITIAL_PAGES_ALLOCATED * Integer.BYTES); } - public InstancePager(int log2PageSize, long objectSizeBytes) { - super((1L << log2PageSize) * objectSizeBytes); - this.log2PageSize = log2PageSize; - this.pageSize = 1 << log2PageSize; - this.pageMask = pageSize - 1; - this.objectSizeBytes = objectSizeBytes; + public static int object2Page(int objectIndex) { + return objectIndex >> LOG_2_PAGE_SIZE; + } - this.storage = new ResizableStorageArray(this.elementSizeBytes); - this.pageTable = new ResizableStorageArray(Integer.BYTES); + public static int page2Object(int pageIndex) { + return pageIndex << LOG_2_PAGE_SIZE; } public Allocation createPage() { @@ -55,20 +54,14 @@ public Allocation createPage() { @Override public long byteCapacity() { - return storage.byteCapacity(); + return storage.capacity(); } @Override - protected void resize() { - if (pageData == null) { - pageData = MemoryBlock.malloc(INITIAL_PAGES_ALLOCATED * Integer.BYTES); - storage.ensureCapacity(INITIAL_PAGES_ALLOCATED); - pageTable.ensureCapacity(INITIAL_PAGES_ALLOCATED); - } else { - pageData = pageData.realloc(pageData.size() * 2); - storage.ensureCapacity(storage.capacity() * 2); - pageTable.ensureCapacity(pageTable.capacity() * 2); - } + protected void grow() { + pageData = pageData.realloc(pageData.size() * 2); + storage.ensureCapacity(storage.capacity() * 2); + pageTable.ensureCapacity(pageTable.capacity() * 2); } public void uploadTable(StagingBuffer stagingBuffer) { @@ -88,6 +81,7 @@ public class Allocation { public int[] pages = new int[0]; private int modelIndex = -1; + private int activeCount = 0; public void modelIndex(int modelIndex) { if (this.modelIndex != modelIndex) { @@ -96,17 +90,28 @@ public void modelIndex(int modelIndex) { } private void updatePageTable() { + if (pages.length == 0) { + return; + } + var ptr = pageData.ptr(); - int fullPage = (modelIndex & 0x3FFFFF) | 0x8000000; + int fullPage = (modelIndex & 0x3FFFFF) | (32 << 26); - for (int page : pages) { + 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 + pageMask)); + var neededPages = object2Page((objectCount + PAGE_MASK)); + activeCount = objectCount; var oldLength = pages.length; @@ -126,7 +131,7 @@ private void grow(int neededPages, int oldLength) { } private void shrink(int oldLength, int neededPages) { - for (int i = oldLength - 1; i > neededPages; i--) { + for (int i = oldLength - 1; i >= neededPages; i--) { var page = pages[i]; InstancePager.this.free(page); MemoryUtil.memPutInt(pageData.ptr() + page * Integer.BYTES, 0); @@ -136,21 +141,13 @@ private void shrink(int oldLength, int neededPages) { } public int capacity() { - return pages.length << log2PageSize; + return pages.length << LOG_2_PAGE_SIZE; } public int pageCount() { return pages.length; } - public int object2Page(int objectIndex) { - return objectIndex >> log2PageSize; - } - - public int page2Object(int pageIndex) { - return pageIndex << log2PageSize; - } - public long page2ByteOffset(int page) { return InstancePager.this.byteOffsetOf(pages[page]); } 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 e45f4ec3d..4186f470d 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 @@ -11,8 +11,8 @@ layout(std430, binding = _FLW_TARGET_BUFFER_BINDING) restrict writeonly buffer T }; // High 6 bits for the number of instances in the page. -const uint _FLW_PAGE_COUNT_OFFSET = 25u; -// Bottom 24 bits for the model index. +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 { From b5680a0fd6199c3affbdcc722a020836a45a6d8b Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Tue, 3 Sep 2024 10:56:46 -0500 Subject: [PATCH 03/26] 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 --- .../backend/engine/AbstractInstancer.java | 6 +- .../engine/indirect/IndirectBuffers.java | 4 +- .../engine/indirect/IndirectCullingGroup.java | 9 +- .../engine/indirect/IndirectInstancer.java | 22 +-- .../engine/indirect/InstancePager.java | 152 ++++++++++++------ 5 files changed, 112 insertions(+), 81 deletions(-) diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java index 744bb12a0..16aa88b64 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java @@ -127,7 +127,7 @@ protected void removeDeletedInstances() { // We definitely shouldn't consider the deleted instances as changed though, // 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. for (int scanPos = writePos; (scanPos < oldSize) && (writePos < newSize); scanPos++, writePos++) { @@ -155,10 +155,6 @@ protected void removeDeletedInstances() { .clear(); } - protected void clearChangedRange(int start, int end) { - changed.clear(start, end); - } - protected void setRangeChanged(int start, int end) { changed.set(start, end); } 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 1ce962eec..193efd004 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 @@ -83,13 +83,13 @@ void updateCounts(int instanceCount, int modelCount, int drawCount) { draw.ensureCapacity(drawCount); 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 + MODEL_INDEX_HANDLE_OFFSET, pageFile.pageTable.handle()); MemoryUtil.memPutInt(ptr + MODEL_HANDLE_OFFSET, model.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 + MODEL_INDEX_SIZE_OFFSET, pageFile.pageTable.capacity()); MemoryUtil.memPutAddress(ptr + MODEL_SIZE_OFFSET, MODEL_STRIDE * modelCount); 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 8a25d5df5..da4f8cfe8 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 @@ -74,8 +74,7 @@ public void flushInstancers() { continue; } - instancer.modelIndex(modelIndex); - instancer.baseInstance(instanceCountThisFrame); + instancer.postUpdate(modelIndex, instanceCountThisFrame); instanceCountThisFrame += instanceCount; modelIndex++; @@ -173,8 +172,8 @@ public boolean hasVisualType(VisualType visualType) { } public void add(IndirectInstancer instancer, InstancerKey key, MeshPool meshPool) { - instancer.pageFile = buffers.pageFile.createPage(); - instancer.modelIndex(instancers.size()); + instancer.pageFile = buffers.pageFile.createAllocation(); + instancer.postUpdate(instancers.size(), -1); instancers.add(instancer); @@ -246,7 +245,7 @@ private void drawBarrier() { private void uploadInstances(StagingBuffer stagingBuffer) { for (var instancer : instancers) { - instancer.uploadInstances(stagingBuffer, buffers.pageFile.storage.handle()); + instancer.uploadInstances(stagingBuffer, buffers.pageFile.objects.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 bbd78d88e..da825ed63 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 @@ -52,13 +52,6 @@ protected void setRangeChanged(int start, int end) { 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) { associatedDraws.add(draw); } @@ -69,8 +62,12 @@ public List draws() { public void update() { 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) { @@ -117,21 +114,14 @@ public void delete() { for (IndirectDraw draw : draws()) { draw.delete(); } - } - public void modelIndex(int modelIndex) { - this.modelIndex = modelIndex; - pageFile.modelIndex(modelIndex); + pageFile.delete(); } public int modelIndex() { return modelIndex; } - public void baseInstance(int baseInstance) { - this.baseInstance = baseInstance; - } - public int baseInstance() { return baseInstance; } 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/InstancePager.java index 9c746be3d..a70b98321 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/InstancePager.java @@ -1,8 +1,6 @@ package dev.engine_room.flywheel.backend.engine.indirect; -import java.util.ArrayList; import java.util.Arrays; -import java.util.List; import org.lwjgl.system.MemoryUtil; @@ -17,24 +15,20 @@ public class InstancePager extends AbstractArena { public static final int INITIAL_PAGES_ALLOCATED = 4; - private final long objectSizeBytes; - - private MemoryBlock pageData; - - public final ResizableStorageBuffer storage; + private MemoryBlock pageTableData; + public final ResizableStorageBuffer objects; public final ResizableStorageBuffer pageTable; - private final List allocations = new ArrayList<>(); + private boolean needsUpload = false; public InstancePager(long objectSizeBytes) { super(PAGE_SIZE * objectSizeBytes); - this.objectSizeBytes = objectSizeBytes; - this.storage = new ResizableStorageBuffer(); + this.objects = new ResizableStorageBuffer(); this.pageTable = new ResizableStorageBuffer(); - pageData = MemoryBlock.malloc(INITIAL_PAGES_ALLOCATED * Integer.BYTES); - storage.ensureCapacity(INITIAL_PAGES_ALLOCATED * elementSizeBytes); + pageTableData = MemoryBlock.malloc(INITIAL_PAGES_ALLOCATED * Integer.BYTES); + objects.ensureCapacity(INITIAL_PAGES_ALLOCATED * elementSizeBytes); pageTable.ensureCapacity(INITIAL_PAGES_ALLOCATED * Integer.BYTES); } @@ -46,79 +40,122 @@ public static int page2Object(int pageIndex) { return pageIndex << LOG_2_PAGE_SIZE; } - public Allocation createPage() { - var out = new Allocation(); - allocations.add(out); - return out; + public Allocation createAllocation() { + return new Allocation(); } @Override public long byteCapacity() { - return storage.capacity(); + return objects.capacity(); + } + + @Override + public void free(int i) { + super.free(i); + MemoryUtil.memPutInt(ptrForPage(i), 0); } @Override protected void grow() { - pageData = pageData.realloc(pageData.size() * 2); - storage.ensureCapacity(storage.capacity() * 2); + pageTableData = pageTableData.realloc(pageTableData.size() * 2); + objects.ensureCapacity(objects.capacity() * 2); pageTable.ensureCapacity(pageTable.capacity() * 2); } public void uploadTable(StagingBuffer stagingBuffer) { - for (Allocation allocation : allocations) { - allocation.updatePageTable(); + if (!needsUpload) { + 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() { - storage.delete(); + objects.delete(); pageTable.delete(); - pageData.free(); + pageTableData.free(); + } + + private long ptrForPage(int page) { + return pageTableData.ptr() + (long) page * Integer.BYTES; } 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 activeCount = 0; - - public void modelIndex(int modelIndex) { - if (this.modelIndex != modelIndex) { - this.modelIndex = modelIndex; + 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. + */ + 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() { - if (pages.length == 0) { + public void update(int modelIndex, int objectCount) { + boolean incremental = this.modelIndex == modelIndex; + + if (incremental && objectCount == this.objectCount) { + // Nothing will change. return; } - var ptr = pageData.ptr(); - - int fullPage = (modelIndex & 0x3FFFFF) | (32 << 26); + InstancePager.this.needsUpload = true; - int remainder = activeCount; + this.modelIndex = modelIndex; + this.objectCount = objectCount; - for (int i = 0; i < pages.length - 1; i++) { - int page = pages[i]; - MemoryUtil.memPutInt(ptr + page * Integer.BYTES, fullPage); - remainder -= PAGE_SIZE; + var oldLength = pages.length; + var newLength = object2Page((objectCount + PAGE_MASK)); + + if (oldLength > newLength) { + // Eagerly free the now unnecessary pages. + // shrink will zero out the pageTable entries for the freed pages. + 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); + } } - MemoryUtil.memPutInt(ptr + pages[pages.length - 1] * Integer.BYTES, (modelIndex & 0x3FFFFF) | (remainder << 26)); + if (!incremental) { + // Update all pages. + updateRange(0, newLength); + } } - public void activeCount(int objectCount) { - var neededPages = object2Page((objectCount + PAGE_MASK)); - activeCount = objectCount; - - var oldLength = pages.length; - - if (oldLength > neededPages) { - shrink(oldLength, neededPages); - } else if (oldLength < neededPages) { - grow(neededPages, oldLength); + 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 @@ private void grow(int neededPages, int oldLength) { pages = Arrays.copyOf(pages, neededPages); for (int i = oldLength; i < neededPages; i++) { - pages[i] = InstancePager.this.alloc(); + var page = InstancePager.this.alloc(); + pages[i] = page; } } @@ -134,7 +172,6 @@ private void shrink(int oldLength, int neededPages) { for (int i = oldLength - 1; i >= neededPages; i--) { var page = pages[i]; InstancePager.this.free(page); - MemoryUtil.memPutInt(pageData.ptr() + page * Integer.BYTES, 0); } pages = Arrays.copyOf(pages, neededPages); @@ -151,5 +188,14 @@ public int pageCount() { 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; + } } } From 81cb2340e7e5ea76f1e85b911281da6616bcd09e Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Tue, 3 Sep 2024 11:23:21 -0500 Subject: [PATCH 04/26] The hardest problem - Rename most InstancePager terminology - Rename MODEL_INDEX buffer stuffs --- .../engine/indirect/BufferBindings.java | 2 +- .../engine/indirect/IndirectBuffers.java | 18 +- .../engine/indirect/IndirectCullingGroup.java | 8 +- .../engine/indirect/IndirectInstancer.java | 19 +- ...{InstancePager.java => ObjectStorage.java} | 167 ++++++++++-------- .../internal/indirect/buffer_bindings.glsl | 2 +- .../flywheel/internal/indirect/cull.glsl | 8 +- 7 files changed, 121 insertions(+), 103 deletions(-) rename common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/{InstancePager.java => ObjectStorage.java} (59%) 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 @@ 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 @@ void updateCounts(int instanceCount, int modelCount, int drawCount) { 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 void bindForCrumbling() { 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 void upload(StagingBuffer stagingBuffer) { // 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 void dispatchCull() { cullProgram.bind(); buffers.bindForCompute(); - glDispatchCompute(buffers.pageFile.capacity(), 1, 1); + glDispatchCompute(buffers.objectStorage.capacity(), 1, 1); } public void dispatchApply() { @@ -172,7 +172,7 @@ public boolean hasVisualType(VisualType visualType) { } 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 @@ private void drawBarrier() { 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 @@ 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 void notifyDirty(int index) { 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 void update() { 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 void writeModel(long ptr) { } 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 void delete() { 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 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 void free(int i) { @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 static int objectIndex2PageIndex(int objectIndex) { + return objectIndex >> LOG_2_PAGE_SIZE; + } + + public static int pageIndex2ObjectIndex(int pageIndex) { + return pageIndex << LOG_2_PAGE_SIZE; } - public class Allocation { - public static final int[] EMPTY_ALLOCATION = new int[0]; - public int[] pages = EMPTY_ALLOCATION; + /** + * 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 void update(int modelIndex, int objectCount) { 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 void update(int modelIndex, int objectCount) { } } + 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 @@ private void grow(int neededPages, int oldLength) { 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 @@ private void grow(int neededPages, int oldLength) { 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; From 01a7936a058d19ba2a0de506bbacece61d6143b4 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Wed, 4 Sep 2024 11:05:04 -0500 Subject: [PATCH 05/26] Joining the occult - Implement hi-z occlusion culling - Generate depth pyramid just before issuing cull dispatches - Currently use raw texel fetches but this may be causing loss - Add _flw_cullData to frame uniforms --- .../backend/compile/IndirectPrograms.java | 13 ++- .../backend/engine/indirect/DepthPyramid.java | 106 ++++++++++++++++++ .../engine/indirect/IndirectDrawManager.java | 11 ++ .../backend/engine/uniform/FrameUniforms.java | 16 ++- .../flywheel/backend/gl/shader/GlProgram.java | 11 ++ .../flywheel/internal/indirect/cull.glsl | 59 +++++++++- .../internal/indirect/depth_reduce.glsl | 29 +++++ .../flywheel/internal/uniforms/frame.glsl | 12 ++ 8 files changed, 251 insertions(+), 6 deletions(-) create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java create mode 100644 common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/depth_reduce.glsl diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java index 3b705754a..df1696657 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java @@ -30,6 +30,7 @@ public class IndirectPrograms extends AtomicReferenceCounted { private static final ResourceLocation CULL_SHADER_MAIN = Flywheel.rl("internal/indirect/cull.glsl"); private static final ResourceLocation APPLY_SHADER_MAIN = Flywheel.rl("internal/indirect/apply.glsl"); private static final ResourceLocation SCATTER_SHADER_MAIN = Flywheel.rl("internal/indirect/scatter.glsl"); + private static final ResourceLocation DEPTH_REDUCE_SHADER_MAIN = Flywheel.rl("internal/indirect/depth_reduce.glsl"); private static final Compile> CULL = new Compile<>(); private static final Compile UTIL = new Compile<>(); @@ -44,12 +45,14 @@ public class IndirectPrograms extends AtomicReferenceCounted { private final Map, GlProgram> culling; private final GlProgram apply; private final GlProgram scatter; + private final GlProgram depthReduce; - private IndirectPrograms(Map pipeline, Map, GlProgram> culling, GlProgram apply, GlProgram scatter) { + private IndirectPrograms(Map pipeline, Map, GlProgram> culling, GlProgram apply, GlProgram scatter, GlProgram depthReduce) { this.pipeline = pipeline; this.culling = culling; this.apply = apply; this.scatter = scatter; + this.depthReduce = depthReduce; } private static List getExtensions(GlslVersion glslVersion) { @@ -94,10 +97,10 @@ static void reload(ShaderSources sources, ImmutableList pipe try { var pipelineResult = pipelineCompiler.compileAndReportErrors(pipelineKeys); var cullingResult = cullingCompiler.compileAndReportErrors(createCullingKeys()); - var utils = utilCompiler.compileAndReportErrors(List.of(APPLY_SHADER_MAIN, SCATTER_SHADER_MAIN)); + var utils = utilCompiler.compileAndReportErrors(List.of(APPLY_SHADER_MAIN, SCATTER_SHADER_MAIN, DEPTH_REDUCE_SHADER_MAIN)); if (pipelineResult != null && cullingResult != null && utils != null) { - newInstance = new IndirectPrograms(pipelineResult, cullingResult, utils.get(APPLY_SHADER_MAIN), utils.get(SCATTER_SHADER_MAIN)); + newInstance = new IndirectPrograms(pipelineResult, cullingResult, utils.get(APPLY_SHADER_MAIN), utils.get(SCATTER_SHADER_MAIN), utils.get(DEPTH_REDUCE_SHADER_MAIN)); } } catch (Throwable t) { FlwPrograms.LOGGER.error("Failed to compile indirect programs", t); @@ -184,6 +187,10 @@ public GlProgram getScatterProgram() { return scatter; } + public GlProgram getDepthReduceProgram() { + return depthReduce; + } + @Override protected void _delete() { pipeline.values() diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java new file mode 100644 index 000000000..30e9524c7 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java @@ -0,0 +1,106 @@ +package dev.engine_room.flywheel.backend.engine.indirect; + +import org.lwjgl.opengl.GL32; +import org.lwjgl.opengl.GL46; + +import com.mojang.blaze3d.platform.GlStateManager; + +import dev.engine_room.flywheel.backend.gl.shader.GlProgram; +import dev.engine_room.flywheel.lib.math.MoreMath; +import net.minecraft.client.Minecraft; + +public class DepthPyramid { + private final GlProgram depthReduceProgram; + + public final int pyramidTextureId; + + private int lastWidth = -1; + private int lastHeight = -1; + + public DepthPyramid(GlProgram depthReduceProgram) { + this.depthReduceProgram = depthReduceProgram; + + pyramidTextureId = GL32.glGenTextures(); + + GlStateManager._bindTexture(pyramidTextureId); + GlStateManager._texParameter(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_NEAREST); + GlStateManager._texParameter(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_NEAREST); + GlStateManager._texParameter(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_COMPARE_MODE, GL32.GL_NONE); + GlStateManager._texParameter(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_S, GL32.GL_CLAMP_TO_EDGE); + GlStateManager._texParameter(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_T, GL32.GL_CLAMP_TO_EDGE); + + } + + public void generate() { + var mainRenderTarget = Minecraft.getInstance() + .getMainRenderTarget(); + + int width = mainRenderTarget.width; + int height = mainRenderTarget.height; + + int mipLevels = getImageMipLevels(width, height); + + createPyramidMips(mipLevels, width, height); + + int depthBufferId = mainRenderTarget.getDepthTextureId(); + + GlStateManager._bindTexture(depthBufferId); + + GL46.glMemoryBarrier(GL46.GL_FRAMEBUFFER_BARRIER_BIT); + + GL46.glActiveTexture(GL32.GL_TEXTURE1); + + depthReduceProgram.bind(); + + for (int i = 0; i < mipLevels; i++) { + int mipWidth = Math.max(1, width >> i); + int mipHeight = Math.max(1, height >> i); + + int srcTexture = (i == 0) ? depthBufferId : pyramidTextureId; + GL46.glBindTexture(GL32.GL_TEXTURE_2D, srcTexture); + + GL46.glBindImageTexture(0, pyramidTextureId, i, false, 0, GL32.GL_WRITE_ONLY, GL32.GL_R32F); + + depthReduceProgram.setUVec2("imageSize", mipWidth, mipHeight); + depthReduceProgram.setInt("lod", Math.max(0, i - 1)); + + GL46.glDispatchCompute(MoreMath.ceilingDiv(mipWidth, 8), MoreMath.ceilingDiv(mipHeight, 8), 1); + + GL46.glMemoryBarrier(GL46.GL_TEXTURE_FETCH_BARRIER_BIT); + } + } + + public void delete() { + GL32.glDeleteTextures(pyramidTextureId); + } + + private void createPyramidMips(int mipLevels, int width, int height) { + if (lastWidth == width && lastHeight == height) { + return; + } + + lastWidth = width; + lastHeight = height; + + GL32.glBindTexture(GL32.GL_TEXTURE_2D, pyramidTextureId); + + for (int i = 0; i < mipLevels; i++) { + int mipWidth = Math.max(1, width >> (i + 1)); + int mipHeight = Math.max(1, height >> (i + 1)); + + GL32.glTexImage2D(GL32.GL_TEXTURE_2D, i, GL32.GL_R32F, mipWidth, mipHeight, 0, GL32.GL_RED, GL32.GL_FLOAT, 0); + } + } + + private static int getImageMipLevels(int width, int height) { + int result = 1; + + while (width > 2 && height > 2) { + result++; + width /= 2; + height /= 2; + } + + return result; + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java index adabbf653..494403045 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java @@ -12,6 +12,8 @@ import java.util.List; import java.util.Map; +import org.lwjgl.opengl.GL46; + import dev.engine_room.flywheel.api.backend.Engine; import dev.engine_room.flywheel.api.instance.Instance; import dev.engine_room.flywheel.api.instance.InstanceType; @@ -46,6 +48,8 @@ public class IndirectDrawManager extends DrawManager> { private final LightBuffers lightBuffers; private final MatrixBuffer matrixBuffer; + private final DepthPyramid depthPyramid; + private boolean needsBarrier = false; public IndirectDrawManager(IndirectPrograms programs) { @@ -58,6 +62,8 @@ public IndirectDrawManager(IndirectPrograms programs) { meshPool.bind(vertexArray); lightBuffers = new LightBuffers(); matrixBuffer = new MatrixBuffer(); + + depthPyramid = new DepthPyramid(programs.getDepthReduceProgram()); } @Override @@ -136,6 +142,8 @@ public void flush(LightStorage lightStorage, EnvironmentStorage environmentStora stagingBuffer.flush(); + depthPyramid.generate(); + // We could probably save some driver calls here when there are // actually zero instances, but that feels like a very rare case @@ -143,6 +151,9 @@ public void flush(LightStorage lightStorage, EnvironmentStorage environmentStora matrixBuffer.bind(); + GL46.glActiveTexture(GL46.GL_TEXTURE0); + GL46.glBindTexture(GL46.GL_TEXTURE_2D, depthPyramid.pyramidTextureId); + for (var group : cullingGroups.values()) { group.dispatchCull(); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java index 33bb81901..b19370099 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java @@ -17,7 +17,7 @@ import net.minecraft.world.phys.Vec3; public final class FrameUniforms extends UniformWriter { - private static final int SIZE = 96 + 64 * 9 + 16 * 5 + 8 * 2 + 8 + 4 * 10; + private static final int SIZE = 96 + 64 * 9 + 16 * 5 + 8 * 2 + 8 + 4 * 16; static final UniformBuffer BUFFER = new UniformBuffer(Uniforms.FRAME_INDEX, SIZE); private static final Matrix4f VIEW = new Matrix4f(); @@ -112,6 +112,8 @@ public static void update(RenderContext context) { ptr = writeInt(ptr, debugMode); + ptr = writeCullData(ptr); + firstWrite = false; BUFFER.markDirty(); } @@ -179,6 +181,18 @@ private static long writeCameraIn(long ptr, Camera camera) { return writeInFluidAndBlock(ptr, level, blockPos, cameraPos); } + private static long writeCullData(long ptr) { + ptr = writeFloat(ptr, 0.05F); // zNear + ptr = writeFloat(ptr, Minecraft.getInstance().gameRenderer.getDepthFar()); // zFar + ptr = writeFloat(ptr, PROJECTION.m00()); // P00 + ptr = writeFloat(ptr, PROJECTION.m11()); // P11 + ptr = writeFloat(ptr, Minecraft.getInstance().getMainRenderTarget().width >> 1); // pyramidWidth + ptr = writeFloat(ptr, Minecraft.getInstance().getMainRenderTarget().height >> 1); // pyramidHeight + ptr = writeInt(ptr, 0); // useMin + + return ptr; + } + /** * Writes the frustum planes of the given projection matrix to the given buffer.

* Uses a different format that is friendly towards an optimized instruction-parallel diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/shader/GlProgram.java b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/shader/GlProgram.java index b221fdddf..9438ef355 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/shader/GlProgram.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/shader/GlProgram.java @@ -11,6 +11,7 @@ import static org.lwjgl.opengl.GL20.glUniformMatrix3fv; import static org.lwjgl.opengl.GL20.glUniformMatrix4fv; import static org.lwjgl.opengl.GL30.glUniform1ui; +import static org.lwjgl.opengl.GL30.glUniform2ui; import static org.lwjgl.opengl.GL31.GL_INVALID_INDEX; import static org.lwjgl.opengl.GL31.glGetUniformBlockIndex; import static org.lwjgl.opengl.GL31.glUniformBlockBinding; @@ -118,6 +119,16 @@ public void setUInt(String glslName, int value) { glUniform1ui(uniform, value); } + public void setUVec2(String name, int x, int y) { + int uniform = getUniformLocation(name); + + if (uniform < 0) { + return; + } + + glUniform2ui(uniform, x, y); + } + public void setInt(String glslName, int value) { int uniform = getUniformLocation(glslName); 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 e128b0daf..6d8be7aaf 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 @@ -23,10 +23,12 @@ layout(std430, binding = _FLW_MODEL_BUFFER_BINDING) restrict buffer ModelBuffer ModelDescriptor _flw_models[]; }; -layout(std430, binding = _FLW_MATRIX_BUFFER_BINDING) restrict buffer MatrixBuffer { +layout(std430, binding = _FLW_MATRIX_BUFFER_BINDING) restrict readonly buffer MatrixBuffer { Matrices _flw_matrices[]; }; +layout(binding = 0) uniform sampler2D _flw_depthPyramid; + // Disgustingly vectorized sphere frustum intersection taking advantage of ahead of time packing. // Only uses 6 fmas and some boolean ops. // See also: @@ -40,6 +42,28 @@ bool _flw_testSphere(vec3 center, float radius) { return all(xyInside) && all(zInside); } +bool projectSphere(vec3 c, float r, float znear, float P00, float P11, out vec4 aabb) { + if (c.z > r + znear) { + return false; + } + + vec3 cr = c * r; + float czr2 = c.z * c.z - r * r; + + float vx = sqrt(c.x * c.x + czr2); + float minx = (vx * c.x - cr.z) / (vx * c.z + cr.x); + float maxx = (vx * c.x + cr.z) / (vx * c.z - cr.x); + + float vy = sqrt(c.y * c.y + czr2); + float miny = (vy * c.y - cr.z) / (vy * c.z + cr.y); + float maxy = (vy * c.y + cr.z) / (vy * c.z - cr.y); + + aabb = vec4(minx * P00, miny * P11, maxx * P00, maxy * P11); + aabb = aabb.xwzy * vec4(-0.5f, -0.5f, -0.5f, -0.5f) + vec4(0.5f); // clip space -> uv space + + return true; +} + bool _flw_isVisible(uint instanceIndex, uint modelIndex) { uint matrixIndex = _flw_models[modelIndex].matrixIndex; BoundingSphere sphere = _flw_models[modelIndex].boundingSphere; @@ -56,7 +80,38 @@ bool _flw_isVisible(uint instanceIndex, uint modelIndex) { transformBoundingSphere(_flw_matrices[matrixIndex].pose, center, radius); } - return _flw_testSphere(center, radius); + bool isVisible = _flw_testSphere(center, radius); + + if (isVisible) { + transformBoundingSphere(flw_view, center, radius); + + vec4 aabb; + if (projectSphere(center, radius, _flw_cullData.znear, _flw_cullData.P00, _flw_cullData.P11, aabb)) + { + float width = (aabb.z - aabb.x) * _flw_cullData.pyramidWidth; + float height = (aabb.w - aabb.y) * _flw_cullData.pyramidHeight; + + float level = floor(log2(max(width, height))); + + float depth01 = textureLod(_flw_depthPyramid, aabb.xw, level).r; + float depth11 = textureLod(_flw_depthPyramid, aabb.zw, level).r; + float depth10 = textureLod(_flw_depthPyramid, aabb.zy, level).r; + float depth00 = textureLod(_flw_depthPyramid, aabb.xy, level).r; + + float depth; + if (_flw_cullData.useMin == 0) { + depth = max(max(depth00, depth01), max(depth10, depth11)); + } else { + depth = min(min(depth00, depth01), min(depth10, depth11)); + } + + float depthSphere = 1. + _flw_cullData.znear / (center.z + radius); + + isVisible = isVisible && depthSphere <= depth; + } + } + + return isVisible; } void main() { diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/depth_reduce.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/depth_reduce.glsl new file mode 100644 index 000000000..42bcd7f4e --- /dev/null +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/depth_reduce.glsl @@ -0,0 +1,29 @@ +layout(local_size_x = 8, local_size_y = 8) in; + +layout(binding = 0, r32f) uniform writeonly image2D outImage; +layout(binding = 1) uniform sampler2D inImage; + +uniform uvec2 imageSize; +uniform int lod; + +uniform int useMin = 0; + +void main() { + uvec2 pos = gl_GlobalInvocationID.xy; + + ivec2 samplePos = ivec2(pos) * 2; + + float depth01 = texelFetchOffset(inImage, samplePos, lod, ivec2(0, 1)).r; + float depth11 = texelFetchOffset(inImage, samplePos, lod, ivec2(1, 1)).r; + float depth10 = texelFetchOffset(inImage, samplePos, lod, ivec2(1, 0)).r; + float depth00 = texelFetchOffset(inImage, samplePos, lod, ivec2(0, 0)).r; + + float depth; + if (useMin == 0) { + depth = max(max(depth00, depth01), max(depth10, depth11)); + } else { + depth = min(min(depth00, depth01), min(depth10, depth11)); + } + + imageStore(outImage, ivec2(pos), vec4(depth)); +} diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl index 4b3cfe69f..4ce722400 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl @@ -9,6 +9,16 @@ struct FrustumPlanes { vec2 zW; // }; +struct _FlwCullData { + float znear; + float zfar; + float P00; + float P11; + float pyramidWidth; + float pyramidHeight; + uint useMin; +}; + layout(std140) uniform _FlwFrameUniforms { FrustumPlanes flw_frustumPlanes; @@ -47,6 +57,8 @@ layout(std140) uniform _FlwFrameUniforms { uint flw_cameraInBlock; uint _flw_debugMode; + + _FlwCullData _flw_cullData; }; #define flw_renderOrigin (_flw_renderOrigin.xyz) From 6c1fbf610dd496b874e44be7b4da4bd75a0a250e Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Thu, 5 Sep 2024 12:38:05 -0500 Subject: [PATCH 06/26] The depths of the rabbit hole - Fix mip levels being half the size they should be - Use the next lowest po2 from the main render target size for mip 0 - Map from dst texel to src texel rather than naively multiply by 2 - Clamp the estimated mip level in the cull shader - Use texel fetches in the cull shader (not sure if necessary?) --- .../backend/engine/indirect/DepthPyramid.java | 28 ++++++++++++------- .../backend/engine/uniform/FrameUniforms.java | 17 ++++++++--- .../flywheel/internal/indirect/cull.glsl | 16 +++++++---- .../internal/indirect/depth_reduce.glsl | 6 ++-- .../flywheel/internal/uniforms/frame.glsl | 1 + 5 files changed, 47 insertions(+), 21 deletions(-) diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java index 30e9524c7..cb17f5276 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java @@ -35,8 +35,8 @@ public void generate() { var mainRenderTarget = Minecraft.getInstance() .getMainRenderTarget(); - int width = mainRenderTarget.width; - int height = mainRenderTarget.height; + int width = mip0Size(mainRenderTarget.width); + int height = mip0Size(mainRenderTarget.height); int mipLevels = getImageMipLevels(width, height); @@ -53,15 +53,15 @@ public void generate() { depthReduceProgram.bind(); for (int i = 0; i < mipLevels; i++) { - int mipWidth = Math.max(1, width >> i); - int mipHeight = Math.max(1, height >> i); + int mipWidth = mipSize(width, i); + int mipHeight = mipSize(height, i); int srcTexture = (i == 0) ? depthBufferId : pyramidTextureId; GL46.glBindTexture(GL32.GL_TEXTURE_2D, srcTexture); GL46.glBindImageTexture(0, pyramidTextureId, i, false, 0, GL32.GL_WRITE_ONLY, GL32.GL_R32F); - depthReduceProgram.setUVec2("imageSize", mipWidth, mipHeight); + depthReduceProgram.setVec2("imageSize", mipWidth, mipHeight); depthReduceProgram.setInt("lod", Math.max(0, i - 1)); GL46.glDispatchCompute(MoreMath.ceilingDiv(mipWidth, 8), MoreMath.ceilingDiv(mipHeight, 8), 1); @@ -85,20 +85,28 @@ private void createPyramidMips(int mipLevels, int width, int height) { GL32.glBindTexture(GL32.GL_TEXTURE_2D, pyramidTextureId); for (int i = 0; i < mipLevels; i++) { - int mipWidth = Math.max(1, width >> (i + 1)); - int mipHeight = Math.max(1, height >> (i + 1)); + int mipWidth = mipSize(width, i); + int mipHeight = mipSize(height, i); GL32.glTexImage2D(GL32.GL_TEXTURE_2D, i, GL32.GL_R32F, mipWidth, mipHeight, 0, GL32.GL_RED, GL32.GL_FLOAT, 0); } } - private static int getImageMipLevels(int width, int height) { + public static int mipSize(int mip0Size, int level) { + return Math.max(1, mip0Size >> level); + } + + public static int mip0Size(int screenSize) { + return Integer.highestOneBit(screenSize); + } + + public static int getImageMipLevels(int width, int height) { int result = 1; while (width > 2 && height > 2) { result++; - width /= 2; - height /= 2; + width >>= 1; + height >>= 1; } return result; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java index b19370099..3f7b8e871 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/FrameUniforms.java @@ -8,6 +8,7 @@ import dev.engine_room.flywheel.api.RenderContext; import dev.engine_room.flywheel.api.visualization.VisualizationManager; +import dev.engine_room.flywheel.backend.engine.indirect.DepthPyramid; import dev.engine_room.flywheel.backend.mixin.LevelRendererAccessor; import net.minecraft.client.Camera; import net.minecraft.client.Minecraft; @@ -17,7 +18,7 @@ import net.minecraft.world.phys.Vec3; public final class FrameUniforms extends UniformWriter { - private static final int SIZE = 96 + 64 * 9 + 16 * 5 + 8 * 2 + 8 + 4 * 16; + private static final int SIZE = 96 + 64 * 9 + 16 * 5 + 8 * 2 + 8 + 4 * 17; static final UniformBuffer BUFFER = new UniformBuffer(Uniforms.FRAME_INDEX, SIZE); private static final Matrix4f VIEW = new Matrix4f(); @@ -182,12 +183,20 @@ private static long writeCameraIn(long ptr, Camera camera) { } private static long writeCullData(long ptr) { + var mc = Minecraft.getInstance(); + var mainRenderTarget = mc.getMainRenderTarget(); + + int pyramidWidth = DepthPyramid.mip0Size(mainRenderTarget.width); + int pyramidHeight = DepthPyramid.mip0Size(mainRenderTarget.height); + int pyramidDepth = DepthPyramid.getImageMipLevels(pyramidWidth, pyramidHeight); + ptr = writeFloat(ptr, 0.05F); // zNear - ptr = writeFloat(ptr, Minecraft.getInstance().gameRenderer.getDepthFar()); // zFar + ptr = writeFloat(ptr, mc.gameRenderer.getDepthFar()); // zFar ptr = writeFloat(ptr, PROJECTION.m00()); // P00 ptr = writeFloat(ptr, PROJECTION.m11()); // P11 - ptr = writeFloat(ptr, Minecraft.getInstance().getMainRenderTarget().width >> 1); // pyramidWidth - ptr = writeFloat(ptr, Minecraft.getInstance().getMainRenderTarget().height >> 1); // pyramidHeight + ptr = writeFloat(ptr, pyramidWidth); // pyramidWidth + ptr = writeFloat(ptr, pyramidHeight); // pyramidHeight + ptr = writeInt(ptr, pyramidDepth - 1); // pyramidLevels ptr = writeInt(ptr, 0); // useMin return ptr; 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 6d8be7aaf..1b6436a42 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 @@ -91,12 +91,18 @@ bool _flw_isVisible(uint instanceIndex, uint modelIndex) { float width = (aabb.z - aabb.x) * _flw_cullData.pyramidWidth; float height = (aabb.w - aabb.y) * _flw_cullData.pyramidHeight; - float level = floor(log2(max(width, height))); + int level = clamp(0, int(ceil(log2(max(width, height)))), _flw_cullData.pyramidLevels); - float depth01 = textureLod(_flw_depthPyramid, aabb.xw, level).r; - float depth11 = textureLod(_flw_depthPyramid, aabb.zw, level).r; - float depth10 = textureLod(_flw_depthPyramid, aabb.zy, level).r; - float depth00 = textureLod(_flw_depthPyramid, aabb.xy, level).r; + ivec2 levelSize = textureSize(_flw_depthPyramid, level); + + ivec4 levelSizePair = ivec4(levelSize, levelSize); + + ivec4 bounds = ivec4(aabb * vec4(levelSizePair)); + + float depth01 = texelFetch(_flw_depthPyramid, bounds.xw, level).r; + float depth11 = texelFetch(_flw_depthPyramid, bounds.zw, level).r; + float depth10 = texelFetch(_flw_depthPyramid, bounds.zy, level).r; + float depth00 = texelFetch(_flw_depthPyramid, bounds.xy, level).r; float depth; if (_flw_cullData.useMin == 0) { diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/depth_reduce.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/depth_reduce.glsl index 42bcd7f4e..49bbbf947 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/depth_reduce.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/depth_reduce.glsl @@ -3,7 +3,7 @@ layout(local_size_x = 8, local_size_y = 8) in; layout(binding = 0, r32f) uniform writeonly image2D outImage; layout(binding = 1) uniform sampler2D inImage; -uniform uvec2 imageSize; +uniform vec2 imageSize; uniform int lod; uniform int useMin = 0; @@ -11,7 +11,9 @@ uniform int useMin = 0; void main() { uvec2 pos = gl_GlobalInvocationID.xy; - ivec2 samplePos = ivec2(pos) * 2; + // Map the output texel to an input texel. Properly do the division because generating mip0 maps from the actual + // full resolution depth buffer and the aspect ratio may be different from our Po2 pyramid. + ivec2 samplePos = ivec2(floor(vec2(pos) * vec2(textureSize(inImage, lod)) / imageSize)); float depth01 = texelFetchOffset(inImage, samplePos, lod, ivec2(0, 1)).r; float depth11 = texelFetchOffset(inImage, samplePos, lod, ivec2(1, 1)).r; diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl index 4ce722400..05b3110dc 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/uniforms/frame.glsl @@ -16,6 +16,7 @@ struct _FlwCullData { float P11; float pyramidWidth; float pyramidHeight; + int pyramidLevels; uint useMin; }; From f009cb846c7aa1ab662f58a2a37189de592b764b Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Thu, 5 Sep 2024 13:45:24 -0500 Subject: [PATCH 07/26] Near stability - Fix near plane rejection logic - Fix lod clamp --- .../assets/flywheel/flywheel/internal/indirect/cull.glsl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 1b6436a42..c1ab067bd 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 @@ -43,7 +43,8 @@ bool _flw_testSphere(vec3 center, float radius) { } bool projectSphere(vec3 c, float r, float znear, float P00, float P11, out vec4 aabb) { - if (c.z > r + znear) { + // Closest point on the sphere is between the camera and the near plane, don't even attempt to cull. + if (c.z + r > -znear) { return false; } @@ -91,7 +92,7 @@ bool _flw_isVisible(uint instanceIndex, uint modelIndex) { float width = (aabb.z - aabb.x) * _flw_cullData.pyramidWidth; float height = (aabb.w - aabb.y) * _flw_cullData.pyramidHeight; - int level = clamp(0, int(ceil(log2(max(width, height)))), _flw_cullData.pyramidLevels); + int level = clamp(int(ceil(log2(max(width, height)))), 0, _flw_cullData.pyramidLevels); ivec2 levelSize = textureSize(_flw_depthPyramid, level); From a527af513fba6e7d4c4d9ed7b51b3e74aa975944 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 14 Sep 2024 12:57:15 -0700 Subject: [PATCH 08/26] Harvesting - Cherry-pick misc cleanups from last-frame-visibility - Smarter multibind logic - Make offsets in IndirectBuffers dependent on BufferBindings - Organize buffer bindings based on where they're used to allow each pass to bind exactly which buffers it needs - Use DSA for the depth pyramid - Pass the map of util programs to IndirectPrograms rather than unpacking them individually - Actually delete all the indirect utils --- .../backend/compile/IndirectPrograms.java | 25 ++++---- .../engine/indirect/BufferBindings.java | 7 ++- .../backend/engine/indirect/DepthPyramid.java | 38 +++++------- .../engine/indirect/IndirectBuffers.java | 61 ++++++++++--------- .../engine/indirect/IndirectCullingGroup.java | 4 +- .../internal/indirect/buffer_bindings.glsl | 14 +++-- .../flywheel/internal/indirect/cull.glsl | 2 +- .../flywheel/internal/indirect/main.vert | 2 +- 8 files changed, 76 insertions(+), 77 deletions(-) diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java index df1696657..cc43568da 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java @@ -31,6 +31,7 @@ public class IndirectPrograms extends AtomicReferenceCounted { private static final ResourceLocation APPLY_SHADER_MAIN = Flywheel.rl("internal/indirect/apply.glsl"); private static final ResourceLocation SCATTER_SHADER_MAIN = Flywheel.rl("internal/indirect/scatter.glsl"); private static final ResourceLocation DEPTH_REDUCE_SHADER_MAIN = Flywheel.rl("internal/indirect/depth_reduce.glsl"); + public static final List UTIL_SHADERS = List.of(APPLY_SHADER_MAIN, SCATTER_SHADER_MAIN, DEPTH_REDUCE_SHADER_MAIN); private static final Compile> CULL = new Compile<>(); private static final Compile UTIL = new Compile<>(); @@ -43,16 +44,12 @@ public class IndirectPrograms extends AtomicReferenceCounted { private final Map pipeline; private final Map, GlProgram> culling; - private final GlProgram apply; - private final GlProgram scatter; - private final GlProgram depthReduce; + private final Map utils; - private IndirectPrograms(Map pipeline, Map, GlProgram> culling, GlProgram apply, GlProgram scatter, GlProgram depthReduce) { + private IndirectPrograms(Map pipeline, Map, GlProgram> culling, Map utils) { this.pipeline = pipeline; this.culling = culling; - this.apply = apply; - this.scatter = scatter; - this.depthReduce = depthReduce; + this.utils = utils; } private static List getExtensions(GlslVersion glslVersion) { @@ -97,10 +94,10 @@ static void reload(ShaderSources sources, ImmutableList pipe try { var pipelineResult = pipelineCompiler.compileAndReportErrors(pipelineKeys); var cullingResult = cullingCompiler.compileAndReportErrors(createCullingKeys()); - var utils = utilCompiler.compileAndReportErrors(List.of(APPLY_SHADER_MAIN, SCATTER_SHADER_MAIN, DEPTH_REDUCE_SHADER_MAIN)); + var utils = utilCompiler.compileAndReportErrors(UTIL_SHADERS); if (pipelineResult != null && cullingResult != null && utils != null) { - newInstance = new IndirectPrograms(pipelineResult, cullingResult, utils.get(APPLY_SHADER_MAIN), utils.get(SCATTER_SHADER_MAIN), utils.get(DEPTH_REDUCE_SHADER_MAIN)); + newInstance = new IndirectPrograms(pipelineResult, cullingResult, utils); } } catch (Throwable t) { FlwPrograms.LOGGER.error("Failed to compile indirect programs", t); @@ -180,23 +177,23 @@ public GlProgram getCullingProgram(InstanceType instanceType) { } public GlProgram getApplyProgram() { - return apply; + return utils.get(APPLY_SHADER_MAIN); } public GlProgram getScatterProgram() { - return scatter; + return utils.get(SCATTER_SHADER_MAIN); } public GlProgram getDepthReduceProgram() { - return depthReduce; + return utils.get(DEPTH_REDUCE_SHADER_MAIN); } - @Override protected void _delete() { pipeline.values() .forEach(GlProgram::delete); culling.values() .forEach(GlProgram::delete); - apply.delete(); + utils.values() + .forEach(GlProgram::delete); } } 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 658096695..0b9eea6fd 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 @@ -1,11 +1,12 @@ 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 PAGE_FRAME_DESCRIPTOR = 2; + public static final int PAGE_FRAME_DESCRIPTOR = 0; + public static final int INSTANCE = 1; + public static final int DRAW_INSTANCE_INDEX = 2; public static final int MODEL = 3; public static final int DRAW = 4; + public static final int LIGHT_LUT = 5; public static final int LIGHT_SECTION = 6; public static final int MATRICES = 7; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java index cb17f5276..1ba12b86f 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java @@ -5,6 +5,7 @@ import com.mojang.blaze3d.platform.GlStateManager; +import dev.engine_room.flywheel.backend.gl.GlTextureUnit; import dev.engine_room.flywheel.backend.gl.shader.GlProgram; import dev.engine_room.flywheel.lib.math.MoreMath; import net.minecraft.client.Minecraft; @@ -12,23 +13,13 @@ public class DepthPyramid { private final GlProgram depthReduceProgram; - public final int pyramidTextureId; + public int pyramidTextureId = -1; private int lastWidth = -1; private int lastHeight = -1; public DepthPyramid(GlProgram depthReduceProgram) { this.depthReduceProgram = depthReduceProgram; - - pyramidTextureId = GL32.glGenTextures(); - - GlStateManager._bindTexture(pyramidTextureId); - GlStateManager._texParameter(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_NEAREST); - GlStateManager._texParameter(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_NEAREST); - GlStateManager._texParameter(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_COMPARE_MODE, GL32.GL_NONE); - GlStateManager._texParameter(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_S, GL32.GL_CLAMP_TO_EDGE); - GlStateManager._texParameter(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_WRAP_T, GL32.GL_CLAMP_TO_EDGE); - } public void generate() { @@ -44,11 +35,9 @@ public void generate() { int depthBufferId = mainRenderTarget.getDepthTextureId(); - GlStateManager._bindTexture(depthBufferId); - GL46.glMemoryBarrier(GL46.GL_FRAMEBUFFER_BARRIER_BIT); - GL46.glActiveTexture(GL32.GL_TEXTURE1); + GlTextureUnit.T1.makeActive(); depthReduceProgram.bind(); @@ -57,7 +46,7 @@ public void generate() { int mipHeight = mipSize(height, i); int srcTexture = (i == 0) ? depthBufferId : pyramidTextureId; - GL46.glBindTexture(GL32.GL_TEXTURE_2D, srcTexture); + GlStateManager._bindTexture(srcTexture); GL46.glBindImageTexture(0, pyramidTextureId, i, false, 0, GL32.GL_WRITE_ONLY, GL32.GL_R32F); @@ -71,7 +60,10 @@ public void generate() { } public void delete() { - GL32.glDeleteTextures(pyramidTextureId); + if (pyramidTextureId != -1) { + GL32.glDeleteTextures(pyramidTextureId); + pyramidTextureId = -1; + } } private void createPyramidMips(int mipLevels, int width, int height) { @@ -82,14 +74,16 @@ private void createPyramidMips(int mipLevels, int width, int height) { lastWidth = width; lastHeight = height; - GL32.glBindTexture(GL32.GL_TEXTURE_2D, pyramidTextureId); + delete(); - for (int i = 0; i < mipLevels; i++) { - int mipWidth = mipSize(width, i); - int mipHeight = mipSize(height, i); + pyramidTextureId = GL46.glCreateTextures(GL46.GL_TEXTURE_2D); + GL46.glTextureStorage2D(pyramidTextureId, mipLevels, GL32.GL_R32F, width, height); - GL32.glTexImage2D(GL32.GL_TEXTURE_2D, i, GL32.GL_R32F, mipWidth, mipHeight, 0, GL32.GL_RED, GL32.GL_FLOAT, 0); - } + GL46.glTextureParameteri(pyramidTextureId, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_NEAREST); + GL46.glTextureParameteri(pyramidTextureId, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_NEAREST); + GL46.glTextureParameteri(pyramidTextureId, GL32.GL_TEXTURE_COMPARE_MODE, GL32.GL_NONE); + GL46.glTextureParameteri(pyramidTextureId, GL32.GL_TEXTURE_WRAP_S, GL32.GL_CLAMP_TO_EDGE); + GL46.glTextureParameteri(pyramidTextureId, GL32.GL_TEXTURE_WRAP_T, GL32.GL_CLAMP_TO_EDGE); } public static int mipSize(int mip0Size, int level) { 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 498030fe7..f8597184b 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 @@ -30,18 +30,19 @@ public class IndirectBuffers { private static final long BUFFERS_SIZE_BYTES = SIZE_OFFSET + BUFFER_COUNT * PTR_SIZE; // 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 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; + private static final long PAGE_FRAME_DESCRIPTOR_HANDLE_OFFSET = HANDLE_OFFSET + BufferBindings.PAGE_FRAME_DESCRIPTOR * INT_SIZE; + private static final long INSTANCE_HANDLE_OFFSET = HANDLE_OFFSET + BufferBindings.INSTANCE * INT_SIZE; + private static final long DRAW_INSTANCE_INDEX_HANDLE_OFFSET = HANDLE_OFFSET + BufferBindings.DRAW_INSTANCE_INDEX * INT_SIZE; + private static final long MODEL_HANDLE_OFFSET = HANDLE_OFFSET + BufferBindings.MODEL * INT_SIZE; + private static final long DRAW_HANDLE_OFFSET = HANDLE_OFFSET + BufferBindings.DRAW * INT_SIZE; // 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 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; + private static final long PAGE_FRAME_DESCRIPTOR_SIZE_OFFSET = SIZE_OFFSET + BufferBindings.PAGE_FRAME_DESCRIPTOR * PTR_SIZE; + private static final long INSTANCE_SIZE_OFFSET = SIZE_OFFSET + BufferBindings.INSTANCE * PTR_SIZE; + private static final long DRAW_INSTANCE_INDEX_SIZE_OFFSET = SIZE_OFFSET + BufferBindings.DRAW_INSTANCE_INDEX * PTR_SIZE; + private static final long MODEL_SIZE_OFFSET = SIZE_OFFSET + BufferBindings.MODEL * PTR_SIZE; + private static final long DRAW_SIZE_OFFSET = SIZE_OFFSET + BufferBindings.DRAW * PTR_SIZE; + private static final float INSTANCE_GROWTH_FACTOR = 1.25f; private static final float MODEL_GROWTH_FACTOR = 2f; @@ -60,69 +61,71 @@ public class IndirectBuffers { * one for the instance buffer, target buffer, model index buffer, model buffer, and draw buffer. */ private final MemoryBlock multiBindBlock; - private final long instanceStride; public final ObjectStorage objectStorage; - public final ResizableStorageArray target; + public final ResizableStorageArray drawInstanceIndex; public final ResizableStorageArray model; public final ResizableStorageArray draw; IndirectBuffers(long instanceStride) { - this.instanceStride = instanceStride; this.multiBindBlock = MemoryBlock.calloc(BUFFERS_SIZE_BYTES, 1); objectStorage = new ObjectStorage(instanceStride); - target = new ResizableStorageArray(INT_SIZE, INSTANCE_GROWTH_FACTOR); + drawInstanceIndex = new ResizableStorageArray(INT_SIZE, INSTANCE_GROWTH_FACTOR); model = new ResizableStorageArray(MODEL_STRIDE, MODEL_GROWTH_FACTOR); draw = new ResizableStorageArray(DRAW_COMMAND_STRIDE, DRAW_GROWTH_FACTOR); } void updateCounts(int instanceCount, int modelCount, int drawCount) { - target.ensureCapacity(instanceCount); + drawInstanceIndex.ensureCapacity(instanceCount); model.ensureCapacity(modelCount); draw.ensureCapacity(drawCount); final long ptr = multiBindBlock.ptr(); - MemoryUtil.memPutInt(ptr + INSTANCE_HANDLE_OFFSET, objectStorage.objectBuffer.handle()); - MemoryUtil.memPutInt(ptr + TARGET_HANDLE_OFFSET, target.handle()); + MemoryUtil.memPutInt(ptr + PAGE_FRAME_DESCRIPTOR_HANDLE_OFFSET, objectStorage.frameDescriptorBuffer.handle()); + MemoryUtil.memPutInt(ptr + INSTANCE_HANDLE_OFFSET, objectStorage.objectBuffer.handle()); + MemoryUtil.memPutInt(ptr + DRAW_INSTANCE_INDEX_HANDLE_OFFSET, drawInstanceIndex.handle()); MemoryUtil.memPutInt(ptr + MODEL_HANDLE_OFFSET, model.handle()); MemoryUtil.memPutInt(ptr + DRAW_HANDLE_OFFSET, draw.handle()); - MemoryUtil.memPutAddress(ptr + INSTANCE_SIZE_OFFSET, objectStorage.objectBuffer.capacity()); - MemoryUtil.memPutAddress(ptr + TARGET_SIZE_OFFSET, INT_SIZE * instanceCount); MemoryUtil.memPutAddress(ptr + PAGE_FRAME_DESCRIPTOR_SIZE_OFFSET, objectStorage.frameDescriptorBuffer.capacity()); + MemoryUtil.memPutAddress(ptr + INSTANCE_SIZE_OFFSET, objectStorage.objectBuffer.capacity()); + MemoryUtil.memPutAddress(ptr + DRAW_INSTANCE_INDEX_SIZE_OFFSET, INT_SIZE * instanceCount); MemoryUtil.memPutAddress(ptr + MODEL_SIZE_OFFSET, MODEL_STRIDE * modelCount); MemoryUtil.memPutAddress(ptr + DRAW_SIZE_OFFSET, DRAW_COMMAND_STRIDE * drawCount); } - public void bindForCompute() { - multiBind(); + public void bindForCull() { + multiBind(0, 4); } - public void bindForDraw() { - multiBind(); - GlBufferType.DRAW_INDIRECT_BUFFER.bind(draw.handle()); + public void bindForApply() { + multiBind(3, 2); } - private void multiBind() { - final long ptr = multiBindBlock.ptr(); - nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE, IndirectBuffers.BUFFER_COUNT, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET); + public void bindForDraw() { + multiBind(1, 4); + GlBufferType.DRAW_INDIRECT_BUFFER.bind(draw.handle()); } /** * Bind all buffers except the draw command buffer. */ public void bindForCrumbling() { + multiBind(3, 3); + } + + private void multiBind(int base, int count) { final long ptr = multiBindBlock.ptr(); - nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE, 4, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET); + nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, base, count, ptr + base * INT_SIZE, ptr + OFFSET_OFFSET + base * PTR_SIZE, ptr + SIZE_OFFSET + base * PTR_SIZE); } public void delete() { multiBindBlock.free(); objectStorage.delete(); - target.delete(); + drawInstanceIndex.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 961d5b2ce..c12277860 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 @@ -118,7 +118,7 @@ public void dispatchCull() { Uniforms.bindAll(); cullProgram.bind(); - buffers.bindForCompute(); + buffers.bindForCull(); glDispatchCompute(buffers.objectStorage.capacity(), 1, 1); } @@ -127,7 +127,7 @@ public void dispatchApply() { return; } - buffers.bindForCompute(); + buffers.bindForApply(); glDispatchCompute(GlCompat.getComputeGroupCount(indirectDraws.size()), 1, 1); } 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 449836630..256bd68eb 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,8 +1,12 @@ -#define _FLW_INSTANCE_BUFFER_BINDING 0 -#define _FLW_TARGET_BUFFER_BINDING 1 -#define _FLW_PAGE_FRAME_DESCRIPTOR_BUFFER_BINDING 2 -#define _FLW_MODEL_BUFFER_BINDING 3 -#define _FLW_DRAW_BUFFER_BINDING 4 +// Per culling group +#define _FLW_PAGE_FRAME_DESCRIPTOR_BUFFER_BINDING 0// cull +#define _FLW_INSTANCE_BUFFER_BINDING 1// cull, draw +#define _FLW_DRAW_INSTANCE_INDEX_BUFFER_BINDING 2// cull, draw +#define _FLW_MODEL_BUFFER_BINDING 3// cull, apply +#define _FLW_DRAW_BUFFER_BINDING 4// apply, draw + +// Global to the engine #define _FLW_LIGHT_LUT_BUFFER_BINDING 5 #define _FLW_LIGHT_SECTIONS_BUFFER_BINDING 6 + #define _FLW_MATRIX_BUFFER_BINDING 7 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 c1ab067bd..38724599f 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 @@ -6,7 +6,7 @@ layout(local_size_x = 32) in; -layout(std430, binding = _FLW_TARGET_BUFFER_BINDING) restrict writeonly buffer TargetBuffer { +layout(std430, binding = _FLW_DRAW_INSTANCE_INDEX_BUFFER_BINDING) restrict writeonly buffer TargetBuffer { uint _flw_instanceIndices[]; }; diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert index e53dff313..d94725fcb 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert @@ -5,7 +5,7 @@ #include "flywheel:internal/indirect/light.glsl" #include "flywheel:internal/indirect/matrices.glsl" -layout(std430, binding = _FLW_TARGET_BUFFER_BINDING) restrict readonly buffer TargetBuffer { +layout(std430, binding = _FLW_DRAW_INSTANCE_INDEX_BUFFER_BINDING) restrict readonly buffer TargetBuffer { uint _flw_instanceIndices[]; }; From ddb04501052badb72f710aa138f259c7cbfd2d49 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Thu, 12 Sep 2024 21:32:13 -0700 Subject: [PATCH 09/26] Rapid descent - Implement single (but actually 2) pass downsampling --- .../backend/compile/IndirectPrograms.java | 14 +- .../backend/engine/indirect/DepthPyramid.java | 49 ++++-- .../engine/indirect/IndirectDrawManager.java | 9 +- .../internal/indirect/depth_reduce.glsl | 31 ---- .../internal/indirect/downsample.glsl | 33 ++++ .../internal/indirect/downsample_first.glsl | 150 ++++++++++++++++++ .../internal/indirect/downsample_second.glsl | 136 ++++++++++++++++ 7 files changed, 367 insertions(+), 55 deletions(-) delete mode 100644 common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/depth_reduce.glsl create mode 100644 common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample.glsl create mode 100644 common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_first.glsl create mode 100644 common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_second.glsl diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java index cc43568da..645f00451 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/IndirectPrograms.java @@ -30,8 +30,9 @@ public class IndirectPrograms extends AtomicReferenceCounted { private static final ResourceLocation CULL_SHADER_MAIN = Flywheel.rl("internal/indirect/cull.glsl"); private static final ResourceLocation APPLY_SHADER_MAIN = Flywheel.rl("internal/indirect/apply.glsl"); private static final ResourceLocation SCATTER_SHADER_MAIN = Flywheel.rl("internal/indirect/scatter.glsl"); - private static final ResourceLocation DEPTH_REDUCE_SHADER_MAIN = Flywheel.rl("internal/indirect/depth_reduce.glsl"); - public static final List UTIL_SHADERS = List.of(APPLY_SHADER_MAIN, SCATTER_SHADER_MAIN, DEPTH_REDUCE_SHADER_MAIN); + private static final ResourceLocation DOWNSAMPLE_FIRST = Flywheel.rl("internal/indirect/downsample_first.glsl"); + private static final ResourceLocation DOWNSAMPLE_SECOND = Flywheel.rl("internal/indirect/downsample_second.glsl"); + public static final List UTIL_SHADERS = List.of(APPLY_SHADER_MAIN, SCATTER_SHADER_MAIN, DOWNSAMPLE_FIRST, DOWNSAMPLE_SECOND); private static final Compile> CULL = new Compile<>(); private static final Compile UTIL = new Compile<>(); @@ -184,9 +185,14 @@ public GlProgram getScatterProgram() { return utils.get(SCATTER_SHADER_MAIN); } - public GlProgram getDepthReduceProgram() { - return utils.get(DEPTH_REDUCE_SHADER_MAIN); + public GlProgram getDownsampleFirstProgram() { + return utils.get(DOWNSAMPLE_FIRST); } + + public GlProgram getDownsampleSecondProgram() { + return utils.get(DOWNSAMPLE_SECOND); + } + @Override protected void _delete() { pipeline.values() diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java index 1ba12b86f..56400e6f6 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/DepthPyramid.java @@ -11,15 +11,17 @@ import net.minecraft.client.Minecraft; public class DepthPyramid { - private final GlProgram depthReduceProgram; + private final GlProgram downsampleFirstProgram; + private final GlProgram downsampleSecondProgram; public int pyramidTextureId = -1; private int lastWidth = -1; private int lastHeight = -1; - public DepthPyramid(GlProgram depthReduceProgram) { - this.depthReduceProgram = depthReduceProgram; + public DepthPyramid(GlProgram downsampleFirstProgram, GlProgram downsampleSecondProgram) { + this.downsampleFirstProgram = downsampleFirstProgram; + this.downsampleSecondProgram = downsampleSecondProgram; } public void generate() { @@ -37,26 +39,43 @@ public void generate() { GL46.glMemoryBarrier(GL46.GL_FRAMEBUFFER_BARRIER_BIT); - GlTextureUnit.T1.makeActive(); + GlTextureUnit.T0.makeActive(); + GlStateManager._bindTexture(depthBufferId); - depthReduceProgram.bind(); + downsampleFirstProgram.bind(); + downsampleFirstProgram.setUInt("max_mip_level", mipLevels); - for (int i = 0; i < mipLevels; i++) { - int mipWidth = mipSize(width, i); - int mipHeight = mipSize(height, i); + for (int i = 0; i < Math.min(6, mipLevels); i++) { + GL46.glBindImageTexture(i + 1, pyramidTextureId, i, false, 0, GL32.GL_WRITE_ONLY, GL32.GL_R32F); + } - int srcTexture = (i == 0) ? depthBufferId : pyramidTextureId; - GlStateManager._bindTexture(srcTexture); + GL46.glDispatchCompute(MoreMath.ceilingDiv(width << 1, 64), MoreMath.ceilingDiv(height << 1, 64), 1); - GL46.glBindImageTexture(0, pyramidTextureId, i, false, 0, GL32.GL_WRITE_ONLY, GL32.GL_R32F); + if (mipLevels < 7) { + GL46.glMemoryBarrier(GL46.GL_TEXTURE_FETCH_BARRIER_BIT); - depthReduceProgram.setVec2("imageSize", mipWidth, mipHeight); - depthReduceProgram.setInt("lod", Math.max(0, i - 1)); + return; + } - GL46.glDispatchCompute(MoreMath.ceilingDiv(mipWidth, 8), MoreMath.ceilingDiv(mipHeight, 8), 1); + GL46.glMemoryBarrier(GL46.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); - GL46.glMemoryBarrier(GL46.GL_TEXTURE_FETCH_BARRIER_BIT); + downsampleSecondProgram.bind(); + downsampleSecondProgram.setUInt("max_mip_level", mipLevels); + + // Note: mip_6 in the shader is actually mip level 5 in the texture + GL46.glBindImageTexture(0, pyramidTextureId, 5, false, 0, GL32.GL_READ_ONLY, GL32.GL_R32F); + for (int i = 6; i < Math.min(12, mipLevels); i++) { + GL46.glBindImageTexture(i - 5, pyramidTextureId, i, false, 0, GL32.GL_WRITE_ONLY, GL32.GL_R32F); } + + GL46.glDispatchCompute(1, 1, 1); + + GL46.glMemoryBarrier(GL46.GL_TEXTURE_FETCH_BARRIER_BIT); + } + + public void bindForCull() { + GlTextureUnit.T0.makeActive(); + GlStateManager._bindTexture(pyramidTextureId); } public void delete() { diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java index 494403045..99ff35707 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java @@ -12,8 +12,6 @@ import java.util.List; import java.util.Map; -import org.lwjgl.opengl.GL46; - import dev.engine_room.flywheel.api.backend.Engine; import dev.engine_room.flywheel.api.instance.Instance; import dev.engine_room.flywheel.api.instance.InstanceType; @@ -63,7 +61,7 @@ public IndirectDrawManager(IndirectPrograms programs) { lightBuffers = new LightBuffers(); matrixBuffer = new MatrixBuffer(); - depthPyramid = new DepthPyramid(programs.getDepthReduceProgram()); + depthPyramid = new DepthPyramid(programs.getDownsampleFirstProgram(), programs.getDownsampleSecondProgram()); } @Override @@ -151,8 +149,7 @@ public void flush(LightStorage lightStorage, EnvironmentStorage environmentStora matrixBuffer.bind(); - GL46.glActiveTexture(GL46.GL_TEXTURE0); - GL46.glBindTexture(GL46.GL_TEXTURE_2D, depthPyramid.pyramidTextureId); + depthPyramid.bindForCull(); for (var group : cullingGroups.values()) { group.dispatchCull(); @@ -185,6 +182,8 @@ public void delete() { crumblingDrawBuffer.delete(); programs.release(); + + depthPyramid.delete(); } public void renderCrumbling(List crumblingBlocks) { diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/depth_reduce.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/depth_reduce.glsl deleted file mode 100644 index 49bbbf947..000000000 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/depth_reduce.glsl +++ /dev/null @@ -1,31 +0,0 @@ -layout(local_size_x = 8, local_size_y = 8) in; - -layout(binding = 0, r32f) uniform writeonly image2D outImage; -layout(binding = 1) uniform sampler2D inImage; - -uniform vec2 imageSize; -uniform int lod; - -uniform int useMin = 0; - -void main() { - uvec2 pos = gl_GlobalInvocationID.xy; - - // Map the output texel to an input texel. Properly do the division because generating mip0 maps from the actual - // full resolution depth buffer and the aspect ratio may be different from our Po2 pyramid. - ivec2 samplePos = ivec2(floor(vec2(pos) * vec2(textureSize(inImage, lod)) / imageSize)); - - float depth01 = texelFetchOffset(inImage, samplePos, lod, ivec2(0, 1)).r; - float depth11 = texelFetchOffset(inImage, samplePos, lod, ivec2(1, 1)).r; - float depth10 = texelFetchOffset(inImage, samplePos, lod, ivec2(1, 0)).r; - float depth00 = texelFetchOffset(inImage, samplePos, lod, ivec2(0, 0)).r; - - float depth; - if (useMin == 0) { - depth = max(max(depth00, depth01), max(depth10, depth11)); - } else { - depth = min(min(depth00, depth01), min(depth10, depth11)); - } - - imageStore(outImage, ivec2(pos), vec4(depth)); -} diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample.glsl new file mode 100644 index 000000000..48fd55ec2 --- /dev/null +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample.glsl @@ -0,0 +1,33 @@ +layout(local_size_x = 256) in; + +uniform uint max_mip_level; + +/// Generates a hierarchical depth buffer. +/// Based on FidelityFX SPD v2.1 https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK/blob/d7531ae47d8b36a5d4025663e731a47a38be882f/sdk/include/FidelityFX/gpu/spd/ffx_spd.h#L528 +/// Based on Bevy's more readable implementation https://github.com/JMS55/bevy/blob/ca2c8e63b9562f88c8cd7e1d88a17a4eea20aaf4/crates/bevy_pbr/src/meshlet/downsample_depth.wgsl + +shared float[16][16] intermediate_memory; + +// These are builtins in wgsl but we can trivially emulate them. +uint extractBits(uint e, uint offset, uint count) { + return (e >> offset) & ((1u << count) - 1u); +} + +uint insertBits(uint e, uint newbits, uint offset, uint count) { + uint countMask = ((1u << count) - 1u); + // zero out the bits we're going to replace first + return (e & ~(countMask << offset)) | ((newbits & countMask) << offset); +} + +// I do not understand how this works but it seems cool. +uvec2 remap_for_wave_reduction(uint a) { + return uvec2( + insertBits(extractBits(a, 2u, 3u), a, 0u, 1u), + insertBits(extractBits(a, 3u, 3u), extractBits(a, 1u, 2u), 0u, 2u) + ); +} + +float reduce_4(vec4 v) { + return max(max(v.x, v.y), max(v.z, v.w)); +} + diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_first.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_first.glsl new file mode 100644 index 000000000..351995767 --- /dev/null +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_first.glsl @@ -0,0 +1,150 @@ +#include "flywheel:internal/indirect/downsample.glsl" + +layout(binding = 0) uniform sampler2D mip_0; +layout(binding = 1, r32f) uniform writeonly image2D mip_1; +layout(binding = 2, r32f) uniform writeonly image2D mip_2; +layout(binding = 3, r32f) uniform writeonly image2D mip_3; +layout(binding = 4, r32f) uniform writeonly image2D mip_4; +layout(binding = 5, r32f) uniform writeonly image2D mip_5; +layout(binding = 6, r32f) uniform writeonly image2D mip_6; + +float reduce_load_mip_0(uvec2 tex) { + // NOTE: mip_0 is the actual depth buffer, and mip_1 is the "base" of our depth pyramid and has the next + // smallest Po2 dimensions to mip_1's dimensions. We dispatch enough invocations to cover the entire mip_1 + // and will very likely oversample mip_0, but that's okay because we need to ensure conservative coverage. + // All following mip levels are proper halvings of their parents and will not waste any work. + vec2 uv = (vec2(tex) + 0.5) / vec2(imageSize(mip_1)) * 0.5; + return reduce_4(textureGather(mip_0, uv)); +} + +void downsample_mips_0_and_1(uint x, uint y, ivec2 workgroup_id, uint local_invocation_index) { + vec4 v; + + ivec2 tex = workgroup_id * 64 + ivec2(x * 2u, y * 2u); + ivec2 pix = workgroup_id * 32 + ivec2(x, y); + v[0] = reduce_load_mip_0(tex); + imageStore(mip_1, pix, vec4(v[0])); + + tex = workgroup_id * 64 + ivec2(x * 2u + 32u, y * 2u); + pix = workgroup_id * 32 + ivec2(x + 16u, y); + v[1] = reduce_load_mip_0(tex); + imageStore(mip_1, pix, vec4(v[1])); + + tex = workgroup_id * 64 + ivec2(x * 2u, y * 2u + 32u); + pix = workgroup_id * 32 + ivec2(x, y + 16u); + v[2] = reduce_load_mip_0(tex); + imageStore(mip_1, pix, vec4(v[2])); + + tex = workgroup_id * 64 + ivec2(x * 2u + 32u, y * 2u + 32u); + pix = workgroup_id * 32 + ivec2(x + 16u, y + 16u); + v[3] = reduce_load_mip_0(tex); + imageStore(mip_1, pix, vec4(v[3])); + + if (max_mip_level <= 1u) { return; } + + for (uint i = 0u; i < 4u; i++) { + intermediate_memory[x][y] = v[i]; + barrier(); + if (local_invocation_index < 64u) { + v[i] = reduce_4(vec4( + intermediate_memory[x * 2u + 0u][y * 2u + 0u], + intermediate_memory[x * 2u + 1u][y * 2u + 0u], + intermediate_memory[x * 2u + 0u][y * 2u + 1u], + intermediate_memory[x * 2u + 1u][y * 2u + 1u] + )); + pix = (workgroup_id * 16) + ivec2( + x + (i % 2u) * 8u, + y + (i / 2u) * 8u + ); + imageStore(mip_2, pix, vec4(v[i])); + } + barrier(); + } + + if (local_invocation_index < 64u) { + intermediate_memory[x + 0u][y + 0u] = v[0]; + intermediate_memory[x + 8u][y + 0u] = v[1]; + intermediate_memory[x + 0u][y + 8u] = v[2]; + intermediate_memory[x + 8u][y + 8u] = v[3]; + } +} + + +void downsample_mip_2(uint x, uint y, ivec2 workgroup_id, uint local_invocation_index) { + if (local_invocation_index < 64u) { + float v = reduce_4(vec4( + intermediate_memory[x * 2u + 0u][y * 2u + 0u], + intermediate_memory[x * 2u + 1u][y * 2u + 0u], + intermediate_memory[x * 2u + 0u][y * 2u + 1u], + intermediate_memory[x * 2u + 1u][y * 2u + 1u] + )); + imageStore(mip_3, (workgroup_id * 8) + ivec2(x, y), vec4(v)); + intermediate_memory[x * 2u + y % 2u][y * 2u] = v; + } +} + +void downsample_mip_3(uint x, uint y, ivec2 workgroup_id, uint local_invocation_index) { + if (local_invocation_index < 16u) { + float v = reduce_4(vec4( + intermediate_memory[x * 4u + 0u + 0u][y * 4u + 0u], + intermediate_memory[x * 4u + 2u + 0u][y * 4u + 0u], + intermediate_memory[x * 4u + 0u + 1u][y * 4u + 2u], + intermediate_memory[x * 4u + 2u + 1u][y * 4u + 2u] + )); + imageStore(mip_4, (workgroup_id * 4) + ivec2(x, y), vec4(v)); + intermediate_memory[x * 4u + y][y * 4u] = v; + } +} + +void downsample_mip_4(uint x, uint y, ivec2 workgroup_id, uint local_invocation_index) { + if (local_invocation_index < 4u) { + float v = reduce_4(vec4( + intermediate_memory[x * 8u + 0u + 0u + y * 2u][y * 8u + 0u], + intermediate_memory[x * 8u + 4u + 0u + y * 2u][y * 8u + 0u], + intermediate_memory[x * 8u + 0u + 1u + y * 2u][y * 8u + 4u], + intermediate_memory[x * 8u + 4u + 1u + y * 2u][y * 8u + 4u] + )); + imageStore(mip_5, (workgroup_id * 2) + ivec2(x, y), vec4(v)); + intermediate_memory[x + y * 2u][0u] = v; + } +} + +void downsample_mip_5(ivec2 workgroup_id, uint local_invocation_index) { + if (local_invocation_index < 1u) { + float v = reduce_4(vec4( + intermediate_memory[0u][0u], + intermediate_memory[1u][0u], + intermediate_memory[2u][0u], + intermediate_memory[3u][0u] + )); + imageStore(mip_6, workgroup_id, vec4(v)); + } +} + +void downsample_mips_2_to_5(uint x, uint y, ivec2 workgroup_id, uint local_invocation_index) { + if (max_mip_level <= 2u) { return; } + barrier(); + downsample_mip_2(x, y, workgroup_id, local_invocation_index); + + if (max_mip_level <= 3u) { return; } + barrier(); + downsample_mip_3(x, y, workgroup_id, local_invocation_index); + + if (max_mip_level <= 4u) { return; } + barrier(); + downsample_mip_4(x, y, workgroup_id, local_invocation_index); + + if (max_mip_level <= 5u) { return; } + barrier(); + downsample_mip_5(workgroup_id, local_invocation_index); +} + +void main() { + uvec2 sub_xy = remap_for_wave_reduction(gl_LocalInvocationIndex % 64u); + uint x = sub_xy.x + 8u * ((gl_LocalInvocationIndex >> 6u) % 2u); + uint y = sub_xy.y + 8u * (gl_LocalInvocationIndex >> 7u); + + downsample_mips_0_and_1(x, y, ivec2(gl_WorkGroupID.xy), gl_LocalInvocationIndex); + + downsample_mips_2_to_5(x, y, ivec2(gl_WorkGroupID.xy), gl_LocalInvocationIndex); +} diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_second.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_second.glsl new file mode 100644 index 000000000..afedc061c --- /dev/null +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_second.glsl @@ -0,0 +1,136 @@ +#include "flywheel:internal/indirect/downsample.glsl" + +layout(binding = 0, r32f) uniform readonly image2D mip_6; +layout(binding = 1, r32f) uniform writeonly image2D mip_7; +layout(binding = 2, r32f) uniform writeonly image2D mip_8; +layout(binding = 3, r32f) uniform writeonly image2D mip_9; +layout(binding = 4, r32f) uniform writeonly image2D mip_10; +layout(binding = 5, r32f) uniform writeonly image2D mip_11; +layout(binding = 6, r32f) uniform writeonly image2D mip_12; + +float reduce_load_mip_6(ivec2 tex) { + // NOTE: We could bind mip_6 as a sampler2D and use textureGather, + // but it's already written to as an image in the first pass so I think this is fine. + return reduce_4(vec4( + imageLoad(mip_6, tex + ivec2(0u, 0u)).r, + imageLoad(mip_6, tex + ivec2(0u, 1u)).r, + imageLoad(mip_6, tex + ivec2(1u, 0u)).r, + imageLoad(mip_6, tex + ivec2(1u, 1u)).r + )); +} + +void downsample_mips_6_and_7(uint x, uint y) { + vec4 v; + + ivec2 tex = ivec2(x * 4u + 0u, y * 4u + 0u); + ivec2 pix = ivec2(x * 2u + 0u, y * 2u + 0u); + v[0] = reduce_load_mip_6(tex); + imageStore(mip_7, pix, vec4(v[0])); + + tex = ivec2(x * 4u + 2u, y * 4u + 0u); + pix = ivec2(x * 2u + 1u, y * 2u + 0u); + v[1] = reduce_load_mip_6(tex); + imageStore(mip_7, pix, vec4(v[1])); + + tex = ivec2(x * 4u + 0u, y * 4u + 2u); + pix = ivec2(x * 2u + 0u, y * 2u + 1u); + v[2] = reduce_load_mip_6(tex); + imageStore(mip_7, pix, vec4(v[2])); + + tex = ivec2(x * 4u + 2u, y * 4u + 2u); + pix = ivec2(x * 2u + 1u, y * 2u + 1u); + v[3] = reduce_load_mip_6(tex); + imageStore(mip_7, pix, vec4(v[3])); + + if (max_mip_level <= 7u) { return; } + + float vr = reduce_4(v); + imageStore(mip_8, ivec2(x, y), vec4(vr)); + intermediate_memory[x][y] = vr; +} + + +void downsample_mip_8(uint x, uint y, uint local_invocation_index) { + if (local_invocation_index < 64u) { + float v = reduce_4(vec4( + intermediate_memory[x * 2u + 0u][y * 2u + 0u], + intermediate_memory[x * 2u + 1u][y * 2u + 0u], + intermediate_memory[x * 2u + 0u][y * 2u + 1u], + intermediate_memory[x * 2u + 1u][y * 2u + 1u] + )); + imageStore(mip_9, ivec2(x, y), vec4(v)); + intermediate_memory[x * 2u + y % 2u][y * 2u] = v; + } +} + +void downsample_mip_9(uint x, uint y, uint local_invocation_index) { + if (local_invocation_index < 16u) { + float v = reduce_4(vec4( + intermediate_memory[x * 4u + 0u + 0u][y * 4u + 0u], + intermediate_memory[x * 4u + 2u + 0u][y * 4u + 0u], + intermediate_memory[x * 4u + 0u + 1u][y * 4u + 2u], + intermediate_memory[x * 4u + 2u + 1u][y * 4u + 2u] + )); + imageStore(mip_10, ivec2(x, y), vec4(v)); + intermediate_memory[x * 4u + y][y * 4u] = v; + } +} + +void downsample_mip_10(uint x, uint y, uint local_invocation_index) { + if (local_invocation_index < 4u) { + float v = reduce_4(vec4( + intermediate_memory[x * 8u + 0u + 0u + y * 2u][y * 8u + 0u], + intermediate_memory[x * 8u + 4u + 0u + y * 2u][y * 8u + 0u], + intermediate_memory[x * 8u + 0u + 1u + y * 2u][y * 8u + 4u], + intermediate_memory[x * 8u + 4u + 1u + y * 2u][y * 8u + 4u] + )); + imageStore(mip_11, ivec2(x, y), vec4(v)); + intermediate_memory[x + y * 2u][0u] = v; + } +} + +void downsample_mip_11(uint local_invocation_index) { + if (local_invocation_index < 1u) { + float v = reduce_4(vec4( + intermediate_memory[0u][0u], + intermediate_memory[1u][0u], + intermediate_memory[2u][0u], + intermediate_memory[3u][0u] + )); + + imageStore(mip_12, ivec2(0u, 0u), vec4(v)); + } +} + + +void downsample_mips_8_to_11(uint x, uint y, uint local_invocation_index) { + if (max_mip_level <= 8u) { return; } + barrier(); + downsample_mip_8(x, y, local_invocation_index); + + if (max_mip_level <= 9u) { return; } + barrier(); + downsample_mip_9(x, y, local_invocation_index); + + if (max_mip_level <= 10u) { return; } + barrier(); + downsample_mip_10(x, y, local_invocation_index); + + if (max_mip_level <= 11u) { return; } + barrier(); + downsample_mip_11(local_invocation_index); +} + +void downsample_depth_second() { + uvec2 sub_xy = remap_for_wave_reduction(gl_LocalInvocationIndex % 64u); + uint x = sub_xy.x + 8u * ((gl_LocalInvocationIndex >> 6u) % 2u); + uint y = sub_xy.y + 8u * (gl_LocalInvocationIndex >> 7u); + + downsample_mips_6_and_7(x, y); + + downsample_mips_8_to_11(x, y, gl_LocalInvocationIndex); +} + +void main() { + downsample_depth_second(); +} From 295ebc7573ed2cd155285a01e3937048a81e4f2c Mon Sep 17 00:00:00 2001 From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> Date: Sat, 14 Sep 2024 15:00:27 -0700 Subject: [PATCH 10/26] Treefactors - Add MeshTree and InstanceTree - Deprecate ModelPartConverter for removal - Refactor ChestVisual to use InstanceTree - Combine double chest light in ChestVisual --- .../flywheel/lib/internal/FlwLibLink.java | 8 + .../flywheel/lib/model/RetexturedMesh.java | 38 +++ .../flywheel/lib/model/part/InstanceTree.java | 295 ++++++++++++++++++ .../flywheel/lib/model/part/MeshTree.java | 136 ++++++++ .../lib/model/part/ModelPartConverter.java | 1 + .../lib/vertex/VertexTransformations.java | 20 ++ .../flywheel/impl/FlwLibLinkImpl.java | 15 + .../impl/mixin/ModelPartAccessor.java | 21 ++ .../flywheel/vanilla/ChestVisual.java | 211 ++++++++----- .../main/resources/flywheel.impl.mixins.json | 1 + .../flywheel/impl/FlywheelFabric.java | 3 + .../flywheel/impl/FlywheelForge.java | 2 + gradle.properties | 2 +- 13 files changed, 669 insertions(+), 84 deletions(-) create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/RetexturedMesh.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/vertex/VertexTransformations.java create mode 100644 common/src/main/java/dev/engine_room/flywheel/impl/mixin/ModelPartAccessor.java diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java index 158c12924..fc0ae8dfe 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java @@ -1,11 +1,15 @@ package dev.engine_room.flywheel.lib.internal; +import java.util.Map; + import org.slf4j.Logger; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; import dev.engine_room.flywheel.api.internal.DependencyInjection; import dev.engine_room.flywheel.lib.transform.PoseTransformStack; +import net.minecraft.client.model.geom.ModelPart; public interface FlwLibLink { FlwLibLink INSTANCE = DependencyInjection.load(FlwLibLink.class, "dev.engine_room.flywheel.impl.FlwLibLinkImpl"); @@ -13,4 +17,8 @@ public interface FlwLibLink { Logger getLogger(); PoseTransformStack getPoseTransformStackOf(PoseStack stack); + + Map getModelPartChildren(ModelPart part); + + void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha); } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/RetexturedMesh.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/RetexturedMesh.java new file mode 100644 index 000000000..2aa476891 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/RetexturedMesh.java @@ -0,0 +1,38 @@ +package dev.engine_room.flywheel.lib.model; + +import org.joml.Vector4fc; + +import dev.engine_room.flywheel.api.model.IndexSequence; +import dev.engine_room.flywheel.api.model.Mesh; +import dev.engine_room.flywheel.api.vertex.MutableVertexList; +import dev.engine_room.flywheel.lib.vertex.VertexTransformations; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; + +public record RetexturedMesh(Mesh mesh, TextureAtlasSprite sprite) implements Mesh { + @Override + public int vertexCount() { + return mesh.vertexCount(); + } + + @Override + public void write(MutableVertexList vertexList) { + mesh.write(vertexList); + VertexTransformations.retexture(vertexList, sprite); + } + + @Override + public IndexSequence indexSequence() { + return mesh.indexSequence(); + } + + @Override + public int indexCount() { + return mesh.indexCount(); + } + + @Override + public Vector4fc boundingSphere() { + return mesh.boundingSphere(); + } +} + diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java new file mode 100644 index 000000000..ab1ded3bc --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java @@ -0,0 +1,295 @@ +package dev.engine_room.flywheel.lib.model.part; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.ObjIntConsumer; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; +import org.joml.Quaternionf; +import org.joml.Vector3fc; + +import com.mojang.blaze3d.vertex.PoseStack; + +import dev.engine_room.flywheel.api.instance.InstancerProvider; +import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.api.model.Mesh; +import dev.engine_room.flywheel.api.model.Model; +import dev.engine_room.flywheel.lib.instance.InstanceTypes; +import dev.engine_room.flywheel.lib.instance.TransformedInstance; +import dev.engine_room.flywheel.lib.model.ModelCache; +import dev.engine_room.flywheel.lib.model.SingleMeshModel; +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.model.geom.PartPose; + +public final class InstanceTree { + private static final ModelCache MODEL_CACHE = new ModelCache<>(entry -> new SingleMeshModel(entry.mesh(), entry.material())); + + @Nullable + private final TransformedInstance instance; + private final PartPose initialPose; + @Unmodifiable + private final Map children; + + private final Quaternionf rotation = new Quaternionf(); + + public float x; + public float y; + public float z; + public float xRot; + public float yRot; + public float zRot; + public float xScale; + public float yScale; + public float zScale; + public boolean visible = true; + public boolean skipDraw; + + private InstanceTree(@Nullable TransformedInstance instance, PartPose initialPose, @Unmodifiable Map children) { + this.instance = instance; + this.initialPose = initialPose; + this.children = children; + resetPose(); + } + + private static InstanceTree create(InstancerProvider provider, MeshTree meshTree, BiFunction meshFinalizerFunc, String path) { + Map children = new HashMap<>(); + String pathSlash = path + "/"; + + meshTree.children().forEach((name, meshTreeChild) -> { + children.put(name, InstanceTree.create(provider, meshTreeChild, meshFinalizerFunc, pathSlash + name)); + }); + + Mesh mesh = meshTree.mesh(); + TransformedInstance instance; + if (mesh != null) { + Model.ConfiguredMesh configuredMesh = meshFinalizerFunc.apply(path, mesh); + instance = provider.instancer(InstanceTypes.TRANSFORMED, MODEL_CACHE.get(configuredMesh)) + .createInstance(); + } else { + instance = null; + } + + return new InstanceTree(instance, meshTree.initialPose(), Collections.unmodifiableMap(children)); + } + + public static InstanceTree create(InstancerProvider provider, MeshTree meshTree, BiFunction meshFinalizerFunc) { + return create(provider, meshTree, meshFinalizerFunc, ""); + } + + public static InstanceTree create(InstancerProvider provider, ModelLayerLocation layer, BiFunction meshFinalizerFunc) { + return create(provider, MeshTree.of(layer), meshFinalizerFunc); + } + + public static InstanceTree create(InstancerProvider provider, MeshTree meshTree, Material material) { + return create(provider, meshTree, (path, mesh) -> new Model.ConfiguredMesh(material, mesh)); + } + + public static InstanceTree create(InstancerProvider provider, ModelLayerLocation layer, Material material) { + return create(provider, MeshTree.of(layer), material); + } + + @Nullable + public TransformedInstance instance() { + return instance; + } + + public PartPose initialPose() { + return initialPose; + } + + @Unmodifiable + public Map children() { + return children; + } + + public boolean hasChild(String name) { + return children.containsKey(name); + } + + @Nullable + public InstanceTree child(String name) { + return children.get(name); + } + + public InstanceTree childOrThrow(String name) { + InstanceTree child = child(name); + + if (child == null) { + throw new NoSuchElementException("Can't find part " + name); + } + + return child; + } + + public void traverse(Consumer consumer) { + if (instance != null) { + consumer.accept(instance); + } + for (InstanceTree child : children.values()) { + child.traverse(consumer); + } + } + + @ApiStatus.Experimental + public void traverse(int i, ObjIntConsumer consumer) { + if (instance != null) { + consumer.accept(instance, i); + } + for (InstanceTree child : children.values()) { + child.traverse(i, consumer); + } + } + + @ApiStatus.Experimental + public void traverse(int i, int j, ObjIntIntConsumer consumer) { + if (instance != null) { + consumer.accept(instance, i, j); + } + for (InstanceTree child : children.values()) { + child.traverse(i, j, consumer); + } + } + + public void translateAndRotate(PoseStack poseStack) { + poseStack.translate(x / 16.0F, y / 16.0F, z / 16.0F); + + if (xRot != 0.0F || yRot != 0.0F || zRot != 0.0F) { + poseStack.mulPose(rotation.rotationZYX(zRot, yRot, xRot)); + } + + if (xScale != 1.0F || yScale != 1.0F || zScale != 1.0F) { + poseStack.scale(xScale, yScale, zScale); + } + } + + public void updateInstances(PoseStack poseStack) { + if (visible) { + poseStack.pushPose(); + translateAndRotate(poseStack); + + if (instance != null && !skipDraw) { + instance.setTransform(poseStack.last()) + .setChanged(); + } + + for (InstanceTree child : children.values()) { + child.updateInstances(poseStack); + } + + poseStack.popPose(); + } + } + + public void pos(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void rotation(float xRot, float yRot, float zRot) { + this.xRot = xRot; + this.yRot = yRot; + this.zRot = zRot; + } + + public void scale(float xScale, float yScale, float zScale) { + this.xScale = xScale; + this.yScale = yScale; + this.zScale = zScale; + } + + public void offsetPos(float xOffset, float yOffset, float zOffset) { + x += xOffset; + y += yOffset; + z += zOffset; + } + + public void offsetRotation(float xOffset, float yOffset, float zOffset) { + xRot += xOffset; + yRot += yOffset; + zRot += zOffset; + } + + public void offsetScale(float xOffset, float yOffset, float zOffset) { + xScale += xOffset; + yScale += yOffset; + zScale += zOffset; + } + + public void offsetPos(Vector3fc offset) { + offsetPos(offset.x(), offset.y(), offset.z()); + } + + public void offsetRotation(Vector3fc offset) { + offsetRotation(offset.x(), offset.y(), offset.z()); + } + + public void offsetScale(Vector3fc offset) { + offsetScale(offset.x(), offset.y(), offset.z()); + } + + public PartPose storePose() { + return PartPose.offsetAndRotation(x, y, z, xRot, yRot, zRot); + } + + public void loadPose(PartPose pose) { + x = pose.x; + y = pose.y; + z = pose.z; + xRot = pose.xRot; + yRot = pose.yRot; + zRot = pose.zRot; + xScale = ModelPart.DEFAULT_SCALE; + yScale = ModelPart.DEFAULT_SCALE; + zScale = ModelPart.DEFAULT_SCALE; + } + + public void resetPose() { + loadPose(initialPose); + } + + public void copyTransform(InstanceTree tree) { + x = tree.x; + y = tree.y; + z = tree.z; + xRot = tree.xRot; + yRot = tree.yRot; + zRot = tree.zRot; + xScale = tree.xScale; + yScale = tree.yScale; + zScale = tree.zScale; + } + + public void copyTransform(ModelPart modelPart) { + x = modelPart.x; + y = modelPart.y; + z = modelPart.z; + xRot = modelPart.xRot; + yRot = modelPart.yRot; + zRot = modelPart.zRot; + xScale = modelPart.xScale; + yScale = modelPart.yScale; + zScale = modelPart.zScale; + } + + public void delete() { + if (instance != null) { + instance.delete(); + } + children.values() + .forEach(InstanceTree::delete); + } + + @ApiStatus.Experimental + @FunctionalInterface + public interface ObjIntIntConsumer { + void accept(T t, int i, int j); + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java new file mode 100644 index 000000000..d097266d3 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java @@ -0,0 +1,136 @@ +package dev.engine_room.flywheel.lib.model.part; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Unmodifiable; + +import com.mojang.blaze3d.vertex.PoseStack; + +import dev.engine_room.flywheel.api.model.Mesh; +import dev.engine_room.flywheel.lib.internal.FlwLibLink; +import dev.engine_room.flywheel.lib.memory.MemoryBlock; +import dev.engine_room.flywheel.lib.model.SimpleQuadMesh; +import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView; +import dev.engine_room.flywheel.lib.vertex.VertexView; +import net.minecraft.client.Minecraft; +import net.minecraft.client.model.geom.EntityModelSet; +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.client.model.geom.ModelPart; +import net.minecraft.client.model.geom.PartPose; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.texture.OverlayTexture; + +public final class MeshTree { + private static final ThreadLocal THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new); + private static final PoseStack.Pose IDENTITY_POSE = new PoseStack().last(); + private static final Map CACHE = new ConcurrentHashMap<>(); + + @Nullable + private final Mesh mesh; + private final PartPose initialPose; + @Unmodifiable + private final Map children; + + private MeshTree(@Nullable Mesh mesh, PartPose initialPose, @Unmodifiable Map children) { + this.mesh = mesh; + this.initialPose = initialPose; + this.children = children; + } + + public static MeshTree of(ModelLayerLocation layer) { + return CACHE.computeIfAbsent(layer, MeshTree::convert); + } + + private static MeshTree convert(ModelLayerLocation layer) { + EntityModelSet entityModels = Minecraft.getInstance() + .getEntityModels(); + ModelPart modelPart = entityModels.bakeLayer(layer); + + return convert(modelPart, THREAD_LOCAL_OBJECTS.get()); + } + + private static MeshTree convert(ModelPart modelPart, ThreadLocalObjects objects) { + var modelPartChildren = FlwLibLink.INSTANCE.getModelPartChildren(modelPart); + Map children = new HashMap<>(); + + modelPartChildren.forEach((name, modelPartChild) -> { + children.put(name, convert(modelPartChild, objects)); + }); + + return new MeshTree(compile(modelPart, objects), modelPart.getInitialPose(), Collections.unmodifiableMap(children)); + } + + @Nullable + private static Mesh compile(ModelPart modelPart, ThreadLocalObjects objects) { + if (modelPart.isEmpty()) { + return null; + } + + VertexWriter vertexWriter = objects.vertexWriter; + FlwLibLink.INSTANCE.compileModelPart(modelPart, IDENTITY_POSE, vertexWriter, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, 1.0F, 1.0F, 1.0F, 1.0F); + MemoryBlock data = vertexWriter.copyDataAndReset(); + + VertexView vertexView = new PosTexNormalVertexView(); + vertexView.load(data); + return new SimpleQuadMesh(vertexView, "source=MeshTree"); + } + + @Nullable + public Mesh mesh() { + return mesh; + } + + public PartPose initialPose() { + return initialPose; + } + + @Unmodifiable + public Map children() { + return children; + } + + public boolean hasChild(String name) { + return children.containsKey(name); + } + + @Nullable + public MeshTree child(String name) { + return children.get(name); + } + + public MeshTree childOrThrow(String name) { + MeshTree child = child(name); + + if (child == null) { + throw new NoSuchElementException("Can't find part " + name); + } + + return child; + } + + public void traverse(Consumer consumer) { + if (mesh != null) { + consumer.accept(mesh); + } + for (MeshTree child : children.values()) { + child.traverse(consumer); + } + } + + @ApiStatus.Internal + public static void onEndClientResourceReload() { + CACHE.clear(); + } + + private static class ThreadLocalObjects { + public final VertexWriter vertexWriter = new VertexWriter(); + } +} + diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java index 1e4a17d27..b5999cdac 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java @@ -18,6 +18,7 @@ import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.client.renderer.texture.TextureAtlasSprite; +@Deprecated(forRemoval = true) public final class ModelPartConverter { private static final ThreadLocal THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/vertex/VertexTransformations.java b/common/src/lib/java/dev/engine_room/flywheel/lib/vertex/VertexTransformations.java new file mode 100644 index 000000000..bf06084a2 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/vertex/VertexTransformations.java @@ -0,0 +1,20 @@ +package dev.engine_room.flywheel.lib.vertex; + +import dev.engine_room.flywheel.api.vertex.MutableVertexList; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; + +public final class VertexTransformations { + private VertexTransformations() { + } + + public static void retexture(MutableVertexList vertexList, int index, TextureAtlasSprite sprite) { + vertexList.u(index, sprite.getU(vertexList.u(index) * 16)); + vertexList.v(index, sprite.getV(vertexList.v(index) * 16)); + } + + public static void retexture(MutableVertexList vertexList, TextureAtlasSprite sprite) { + for (int i = 0; i < vertexList.vertexCount(); i++) { + retexture(vertexList, i, sprite); + } + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java index 7b0e0e682..003037264 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java @@ -1,12 +1,17 @@ package dev.engine_room.flywheel.impl; +import java.util.Map; + import org.slf4j.Logger; import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; import dev.engine_room.flywheel.impl.extension.PoseStackExtension; +import dev.engine_room.flywheel.impl.mixin.ModelPartAccessor; import dev.engine_room.flywheel.lib.internal.FlwLibLink; import dev.engine_room.flywheel.lib.transform.PoseTransformStack; +import net.minecraft.client.model.geom.ModelPart; public class FlwLibLinkImpl implements FlwLibLink { @Override @@ -18,4 +23,14 @@ public Logger getLogger() { public PoseTransformStack getPoseTransformStackOf(PoseStack stack) { return ((PoseStackExtension) stack).flywheel$transformStack(); } + + @Override + public Map getModelPartChildren(ModelPart part) { + return ((ModelPartAccessor) (Object) part).flywheel$children(); + } + + @Override + public void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha) { + ((ModelPartAccessor) (Object) part).flywheel$compile(pose, consumer, light, overlay, red, green, blue, alpha); + } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ModelPartAccessor.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ModelPartAccessor.java new file mode 100644 index 000000000..274e7a829 --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ModelPartAccessor.java @@ -0,0 +1,21 @@ +package dev.engine_room.flywheel.impl.mixin; + +import java.util.Map; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; + +import net.minecraft.client.model.geom.ModelPart; + +@Mixin(ModelPart.class) +public interface ModelPartAccessor { + @Accessor("children") + Map flywheel$children(); + + @Invoker("compile") + void flywheel$compile(PoseStack.Pose pose, VertexConsumer vertexConsumer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha); +} diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java index 086a11841..16c3849b2 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java @@ -5,26 +5,30 @@ import java.util.Map; import java.util.function.Consumer; -import org.joml.Quaternionf; +import org.jetbrains.annotations.Nullable; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Axis; import dev.engine_room.flywheel.api.instance.Instance; +import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.api.visualization.VisualizationContext; -import dev.engine_room.flywheel.lib.instance.InstanceTypes; -import dev.engine_room.flywheel.lib.instance.OrientedInstance; -import dev.engine_room.flywheel.lib.instance.TransformedInstance; import dev.engine_room.flywheel.lib.material.CutoutShaders; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.ModelCache; -import dev.engine_room.flywheel.lib.model.SingleMeshModel; -import dev.engine_room.flywheel.lib.model.part.ModelPartConverter; -import dev.engine_room.flywheel.lib.util.Pair; +import dev.engine_room.flywheel.lib.model.RetexturedMesh; +import dev.engine_room.flywheel.lib.model.part.InstanceTree; +import dev.engine_room.flywheel.lib.transform.TransformStack; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import it.unimi.dsi.fastutil.floats.Float2FloatFunction; +import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.ModelLayers; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.Sheets; -import net.minecraft.client.resources.model.Material; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.SectionPos; import net.minecraft.world.level.block.AbstractChestBlock; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.ChestBlock; @@ -35,7 +39,7 @@ import net.minecraft.world.level.block.state.properties.ChestType; public class ChestVisual extends AbstractBlockEntityVisual implements SimpleDynamicVisual { - public static final dev.engine_room.flywheel.api.material.Material MATERIAL = SimpleMaterial.builder() + private static final dev.engine_room.flywheel.api.material.Material MATERIAL = SimpleMaterial.builder() .cutout(CutoutShaders.ONE_TENTH) .texture(Sheets.CHEST_SHEET) .mipmap(false) @@ -48,68 +52,55 @@ public class ChestVisual extends Abstrac LAYER_LOCATIONS.put(ChestType.RIGHT, ModelLayers.DOUBLE_CHEST_RIGHT); } - private static final ModelCache> BOTTOM_MODELS = new ModelCache<>(key -> { - return new SingleMeshModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "bottom"), MATERIAL); - }); - private static final ModelCache> LID_MODELS = new ModelCache<>(key -> { - return new SingleMeshModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "lid"), MATERIAL); - }); - private static final ModelCache> LOCK_MODELS = new ModelCache<>(key -> { - return new SingleMeshModel(ModelPartConverter.convert(LAYER_LOCATIONS.get(key.first()), key.second().sprite(), "lock"), MATERIAL); - }); - - private final OrientedInstance bottom; - private final TransformedInstance lid; - private final TransformedInstance lock; - - private final ChestType chestType; + @Nullable + private final InstanceTree instances; + @Nullable + private final InstanceTree lid; + @Nullable + private final InstanceTree lock; + + private final PoseStack poseStack = new PoseStack(); + private final BrightnessCombiner brightnessCombiner = new BrightnessCombiner(); + @Nullable + private final DoubleBlockCombiner.NeighborCombineResult neighborCombineResult; + @Nullable private final Float2FloatFunction lidProgress; - private final Quaternionf baseRotation = new Quaternionf(); - private float lastProgress = Float.NaN; public ChestVisual(VisualizationContext ctx, T blockEntity, float partialTick) { super(ctx, blockEntity, partialTick); - chestType = blockState.hasProperty(ChestBlock.TYPE) ? blockState.getValue(ChestBlock.TYPE) : ChestType.SINGLE; - Material texture = Sheets.chooseMaterial(blockEntity, chestType, isChristmas()); - - bottom = createBottomInstance(texture).position(getVisualPosition()); - lid = createLidInstance(texture); - lock = createLockInstance(texture); - Block block = blockState.getBlock(); if (block instanceof AbstractChestBlock chestBlock) { - float horizontalAngle = blockState.getValue(ChestBlock.FACING).toYRot(); - baseRotation.setAngleAxis(Math.toRadians(-horizontalAngle), 0, 1, 0); + ChestType chestType = blockState.hasProperty(ChestBlock.TYPE) ? blockState.getValue(ChestBlock.TYPE) : ChestType.SINGLE; + TextureAtlasSprite sprite = Sheets.chooseMaterial(blockEntity, chestType, isChristmas()).sprite(); - DoubleBlockCombiner.NeighborCombineResult wrapper = chestBlock.combine(blockState, level, pos, true); - lidProgress = wrapper.apply(ChestBlock.opennessCombiner(blockEntity)); - } else { - baseRotation.identity(); - lidProgress = $ -> 0f; - } - - bottom.rotation(baseRotation); - bottom.setChanged(); + instances = InstanceTree.create(instancerProvider, LAYER_LOCATIONS.get(chestType), (path, mesh) -> { + return new Model.ConfiguredMesh(MATERIAL, new RetexturedMesh(mesh, sprite)); + }); + lid = instances.childOrThrow("lid"); + lock = instances.childOrThrow("lock"); - applyLidTransform(lidProgress.get(partialTick)); - } - - private OrientedInstance createBottomInstance(Material texture) { - return instancerProvider.instancer(InstanceTypes.ORIENTED, BOTTOM_MODELS.get(Pair.of(chestType, texture))) - .createInstance(); - } + poseStack.pushPose(); + TransformStack.of(poseStack).translate(getVisualPosition()); + float horizontalAngle = blockState.getValue(ChestBlock.FACING).toYRot(); + poseStack.translate(0.5F, 0.5F, 0.5F); + poseStack.mulPose(Axis.YP.rotationDegrees(-horizontalAngle)); + poseStack.translate(-0.5F, -0.5F, -0.5F); - private TransformedInstance createLidInstance(Material texture) { - return instancerProvider.instancer(InstanceTypes.TRANSFORMED, LID_MODELS.get(Pair.of(chestType, texture))) - .createInstance(); - } + neighborCombineResult = chestBlock.combine(blockState, level, pos, true); + lidProgress = neighborCombineResult.apply(ChestBlock.opennessCombiner(blockEntity)); - private TransformedInstance createLockInstance(Material texture) { - return instancerProvider.instancer(InstanceTypes.TRANSFORMED, LOCK_MODELS.get(Pair.of(chestType, texture))) - .createInstance(); + lastProgress = lidProgress.get(partialTick); + applyLidTransform(lastProgress); + } else { + instances = null; + lid = null; + lock = null; + neighborCombineResult = null; + lidProgress = null; + } } private static boolean isChristmas() { @@ -117,8 +108,23 @@ private static boolean isChristmas() { return calendar.get(Calendar.MONTH) + 1 == 12 && calendar.get(Calendar.DATE) >= 24 && calendar.get(Calendar.DATE) <= 26; } + @Override + public void setSectionCollector(SectionCollector sectionCollector) { + this.lightSections = sectionCollector; + + if (neighborCombineResult != null) { + lightSections.sections(neighborCombineResult.apply(new SectionPosCombiner())); + } else { + lightSections.sections(LongSet.of(SectionPos.asLong(pos))); + } + } + @Override public void beginFrame(Context context) { + if (instances == null) { + return; + } + if (doDistanceLimitThisFrame(context) || !isVisible(context.frustum())) { return; } @@ -136,41 +142,80 @@ private void applyLidTransform(float progress) { progress = 1.0F - progress; progress = 1.0F - progress * progress * progress; - float angleX = -(progress * ((float) Math.PI / 2F)); - - lid.setIdentityTransform() - .translate(getVisualPosition()) - .rotateCentered(baseRotation) - .translate(0, 9f / 16f, 1f / 16f) - .rotateX(angleX) - .translate(0, -9f / 16f, -1f / 16f) - .setChanged(); - - lock.setIdentityTransform() - .translate(getVisualPosition()) - .rotateCentered(baseRotation) - .translate(0, 8f / 16f, 0) - .rotateX(angleX) - .translate(0, -8f / 16f, 0) - .setChanged(); + lid.xRot = -(progress * ((float) Math.PI / 2F)); + lock.xRot = lid.xRot; + instances.updateInstances(poseStack); } @Override public void updateLight(float partialTick) { - relight(bottom, lid, lock); + if (instances != null) { + int packedLight = neighborCombineResult.apply(brightnessCombiner); + instances.traverse(instance -> { + instance.light(packedLight) + .setChanged(); + }); + } } @Override public void collectCrumblingInstances(Consumer consumer) { - consumer.accept(bottom); - consumer.accept(lid); - consumer.accept(lock); + if (instances != null) { + instances.traverse(consumer); + } } @Override protected void _delete() { - bottom.delete(); - lid.delete(); - lock.delete(); + if (instances != null) { + instances.delete(); + } + } + + private class SectionPosCombiner implements DoubleBlockCombiner.Combiner { + @Override + public LongSet acceptDouble(BlockEntity first, BlockEntity second) { + long firstSection = SectionPos.asLong(first.getBlockPos()); + long secondSection = SectionPos.asLong(second.getBlockPos()); + + if (firstSection == secondSection) { + return LongSet.of(firstSection); + } else { + return LongSet.of(firstSection, secondSection); + } + } + + @Override + public LongSet acceptSingle(BlockEntity single) { + return LongSet.of(SectionPos.asLong(single.getBlockPos())); + } + + @Override + public LongSet acceptNone() { + return LongSet.of(SectionPos.asLong(pos)); + } + } + + private class BrightnessCombiner implements DoubleBlockCombiner.Combiner { + @Override + public Integer acceptDouble(BlockEntity first, BlockEntity second) { + int firstLight = LevelRenderer.getLightColor(first.getLevel(), first.getBlockPos()); + int secondLight = LevelRenderer.getLightColor(second.getLevel(), second.getBlockPos()); + int firstBlockLight = LightTexture.block(firstLight); + int secondBlockLight = LightTexture.block(secondLight); + int firstSkyLight = LightTexture.sky(firstLight); + int secondSkyLight = LightTexture.sky(secondLight); + return LightTexture.pack(Math.max(firstBlockLight, secondBlockLight), Math.max(firstSkyLight, secondSkyLight)); + } + + @Override + public Integer acceptSingle(BlockEntity single) { + return LevelRenderer.getLightColor(single.getLevel(), single.getBlockPos()); + } + + @Override + public Integer acceptNone() { + return LevelRenderer.getLightColor(level, pos); + } } } diff --git a/common/src/main/resources/flywheel.impl.mixins.json b/common/src/main/resources/flywheel.impl.mixins.json index c8d878e85..0f914a73c 100644 --- a/common/src/main/resources/flywheel.impl.mixins.json +++ b/common/src/main/resources/flywheel.impl.mixins.json @@ -12,6 +12,7 @@ "LevelMixin", "LevelRendererMixin", "MinecraftMixin", + "ModelPartAccessor", "PoseStackMixin", "fix.FixFabulousDepthMixin", "fix.FixNormalScalingMixin", diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java index 5e8ab76ac..d05ebb115 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java @@ -12,6 +12,7 @@ import dev.engine_room.flywheel.lib.model.ModelCache; import dev.engine_room.flywheel.lib.model.ModelHolder; import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler; +import dev.engine_room.flywheel.lib.model.part.MeshTree; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; @@ -71,6 +72,8 @@ private static void setupLib() { ModelCache.onEndClientResourceReload()); EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> ModelHolder.onEndClientResourceReload()); + EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> + MeshTree.onEndClientResourceReload()); ModelLoadingPlugin.register(ctx -> { ctx.addModels(PartialModelEventHandler.onRegisterAdditional()); diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java b/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java index f8ba8d83b..8fb860d6a 100644 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java @@ -13,6 +13,7 @@ import dev.engine_room.flywheel.lib.model.ModelCache; import dev.engine_room.flywheel.lib.model.ModelHolder; import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler; +import dev.engine_room.flywheel.lib.model.part.MeshTree; import dev.engine_room.flywheel.lib.util.LevelAttached; import net.minecraft.client.Minecraft; import net.minecraft.commands.synchronization.ArgumentTypeInfos; @@ -111,6 +112,7 @@ private static void registerLibEventListeners(IEventBus forgeEventBus, IEventBus modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelCache.onEndClientResourceReload()); modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelHolder.onEndClientResourceReload()); + modEventBus.addListener((EndClientResourceReloadEvent e) -> MeshTree.onEndClientResourceReload()); modEventBus.addListener(PartialModelEventHandler::onRegisterAdditional); modEventBus.addListener(PartialModelEventHandler::onBakingCompleted); diff --git a/gradle.properties b/gradle.properties index 86a12b351..d8c4ff106 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,7 +19,7 @@ forge_version_range = [47.0.0,) # General build dependency versions java_version = 17 -arch_loom_version=1.7.412 +arch_loom_version = 1.7.412 cursegradle_version = 1.4.0 parchment_version = 2023.09.03 From ed35c5a429f35342fccee2f09bf5fdf9bcaca194 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 14 Sep 2024 16:50:02 -0700 Subject: [PATCH 11/26] Reduce and reuse - Create subclass to recycle PoseStack.Pose objects - Add mixin/liblink to access a PoseStack's inner deque --- .../flywheel/lib/internal/FlwLibLink.java | 3 ++ .../flywheel/lib/util/RecyclingPoseStack.java | 42 +++++++++++++++++++ .../flywheel/impl/FlwLibLinkImpl.java | 7 ++++ .../impl/mixin/PoseStackAccessor.java | 14 +++++++ .../flywheel/vanilla/ChestVisual.java | 3 +- .../flywheel/vanilla/MinecartVisual.java | 3 +- .../flywheel/vanilla/ShulkerBoxVisual.java | 3 +- .../main/resources/flywheel.impl.mixins.json | 1 + 8 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/util/RecyclingPoseStack.java create mode 100644 common/src/main/java/dev/engine_room/flywheel/impl/mixin/PoseStackAccessor.java diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java index fc0ae8dfe..9a112c07d 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/internal/FlwLibLink.java @@ -1,5 +1,6 @@ package dev.engine_room.flywheel.lib.internal; +import java.util.Deque; import java.util.Map; import org.slf4j.Logger; @@ -21,4 +22,6 @@ public interface FlwLibLink { Map getModelPartChildren(ModelPart part); void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha); + + Deque getPoseStack(PoseStack stack); } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/RecyclingPoseStack.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/RecyclingPoseStack.java new file mode 100644 index 000000000..0c0fefebe --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/util/RecyclingPoseStack.java @@ -0,0 +1,42 @@ +package dev.engine_room.flywheel.lib.util; + +import java.util.ArrayDeque; +import java.util.Deque; + +import com.mojang.blaze3d.vertex.PoseStack; + +import dev.engine_room.flywheel.lib.internal.FlwLibLink; + +/** + * A {@link PoseStack} that recycles {@link PoseStack.Pose} objects. + * + *

Vanilla's {@link PoseStack} can get quite expensive to use when each game object needs to + * maintain their own stack. This class helps alleviate memory pressure by making Pose objects + * long-lived. Note that this means that you CANNOT safely store a Pose object outside + * the RecyclingPoseStack that created it. + */ +public class RecyclingPoseStack extends PoseStack { + private final Deque recycleBin = new ArrayDeque<>(); + + @Override + public void pushPose() { + if (recycleBin.isEmpty()) { + super.pushPose(); + } else { + var last = last(); + var recycle = recycleBin.pop(); + recycle.pose() + .set(last.pose()); + recycle.normal() + .set(last.normal()); + FlwLibLink.INSTANCE.getPoseStack(this) + .addLast(recycle); + } + } + + @Override + public void popPose() { + recycleBin.push(FlwLibLink.INSTANCE.getPoseStack(this) + .removeLast()); + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java index 003037264..47fffeeb6 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/FlwLibLinkImpl.java @@ -1,5 +1,6 @@ package dev.engine_room.flywheel.impl; +import java.util.Deque; import java.util.Map; import org.slf4j.Logger; @@ -9,6 +10,7 @@ import dev.engine_room.flywheel.impl.extension.PoseStackExtension; import dev.engine_room.flywheel.impl.mixin.ModelPartAccessor; +import dev.engine_room.flywheel.impl.mixin.PoseStackAccessor; import dev.engine_room.flywheel.lib.internal.FlwLibLink; import dev.engine_room.flywheel.lib.transform.PoseTransformStack; import net.minecraft.client.model.geom.ModelPart; @@ -33,4 +35,9 @@ public Map getModelPartChildren(ModelPart part) { public void compileModelPart(ModelPart part, PoseStack.Pose pose, VertexConsumer consumer, int light, int overlay, float red, float green, float blue, float alpha) { ((ModelPartAccessor) (Object) part).flywheel$compile(pose, consumer, light, overlay, red, green, blue, alpha); } + + @Override + public Deque getPoseStack(PoseStack stack) { + return ((PoseStackAccessor) stack).flywheel$getPoseStack(); + } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/PoseStackAccessor.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/PoseStackAccessor.java new file mode 100644 index 000000000..22478d2a3 --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/PoseStackAccessor.java @@ -0,0 +1,14 @@ +package dev.engine_room.flywheel.impl.mixin; + +import java.util.Deque; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import com.mojang.blaze3d.vertex.PoseStack; + +@Mixin(PoseStack.class) +public interface PoseStackAccessor { + @Accessor("poseStack") + Deque flywheel$getPoseStack(); +} diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java index 16c3849b2..9025f5d0a 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java @@ -18,6 +18,7 @@ import dev.engine_room.flywheel.lib.model.RetexturedMesh; import dev.engine_room.flywheel.lib.model.part.InstanceTree; import dev.engine_room.flywheel.lib.transform.TransformStack; +import dev.engine_room.flywheel.lib.util.RecyclingPoseStack; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import it.unimi.dsi.fastutil.floats.Float2FloatFunction; @@ -59,7 +60,7 @@ public class ChestVisual extends Abstrac @Nullable private final InstanceTree lock; - private final PoseStack poseStack = new PoseStack(); + private final PoseStack poseStack = new RecyclingPoseStack(); private final BrightnessCombiner brightnessCombiner = new BrightnessCombiner(); @Nullable private final DoubleBlockCombiner.NeighborCombineResult neighborCombineResult; diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java index bc58879d8..0ba3efc60 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java @@ -16,6 +16,7 @@ import dev.engine_room.flywheel.lib.model.Models; import dev.engine_room.flywheel.lib.model.SingleMeshModel; import dev.engine_room.flywheel.lib.model.part.ModelPartConverter; +import dev.engine_room.flywheel.lib.util.RecyclingPoseStack; import dev.engine_room.flywheel.lib.visual.ComponentEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import dev.engine_room.flywheel.lib.visual.SimpleTickableVisual; @@ -52,7 +53,7 @@ public class MinecartVisual extends ComponentEntityV private final ModelHolder bodyModel; - private final PoseStack stack = new PoseStack(); + private final PoseStack stack = new RecyclingPoseStack(); private BlockState blockState; private boolean active; diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java index 9e9e17925..084c5fb03 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java @@ -17,6 +17,7 @@ import dev.engine_room.flywheel.lib.model.SingleMeshModel; import dev.engine_room.flywheel.lib.model.part.ModelPartConverter; import dev.engine_room.flywheel.lib.transform.TransformStack; +import dev.engine_room.flywheel.lib.util.RecyclingPoseStack; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import net.minecraft.client.model.geom.ModelLayers; @@ -45,7 +46,7 @@ public class ShulkerBoxVisual extends AbstractBlockEntityVisual Date: Sat, 14 Sep 2024 17:46:54 -0700 Subject: [PATCH 12/26] Walk this way - Update instances using a walker object created at the top level --- .../flywheel/lib/model/part/InstanceTree.java | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java index ab1ded3bc..17c91358c 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.Map; import java.util.NoSuchElementException; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.ObjIntConsumer; @@ -170,6 +171,16 @@ public void translateAndRotate(PoseStack poseStack) { } public void updateInstances(PoseStack poseStack) { + // Need to use an anonymous class so it can reference this. + updateInstancesInner(poseStack, new Walker() { + @Override + public void accept(InstanceTree child) { + child.updateInstancesInner(poseStack, this); + } + }); + } + + private void updateInstancesInner(PoseStack poseStack, Walker walker) { if (visible) { poseStack.pushPose(); translateAndRotate(poseStack); @@ -179,9 +190,9 @@ public void updateInstances(PoseStack poseStack) { .setChanged(); } - for (InstanceTree child : children.values()) { - child.updateInstances(poseStack); - } + // Use the bare HashMap.forEach because .values() always allocates a new collection. + // We also don't want to store an array of children because that would statically use a lot more memory. + children.forEach(walker); poseStack.popPose(); } @@ -292,4 +303,14 @@ public void delete() { public interface ObjIntIntConsumer { void accept(T t, int i, int j); } + + // Helper interface for writing walking classes. + private interface Walker extends BiConsumer { + void accept(InstanceTree child); + + @Override + default void accept(String name, InstanceTree child) { + accept(child); + } + } } From 6f2b8fd3fb04b830338b11c07e7209a43d1d76c1 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 14 Sep 2024 19:38:55 -0700 Subject: [PATCH 13/26] Shiver me timbers - Use arrrays in the trees - MeshTree tracks parallel arrays of children and keys - InstanceTree tracks which mesh tree it came from for lookup purposes - Remove walker object - Make RecyclingPoseStack use add/removeLast instead of push/pop --- .../flywheel/lib/model/part/InstanceTree.java | 73 +++++++------------ .../flywheel/lib/model/part/MeshTree.java | 54 +++++++++----- .../flywheel/lib/util/RecyclingPoseStack.java | 4 +- 3 files changed, 67 insertions(+), 64 deletions(-) diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java index 17c91358c..cf8f22037 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java @@ -1,10 +1,6 @@ package dev.engine_room.flywheel.lib.model.part; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import java.util.NoSuchElementException; -import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.ObjIntConsumer; @@ -32,11 +28,12 @@ public final class InstanceTree { private static final ModelCache MODEL_CACHE = new ModelCache<>(entry -> new SingleMeshModel(entry.mesh(), entry.material())); + private final MeshTree source; @Nullable private final TransformedInstance instance; private final PartPose initialPose; @Unmodifiable - private final Map children; + private final InstanceTree[] children; private final Quaternionf rotation = new Quaternionf(); @@ -52,7 +49,8 @@ public final class InstanceTree { public boolean visible = true; public boolean skipDraw; - private InstanceTree(@Nullable TransformedInstance instance, PartPose initialPose, @Unmodifiable Map children) { + private InstanceTree(MeshTree source, @Nullable TransformedInstance instance, PartPose initialPose, InstanceTree[] children) { + this.source = source; this.instance = instance; this.initialPose = initialPose; this.children = children; @@ -60,12 +58,15 @@ private InstanceTree(@Nullable TransformedInstance instance, PartPose initialPos } private static InstanceTree create(InstancerProvider provider, MeshTree meshTree, BiFunction meshFinalizerFunc, String path) { - Map children = new HashMap<>(); + InstanceTree[] children = new InstanceTree[meshTree.childCount()]; String pathSlash = path + "/"; - meshTree.children().forEach((name, meshTreeChild) -> { - children.put(name, InstanceTree.create(provider, meshTreeChild, meshFinalizerFunc, pathSlash + name)); - }); + for (int i = 0; i < meshTree.childCount(); i++) { + var meshTreeChild = meshTree.child(i); + String name = meshTree.childName(i); + children[i] = InstanceTree.create(provider, meshTreeChild, meshFinalizerFunc, pathSlash + name); + + } Mesh mesh = meshTree.mesh(); TransformedInstance instance; @@ -77,7 +78,7 @@ private static InstanceTree create(InstancerProvider provider, MeshTree meshTree instance = null; } - return new InstanceTree(instance, meshTree.initialPose(), Collections.unmodifiableMap(children)); + return new InstanceTree(meshTree, instance, meshTree.initialPose(), children); } public static InstanceTree create(InstancerProvider provider, MeshTree meshTree, BiFunction meshFinalizerFunc) { @@ -105,18 +106,17 @@ public PartPose initialPose() { return initialPose; } - @Unmodifiable - public Map children() { - return children; - } - - public boolean hasChild(String name) { - return children.containsKey(name); + @Nullable + public InstanceTree child(int index) { + if (index < 0 || index >= children.length) { + return null; + } + return children[index]; } @Nullable public InstanceTree child(String name) { - return children.get(name); + return child(source.childIndex(name)); } public InstanceTree childOrThrow(String name) { @@ -133,7 +133,7 @@ public void traverse(Consumer consumer) { if (instance != null) { consumer.accept(instance); } - for (InstanceTree child : children.values()) { + for (InstanceTree child : children) { child.traverse(consumer); } } @@ -143,7 +143,7 @@ public void traverse(int i, ObjIntConsumer consumer if (instance != null) { consumer.accept(instance, i); } - for (InstanceTree child : children.values()) { + for (InstanceTree child : children) { child.traverse(i, consumer); } } @@ -153,7 +153,7 @@ public void traverse(int i, int j, ObjIntIntConsumer { void accept(T t, int i, int j); } - - // Helper interface for writing walking classes. - private interface Walker extends BiConsumer { - void accept(InstanceTree child); - - @Override - default void accept(String name, InstanceTree child) { - accept(child); - } - } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java index d097266d3..60c0e8eba 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java @@ -1,7 +1,6 @@ package dev.engine_room.flywheel.lib.model.part; -import java.util.Collections; -import java.util.HashMap; +import java.util.Arrays; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.ConcurrentHashMap; @@ -9,7 +8,6 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Unmodifiable; import com.mojang.blaze3d.vertex.PoseStack; @@ -35,12 +33,13 @@ public final class MeshTree { @Nullable private final Mesh mesh; private final PartPose initialPose; - @Unmodifiable - private final Map children; + private final MeshTree[] children; + private final String[] childKeys; - private MeshTree(@Nullable Mesh mesh, PartPose initialPose, @Unmodifiable Map children) { + private MeshTree(@Nullable Mesh mesh, PartPose initialPose, MeshTree[] children, String[] childKeys) { this.mesh = mesh; this.initialPose = initialPose; + this.childKeys = childKeys; this.children = children; } @@ -58,13 +57,17 @@ private static MeshTree convert(ModelLayerLocation layer) { private static MeshTree convert(ModelPart modelPart, ThreadLocalObjects objects) { var modelPartChildren = FlwLibLink.INSTANCE.getModelPartChildren(modelPart); - Map children = new HashMap<>(); - modelPartChildren.forEach((name, modelPartChild) -> { - children.put(name, convert(modelPartChild, objects)); - }); + // Freeze the ordering here. Maybe we want to sort this? + String[] childKeys = modelPartChildren.keySet() + .toArray(new String[0]); - return new MeshTree(compile(modelPart, objects), modelPart.getInitialPose(), Collections.unmodifiableMap(children)); + MeshTree[] children = new MeshTree[modelPartChildren.size()]; + for (int i = 0; i < childKeys.length; i++) { + children[i] = convert(modelPartChildren.get(childKeys[i]), objects); + } + + return new MeshTree(compile(modelPart, objects), modelPart.getInitialPose(), children, childKeys); } @Nullable @@ -91,18 +94,35 @@ public PartPose initialPose() { return initialPose; } - @Unmodifiable - public Map children() { - return children; + public int childCount() { + return children.length; + } + + public MeshTree child(int index) { + return children[index]; + } + + public String childName(int index) { + return childKeys[index]; + } + + public int childIndex(String name) { + return Arrays.binarySearch(childKeys, name); } public boolean hasChild(String name) { - return children.containsKey(name); + return childIndex(name) >= 0; } @Nullable public MeshTree child(String name) { - return children.get(name); + int index = childIndex(name); + + if (index < 0) { + return null; + } + + return child(index); } public MeshTree childOrThrow(String name) { @@ -119,7 +139,7 @@ public void traverse(Consumer consumer) { if (mesh != null) { consumer.accept(mesh); } - for (MeshTree child : children.values()) { + for (MeshTree child : children) { child.traverse(consumer); } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/RecyclingPoseStack.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/RecyclingPoseStack.java index 0c0fefebe..fb73e087a 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/util/RecyclingPoseStack.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/util/RecyclingPoseStack.java @@ -24,7 +24,7 @@ public void pushPose() { super.pushPose(); } else { var last = last(); - var recycle = recycleBin.pop(); + var recycle = recycleBin.removeLast(); recycle.pose() .set(last.pose()); recycle.normal() @@ -36,7 +36,7 @@ public void pushPose() { @Override public void popPose() { - recycleBin.push(FlwLibLink.INSTANCE.getPoseStack(this) + recycleBin.addLast(FlwLibLink.INSTANCE.getPoseStack(this) .removeLast()); } } From 41e0aa6bba98e02b0ea2a54d3c22906445f2795b Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 14 Sep 2024 21:46:49 -0700 Subject: [PATCH 14/26] Strike a pose - Rename TransformedInstance -> PosedInstance - Add TransformedInstance, which does not have a normal matrix - Use mat3(transpose(inverse(i.pose))) in the vertex shader, but for most cases that will be overkill --- .../flywheel/lib/instance/InstanceTypes.java | 24 +++- .../flywheel/lib/instance/PosedInstance.java | 104 ++++++++++++++++++ .../lib/instance/TransformedInstance.java | 41 +------ .../flywheel/instance/cull/posed.glsl | 5 + .../flywheel/flywheel/instance/posed.vert | 8 ++ .../flywheel/instance/transformed.vert | 2 +- 6 files changed, 143 insertions(+), 41 deletions(-) create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java create mode 100644 common/src/lib/resources/assets/flywheel/flywheel/instance/cull/posed.glsl create mode 100644 common/src/lib/resources/assets/flywheel/flywheel/instance/posed.vert diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java index 8cb66a7f0..433eb5445 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java @@ -17,7 +17,6 @@ public final class InstanceTypes { .vector("overlay", IntegerRepr.SHORT, 2) .vector("light", FloatRepr.UNSIGNED_SHORT, 2) .matrix("pose", FloatRepr.FLOAT, 4) - .matrix("normal", FloatRepr.FLOAT, 3) .build()) .writer((ptr, instance) -> { MemoryUtil.memPutByte(ptr, instance.red); @@ -27,12 +26,33 @@ public final class InstanceTypes { ExtraMemoryOps.put2x16(ptr + 4, instance.overlay); ExtraMemoryOps.put2x16(ptr + 8, instance.light); ExtraMemoryOps.putMatrix4f(ptr + 12, instance.model); - ExtraMemoryOps.putMatrix3f(ptr + 76, instance.normal); }) .vertexShader(Flywheel.rl("instance/transformed.vert")) .cullShader(Flywheel.rl("instance/cull/transformed.glsl")) .register(); + public static final InstanceType POSED = SimpleInstanceType.builder(PosedInstance::new) + .layout(LayoutBuilder.create() + .vector("color", FloatRepr.NORMALIZED_UNSIGNED_BYTE, 4) + .vector("overlay", IntegerRepr.SHORT, 2) + .vector("light", FloatRepr.UNSIGNED_SHORT, 2) + .matrix("pose", FloatRepr.FLOAT, 4) + .matrix("normal", FloatRepr.FLOAT, 3) + .build()) + .writer((ptr, instance) -> { + MemoryUtil.memPutByte(ptr, instance.red); + MemoryUtil.memPutByte(ptr + 1, instance.green); + MemoryUtil.memPutByte(ptr + 2, instance.blue); + MemoryUtil.memPutByte(ptr + 3, instance.alpha); + ExtraMemoryOps.put2x16(ptr + 4, instance.overlay); + ExtraMemoryOps.put2x16(ptr + 8, instance.light); + ExtraMemoryOps.putMatrix4f(ptr + 12, instance.model); + ExtraMemoryOps.putMatrix3f(ptr + 76, instance.normal); + }) + .vertexShader(Flywheel.rl("instance/posed.vert")) + .cullShader(Flywheel.rl("instance/cull/posed.glsl")) + .register(); + public static final InstanceType ORIENTED = SimpleInstanceType.builder(OrientedInstance::new) .layout(LayoutBuilder.create() .vector("color", FloatRepr.NORMALIZED_UNSIGNED_BYTE, 4) diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java new file mode 100644 index 000000000..d21b5e17f --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java @@ -0,0 +1,104 @@ +package dev.engine_room.flywheel.lib.instance; + +import org.joml.Matrix3f; +import org.joml.Matrix3fc; +import org.joml.Matrix4f; +import org.joml.Matrix4fc; +import org.joml.Quaternionfc; + +import com.mojang.blaze3d.vertex.PoseStack; + +import dev.engine_room.flywheel.api.instance.InstanceHandle; +import dev.engine_room.flywheel.api.instance.InstanceType; +import dev.engine_room.flywheel.lib.transform.Transform; +import net.minecraft.util.Mth; + +public class PosedInstance extends ColoredLitInstance implements Transform { + public final Matrix4f model = new Matrix4f(); + public final Matrix3f normal = new Matrix3f(); + + public PosedInstance(InstanceType type, InstanceHandle handle) { + super(type, handle); + } + + @Override + public PosedInstance mulPose(Matrix4fc pose) { + this.model.mul(pose); + return this; + } + + @Override + public PosedInstance mulNormal(Matrix3fc normal) { + this.normal.mul(normal); + return this; + } + + @Override + public PosedInstance rotateAround(Quaternionfc quaternion, float x, float y, float z) { + model.rotateAround(quaternion, x, y, z); + normal.rotate(quaternion); + return this; + } + + @Override + public PosedInstance translate(float x, float y, float z) { + model.translate(x, y, z); + return this; + } + + @Override + public PosedInstance rotate(Quaternionfc quaternion) { + model.rotate(quaternion); + normal.rotate(quaternion); + return this; + } + + @Override + public PosedInstance scale(float x, float y, float z) { + model.scale(x, y, z); + + if (x == y && y == z) { + if (x < 0.0f) { + normal.scale(-1.0f); + } + + return this; + } + + float invX = 1.0f / x; + float invY = 1.0f / y; + float invZ = 1.0f / z; + float f = Mth.fastInvCubeRoot(Math.abs(invX * invY * invZ)); + normal.scale(f * invX, f * invY, f * invZ); + return this; + } + + public PosedInstance setTransform(PoseStack.Pose pose) { + model.set(pose.pose()); + normal.set(pose.normal()); + return this; + } + + public PosedInstance setTransform(PoseStack stack) { + return setTransform(stack.last()); + } + + public PosedInstance setIdentityTransform() { + model.identity(); + normal.identity(); + return this; + } + + /** + * Sets the transform matrices to be all zeros. + * + *

+ * This will allow the GPU to quickly discard all geometry for this instance, effectively "turning it off". + *

+ */ + public PosedInstance setZeroTransform() { + model.zero(); + normal.zero(); + return this; + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java index 13938d201..73e57e4d0 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java @@ -1,42 +1,25 @@ package dev.engine_room.flywheel.lib.instance; -import org.joml.Matrix3f; -import org.joml.Matrix3fc; import org.joml.Matrix4f; -import org.joml.Matrix4fc; import org.joml.Quaternionfc; import com.mojang.blaze3d.vertex.PoseStack; import dev.engine_room.flywheel.api.instance.InstanceHandle; import dev.engine_room.flywheel.api.instance.InstanceType; -import dev.engine_room.flywheel.lib.transform.Transform; -import net.minecraft.util.Mth; +import dev.engine_room.flywheel.lib.transform.Affine; -public class TransformedInstance extends ColoredLitInstance implements Transform { + +public class TransformedInstance extends ColoredLitInstance implements Affine { public final Matrix4f model = new Matrix4f(); - public final Matrix3f normal = new Matrix3f(); public TransformedInstance(InstanceType type, InstanceHandle handle) { super(type, handle); } - @Override - public TransformedInstance mulPose(Matrix4fc pose) { - this.model.mul(pose); - return this; - } - - @Override - public TransformedInstance mulNormal(Matrix3fc normal) { - this.normal.mul(normal); - return this; - } - @Override public TransformedInstance rotateAround(Quaternionfc quaternion, float x, float y, float z) { model.rotateAround(quaternion, x, y, z); - normal.rotate(quaternion); return this; } @@ -49,33 +32,17 @@ public TransformedInstance translate(float x, float y, float z) { @Override public TransformedInstance rotate(Quaternionfc quaternion) { model.rotate(quaternion); - normal.rotate(quaternion); return this; } @Override public TransformedInstance scale(float x, float y, float z) { model.scale(x, y, z); - - if (x == y && y == z) { - if (x < 0.0f) { - normal.scale(-1.0f); - } - - return this; - } - - float invX = 1.0f / x; - float invY = 1.0f / y; - float invZ = 1.0f / z; - float f = Mth.fastInvCubeRoot(Math.abs(invX * invY * invZ)); - normal.scale(f * invX, f * invY, f * invZ); return this; } public TransformedInstance setTransform(PoseStack.Pose pose) { model.set(pose.pose()); - normal.set(pose.normal()); return this; } @@ -85,7 +52,6 @@ public TransformedInstance setTransform(PoseStack stack) { public TransformedInstance setIdentityTransform() { model.identity(); - normal.identity(); return this; } @@ -98,7 +64,6 @@ public TransformedInstance setIdentityTransform() { */ public TransformedInstance setZeroTransform() { model.zero(); - normal.zero(); return this; } } diff --git a/common/src/lib/resources/assets/flywheel/flywheel/instance/cull/posed.glsl b/common/src/lib/resources/assets/flywheel/flywheel/instance/cull/posed.glsl new file mode 100644 index 000000000..c8d9a275c --- /dev/null +++ b/common/src/lib/resources/assets/flywheel/flywheel/instance/cull/posed.glsl @@ -0,0 +1,5 @@ +#include "flywheel:util/matrix.glsl" + +void flw_transformBoundingSphere(in FlwInstance i, inout vec3 center, inout float radius) { + transformBoundingSphere(i.pose, center, radius); +} diff --git a/common/src/lib/resources/assets/flywheel/flywheel/instance/posed.vert b/common/src/lib/resources/assets/flywheel/flywheel/instance/posed.vert new file mode 100644 index 000000000..918883e84 --- /dev/null +++ b/common/src/lib/resources/assets/flywheel/flywheel/instance/posed.vert @@ -0,0 +1,8 @@ +void flw_instanceVertex(in FlwInstance i) { + flw_vertexPos = i.pose * flw_vertexPos; + flw_vertexNormal = i.normal * flw_vertexNormal; + flw_vertexColor *= i.color; + flw_vertexOverlay = i.overlay; + // Some drivers have a bug where uint over float division is invalid, so use an explicit cast. + flw_vertexLight = vec2(i.light) / 256.0; +} diff --git a/common/src/lib/resources/assets/flywheel/flywheel/instance/transformed.vert b/common/src/lib/resources/assets/flywheel/flywheel/instance/transformed.vert index 918883e84..e0abf722b 100644 --- a/common/src/lib/resources/assets/flywheel/flywheel/instance/transformed.vert +++ b/common/src/lib/resources/assets/flywheel/flywheel/instance/transformed.vert @@ -1,6 +1,6 @@ void flw_instanceVertex(in FlwInstance i) { flw_vertexPos = i.pose * flw_vertexPos; - flw_vertexNormal = i.normal * flw_vertexNormal; + flw_vertexNormal = mat3(transpose(inverse(i.pose))) * flw_vertexNormal; flw_vertexColor *= i.color; flw_vertexOverlay = i.overlay; // Some drivers have a bug where uint over float division is invalid, so use an explicit cast. From b24e87262b189cd568ba5fb4926851490f8d6604 Mon Sep 17 00:00:00 2001 From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> Date: Sat, 14 Sep 2024 22:37:48 -0700 Subject: [PATCH 15/26] Stackless - Store a pose matrix in each InstanceTree, equivalent to its instance's pose matrix if the instance exists - Directly transform the current InstanceTree's pose matrix instead of transforming a PoseStack and copying its matrix to the instance, eliminating the need to push and pop stack entries - Remove InstanceTree.rotation - Add more InstanceTree methods to allow full inspection of children --- .../flywheel/lib/instance/InstanceTypes.java | 4 +- .../flywheel/lib/instance/PosedInstance.java | 18 ++-- .../lib/instance/TransformedInstance.java | 17 ++-- .../flywheel/lib/model/part/InstanceTree.java | 97 +++++++++++++------ .../flywheel/lib/model/part/MeshTree.java | 22 ++--- .../flywheel/vanilla/ChestVisual.java | 11 ++- .../flywheel/vanilla/MinecartVisual.java | 2 +- .../flywheel/vanilla/ShulkerBoxVisual.java | 3 +- 8 files changed, 108 insertions(+), 66 deletions(-) diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java index 433eb5445..ffb0d4f0a 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/InstanceTypes.java @@ -25,7 +25,7 @@ public final class InstanceTypes { MemoryUtil.memPutByte(ptr + 3, instance.alpha); ExtraMemoryOps.put2x16(ptr + 4, instance.overlay); ExtraMemoryOps.put2x16(ptr + 8, instance.light); - ExtraMemoryOps.putMatrix4f(ptr + 12, instance.model); + ExtraMemoryOps.putMatrix4f(ptr + 12, instance.pose); }) .vertexShader(Flywheel.rl("instance/transformed.vert")) .cullShader(Flywheel.rl("instance/cull/transformed.glsl")) @@ -46,7 +46,7 @@ public final class InstanceTypes { MemoryUtil.memPutByte(ptr + 3, instance.alpha); ExtraMemoryOps.put2x16(ptr + 4, instance.overlay); ExtraMemoryOps.put2x16(ptr + 8, instance.light); - ExtraMemoryOps.putMatrix4f(ptr + 12, instance.model); + ExtraMemoryOps.putMatrix4f(ptr + 12, instance.pose); ExtraMemoryOps.putMatrix3f(ptr + 76, instance.normal); }) .vertexShader(Flywheel.rl("instance/posed.vert")) diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java index d21b5e17f..365450acf 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java @@ -14,7 +14,7 @@ import net.minecraft.util.Mth; public class PosedInstance extends ColoredLitInstance implements Transform { - public final Matrix4f model = new Matrix4f(); + public final Matrix4f pose = new Matrix4f(); public final Matrix3f normal = new Matrix3f(); public PosedInstance(InstanceType type, InstanceHandle handle) { @@ -23,7 +23,7 @@ public PosedInstance(InstanceType type, InstanceHandle @Override public PosedInstance mulPose(Matrix4fc pose) { - this.model.mul(pose); + this.pose.mul(pose); return this; } @@ -35,27 +35,27 @@ public PosedInstance mulNormal(Matrix3fc normal) { @Override public PosedInstance rotateAround(Quaternionfc quaternion, float x, float y, float z) { - model.rotateAround(quaternion, x, y, z); + pose.rotateAround(quaternion, x, y, z); normal.rotate(quaternion); return this; } @Override public PosedInstance translate(float x, float y, float z) { - model.translate(x, y, z); + pose.translate(x, y, z); return this; } @Override public PosedInstance rotate(Quaternionfc quaternion) { - model.rotate(quaternion); + pose.rotate(quaternion); normal.rotate(quaternion); return this; } @Override public PosedInstance scale(float x, float y, float z) { - model.scale(x, y, z); + pose.scale(x, y, z); if (x == y && y == z) { if (x < 0.0f) { @@ -74,7 +74,7 @@ public PosedInstance scale(float x, float y, float z) { } public PosedInstance setTransform(PoseStack.Pose pose) { - model.set(pose.pose()); + this.pose.set(pose.pose()); normal.set(pose.normal()); return this; } @@ -84,7 +84,7 @@ public PosedInstance setTransform(PoseStack stack) { } public PosedInstance setIdentityTransform() { - model.identity(); + pose.identity(); normal.identity(); return this; } @@ -97,7 +97,7 @@ public PosedInstance setIdentityTransform() { *

*/ public PosedInstance setZeroTransform() { - model.zero(); + pose.zero(); normal.zero(); return this; } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java index 73e57e4d0..70a382c5b 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java @@ -9,9 +9,8 @@ import dev.engine_room.flywheel.api.instance.InstanceType; import dev.engine_room.flywheel.lib.transform.Affine; - public class TransformedInstance extends ColoredLitInstance implements Affine { - public final Matrix4f model = new Matrix4f(); + public final Matrix4f pose = new Matrix4f(); public TransformedInstance(InstanceType type, InstanceHandle handle) { super(type, handle); @@ -19,30 +18,30 @@ public TransformedInstance(InstanceType type, Ins @Override public TransformedInstance rotateAround(Quaternionfc quaternion, float x, float y, float z) { - model.rotateAround(quaternion, x, y, z); + pose.rotateAround(quaternion, x, y, z); return this; } @Override public TransformedInstance translate(float x, float y, float z) { - model.translate(x, y, z); + pose.translate(x, y, z); return this; } @Override public TransformedInstance rotate(Quaternionfc quaternion) { - model.rotate(quaternion); + pose.rotate(quaternion); return this; } @Override public TransformedInstance scale(float x, float y, float z) { - model.scale(x, y, z); + pose.scale(x, y, z); return this; } public TransformedInstance setTransform(PoseStack.Pose pose) { - model.set(pose.pose()); + this.pose.set(pose.pose()); return this; } @@ -51,7 +50,7 @@ public TransformedInstance setTransform(PoseStack stack) { } public TransformedInstance setIdentityTransform() { - model.identity(); + pose.identity(); return this; } @@ -63,7 +62,7 @@ public TransformedInstance setIdentityTransform() { *

*/ public TransformedInstance setZeroTransform() { - model.zero(); + pose.zero(); return this; } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java index cf8f22037..7f15dec1c 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java @@ -7,7 +7,8 @@ import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.Unmodifiable; +import org.joml.Matrix4f; +import org.joml.Matrix4fc; import org.joml.Quaternionf; import org.joml.Vector3fc; @@ -21,6 +22,8 @@ import dev.engine_room.flywheel.lib.instance.TransformedInstance; import dev.engine_room.flywheel.lib.model.ModelCache; import dev.engine_room.flywheel.lib.model.SingleMeshModel; +import dev.engine_room.flywheel.lib.transform.Affine; +import dev.engine_room.flywheel.lib.transform.TransformStack; import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.ModelPart; import net.minecraft.client.model.geom.PartPose; @@ -32,10 +35,9 @@ public final class InstanceTree { @Nullable private final TransformedInstance instance; private final PartPose initialPose; - @Unmodifiable private final InstanceTree[] children; - private final Quaternionf rotation = new Quaternionf(); + private final Matrix4f poseMatrix; public float x; public float y; @@ -46,7 +48,9 @@ public final class InstanceTree { public float xScale; public float yScale; public float zScale; + @ApiStatus.Experimental public boolean visible = true; + @ApiStatus.Experimental public boolean skipDraw; private InstanceTree(MeshTree source, @Nullable TransformedInstance instance, PartPose initialPose, InstanceTree[] children) { @@ -54,6 +58,13 @@ private InstanceTree(MeshTree source, @Nullable TransformedInstance instance, Pa this.instance = instance; this.initialPose = initialPose; this.children = children; + + if (instance != null) { + poseMatrix = instance.pose; + } else { + poseMatrix = new Matrix4f(); + } + resetPose(); } @@ -64,8 +75,7 @@ private static InstanceTree create(InstancerProvider provider, MeshTree meshTree for (int i = 0; i < meshTree.childCount(); i++) { var meshTreeChild = meshTree.child(i); String name = meshTree.childName(i); - children[i] = InstanceTree.create(provider, meshTreeChild, meshFinalizerFunc, pathSlash + name); - + children[i] = create(provider, meshTreeChild, meshFinalizerFunc, pathSlash + name); } Mesh mesh = meshTree.mesh(); @@ -106,17 +116,35 @@ public PartPose initialPose() { return initialPose; } - @Nullable + public int childCount() { + return children.length; + } + public InstanceTree child(int index) { - if (index < 0 || index >= children.length) { - return null; - } return children[index]; } + public String childName(int index) { + return source.childName(index); + } + + public int childIndex(String name) { + return source.childIndex(name); + } + + public boolean hasChild(String name) { + return childIndex(name) >= 0; + } + @Nullable public InstanceTree child(String name) { - return child(source.childIndex(name)); + int index = childIndex(name); + + if (index < 0) { + return null; + } + + return child(index); } public InstanceTree childOrThrow(String name) { @@ -158,35 +186,48 @@ public void traverse(int i, int j, ObjIntIntConsumer affine, Quaternionf tempQuaternion) { + affine.translate(x / 16.0F, y / 16.0F, z / 16.0F); if (xRot != 0.0F || yRot != 0.0F || zRot != 0.0F) { - poseStack.mulPose(rotation.rotationZYX(zRot, yRot, xRot)); + affine.rotate(tempQuaternion.rotationZYX(zRot, yRot, xRot)); } if (xScale != 1.0F || yScale != 1.0F || zScale != 1.0F) { - poseStack.scale(xScale, yScale, zScale); + affine.scale(xScale, yScale, zScale); } } - public void updateInstances(PoseStack poseStack) { - if (visible) { - poseStack.pushPose(); - translateAndRotate(poseStack); + public void translateAndRotate(PoseStack poseStack, Quaternionf tempQuaternion) { + translateAndRotate(TransformStack.of(poseStack), tempQuaternion); + } - if (instance != null && !skipDraw) { - instance.setTransform(poseStack.last()) - .setChanged(); - } + public void translateAndRotate(Matrix4f pose) { + pose.translate(x / 16.0F, y / 16.0F, z / 16.0F); - // Use the bare HashMap.forEach because .values() always allocates a new collection. - // We also don't want to store an array of children because that would statically use a lot more memory. - for (InstanceTree child : children) { - child.updateInstances(poseStack); - } + if (xRot != 0.0F || yRot != 0.0F || zRot != 0.0F) { + pose.rotateZYX(zRot, yRot, xRot); + } - poseStack.popPose(); + if (xScale != ModelPart.DEFAULT_SCALE || yScale != ModelPart.DEFAULT_SCALE || zScale != ModelPart.DEFAULT_SCALE) { + pose.scale(xScale, yScale, zScale); + } + } + + public void updateInstances(Matrix4fc initialPose) { + if (!visible) { + return; + } + + poseMatrix.set(initialPose); + translateAndRotate(poseMatrix); + + if (instance != null && !skipDraw) { + instance.setChanged(); + } + + for (InstanceTree child : children) { + child.updateInstances(poseMatrix); } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java index 60c0e8eba..6ec94921e 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java @@ -34,13 +34,13 @@ public final class MeshTree { private final Mesh mesh; private final PartPose initialPose; private final MeshTree[] children; - private final String[] childKeys; + private final String[] childNames; - private MeshTree(@Nullable Mesh mesh, PartPose initialPose, MeshTree[] children, String[] childKeys) { + private MeshTree(@Nullable Mesh mesh, PartPose initialPose, MeshTree[] children, String[] childNames) { this.mesh = mesh; this.initialPose = initialPose; - this.childKeys = childKeys; this.children = children; + this.childNames = childNames; } public static MeshTree of(ModelLayerLocation layer) { @@ -59,15 +59,15 @@ private static MeshTree convert(ModelPart modelPart, ThreadLocalObjects objects) var modelPartChildren = FlwLibLink.INSTANCE.getModelPartChildren(modelPart); // Freeze the ordering here. Maybe we want to sort this? - String[] childKeys = modelPartChildren.keySet() - .toArray(new String[0]); + String[] childNames = modelPartChildren.keySet() + .toArray(String[]::new); - MeshTree[] children = new MeshTree[modelPartChildren.size()]; - for (int i = 0; i < childKeys.length; i++) { - children[i] = convert(modelPartChildren.get(childKeys[i]), objects); + MeshTree[] children = new MeshTree[childNames.length]; + for (int i = 0; i < childNames.length; i++) { + children[i] = convert(modelPartChildren.get(childNames[i]), objects); } - return new MeshTree(compile(modelPart, objects), modelPart.getInitialPose(), children, childKeys); + return new MeshTree(compile(modelPart, objects), modelPart.getInitialPose(), children, childNames); } @Nullable @@ -103,11 +103,11 @@ public MeshTree child(int index) { } public String childName(int index) { - return childKeys[index]; + return childNames[index]; } public int childIndex(String name) { - return Arrays.binarySearch(childKeys, name); + return Arrays.binarySearch(childNames, name); } public boolean hasChild(String name) { diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java index 9025f5d0a..7953d549e 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java @@ -6,6 +6,7 @@ import java.util.function.Consumer; import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4fc; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Axis; @@ -18,7 +19,6 @@ import dev.engine_room.flywheel.lib.model.RetexturedMesh; import dev.engine_room.flywheel.lib.model.part.InstanceTree; import dev.engine_room.flywheel.lib.transform.TransformStack; -import dev.engine_room.flywheel.lib.util.RecyclingPoseStack; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import it.unimi.dsi.fastutil.floats.Float2FloatFunction; @@ -60,7 +60,8 @@ public class ChestVisual extends Abstrac @Nullable private final InstanceTree lock; - private final PoseStack poseStack = new RecyclingPoseStack(); + @Nullable + private final Matrix4fc initialPose; private final BrightnessCombiner brightnessCombiner = new BrightnessCombiner(); @Nullable private final DoubleBlockCombiner.NeighborCombineResult neighborCombineResult; @@ -83,12 +84,13 @@ public ChestVisual(VisualizationContext ctx, T blockEntity, float partialTick) { lid = instances.childOrThrow("lid"); lock = instances.childOrThrow("lock"); - poseStack.pushPose(); + PoseStack poseStack = new PoseStack(); TransformStack.of(poseStack).translate(getVisualPosition()); float horizontalAngle = blockState.getValue(ChestBlock.FACING).toYRot(); poseStack.translate(0.5F, 0.5F, 0.5F); poseStack.mulPose(Axis.YP.rotationDegrees(-horizontalAngle)); poseStack.translate(-0.5F, -0.5F, -0.5F); + initialPose = poseStack.last().pose(); neighborCombineResult = chestBlock.combine(blockState, level, pos, true); lidProgress = neighborCombineResult.apply(ChestBlock.opennessCombiner(blockEntity)); @@ -99,6 +101,7 @@ public ChestVisual(VisualizationContext ctx, T blockEntity, float partialTick) { instances = null; lid = null; lock = null; + initialPose = null; neighborCombineResult = null; lidProgress = null; } @@ -145,7 +148,7 @@ private void applyLidTransform(float progress) { lid.xRot = -(progress * ((float) Math.PI / 2F)); lock.xRot = lid.xRot; - instances.updateInstances(poseStack); + instances.updateInstances(initialPose); } @Override diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java index 0ba3efc60..50f8f0341 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java @@ -53,7 +53,7 @@ public class MinecartVisual extends ComponentEntityV private final ModelHolder bodyModel; - private final PoseStack stack = new RecyclingPoseStack(); + private final RecyclingPoseStack stack = new RecyclingPoseStack(); private BlockState blockState; private boolean active; diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java index 084c5fb03..7e1a2f538 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java @@ -4,7 +4,6 @@ import org.joml.Quaternionf; -import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.math.Axis; import dev.engine_room.flywheel.api.instance.Instance; @@ -46,7 +45,7 @@ public class ShulkerBoxVisual extends AbstractBlockEntityVisual Date: Sat, 14 Sep 2024 22:45:16 -0700 Subject: [PATCH 16/26] A little encapsulation goes a long way - Encapsulate instancerProvider and renderOrigin in AbstractVisual - Saves 8 bytes per visual --- .../lib/visual/AbstractBlockEntityVisual.java | 2 +- .../flywheel/lib/visual/AbstractEntityVisual.java | 2 ++ .../flywheel/lib/visual/AbstractVisual.java | 12 ++++++++---- .../dev/engine_room/flywheel/vanilla/BellVisual.java | 2 +- .../engine_room/flywheel/vanilla/ChestVisual.java | 2 +- .../engine_room/flywheel/vanilla/MinecartVisual.java | 5 +++-- .../flywheel/vanilla/ShulkerBoxVisual.java | 4 ++-- 7 files changed, 18 insertions(+), 11 deletions(-) diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java index a82e08459..e31332743 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java @@ -52,7 +52,7 @@ public AbstractBlockEntityVisual(VisualizationContext ctx, T blockEntity, float this.blockEntity = blockEntity; this.pos = blockEntity.getBlockPos(); this.blockState = blockEntity.getBlockState(); - this.visualPos = pos.subtract(renderOrigin); + this.visualPos = pos.subtract(ctx.renderOrigin()); } @Override diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractEntityVisual.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractEntityVisual.java index f9aecce1e..65f0643ba 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractEntityVisual.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractEntityVisual.java @@ -67,6 +67,7 @@ public double distanceSquared(double x, double y, double z) { */ public Vector3f getVisualPosition() { Vec3 pos = entity.position(); + var renderOrigin = renderOrigin(); return new Vector3f((float) (pos.x - renderOrigin.getX()), (float) (pos.y - renderOrigin.getY()), (float) (pos.z - renderOrigin.getZ())); @@ -81,6 +82,7 @@ public Vector3f getVisualPosition() { */ public Vector3f getVisualPosition(float partialTick) { Vec3 pos = entity.position(); + var renderOrigin = renderOrigin(); return new Vector3f((float) (Mth.lerp(partialTick, entity.xOld, pos.x) - renderOrigin.getX()), (float) (Mth.lerp(partialTick, entity.yOld, pos.y) - renderOrigin.getY()), (float) (Mth.lerp(partialTick, entity.zOld, pos.z) - renderOrigin.getZ())); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractVisual.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractVisual.java index c21866435..e6ed8c227 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractVisual.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractVisual.java @@ -13,16 +13,12 @@ public abstract class AbstractVisual implements Visual { * Useful for passing to child visuals. */ protected final VisualizationContext visualizationContext; - protected final InstancerProvider instancerProvider; - protected final Vec3i renderOrigin; protected final Level level; protected boolean deleted = false; public AbstractVisual(VisualizationContext ctx, Level level, float partialTick) { this.visualizationContext = ctx; - this.instancerProvider = ctx.instancerProvider(); - this.renderOrigin = ctx.renderOrigin(); this.level = level; } @@ -32,6 +28,14 @@ public void update(float partialTick) { protected abstract void _delete(); + protected InstancerProvider instancerProvider() { + return visualizationContext.instancerProvider(); + } + + protected Vec3i renderOrigin() { + return visualizationContext.renderOrigin(); + } + @Override public final void delete() { if (deleted) { diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java index ad7648c7b..063a9d59b 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java @@ -46,7 +46,7 @@ public BellVisual(VisualizationContext ctx, BellBlockEntity blockEntity, float p } private OrientedInstance createBellInstance() { - return instancerProvider.instancer(InstanceTypes.ORIENTED, BELL_MODEL.get()) + return instancerProvider().instancer(InstanceTypes.ORIENTED, BELL_MODEL.get()) .createInstance(); } diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java index 7953d549e..51f7ef8df 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java @@ -78,7 +78,7 @@ public ChestVisual(VisualizationContext ctx, T blockEntity, float partialTick) { ChestType chestType = blockState.hasProperty(ChestBlock.TYPE) ? blockState.getValue(ChestBlock.TYPE) : ChestType.SINGLE; TextureAtlasSprite sprite = Sheets.chooseMaterial(blockEntity, chestType, isChristmas()).sprite(); - instances = InstanceTree.create(instancerProvider, LAYER_LOCATIONS.get(chestType), (path, mesh) -> { + instances = InstanceTree.create(instancerProvider(), LAYER_LOCATIONS.get(chestType), (path, mesh) -> { return new Model.ConfiguredMesh(MATERIAL, new RetexturedMesh(mesh, sprite)); }); lid = instances.childOrThrow("lid"); diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java index 50f8f0341..b7fc711df 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java @@ -82,7 +82,7 @@ private static ModelHolder createBodyModelHolder(ModelLayerLocation layer) { } private TransformedInstance createBodyInstance() { - return instancerProvider.instancer(InstanceTypes.TRANSFORMED, bodyModel.get()) + return instancerProvider().instancer(InstanceTypes.TRANSFORMED, bodyModel.get()) .createInstance(); } @@ -101,7 +101,7 @@ private TransformedInstance createContentsInstance() { return null; } - return instancerProvider.instancer(InstanceTypes.TRANSFORMED, Models.block(blockState)) + return instancerProvider().instancer(InstanceTypes.TRANSFORMED, Models.block(blockState)) .createInstance(); } @@ -141,6 +141,7 @@ private void updateInstances(float partialTick) { double posY = Mth.lerp(partialTick, entity.yOld, entity.getY()); double posZ = Mth.lerp(partialTick, entity.zOld, entity.getZ()); + var renderOrigin = renderOrigin(); stack.translate(posX - renderOrigin.getX(), posY - renderOrigin.getY(), posZ - renderOrigin.getZ()); float yaw = Mth.lerp(partialTick, entity.yRotO, entity.getYRot()); diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java index 7e1a2f538..be5d85ba7 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java @@ -78,12 +78,12 @@ public ShulkerBoxVisual(VisualizationContext ctx, ShulkerBoxBlockEntity blockEnt } private TransformedInstance createBaseInstance(Material texture) { - return instancerProvider.instancer(InstanceTypes.TRANSFORMED, BASE_MODELS.get(texture)) + return instancerProvider().instancer(InstanceTypes.TRANSFORMED, BASE_MODELS.get(texture)) .createInstance(); } private TransformedInstance createLidInstance(Material texture) { - return instancerProvider.instancer(InstanceTypes.TRANSFORMED, LID_MODELS.get(texture)) + return instancerProvider().instancer(InstanceTypes.TRANSFORMED, LID_MODELS.get(texture)) .createInstance(); } From ed727e7a1b0ffa95f2d87828448fda6396a0ac03 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 15 Sep 2024 00:28:10 -0700 Subject: [PATCH 17/26] Things are changing - Track if the transforms for an InstanceTree have changed - Pass a boolean down the tree and || it with our changed flag to force updates - Expose the force flag to visuals so they can hint to us if their root transforms never change - Add 2 wrapper methods to make the distinction more clear --- .../flywheel/lib/model/part/InstanceTree.java | 212 ++++++++++++++++-- .../flywheel/vanilla/ChestVisual.java | 6 +- 2 files changed, 195 insertions(+), 23 deletions(-) diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java index 7f15dec1c..fd08c61ad 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java @@ -34,29 +34,29 @@ public final class InstanceTree { private final MeshTree source; @Nullable private final TransformedInstance instance; - private final PartPose initialPose; private final InstanceTree[] children; private final Matrix4f poseMatrix; - public float x; - public float y; - public float z; - public float xRot; - public float yRot; - public float zRot; - public float xScale; - public float yScale; - public float zScale; + private float x; + private float y; + private float z; + private float xRot; + private float yRot; + private float zRot; + private float xScale; + private float yScale; + private float zScale; @ApiStatus.Experimental public boolean visible = true; @ApiStatus.Experimental public boolean skipDraw; - private InstanceTree(MeshTree source, @Nullable TransformedInstance instance, PartPose initialPose, InstanceTree[] children) { + private boolean changed; + + private InstanceTree(MeshTree source, @Nullable TransformedInstance instance, InstanceTree[] children) { this.source = source; this.instance = instance; - this.initialPose = initialPose; this.children = children; if (instance != null) { @@ -88,7 +88,7 @@ private static InstanceTree create(InstancerProvider provider, MeshTree meshTree instance = null; } - return new InstanceTree(meshTree, instance, meshTree.initialPose(), children); + return new InstanceTree(meshTree, instance, children); } public static InstanceTree create(InstancerProvider provider, MeshTree meshTree, BiFunction meshFinalizerFunc) { @@ -113,7 +113,7 @@ public TransformedInstance instance() { } public PartPose initialPose() { - return initialPose; + return source.initialPose(); } public int childCount() { @@ -214,57 +214,222 @@ public void translateAndRotate(Matrix4f pose) { } } + /** + * Update the instances in this tree, assuming initialPose changes. + * + *

This is the preferred method for entity visuals, or if you're not sure which you need. + * + * @param initialPose The root transformation matrix. + */ public void updateInstances(Matrix4fc initialPose) { + propagateAnimation(initialPose, true); + } + + /** + * Update the instances in this tree, assuming initialPose doesn't change between invocations. + * + *

This is the preferred method for block entity visuals. + * + * @param initialPose The root transformation matrix. + */ + public void updateInstancesStatic(Matrix4fc initialPose) { + propagateAnimation(initialPose, false); + } + + /** + * Propagate pose transformations to this tree and all its children. + * + * @param initialPose The root transformation matrix. + * @param force Whether to force the update even if this node's transformations haven't changed. + */ + public void propagateAnimation(Matrix4fc initialPose, boolean force) { if (!visible) { return; } - poseMatrix.set(initialPose); - translateAndRotate(poseMatrix); + if (changed || force) { + poseMatrix.set(initialPose); + translateAndRotate(poseMatrix); + force = true; - if (instance != null && !skipDraw) { - instance.setChanged(); + if (instance != null && !skipDraw) { + instance.setChanged(); + } } for (InstanceTree child : children) { - child.updateInstances(poseMatrix); + child.propagateAnimation(poseMatrix, force); } + + changed = false; + } + + public float xPos() { + return x; + } + + public float yPos() { + return y; + } + + public float zPos() { + return z; + } + + public float xRot() { + return xRot; + } + + public float yRot() { + return yRot; + } + + public float zRot() { + return zRot; + } + + public float xScale() { + return xScale; + } + + public float yScale() { + return yScale; + } + + public float zScale() { + return zScale; } public void pos(float x, float y, float z) { this.x = x; this.y = y; this.z = z; + setChanged(); + } + + public void xPos(float x) { + this.x = x; + setChanged(); + } + + public void yPos(float y) { + this.y = y; + setChanged(); + } + + public void zPos(float z) { + this.z = z; + setChanged(); } public void rotation(float xRot, float yRot, float zRot) { this.xRot = xRot; this.yRot = yRot; this.zRot = zRot; + setChanged(); + } + + public void xRot(float xRot) { + this.xRot = xRot; + setChanged(); + } + + public void yRot(float yRot) { + this.yRot = yRot; + setChanged(); + } + + public void zRot(float zRot) { + this.zRot = zRot; + setChanged(); } public void scale(float xScale, float yScale, float zScale) { this.xScale = xScale; this.yScale = yScale; this.zScale = zScale; + setChanged(); + } + + public void xScale(float xScale) { + this.xScale = xScale; + setChanged(); + } + + public void yScale(float yScale) { + this.yScale = yScale; + setChanged(); + } + + public void zScale(float zScale) { + this.zScale = zScale; + setChanged(); } public void offsetPos(float xOffset, float yOffset, float zOffset) { x += xOffset; y += yOffset; z += zOffset; + setChanged(); + } + + public void offsetXPos(float xOffset) { + x += xOffset; + setChanged(); + } + + public void offsetYPos(float yOffset) { + y += yOffset; + setChanged(); + } + + public void offsetZPos(float zOffset) { + z += zOffset; + setChanged(); } public void offsetRotation(float xOffset, float yOffset, float zOffset) { xRot += xOffset; yRot += yOffset; zRot += zOffset; + setChanged(); + } + + public void offsetXRot(float xOffset) { + xRot += xOffset; + setChanged(); + } + + public void offsetYRot(float yOffset) { + yRot += yOffset; + setChanged(); + } + + public void offsetZRot(float zOffset) { + zRot += zOffset; + setChanged(); } public void offsetScale(float xOffset, float yOffset, float zOffset) { xScale += xOffset; yScale += yOffset; zScale += zOffset; + setChanged(); + } + + public void offsetXScale(float xOffset) { + xScale += xOffset; + setChanged(); + } + + public void offsetYScale(float yOffset) { + yScale += yOffset; + setChanged(); + } + + public void offsetZScale(float zOffset) { + zScale += zOffset; + setChanged(); } public void offsetPos(Vector3fc offset) { @@ -293,10 +458,11 @@ public void loadPose(PartPose pose) { xScale = ModelPart.DEFAULT_SCALE; yScale = ModelPart.DEFAULT_SCALE; zScale = ModelPart.DEFAULT_SCALE; + setChanged(); } public void resetPose() { - loadPose(initialPose); + loadPose(source.initialPose()); } public void copyTransform(InstanceTree tree) { @@ -309,6 +475,7 @@ public void copyTransform(InstanceTree tree) { xScale = tree.xScale; yScale = tree.yScale; zScale = tree.zScale; + setChanged(); } public void copyTransform(ModelPart modelPart) { @@ -321,6 +488,7 @@ public void copyTransform(ModelPart modelPart) { xScale = modelPart.xScale; yScale = modelPart.yScale; zScale = modelPart.zScale; + setChanged(); } public void delete() { @@ -332,6 +500,10 @@ public void delete() { } } + private void setChanged() { + changed = true; + } + @ApiStatus.Experimental @FunctionalInterface public interface ObjIntIntConsumer { diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java index 51f7ef8df..d49354e55 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java @@ -146,9 +146,9 @@ private void applyLidTransform(float progress) { progress = 1.0F - progress; progress = 1.0F - progress * progress * progress; - lid.xRot = -(progress * ((float) Math.PI / 2F)); - lock.xRot = lid.xRot; - instances.updateInstances(initialPose); + lid.xRot(-(progress * ((float) Math.PI / 2F))); + lock.xRot(lid.xRot()); + instances.updateInstancesStatic(initialPose); } @Override From 904933e22ec10af6b7eaa105059b5e7cab65556e Mon Sep 17 00:00:00 2001 From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> Date: Sun, 15 Sep 2024 13:27:08 -0700 Subject: [PATCH 18/26] Treeify bells and minecarts - Use InstanceTrees in BellVisual and MinecartVisual - Use JOML Matrix4fStack instead of PoseStack - Directly transform Matrix4f instead of using PoseStack to compute initial pose --- .../flywheel/lib/instance/PosedInstance.java | 6 ++ .../lib/instance/TransformedInstance.java | 6 ++ .../flywheel/lib/model/part/InstanceTree.java | 56 ++++++------- .../lib/visual/AbstractBlockEntityVisual.java | 4 + .../lib/visual/AbstractEntityVisual.java | 9 +- .../flywheel/vanilla/BellVisual.java | 69 ++++++++------- .../flywheel/vanilla/ChestVisual.java | 28 ++++--- .../flywheel/vanilla/MinecartVisual.java | 83 +++++++------------ .../flywheel/vanilla/TntMinecartVisual.java | 11 +-- .../flywheel/vanilla/VanillaVisuals.java | 13 +-- 10 files changed, 146 insertions(+), 139 deletions(-) diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java index 365450acf..43e6df47b 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/PosedInstance.java @@ -73,6 +73,12 @@ public PosedInstance scale(float x, float y, float z) { return this; } + public PosedInstance setTransform(Matrix4fc pose, Matrix3fc normal) { + this.pose.set(pose); + this.normal.set(normal); + return this; + } + public PosedInstance setTransform(PoseStack.Pose pose) { this.pose.set(pose.pose()); normal.set(pose.normal()); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java index 70a382c5b..74700e480 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/instance/TransformedInstance.java @@ -1,6 +1,7 @@ package dev.engine_room.flywheel.lib.instance; import org.joml.Matrix4f; +import org.joml.Matrix4fc; import org.joml.Quaternionfc; import com.mojang.blaze3d.vertex.PoseStack; @@ -40,6 +41,11 @@ public TransformedInstance scale(float x, float y, float z) { return this; } + public TransformedInstance setTransform(Matrix4fc pose) { + this.pose.set(pose); + return this; + } + public TransformedInstance setTransform(PoseStack.Pose pose) { this.pose.set(pose.pose()); return this; diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java index fd08c61ad..48cb078bc 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java @@ -193,7 +193,7 @@ public void translateAndRotate(Affine affine, Quaternionf tempQuaternion) { affine.rotate(tempQuaternion.rotationZYX(zRot, yRot, xRot)); } - if (xScale != 1.0F || yScale != 1.0F || zScale != 1.0F) { + if (xScale != ModelPart.DEFAULT_SCALE || yScale != ModelPart.DEFAULT_SCALE || zScale != ModelPart.DEFAULT_SCALE) { affine.scale(xScale, yScale, zScale); } } @@ -300,13 +300,6 @@ public float zScale() { return zScale; } - public void pos(float x, float y, float z) { - this.x = x; - this.y = y; - this.z = z; - setChanged(); - } - public void xPos(float x) { this.x = x; setChanged(); @@ -322,10 +315,10 @@ public void zPos(float z) { setChanged(); } - public void rotation(float xRot, float yRot, float zRot) { - this.xRot = xRot; - this.yRot = yRot; - this.zRot = zRot; + public void pos(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; setChanged(); } @@ -344,10 +337,10 @@ public void zRot(float zRot) { setChanged(); } - public void scale(float xScale, float yScale, float zScale) { - this.xScale = xScale; - this.yScale = yScale; - this.zScale = zScale; + public void rotation(float xRot, float yRot, float zRot) { + this.xRot = xRot; + this.yRot = yRot; + this.zRot = zRot; setChanged(); } @@ -366,6 +359,13 @@ public void zScale(float zScale) { setChanged(); } + public void scale(float xScale, float yScale, float zScale) { + this.xScale = xScale; + this.yScale = yScale; + this.zScale = zScale; + setChanged(); + } + public void offsetPos(float xOffset, float yOffset, float zOffset) { x += xOffset; y += yOffset; @@ -388,6 +388,10 @@ public void offsetZPos(float zOffset) { setChanged(); } + public void offsetPos(Vector3fc offset) { + offsetPos(offset.x(), offset.y(), offset.z()); + } + public void offsetRotation(float xOffset, float yOffset, float zOffset) { xRot += xOffset; yRot += yOffset; @@ -410,6 +414,10 @@ public void offsetZRot(float zOffset) { setChanged(); } + public void offsetRotation(Vector3fc offset) { + offsetRotation(offset.x(), offset.y(), offset.z()); + } + public void offsetScale(float xOffset, float yOffset, float zOffset) { xScale += xOffset; yScale += yOffset; @@ -432,14 +440,6 @@ public void offsetZScale(float zOffset) { setChanged(); } - public void offsetPos(Vector3fc offset) { - offsetPos(offset.x(), offset.y(), offset.z()); - } - - public void offsetRotation(Vector3fc offset) { - offsetRotation(offset.x(), offset.y(), offset.z()); - } - public void offsetScale(Vector3fc offset) { offsetScale(offset.x(), offset.y(), offset.z()); } @@ -491,6 +491,10 @@ public void copyTransform(ModelPart modelPart) { setChanged(); } + private void setChanged() { + changed = true; + } + public void delete() { if (instance != null) { instance.delete(); @@ -500,10 +504,6 @@ public void delete() { } } - private void setChanged() { - changed = true; - } - @ApiStatus.Experimental @FunctionalInterface public interface ObjIntIntConsumer { diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java index e31332743..25d05d481 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java @@ -99,6 +99,10 @@ public boolean doDistanceLimitThisFrame(DynamicVisual.Context context) { .shouldUpdate(pos.distToCenterSqr(context.camera().getPosition())); } + protected int computePackedLight() { + return LevelRenderer.getLightColor(level, pos); + } + protected void relight(BlockPos pos, @Nullable FlatLit... instances) { FlatLit.relight(LevelRenderer.getLightColor(level, pos), instances); } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractEntityVisual.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractEntityVisual.java index 65f0643ba..80ffa73a0 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractEntityVisual.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractEntityVisual.java @@ -92,11 +92,14 @@ public boolean isVisible(FrustumIntersection frustum) { return entity.noCulling || visibilityTester.check(frustum); } - protected void relight(float partialTick, @Nullable FlatLit... instances) { + protected int computePackedLight(float partialTick) { BlockPos pos = BlockPos.containing(entity.getLightProbePosition(partialTick)); int blockLight = entity.isOnFire() ? 15 : level.getBrightness(LightLayer.BLOCK, pos); int skyLight = level.getBrightness(LightLayer.SKY, pos); - int light = LightTexture.pack(blockLight, skyLight); - FlatLit.relight(light, instances); + return LightTexture.pack(blockLight, skyLight); + } + + protected void relight(float partialTick, @Nullable FlatLit... instances) { + FlatLit.relight(computePackedLight(partialTick), instances); } } diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java index 063a9d59b..c5cb2af14 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java @@ -2,23 +2,22 @@ import java.util.function.Consumer; -import org.joml.AxisAngle4f; -import org.joml.Quaternionf; -import org.joml.Vector3f; +import org.joml.Matrix4f; +import org.joml.Matrix4fc; import dev.engine_room.flywheel.api.instance.Instance; import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.api.visualization.VisualizationContext; -import dev.engine_room.flywheel.lib.instance.InstanceTypes; -import dev.engine_room.flywheel.lib.instance.OrientedInstance; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.ModelHolder; -import dev.engine_room.flywheel.lib.model.SingleMeshModel; -import dev.engine_room.flywheel.lib.model.part.ModelPartConverter; +import dev.engine_room.flywheel.lib.model.RetexturedMesh; +import dev.engine_room.flywheel.lib.model.part.InstanceTree; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.client.renderer.blockentity.BellRenderer; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.BlockPos; import net.minecraft.util.Mth; import net.minecraft.world.level.block.entity.BellBlockEntity; @@ -27,27 +26,26 @@ public class BellVisual extends AbstractBlockEntityVisual imple .mipmap(false) .build(); - private static final ModelHolder BELL_MODEL = new ModelHolder(() -> { - return new SingleMeshModel(ModelPartConverter.convert(ModelLayers.BELL, BellRenderer.BELL_RESOURCE_LOCATION.sprite(), "bell_body"), MATERIAL); - }); + private final InstanceTree instances; + private final InstanceTree bellBody; - private final OrientedInstance bell; + private final Matrix4fc initialPose; private boolean wasShaking = false; public BellVisual(VisualizationContext ctx, BellBlockEntity blockEntity, float partialTick) { super(ctx, blockEntity, partialTick); - bell = createBellInstance().pivot(0.5f, 0.75f, 0.5f) - .position(getVisualPosition()); - bell.setChanged(); + TextureAtlasSprite sprite = BellRenderer.BELL_RESOURCE_LOCATION.sprite(); + instances = InstanceTree.create(instancerProvider(), ModelLayers.BELL, (path, mesh) -> { + return new Model.ConfiguredMesh(MATERIAL, new RetexturedMesh(mesh, sprite)); + }); + bellBody = instances.childOrThrow("bell_body"); - updateRotation(partialTick); - } + BlockPos visualPos = getVisualPosition(); + initialPose = new Matrix4f().translate(visualPos.getX(), visualPos.getY(), visualPos.getZ()); - private OrientedInstance createBellInstance() { - return instancerProvider().instancer(InstanceTypes.ORIENTED, BELL_MODEL.get()) - .createInstance(); + updateRotation(partialTick); } @Override @@ -60,37 +58,46 @@ public void beginFrame(Context context) { } private void updateRotation(float partialTick) { + float xRot = 0; + float zRot = 0; + if (blockEntity.shaking) { float ringTime = (float) blockEntity.ticks + partialTick; float angle = Mth.sin(ringTime / (float) Math.PI) / (4.0F + ringTime / 3.0F); - Vector3f ringAxis = blockEntity.clickDirection.getCounterClockWise() - .step(); - - bell.rotation(new Quaternionf(new AxisAngle4f(angle, ringAxis))) - .setChanged(); + switch (blockEntity.clickDirection) { + case NORTH -> xRot = -angle; + case SOUTH -> xRot = angle; + case EAST -> zRot = -angle; + case WEST -> zRot = angle; + } wasShaking = true; } else if (wasShaking) { - bell.rotation(new Quaternionf()) - .setChanged(); - wasShaking = false; } + + bellBody.xRot(xRot); + bellBody.zRot(zRot); + instances.updateInstancesStatic(initialPose); } @Override public void updateLight(float partialTick) { - relight(bell); + int packedLight = computePackedLight(); + instances.traverse(instance -> { + instance.light(packedLight) + .setChanged(); + }); } @Override public void collectCrumblingInstances(Consumer consumer) { - consumer.accept(bell); + instances.traverse(consumer); } @Override protected void _delete() { - bell.delete(); + instances.delete(); } } diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java index d49354e55..90063c138 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java @@ -6,19 +6,17 @@ import java.util.function.Consumer; import org.jetbrains.annotations.Nullable; +import org.joml.Matrix4f; import org.joml.Matrix4fc; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.math.Axis; - import dev.engine_room.flywheel.api.instance.Instance; +import dev.engine_room.flywheel.api.material.Material; import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.api.visualization.VisualizationContext; import dev.engine_room.flywheel.lib.material.CutoutShaders; import dev.engine_room.flywheel.lib.material.SimpleMaterial; import dev.engine_room.flywheel.lib.model.RetexturedMesh; import dev.engine_room.flywheel.lib.model.part.InstanceTree; -import dev.engine_room.flywheel.lib.transform.TransformStack; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import it.unimi.dsi.fastutil.floats.Float2FloatFunction; @@ -29,7 +27,9 @@ import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.Sheets; import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; +import net.minecraft.util.Mth; import net.minecraft.world.level.block.AbstractChestBlock; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.ChestBlock; @@ -40,7 +40,7 @@ import net.minecraft.world.level.block.state.properties.ChestType; public class ChestVisual extends AbstractBlockEntityVisual implements SimpleDynamicVisual { - private static final dev.engine_room.flywheel.api.material.Material MATERIAL = SimpleMaterial.builder() + private static final Material MATERIAL = SimpleMaterial.builder() .cutout(CutoutShaders.ONE_TENTH) .texture(Sheets.CHEST_SHEET) .mipmap(false) @@ -84,14 +84,7 @@ public ChestVisual(VisualizationContext ctx, T blockEntity, float partialTick) { lid = instances.childOrThrow("lid"); lock = instances.childOrThrow("lock"); - PoseStack poseStack = new PoseStack(); - TransformStack.of(poseStack).translate(getVisualPosition()); - float horizontalAngle = blockState.getValue(ChestBlock.FACING).toYRot(); - poseStack.translate(0.5F, 0.5F, 0.5F); - poseStack.mulPose(Axis.YP.rotationDegrees(-horizontalAngle)); - poseStack.translate(-0.5F, -0.5F, -0.5F); - initialPose = poseStack.last().pose(); - + initialPose = createInitialPose(); neighborCombineResult = chestBlock.combine(blockState, level, pos, true); lidProgress = neighborCombineResult.apply(ChestBlock.opennessCombiner(blockEntity)); @@ -112,6 +105,15 @@ private static boolean isChristmas() { return calendar.get(Calendar.MONTH) + 1 == 12 && calendar.get(Calendar.DATE) >= 24 && calendar.get(Calendar.DATE) <= 26; } + private Matrix4f createInitialPose() { + BlockPos visualPos = getVisualPosition(); + float horizontalAngle = blockState.getValue(ChestBlock.FACING).toYRot(); + return new Matrix4f().translate(visualPos.getX(), visualPos.getY(), visualPos.getZ()) + .translate(0.5F, 0.5F, 0.5F) + .rotateY(-horizontalAngle * Mth.DEG_TO_RAD) + .translate(-0.5F, -0.5F, -0.5F); + } + @Override public void setSectionCollector(SectionCollector sectionCollector) { this.lightSections = sectionCollector; diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java index b7fc711df..c7310c90e 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java @@ -1,22 +1,19 @@ package dev.engine_room.flywheel.vanilla; import org.jetbrains.annotations.Nullable; - -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.math.Axis; +import org.joml.Matrix4f; +import org.joml.Matrix4fStack; import dev.engine_room.flywheel.api.material.Material; import dev.engine_room.flywheel.api.visual.DynamicVisual; import dev.engine_room.flywheel.api.visual.TickableVisual; import dev.engine_room.flywheel.api.visualization.VisualizationContext; +import dev.engine_room.flywheel.lib.instance.FlatLit; import dev.engine_room.flywheel.lib.instance.InstanceTypes; import dev.engine_room.flywheel.lib.instance.TransformedInstance; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.ModelHolder; import dev.engine_room.flywheel.lib.model.Models; -import dev.engine_room.flywheel.lib.model.SingleMeshModel; -import dev.engine_room.flywheel.lib.model.part.ModelPartConverter; -import dev.engine_room.flywheel.lib.util.RecyclingPoseStack; +import dev.engine_room.flywheel.lib.model.part.InstanceTree; import dev.engine_room.flywheel.lib.visual.ComponentEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import dev.engine_room.flywheel.lib.visual.SimpleTickableVisual; @@ -24,7 +21,6 @@ import dev.engine_room.flywheel.lib.visual.component.HitboxComponent; import dev.engine_room.flywheel.lib.visual.component.ShadowComponent; import net.minecraft.client.model.geom.ModelLayerLocation; -import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; import net.minecraft.world.entity.vehicle.AbstractMinecart; @@ -39,31 +35,19 @@ public class MinecartVisual extends ComponentEntityV .mipmap(false) .build(); - public static final ModelHolder CHEST_BODY_MODEL = createBodyModelHolder(ModelLayers.CHEST_MINECART); - public static final ModelHolder COMMAND_BLOCK_BODY_MODEL = createBodyModelHolder(ModelLayers.COMMAND_BLOCK_MINECART); - public static final ModelHolder FURNACE_BODY_MODEL = createBodyModelHolder(ModelLayers.FURNACE_MINECART); - public static final ModelHolder HOPPER_BODY_MODEL = createBodyModelHolder(ModelLayers.HOPPER_MINECART); - public static final ModelHolder STANDARD_BODY_MODEL = createBodyModelHolder(ModelLayers.MINECART); - public static final ModelHolder SPAWNER_BODY_MODEL = createBodyModelHolder(ModelLayers.SPAWNER_MINECART); - public static final ModelHolder TNT_BODY_MODEL = createBodyModelHolder(ModelLayers.TNT_MINECART); - - private final TransformedInstance body; + private final InstanceTree instances; @Nullable private TransformedInstance contents; - private final ModelHolder bodyModel; - - private final RecyclingPoseStack stack = new RecyclingPoseStack(); + private final Matrix4fStack stack = new Matrix4fStack(2); private BlockState blockState; private boolean active; - public MinecartVisual(VisualizationContext ctx, T entity, float partialTick, ModelHolder bodyModel) { + public MinecartVisual(VisualizationContext ctx, T entity, float partialTick, ModelLayerLocation layerLocation) { super(ctx, entity, partialTick); - this.bodyModel = bodyModel; - - body = createBodyInstance(); + instances = InstanceTree.create(instancerProvider(), layerLocation, MATERIAL); blockState = entity.getDisplayBlockState(); contents = createContentsInstance(); @@ -75,23 +59,12 @@ public MinecartVisual(VisualizationContext ctx, T entity, float partialTick, Mod updateLight(partialTick); } - private static ModelHolder createBodyModelHolder(ModelLayerLocation layer) { - return new ModelHolder(() -> { - return new SingleMeshModel(ModelPartConverter.convert(layer), MATERIAL); - }); - } - - private TransformedInstance createBodyInstance() { - return instancerProvider().instancer(InstanceTypes.TRANSFORMED, bodyModel.get()) - .createInstance(); - } - @Nullable private TransformedInstance createContentsInstance() { RenderShape shape = blockState.getRenderShape(); if (shape == RenderShape.ENTITYBLOCK_ANIMATED) { - body.setZeroTransform(); + instances.traverse(instance -> instance.setZeroTransform().setChanged()); active = false; return null; } @@ -135,14 +108,14 @@ public void beginFrame(DynamicVisual.Context context) { } private void updateInstances(float partialTick) { - stack.setIdentity(); + stack.identity(); double posX = Mth.lerp(partialTick, entity.xOld, entity.getX()); double posY = Mth.lerp(partialTick, entity.yOld, entity.getY()); double posZ = Mth.lerp(partialTick, entity.zOld, entity.getZ()); var renderOrigin = renderOrigin(); - stack.translate(posX - renderOrigin.getX(), posY - renderOrigin.getY(), posZ - renderOrigin.getZ()); + stack.translate((float) (posX - renderOrigin.getX()), (float) (posY - renderOrigin.getY()), (float) (posZ - renderOrigin.getZ())); float yaw = Mth.lerp(partialTick, entity.yRotO, entity.getYRot()); long randomBits = entity.getId() * 493286711L; @@ -166,7 +139,7 @@ private void updateInstances(float partialTick) { offset2 = pos; } - stack.translate(pos.x - posX, (offset1.y + offset2.y) / 2.0D - posY, pos.z - posZ); + stack.translate((float) (pos.x - posX), (float) ((offset1.y + offset2.y) / 2.0D - posY), (float) (pos.z - posZ)); Vec3 vec = offset2.add(-offset1.x, -offset1.y, -offset1.z); if (vec.length() != 0.0D) { vec = vec.normalize(); @@ -175,9 +148,9 @@ private void updateInstances(float partialTick) { } } - stack.translate(0.0D, 0.375D, 0.0D); - stack.mulPose(Axis.YP.rotationDegrees(180 - yaw)); - stack.mulPose(Axis.ZP.rotationDegrees(-pitch)); + stack.translate(0.0F, 0.375F, 0.0F); + stack.rotateY((180 - yaw) * Mth.DEG_TO_RAD); + stack.rotateZ(-pitch * Mth.DEG_TO_RAD); float hurtTime = entity.getHurtTime() - partialTick; float damage = entity.getDamage() - partialTick; @@ -187,40 +160,44 @@ private void updateInstances(float partialTick) { } if (hurtTime > 0) { - stack.mulPose(Axis.XP.rotationDegrees(Mth.sin(hurtTime) * hurtTime * damage / 10.0F * (float) entity.getHurtDir())); + stack.rotateX((Mth.sin(hurtTime) * hurtTime * damage / 10.0F * (float) entity.getHurtDir()) * Mth.DEG_TO_RAD); } - int displayOffset = entity.getDisplayOffset(); if (contents != null) { - stack.pushPose(); + int displayOffset = entity.getDisplayOffset(); + stack.pushMatrix(); stack.scale(0.75F, 0.75F, 0.75F); stack.translate(-0.5F, (float) (displayOffset - 8) / 16, 0.5F); - stack.mulPose(Axis.YP.rotationDegrees(90)); + stack.rotateY(90 * Mth.DEG_TO_RAD); updateContents(contents, stack, partialTick); - stack.popPose(); + stack.popMatrix(); } stack.scale(-1.0F, -1.0F, 1.0F); - body.setTransform(stack) - .setChanged(); + instances.updateInstances(stack); // TODO: Use LightUpdatedVisual/ShaderLightVisual if possible. updateLight(partialTick); } - protected void updateContents(TransformedInstance contents, PoseStack stack, float partialTick) { - contents.setTransform(stack) + protected void updateContents(TransformedInstance contents, Matrix4f pose, float partialTick) { + contents.setTransform(pose) .setChanged(); } public void updateLight(float partialTick) { - relight(partialTick, body, contents); + int packedLight = computePackedLight(partialTick); + instances.traverse(instance -> { + instance.light(packedLight) + .setChanged(); + }); + FlatLit.relight(packedLight, contents); } @Override protected void _delete() { super._delete(); - body.delete(); + instances.delete(); if (contents != null) { contents.delete(); } diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/TntMinecartVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/TntMinecartVisual.java index 223c511b9..4ba546586 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/TntMinecartVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/TntMinecartVisual.java @@ -1,9 +1,10 @@ package dev.engine_room.flywheel.vanilla; -import com.mojang.blaze3d.vertex.PoseStack; +import org.joml.Matrix4f; import dev.engine_room.flywheel.api.visualization.VisualizationContext; import dev.engine_room.flywheel.lib.instance.TransformedInstance; +import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.client.renderer.texture.OverlayTexture; import net.minecraft.util.Mth; import net.minecraft.world.entity.vehicle.MinecartTNT; @@ -12,11 +13,11 @@ public class TntMinecartVisual extends MinecartVisual private static final int WHITE_OVERLAY = OverlayTexture.pack(OverlayTexture.u(1.0F), 10); public TntMinecartVisual(VisualizationContext ctx, T entity, float partialTick) { - super(ctx, entity, partialTick, TNT_BODY_MODEL); + super(ctx, entity, partialTick, ModelLayers.TNT_MINECART); } @Override - protected void updateContents(TransformedInstance contents, PoseStack stack, float partialTick) { + protected void updateContents(TransformedInstance contents, Matrix4f pose, float partialTick) { int fuseTime = entity.getFuse(); if (fuseTime > -1 && (float) fuseTime - partialTick + 1.0F < 10.0F) { float f = 1.0F - ((float) fuseTime - partialTick + 1.0F) / 10.0F; @@ -24,7 +25,7 @@ protected void updateContents(TransformedInstance contents, PoseStack stack, flo f *= f; f *= f; float scale = 1.0F + f * 0.3F; - stack.scale(scale, scale, scale); + pose.scale(scale); } int overlay; @@ -34,7 +35,7 @@ protected void updateContents(TransformedInstance contents, PoseStack stack, flo overlay = OverlayTexture.NO_OVERLAY; } - contents.setTransform(stack) + contents.setTransform(pose) .overlay(overlay) .setChanged(); } diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java index ce7410d18..53efc4562 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/VanillaVisuals.java @@ -3,6 +3,7 @@ import static dev.engine_room.flywheel.lib.visualization.SimpleBlockEntityVisualizer.builder; import static dev.engine_room.flywheel.lib.visualization.SimpleEntityVisualizer.builder; +import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.world.entity.EntityType; import net.minecraft.world.level.block.entity.BlockEntityType; @@ -47,27 +48,27 @@ public static void init() { .apply(); builder(EntityType.CHEST_MINECART) - .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, MinecartVisual.CHEST_BODY_MODEL)) + .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.CHEST_MINECART)) .skipVanillaRender(MinecartVisual::shouldSkipRender) .apply(); builder(EntityType.COMMAND_BLOCK_MINECART) - .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, MinecartVisual.COMMAND_BLOCK_BODY_MODEL)) + .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.COMMAND_BLOCK_MINECART)) .skipVanillaRender(MinecartVisual::shouldSkipRender) .apply(); builder(EntityType.FURNACE_MINECART) - .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, MinecartVisual.FURNACE_BODY_MODEL)) + .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.FURNACE_MINECART)) .skipVanillaRender(MinecartVisual::shouldSkipRender) .apply(); builder(EntityType.HOPPER_MINECART) - .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, MinecartVisual.HOPPER_BODY_MODEL)) + .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.HOPPER_MINECART)) .skipVanillaRender(MinecartVisual::shouldSkipRender) .apply(); builder(EntityType.MINECART) - .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, MinecartVisual.STANDARD_BODY_MODEL)) + .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.MINECART)) .skipVanillaRender(MinecartVisual::shouldSkipRender) .apply(); builder(EntityType.SPAWNER_MINECART) - .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, MinecartVisual.SPAWNER_BODY_MODEL)) + .factory((ctx, entity, partialTick) -> new MinecartVisual<>(ctx, entity, partialTick, ModelLayers.SPAWNER_MINECART)) .skipVanillaRender(MinecartVisual::shouldSkipRender) .apply(); builder(EntityType.TNT_MINECART) From 31b3507d62a36baa011ef1ea2977e7ae7d08e38b Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 15 Sep 2024 14:38:15 -0700 Subject: [PATCH 19/26] One big happy family - Add ModelTree - Add LoweringVisitor to traverse a MeshTree and emit ModelTree nodes and Models - Provide some default visitor creation methods - Abstract ModelCache -> ResourceReloadCache - Abstract ModelHolder -> ResourceReloadHolder - Add ModelTreeCache to hide lookup cost if it gets extreme --- .../flywheel/lib/model/ModelCache.java | 33 +----- .../flywheel/lib/model/ModelHolder.java | 53 +-------- .../lib/model/ResourceReloadCache.java | 44 +++++++ .../lib/model/ResourceReloadHolder.java | 60 ++++++++++ .../flywheel/lib/model/part/InstanceTree.java | 43 ++----- .../lib/model/part/LoweringVisitor.java | 93 +++++++++++++++ .../flywheel/lib/model/part/ModelTree.java | 107 ++++++++++++++++++ .../lib/model/part/ModelTreeCache.java | 15 +++ .../flywheel/vanilla/BellVisual.java | 14 +-- .../flywheel/vanilla/ChestVisual.java | 13 ++- .../flywheel/vanilla/MinecartVisual.java | 5 +- .../flywheel/impl/FlywheelFabric.java | 12 +- .../flywheel/impl/FlywheelForge.java | 10 +- 13 files changed, 361 insertions(+), 141 deletions(-) create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/ResourceReloadCache.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/ResourceReloadHolder.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/part/LoweringVisitor.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTree.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTreeCache.java diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelCache.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelCache.java index 9cd66069e..2b99cde80 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelCache.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelCache.java @@ -1,40 +1,11 @@ package dev.engine_room.flywheel.lib.model; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; -import org.jetbrains.annotations.ApiStatus; - import dev.engine_room.flywheel.api.model.Model; -import dev.engine_room.flywheel.lib.util.FlwUtil; - -public final class ModelCache { - private static final Set> ALL = FlwUtil.createWeakHashSet(); - private final Function factory; - private final Map map = new ConcurrentHashMap<>(); +public final class ModelCache extends ResourceReloadCache { public ModelCache(Function factory) { - this.factory = factory; - - synchronized (ALL) { - ALL.add(this); - } - } - - public Model get(T key) { - return map.computeIfAbsent(key, factory); - } - - public void clear() { - map.clear(); - } - - @ApiStatus.Internal - public static void onEndClientResourceReload() { - for (ModelCache cache : ALL) { - cache.clear(); - } + super(factory); } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelHolder.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelHolder.java index 7ca5f5199..7e838bf70 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelHolder.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelHolder.java @@ -1,60 +1,11 @@ package dev.engine_room.flywheel.lib.model; -import java.util.Set; import java.util.function.Supplier; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - import dev.engine_room.flywheel.api.model.Model; -import dev.engine_room.flywheel.lib.util.FlwUtil; - -public final class ModelHolder { - private static final Set ALL = FlwUtil.createWeakHashSet(); - private final Supplier factory; - @Nullable - private volatile Model model; +public final class ModelHolder extends ResourceReloadHolder { public ModelHolder(Supplier factory) { - this.factory = factory; - - synchronized (ALL) { - ALL.add(this); - } - } - - public Model get() { - Model model = this.model; - - if (model == null) { - synchronized (this) { - model = this.model; - if (model == null) { - this.model = model = factory.get(); - } - } - } - - return model; - } - - public void clear() { - Model model = this.model; - - if (model != null) { - synchronized (this) { - model = this.model; - if (model != null) { - this.model = null; - } - } - } - } - - @ApiStatus.Internal - public static void onEndClientResourceReload() { - for (ModelHolder holder : ALL) { - holder.clear(); - } + super(factory); } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ResourceReloadCache.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ResourceReloadCache.java new file mode 100644 index 000000000..363cf61b2 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ResourceReloadCache.java @@ -0,0 +1,44 @@ +package dev.engine_room.flywheel.lib.model; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +import org.jetbrains.annotations.ApiStatus; + +import dev.engine_room.flywheel.lib.util.FlwUtil; + +public class ResourceReloadCache implements Function { + private static final Set> ALL = FlwUtil.createWeakHashSet(); + private final Function factory; + private final Map map = new ConcurrentHashMap<>(); + + public ResourceReloadCache(Function factory) { + this.factory = factory; + + synchronized (ALL) { + ALL.add(this); + } + } + + public final U get(T key) { + return map.computeIfAbsent(key, factory); + } + + @Override + public final U apply(T t) { + return get(t); + } + + public final void clear() { + map.clear(); + } + + @ApiStatus.Internal + public static void onEndClientResourceReload() { + for (ResourceReloadCache cache : ALL) { + cache.clear(); + } + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ResourceReloadHolder.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ResourceReloadHolder.java new file mode 100644 index 000000000..dd00c5e2a --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ResourceReloadHolder.java @@ -0,0 +1,60 @@ +package dev.engine_room.flywheel.lib.model; + +import java.util.Set; +import java.util.function.Supplier; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import dev.engine_room.flywheel.lib.util.FlwUtil; + +public class ResourceReloadHolder implements Supplier { + private static final Set> ALL = FlwUtil.createWeakHashSet(); + private final Supplier factory; + @Nullable + private volatile T obj; + + public ResourceReloadHolder(Supplier factory) { + this.factory = factory; + + synchronized (ALL) { + ALL.add(this); + } + } + + @Override + public final T get() { + T obj = this.obj; + + if (obj == null) { + synchronized (this) { + obj = this.obj; + if (obj == null) { + this.obj = obj = factory.get(); + } + } + } + + return obj; + } + + public final void clear() { + T obj = this.obj; + + if (obj != null) { + synchronized (this) { + obj = this.obj; + if (obj != null) { + this.obj = null; + } + } + } + } + + @ApiStatus.Internal + public static void onEndClientResourceReload() { + for (ResourceReloadHolder holder : ALL) { + holder.clear(); + } + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java index 48cb078bc..da3d64bd6 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/InstanceTree.java @@ -1,7 +1,6 @@ package dev.engine_room.flywheel.lib.model.part; import java.util.NoSuchElementException; -import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.ObjIntConsumer; @@ -15,23 +14,16 @@ import com.mojang.blaze3d.vertex.PoseStack; import dev.engine_room.flywheel.api.instance.InstancerProvider; -import dev.engine_room.flywheel.api.material.Material; -import dev.engine_room.flywheel.api.model.Mesh; import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.lib.instance.InstanceTypes; import dev.engine_room.flywheel.lib.instance.TransformedInstance; -import dev.engine_room.flywheel.lib.model.ModelCache; -import dev.engine_room.flywheel.lib.model.SingleMeshModel; import dev.engine_room.flywheel.lib.transform.Affine; import dev.engine_room.flywheel.lib.transform.TransformStack; -import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.ModelPart; import net.minecraft.client.model.geom.PartPose; public final class InstanceTree { - private static final ModelCache MODEL_CACHE = new ModelCache<>(entry -> new SingleMeshModel(entry.mesh(), entry.material())); - - private final MeshTree source; + private final ModelTree source; @Nullable private final TransformedInstance instance; private final InstanceTree[] children; @@ -54,7 +46,7 @@ public final class InstanceTree { private boolean changed; - private InstanceTree(MeshTree source, @Nullable TransformedInstance instance, InstanceTree[] children) { + private InstanceTree(ModelTree source, @Nullable TransformedInstance instance, InstanceTree[] children) { this.source = source; this.instance = instance; this.children = children; @@ -68,21 +60,16 @@ private InstanceTree(MeshTree source, @Nullable TransformedInstance instance, In resetPose(); } - private static InstanceTree create(InstancerProvider provider, MeshTree meshTree, BiFunction meshFinalizerFunc, String path) { + public static InstanceTree create(InstancerProvider provider, ModelTree meshTree) { InstanceTree[] children = new InstanceTree[meshTree.childCount()]; - String pathSlash = path + "/"; - for (int i = 0; i < meshTree.childCount(); i++) { - var meshTreeChild = meshTree.child(i); - String name = meshTree.childName(i); - children[i] = create(provider, meshTreeChild, meshFinalizerFunc, pathSlash + name); + children[i] = create(provider, meshTree.child(i)); } - Mesh mesh = meshTree.mesh(); + Model model = meshTree.model(); TransformedInstance instance; - if (mesh != null) { - Model.ConfiguredMesh configuredMesh = meshFinalizerFunc.apply(path, mesh); - instance = provider.instancer(InstanceTypes.TRANSFORMED, MODEL_CACHE.get(configuredMesh)) + if (model != null) { + instance = provider.instancer(InstanceTypes.TRANSFORMED, model) .createInstance(); } else { instance = null; @@ -91,22 +78,6 @@ private static InstanceTree create(InstancerProvider provider, MeshTree meshTree return new InstanceTree(meshTree, instance, children); } - public static InstanceTree create(InstancerProvider provider, MeshTree meshTree, BiFunction meshFinalizerFunc) { - return create(provider, meshTree, meshFinalizerFunc, ""); - } - - public static InstanceTree create(InstancerProvider provider, ModelLayerLocation layer, BiFunction meshFinalizerFunc) { - return create(provider, MeshTree.of(layer), meshFinalizerFunc); - } - - public static InstanceTree create(InstancerProvider provider, MeshTree meshTree, Material material) { - return create(provider, meshTree, (path, mesh) -> new Model.ConfiguredMesh(material, mesh)); - } - - public static InstanceTree create(InstancerProvider provider, ModelLayerLocation layer, Material material) { - return create(provider, MeshTree.of(layer), material); - } - @Nullable public TransformedInstance instance() { return instance; diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/LoweringVisitor.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/LoweringVisitor.java new file mode 100644 index 000000000..8b5d30af1 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/LoweringVisitor.java @@ -0,0 +1,93 @@ +package dev.engine_room.flywheel.lib.model.part; + +import java.util.ArrayList; + +import org.jetbrains.annotations.Nullable; + +import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.api.model.Mesh; +import dev.engine_room.flywheel.api.model.Model; +import dev.engine_room.flywheel.lib.model.RetexturedMesh; +import dev.engine_room.flywheel.lib.model.SingleMeshModel; +import net.minecraft.client.model.geom.PartPose; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; + +/** + * A tree walking visitor that lowers a MeshTree to a ModelTree. + */ +public interface LoweringVisitor { + static ModelTree leaf(Model model) { + return leaf(model, PartPose.ZERO); + } + + static ModelTree leaf(Model model, PartPose initialPose) { + return ModelTree.create(model, initialPose, new ModelTree[0], new String[0]); + } + + static LoweringVisitor materialApplyingVisitor(Material material) { + return (path, mesh) -> new SingleMeshModel(mesh, material); + } + + static LoweringVisitor retexturingVisitor(Material material, TextureAtlasSprite sprite) { + return (path, mesh) -> new SingleMeshModel(new RetexturedMesh(mesh, sprite), material); + } + + static String append(String path, String child) { + if (path.isEmpty()) { + return child; + } + + return path + "/" + child; + } + + /** + * Walk the given MeshTree, lowering its Mesh to a Model, and lowering all children using the given visitor. + * + * @param path The absolute path to the MeshTree node. + * @param meshTree The MeshTree to walk. + * @param loweringVisitor The visitor to use to lower the Mesh and MeshTree nodes. + * @return The lowered ModelTree. + */ + static ModelTree walk(String path, MeshTree meshTree, LoweringVisitor loweringVisitor) { + Model out = null; + + if (meshTree.mesh() != null) { + out = loweringVisitor.visit(path, meshTree.mesh()); + } + + ArrayList children = new ArrayList<>(); + ArrayList childNames = new ArrayList<>(); + + for (int i = 0; i < meshTree.childCount(); i++) { + var child = loweringVisitor.visit(append(path, meshTree.childName(i)), meshTree.child(i)); + + if (child != null) { + children.add(child); + childNames.add(meshTree.childName(i)); + } + } + + return ModelTree.create(out, meshTree.initialPose(), children.toArray(new ModelTree[0]), childNames.toArray(new String[0])); + } + + /** + * Visit the given Mesh, converting it to a Model. + * + * @param path The absolute path to the MeshTree node containing the Mesh. + * @param mesh The Mesh to lower. + * @return The lowered Model, or null if the Model should be omitted. + */ + @Nullable Model visit(String path, Mesh mesh); + + /** + * Visit the given MeshTree, converting it to a ModelTree. + * + * @param path The absolute path to the MeshTree node. + * @param meshTree The MeshTree to lower. + * @return The lowered ModelTree, or null if the ModelTree should be omitted. + */ + @Nullable + default ModelTree visit(String path, MeshTree meshTree) { + return walk(path, meshTree, this); + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTree.java new file mode 100644 index 000000000..293394bef --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTree.java @@ -0,0 +1,107 @@ +package dev.engine_room.flywheel.lib.model.part; + +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import dev.engine_room.flywheel.api.model.Model; +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.client.model.geom.PartPose; + +public class ModelTree { + private static final Map CACHE = new ConcurrentHashMap<>(); + + @Nullable + private final Model model; + private final PartPose initialPose; + private final ModelTree[] children; + private final String[] childNames; + + private ModelTree(@Nullable Model model, PartPose initialPose, ModelTree[] children, String[] childNames) { + this.model = model; + this.initialPose = initialPose; + this.children = children; + this.childNames = childNames; + } + + /** + * Create and memoize a ModelTree root. + * + *

This method is intended for use by Visuals. + * + * @param modelLayerLocation The model location to lower. + * @param loweringVisitor The visitor to use to lower the model. + * @return The cached ModelTree root. + */ + public static ModelTree of(ModelLayerLocation modelLayerLocation, LoweringVisitor loweringVisitor) { + return CACHE.computeIfAbsent(new ModelTreeKey(modelLayerLocation, loweringVisitor), k -> { + var meshTree = MeshTree.of(k.modelLayerLocation()); + + var out = k.loweringVisitor() + .visit("", meshTree); + + if (out == null) { + // Should this be an error, or a missing model? + return ModelTree.create(null, PartPose.ZERO, new ModelTree[0], new String[0]); + } + + return out; + }); + } + + /** + * Create a new ModelTree node. + * + *

This method is intended for use by {@link LoweringVisitor} implementations. + * + * @param model The model to associate with this node, or null if this node does not render. + * @param initialPose The initial pose of this node. + * @param children The children of this node. + * @param childNames The names of the children of this node. + * @return A new ModelTree node. + * @throws IllegalArgumentException if children and childNames have different lengths. + */ + public static ModelTree create(@Nullable Model model, PartPose initialPose, ModelTree[] children, String[] childNames) { + if (children.length != childNames.length) { + throw new IllegalArgumentException("children and childNames must have the same length (%s != %s)".formatted(children.length, childNames.length)); + } + + return new ModelTree(model, initialPose, children, childNames); + } + + public int childCount() { + return children.length; + } + + public ModelTree child(int index) { + return children[index]; + } + + public String childName(int index) { + return childNames[index]; + } + + public PartPose initialPose() { + return initialPose; + } + + @Nullable + public Model model() { + return model; + } + + public int childIndex(String name) { + return Arrays.binarySearch(childNames, name); + } + + @ApiStatus.Internal + public static void onEndClientResourceReload() { + CACHE.clear(); + } + + private record ModelTreeKey(ModelLayerLocation modelLayerLocation, LoweringVisitor loweringVisitor) { + } +} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTreeCache.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTreeCache.java new file mode 100644 index 000000000..bf9274965 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTreeCache.java @@ -0,0 +1,15 @@ +package dev.engine_room.flywheel.lib.model.part; + +import java.util.function.Function; + +import dev.engine_room.flywheel.lib.model.ResourceReloadCache; + +/** + * If the lookup to create your model tree directly through {@link ModelTree#of} is particularly expensive, + * you can memoize the arguments here to hide the cost. + */ +public class ModelTreeCache extends ResourceReloadCache { + public ModelTreeCache(Function factory) { + super(factory); + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java index c5cb2af14..1d69910ed 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java @@ -7,16 +7,16 @@ import dev.engine_room.flywheel.api.instance.Instance; import dev.engine_room.flywheel.api.material.Material; -import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.api.visualization.VisualizationContext; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.RetexturedMesh; +import dev.engine_room.flywheel.lib.model.ResourceReloadHolder; import dev.engine_room.flywheel.lib.model.part.InstanceTree; +import dev.engine_room.flywheel.lib.model.part.LoweringVisitor; +import dev.engine_room.flywheel.lib.model.part.ModelTree; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.client.renderer.blockentity.BellRenderer; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.core.BlockPos; import net.minecraft.util.Mth; import net.minecraft.world.level.block.entity.BellBlockEntity; @@ -26,6 +26,9 @@ public class BellVisual extends AbstractBlockEntityVisual imple .mipmap(false) .build(); + // Need to hold the visitor in a ResourceReloadHolder to ensure we have a valid sprite. + private static final ResourceReloadHolder VISITOR = new ResourceReloadHolder<>(() -> LoweringVisitor.retexturingVisitor(MATERIAL, BellRenderer.BELL_RESOURCE_LOCATION.sprite())); + private final InstanceTree instances; private final InstanceTree bellBody; @@ -36,10 +39,7 @@ public class BellVisual extends AbstractBlockEntityVisual imple public BellVisual(VisualizationContext ctx, BellBlockEntity blockEntity, float partialTick) { super(ctx, blockEntity, partialTick); - TextureAtlasSprite sprite = BellRenderer.BELL_RESOURCE_LOCATION.sprite(); - instances = InstanceTree.create(instancerProvider(), ModelLayers.BELL, (path, mesh) -> { - return new Model.ConfiguredMesh(MATERIAL, new RetexturedMesh(mesh, sprite)); - }); + instances = InstanceTree.create(instancerProvider(), ModelTree.of(ModelLayers.BELL, VISITOR.get())); bellBody = instances.childOrThrow("bell_body"); BlockPos visualPos = getVisualPosition(); diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java index 90063c138..d6e0e6a88 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java @@ -4,6 +4,7 @@ import java.util.EnumMap; import java.util.Map; import java.util.function.Consumer; +import java.util.function.Function; import org.jetbrains.annotations.Nullable; import org.joml.Matrix4f; @@ -15,8 +16,11 @@ import dev.engine_room.flywheel.api.visualization.VisualizationContext; import dev.engine_room.flywheel.lib.material.CutoutShaders; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.RetexturedMesh; +import dev.engine_room.flywheel.lib.model.ResourceReloadCache; import dev.engine_room.flywheel.lib.model.part.InstanceTree; +import dev.engine_room.flywheel.lib.model.part.LoweringVisitor; +import dev.engine_room.flywheel.lib.model.part.ModelTree; +import dev.engine_room.flywheel.lib.transform.TransformStack; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import it.unimi.dsi.fastutil.floats.Float2FloatFunction; @@ -53,6 +57,8 @@ public class ChestVisual extends Abstrac LAYER_LOCATIONS.put(ChestType.RIGHT, ModelLayers.DOUBLE_CHEST_RIGHT); } + private static final Function VISITOR = new ResourceReloadCache<>(s -> LoweringVisitor.retexturingVisitor(MATERIAL, s)); + @Nullable private final InstanceTree instances; @Nullable @@ -77,10 +83,7 @@ public ChestVisual(VisualizationContext ctx, T blockEntity, float partialTick) { if (block instanceof AbstractChestBlock chestBlock) { ChestType chestType = blockState.hasProperty(ChestBlock.TYPE) ? blockState.getValue(ChestBlock.TYPE) : ChestType.SINGLE; TextureAtlasSprite sprite = Sheets.chooseMaterial(blockEntity, chestType, isChristmas()).sprite(); - - instances = InstanceTree.create(instancerProvider(), LAYER_LOCATIONS.get(chestType), (path, mesh) -> { - return new Model.ConfiguredMesh(MATERIAL, new RetexturedMesh(mesh, sprite)); - }); + instances = InstanceTree.create(instancerProvider(), ModelTree.of(LAYER_LOCATIONS.get(chestType), VISITOR.apply(sprite))); lid = instances.childOrThrow("lid"); lock = instances.childOrThrow("lock"); diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java index c7310c90e..4b0ad1891 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java @@ -14,6 +14,8 @@ import dev.engine_room.flywheel.lib.material.SimpleMaterial; import dev.engine_room.flywheel.lib.model.Models; import dev.engine_room.flywheel.lib.model.part.InstanceTree; +import dev.engine_room.flywheel.lib.model.part.LoweringVisitor; +import dev.engine_room.flywheel.lib.model.part.ModelTree; import dev.engine_room.flywheel.lib.visual.ComponentEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import dev.engine_room.flywheel.lib.visual.SimpleTickableVisual; @@ -34,6 +36,7 @@ public class MinecartVisual extends ComponentEntityV .texture(TEXTURE) .mipmap(false) .build(); + private static final LoweringVisitor VISITOR = LoweringVisitor.materialApplyingVisitor(MATERIAL); private final InstanceTree instances; @Nullable @@ -47,7 +50,7 @@ public class MinecartVisual extends ComponentEntityV public MinecartVisual(VisualizationContext ctx, T entity, float partialTick, ModelLayerLocation layerLocation) { super(ctx, entity, partialTick); - instances = InstanceTree.create(instancerProvider(), layerLocation, MATERIAL); + instances = InstanceTree.create(instancerProvider(), ModelTree.of(layerLocation, VISITOR)); blockState = entity.getDisplayBlockState(); contents = createContentsInstance(); diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java index d05ebb115..46c561fa8 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java @@ -9,10 +9,11 @@ import dev.engine_room.flywheel.backend.compile.FlwProgramsReloader; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; import dev.engine_room.flywheel.impl.visualization.VisualizationEventHandler; -import dev.engine_room.flywheel.lib.model.ModelCache; -import dev.engine_room.flywheel.lib.model.ModelHolder; +import dev.engine_room.flywheel.lib.model.ResourceReloadCache; +import dev.engine_room.flywheel.lib.model.ResourceReloadHolder; import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler; import dev.engine_room.flywheel.lib.model.part.MeshTree; +import dev.engine_room.flywheel.lib.model.part.ModelTree; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; @@ -68,12 +69,11 @@ private static void setupImpl() { } private static void setupLib() { - EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> - ModelCache.onEndClientResourceReload()); - EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> - ModelHolder.onEndClientResourceReload()); + EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> ResourceReloadCache.onEndClientResourceReload()); + EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> ResourceReloadHolder.onEndClientResourceReload()); EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> MeshTree.onEndClientResourceReload()); + EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> ModelTree.onEndClientResourceReload()); ModelLoadingPlugin.register(ctx -> { ctx.addModels(PartialModelEventHandler.onRegisterAdditional()); diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java b/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java index 8fb860d6a..763324819 100644 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java @@ -10,10 +10,11 @@ import dev.engine_room.flywheel.backend.compile.FlwProgramsReloader; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; import dev.engine_room.flywheel.impl.visualization.VisualizationEventHandler; -import dev.engine_room.flywheel.lib.model.ModelCache; -import dev.engine_room.flywheel.lib.model.ModelHolder; +import dev.engine_room.flywheel.lib.model.ResourceReloadCache; +import dev.engine_room.flywheel.lib.model.ResourceReloadHolder; import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler; import dev.engine_room.flywheel.lib.model.part.MeshTree; +import dev.engine_room.flywheel.lib.model.part.ModelTree; import dev.engine_room.flywheel.lib.util.LevelAttached; import net.minecraft.client.Minecraft; import net.minecraft.commands.synchronization.ArgumentTypeInfos; @@ -110,9 +111,10 @@ private static void registerImplEventListeners(IEventBus forgeEventBus, IEventBu private static void registerLibEventListeners(IEventBus forgeEventBus, IEventBus modEventBus) { forgeEventBus.addListener((LevelEvent.Unload e) -> LevelAttached.invalidateLevel(e.getLevel())); - modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelCache.onEndClientResourceReload()); - modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelHolder.onEndClientResourceReload()); + modEventBus.addListener((EndClientResourceReloadEvent e) -> ResourceReloadCache.onEndClientResourceReload()); + modEventBus.addListener((EndClientResourceReloadEvent e) -> ResourceReloadHolder.onEndClientResourceReload()); modEventBus.addListener((EndClientResourceReloadEvent e) -> MeshTree.onEndClientResourceReload()); + modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelTree.onEndClientResourceReload()); modEventBus.addListener(PartialModelEventHandler::onRegisterAdditional); modEventBus.addListener(PartialModelEventHandler::onBakingCompleted); From d342ae740c9b1a58a7bbfb2b1c1ebe67bd1d54cf Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 15 Sep 2024 15:29:09 -0700 Subject: [PATCH 20/26] Get shulked - Convert ShulkerBoxVisual to use InstanceTree - Add "pruning" helper visitors - Remove ModelPartConverter - Remove TextureMapper and related code from VertexWriter --- .../lib/model/part/LoweringVisitor.java | 39 +++++++- .../lib/model/part/ModelPartConverter.java | 70 --------------- .../flywheel/lib/model/part/VertexWriter.java | 19 ---- .../flywheel/vanilla/BellVisual.java | 2 +- .../flywheel/vanilla/ChestVisual.java | 4 +- .../flywheel/vanilla/MinecartVisual.java | 2 +- .../flywheel/vanilla/ShulkerBoxVisual.java | 90 ++++++++----------- 7 files changed, 75 insertions(+), 151 deletions(-) delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/LoweringVisitor.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/LoweringVisitor.java index 8b5d30af1..29800551b 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/LoweringVisitor.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/LoweringVisitor.java @@ -1,6 +1,7 @@ package dev.engine_room.flywheel.lib.model.part; import java.util.ArrayList; +import java.util.Set; import org.jetbrains.annotations.Nullable; @@ -24,14 +25,48 @@ static ModelTree leaf(Model model, PartPose initialPose) { return ModelTree.create(model, initialPose, new ModelTree[0], new String[0]); } - static LoweringVisitor materialApplyingVisitor(Material material) { + static LoweringVisitor create(Material material) { return (path, mesh) -> new SingleMeshModel(mesh, material); } - static LoweringVisitor retexturingVisitor(Material material, TextureAtlasSprite sprite) { + static LoweringVisitor create(Material material, TextureAtlasSprite sprite) { return (path, mesh) -> new SingleMeshModel(new RetexturedMesh(mesh, sprite), material); } + static LoweringVisitor pruning(Set prune, Material material) { + return new LoweringVisitor() { + @Override + public @Nullable ModelTree visit(String path, MeshTree meshTree) { + if (prune.contains(path)) { + return null; + } + return LoweringVisitor.super.visit(path, meshTree); + } + + @Override + public Model visit(String path, Mesh mesh) { + return new SingleMeshModel(mesh, material); + } + }; + } + + static LoweringVisitor pruning(Set prune, Material material, TextureAtlasSprite sprite) { + return new LoweringVisitor() { + @Override + public @Nullable ModelTree visit(String path, MeshTree meshTree) { + if (prune.contains(path)) { + return null; + } + return LoweringVisitor.super.visit(path, meshTree); + } + + @Override + public Model visit(String path, Mesh mesh) { + return new SingleMeshModel(new RetexturedMesh(mesh, sprite), material); + } + }; + } + static String append(String path, String child) { if (path.isEmpty()) { return child; diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java deleted file mode 100644 index b5999cdac..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelPartConverter.java +++ /dev/null @@ -1,70 +0,0 @@ -package dev.engine_room.flywheel.lib.model.part; - -import org.jetbrains.annotations.Nullable; -import org.joml.Vector2f; - -import com.mojang.blaze3d.vertex.PoseStack; - -import dev.engine_room.flywheel.api.model.Mesh; -import dev.engine_room.flywheel.lib.memory.MemoryBlock; -import dev.engine_room.flywheel.lib.model.SimpleQuadMesh; -import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView; -import dev.engine_room.flywheel.lib.vertex.VertexView; -import net.minecraft.client.Minecraft; -import net.minecraft.client.model.geom.EntityModelSet; -import net.minecraft.client.model.geom.ModelLayerLocation; -import net.minecraft.client.model.geom.ModelPart; -import net.minecraft.client.renderer.LightTexture; -import net.minecraft.client.renderer.texture.OverlayTexture; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; - -@Deprecated(forRemoval = true) -public final class ModelPartConverter { - private static final ThreadLocal THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new); - - private ModelPartConverter() { - } - - public static Mesh convert(ModelPart modelPart, @Nullable PoseStack poseStack, @Nullable TextureMapper textureMapper) { - ThreadLocalObjects objects = THREAD_LOCAL_OBJECTS.get(); - if (poseStack == null) { - poseStack = objects.identityPoseStack; - } - VertexWriter vertexWriter = objects.vertexWriter; - - vertexWriter.setTextureMapper(textureMapper); - modelPart.render(poseStack, vertexWriter, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY); - MemoryBlock data = vertexWriter.copyDataAndReset(); - - VertexView vertexView = new PosTexNormalVertexView(); - vertexView.load(data); - return new SimpleQuadMesh(vertexView, "source=ModelPartConverter"); - } - - public static Mesh convert(ModelLayerLocation layer, @Nullable TextureAtlasSprite sprite, String... childPath) { - EntityModelSet entityModels = Minecraft.getInstance().getEntityModels(); - ModelPart modelPart = entityModels.bakeLayer(layer); - for (String pathPart : childPath) { - modelPart = modelPart.getChild(pathPart); - } - TextureMapper textureMapper = sprite == null ? null : TextureMapper.toSprite(sprite); - return convert(modelPart, null, textureMapper); - } - - public static Mesh convert(ModelLayerLocation layer, String... childPath) { - return convert(layer, null, childPath); - } - - public interface TextureMapper { - void map(Vector2f uv); - - static TextureMapper toSprite(TextureAtlasSprite sprite) { - return uv -> uv.set(sprite.getU(uv.x * 16), sprite.getV(uv.y * 16)); - } - } - - private static class ThreadLocalObjects { - public final PoseStack identityPoseStack = new PoseStack(); - public final VertexWriter vertexWriter = new VertexWriter(); - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/VertexWriter.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/VertexWriter.java index 30855a85e..9f247cfa9 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/VertexWriter.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/VertexWriter.java @@ -1,14 +1,11 @@ package dev.engine_room.flywheel.lib.model.part; -import org.jetbrains.annotations.Nullable; -import org.joml.Vector2f; import org.lwjgl.system.MemoryUtil; import com.mojang.blaze3d.vertex.VertexConsumer; import dev.engine_room.flywheel.lib.math.DataPacker; import dev.engine_room.flywheel.lib.memory.MemoryBlock; -import dev.engine_room.flywheel.lib.model.part.ModelPartConverter.TextureMapper; import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView; class VertexWriter implements VertexConsumer { @@ -16,10 +13,6 @@ class VertexWriter implements VertexConsumer { private MemoryBlock data; - @Nullable - private TextureMapper textureMapper; - private final Vector2f uvVec = new Vector2f(); - private int vertexCount; private boolean filledPosition; private boolean filledTexture; @@ -29,10 +22,6 @@ public VertexWriter() { data = MemoryBlock.malloc(128 * STRIDE); } - public void setTextureMapper(@Nullable TextureMapper mapper) { - textureMapper = mapper; - } - @Override public VertexConsumer vertex(double x, double y, double z) { if (!filledPosition) { @@ -54,13 +43,6 @@ public VertexConsumer color(int red, int green, int blue, int alpha) { @Override public VertexConsumer uv(float u, float v) { if (!filledTexture) { - if (textureMapper != null) { - uvVec.set(u, v); - textureMapper.map(uvVec); - u = uvVec.x; - v = uvVec.y; - } - long ptr = vertexPtr(); MemoryUtil.memPutFloat(ptr + 12, u); MemoryUtil.memPutFloat(ptr + 16, v); @@ -131,7 +113,6 @@ public MemoryBlock copyDataAndReset() { filledPosition = false; filledTexture = false; filledNormal = false; - textureMapper = null; return dataCopy; } diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java index 1d69910ed..080bc2005 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java @@ -27,7 +27,7 @@ public class BellVisual extends AbstractBlockEntityVisual imple .build(); // Need to hold the visitor in a ResourceReloadHolder to ensure we have a valid sprite. - private static final ResourceReloadHolder VISITOR = new ResourceReloadHolder<>(() -> LoweringVisitor.retexturingVisitor(MATERIAL, BellRenderer.BELL_RESOURCE_LOCATION.sprite())); + private static final ResourceReloadHolder VISITOR = new ResourceReloadHolder<>(() -> LoweringVisitor.create(MATERIAL, BellRenderer.BELL_RESOURCE_LOCATION.sprite())); private final InstanceTree instances; private final InstanceTree bellBody; diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java index d6e0e6a88..39af485fc 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java @@ -12,7 +12,6 @@ import dev.engine_room.flywheel.api.instance.Instance; import dev.engine_room.flywheel.api.material.Material; -import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.api.visualization.VisualizationContext; import dev.engine_room.flywheel.lib.material.CutoutShaders; import dev.engine_room.flywheel.lib.material.SimpleMaterial; @@ -20,7 +19,6 @@ import dev.engine_room.flywheel.lib.model.part.InstanceTree; import dev.engine_room.flywheel.lib.model.part.LoweringVisitor; import dev.engine_room.flywheel.lib.model.part.ModelTree; -import dev.engine_room.flywheel.lib.transform.TransformStack; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import it.unimi.dsi.fastutil.floats.Float2FloatFunction; @@ -57,7 +55,7 @@ public class ChestVisual extends Abstrac LAYER_LOCATIONS.put(ChestType.RIGHT, ModelLayers.DOUBLE_CHEST_RIGHT); } - private static final Function VISITOR = new ResourceReloadCache<>(s -> LoweringVisitor.retexturingVisitor(MATERIAL, s)); + private static final Function VISITOR = new ResourceReloadCache<>(s -> LoweringVisitor.create(MATERIAL, s)); @Nullable private final InstanceTree instances; diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java index 4b0ad1891..d8348fbf1 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java @@ -36,7 +36,7 @@ public class MinecartVisual extends ComponentEntityV .texture(TEXTURE) .mipmap(false) .build(); - private static final LoweringVisitor VISITOR = LoweringVisitor.materialApplyingVisitor(MATERIAL); + private static final LoweringVisitor VISITOR = LoweringVisitor.create(MATERIAL); private final InstanceTree instances; @Nullable diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java index be5d85ba7..f3554e39e 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java @@ -1,28 +1,26 @@ package dev.engine_room.flywheel.vanilla; +import java.util.Set; import java.util.function.Consumer; +import java.util.function.Function; -import org.joml.Quaternionf; - -import com.mojang.math.Axis; +import org.joml.Matrix4f; import dev.engine_room.flywheel.api.instance.Instance; import dev.engine_room.flywheel.api.visualization.VisualizationContext; -import dev.engine_room.flywheel.lib.instance.InstanceTypes; -import dev.engine_room.flywheel.lib.instance.TransformedInstance; import dev.engine_room.flywheel.lib.material.CutoutShaders; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.ModelCache; -import dev.engine_room.flywheel.lib.model.SingleMeshModel; -import dev.engine_room.flywheel.lib.model.part.ModelPartConverter; -import dev.engine_room.flywheel.lib.transform.TransformStack; -import dev.engine_room.flywheel.lib.util.RecyclingPoseStack; +import dev.engine_room.flywheel.lib.model.ResourceReloadCache; +import dev.engine_room.flywheel.lib.model.part.InstanceTree; +import dev.engine_room.flywheel.lib.model.part.LoweringVisitor; +import dev.engine_room.flywheel.lib.model.part.ModelTree; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.client.renderer.Sheets; import net.minecraft.client.resources.model.Material; import net.minecraft.core.Direction; +import net.minecraft.util.Mth; import net.minecraft.world.item.DyeColor; import net.minecraft.world.level.block.ShulkerBoxBlock; import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity; @@ -35,17 +33,13 @@ public class ShulkerBoxVisual extends AbstractBlockEntityVisual BASE_MODELS = new ModelCache<>(texture -> { - return new SingleMeshModel(ModelPartConverter.convert(ModelLayers.SHULKER, texture.sprite(), "base"), MATERIAL); - }); - private static final ModelCache LID_MODELS = new ModelCache<>(texture -> { - return new SingleMeshModel(ModelPartConverter.convert(ModelLayers.SHULKER, texture.sprite(), "lid"), MATERIAL); - }); - private final TransformedInstance base; - private final TransformedInstance lid; + private static final Function VISITORS = new ResourceReloadCache<>(m -> LoweringVisitor.pruning(Set.of("head"), MATERIAL, m.sprite())); + + private final InstanceTree instances; + private final InstanceTree lid; - private final RecyclingPoseStack stack = new RecyclingPoseStack(); + private final Matrix4f initialPose; private float lastProgress = Float.NaN; @@ -60,31 +54,22 @@ public ShulkerBoxVisual(VisualizationContext ctx, ShulkerBoxBlockEntity blockEnt texture = Sheets.SHULKER_TEXTURE_LOCATION.get(color.getId()); } - var rotation = getDirection().getRotation(); - - stack.setIdentity(); - TransformStack.of(stack) - .translate(getVisualPosition()) - .translate(0.5f) - .scale(0.9995f) - .rotate(rotation) - .scale(1, -1, -1) - .translateY(-1); + instances = InstanceTree.create(instancerProvider(), ModelTree.of(ModelLayers.SHULKER, VISITORS.apply(texture))); - base = createBaseInstance(texture).setTransform(stack); - base.setChanged(); - lid = createLidInstance(texture).setTransform(stack); - lid.setChanged(); - } + initialPose = createInitialPose(); - private TransformedInstance createBaseInstance(Material texture) { - return instancerProvider().instancer(InstanceTypes.TRANSFORMED, BASE_MODELS.get(texture)) - .createInstance(); + lid = instances.childOrThrow("lid"); } - private TransformedInstance createLidInstance(Material texture) { - return instancerProvider().instancer(InstanceTypes.TRANSFORMED, LID_MODELS.get(texture)) - .createInstance(); + private Matrix4f createInitialPose() { + var rotation = getDirection().getRotation(); + var visualPosition = getVisualPosition(); + return new Matrix4f().translate(visualPosition.getX(), visualPosition.getY(), visualPosition.getZ()) + .translate(0.5f, 0.5f, 0.5f) + .scale(0.9995f) + .rotate(rotation) + .scale(1, -1, -1) + .translate(0, -1, 0); } private Direction getDirection() { @@ -107,33 +92,28 @@ public void beginFrame(Context context) { } lastProgress = progress; - Quaternionf spin = Axis.YP.rotationDegrees(270.0f * progress); - - TransformStack.of(stack) - .pushPose() - .translateY(-progress * 0.5f) - .rotate(spin); - - lid.setTransform(stack) - .setChanged(); + lid.yRot(1.5f * Mth.PI * progress); + lid.yPos(24f - progress * 8f); - stack.popPose(); + instances.updateInstancesStatic(initialPose); } @Override public void updateLight(float partialTick) { - relight(base, lid); + int packedLight = computePackedLight(); + instances.traverse(instance -> { + instance.light(packedLight) + .setChanged(); + }); } @Override public void collectCrumblingInstances(Consumer consumer) { - consumer.accept(base); - consumer.accept(lid); + instances.traverse(consumer); } @Override protected void _delete() { - base.delete(); - lid.delete(); + instances.delete(); } } From 9f938e06731088c6073e3a26514f71d54937882e Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 15 Sep 2024 16:37:12 -0700 Subject: [PATCH 21/26] A slap on the wrist - Add restrict qualifier to images in downsample shaders - Early out when there are no changed pages in IndirectInstancer#uploadInstances --- .../backend/engine/indirect/IndirectInstancer.java | 4 ++++ .../internal/indirect/downsample_first.glsl | 12 ++++++------ .../internal/indirect/downsample_second.glsl | 14 +++++++------- 3 files changed, 17 insertions(+), 13 deletions(-) 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 fc8e1361e..2209069bb 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 @@ -82,6 +82,10 @@ public void writeModel(long ptr) { } public void uploadInstances(StagingBuffer stagingBuffer, int instanceVbo) { + if (changedPages.isEmpty()) { + return; + } + int numPages = mapping.pageCount(); var instanceCount = instances.size(); diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_first.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_first.glsl index 351995767..394667598 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_first.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_first.glsl @@ -1,12 +1,12 @@ #include "flywheel:internal/indirect/downsample.glsl" layout(binding = 0) uniform sampler2D mip_0; -layout(binding = 1, r32f) uniform writeonly image2D mip_1; -layout(binding = 2, r32f) uniform writeonly image2D mip_2; -layout(binding = 3, r32f) uniform writeonly image2D mip_3; -layout(binding = 4, r32f) uniform writeonly image2D mip_4; -layout(binding = 5, r32f) uniform writeonly image2D mip_5; -layout(binding = 6, r32f) uniform writeonly image2D mip_6; +layout(binding = 1, r32f) uniform restrict writeonly image2D mip_1; +layout(binding = 2, r32f) uniform restrict writeonly image2D mip_2; +layout(binding = 3, r32f) uniform restrict writeonly image2D mip_3; +layout(binding = 4, r32f) uniform restrict writeonly image2D mip_4; +layout(binding = 5, r32f) uniform restrict writeonly image2D mip_5; +layout(binding = 6, r32f) uniform restrict writeonly image2D mip_6; float reduce_load_mip_0(uvec2 tex) { // NOTE: mip_0 is the actual depth buffer, and mip_1 is the "base" of our depth pyramid and has the next diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_second.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_second.glsl index afedc061c..29ba31ea0 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_second.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/downsample_second.glsl @@ -1,12 +1,12 @@ #include "flywheel:internal/indirect/downsample.glsl" -layout(binding = 0, r32f) uniform readonly image2D mip_6; -layout(binding = 1, r32f) uniform writeonly image2D mip_7; -layout(binding = 2, r32f) uniform writeonly image2D mip_8; -layout(binding = 3, r32f) uniform writeonly image2D mip_9; -layout(binding = 4, r32f) uniform writeonly image2D mip_10; -layout(binding = 5, r32f) uniform writeonly image2D mip_11; -layout(binding = 6, r32f) uniform writeonly image2D mip_12; +layout(binding = 0, r32f) uniform restrict readonly image2D mip_6; +layout(binding = 1, r32f) uniform restrict writeonly image2D mip_7; +layout(binding = 2, r32f) uniform restrict writeonly image2D mip_8; +layout(binding = 3, r32f) uniform restrict writeonly image2D mip_9; +layout(binding = 4, r32f) uniform restrict writeonly image2D mip_10; +layout(binding = 5, r32f) uniform restrict writeonly image2D mip_11; +layout(binding = 6, r32f) uniform restrict writeonly image2D mip_12; float reduce_load_mip_6(ivec2 tex) { // NOTE: We could bind mip_6 as a sampler2D and use textureGather, From c658b2bfe3328672c21d6cb7b8fed3a55846ab22 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 15 Sep 2024 16:50:02 -0700 Subject: [PATCH 22/26] Pop! Goes the storage - Trigger an upload when an allocation is deleted --- .../flywheel/backend/engine/indirect/ObjectStorage.java | 2 ++ 1 file changed, 2 insertions(+) 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 69b0ddef4..027af4c6c 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 @@ -170,6 +170,8 @@ public void delete() { pages = EMPTY_ALLOCATION; modelIndex = -1; objectCount = 0; + + ObjectStorage.this.needsUpload = true; } /** From e1b594ac479a0b9d3b1f0ad9e7e7718a9e89a7f1 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 15 Sep 2024 21:29:00 -0700 Subject: [PATCH 23/26] Ctrl + Alt + N - IndirectInstancer#uploadInstances: 46% of render thread to 26% - Inline #enqueueCopy to avoid allocating LongConsumers - Do not even bother to track individual changed indices, instead rely on just the changedPage set --- .../engine/indirect/IndirectInstancer.java | 30 +++++++++++++++---- .../engine/indirect/StagingBuffer.java | 2 +- 2 files changed, 25 insertions(+), 7 deletions(-) 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 2209069bb..7e3ef62c3 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 @@ -42,7 +42,6 @@ public void notifyDirty(int index) { if (index < 0 || index >= instanceCount()) { return; } - changed.set(index); changedPages.set(ObjectStorage.objectIndex2PageIndex(index)); } @@ -102,15 +101,34 @@ public void uploadInstances(StagingBuffer stagingBuffer, int instanceVbo) { long baseByte = mapping.page2ByteOffset(page); long size = (endObject - startObject) * instanceStride; - stagingBuffer.enqueueCopy(size, instanceVbo, baseByte, ptr -> { + // Because writes are broken into pages, we end up with significantly more calls into + // StagingBuffer#enqueueCopy and the allocations for the writer got out of hand. Here + // we've inlined the enqueueCopy call and do not allocate the write lambda at all. + // Doing so cut upload times in half. + + // Try to write directly into the staging buffer if there is enough contiguous space. + long direct = stagingBuffer.reserveForCopy(size, instanceVbo, baseByte); + + if (direct != MemoryUtil.NULL) { for (int i = startObject; i < endObject; i++) { - writer.write(ptr, instances.get(i)); - ptr += instanceStride; + var instance = instances.get(i); + writer.write(direct, instance); + direct += instanceStride; } - }); + continue; + } + + // Otherwise, write to a scratch buffer and enqueue a copy. + var block = stagingBuffer.getScratch(size); + var ptr = block.ptr(); + for (int i = startObject; i < endObject; i++) { + var instance = instances.get(i); + writer.write(ptr, instance); + ptr += instanceStride; + } + stagingBuffer.enqueueCopy(block.ptr(), size, instanceVbo, baseByte); } - changed.clear(); changedPages.clear(); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/StagingBuffer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/StagingBuffer.java index c976dcfce..308f97af4 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/StagingBuffer.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/StagingBuffer.java @@ -223,7 +223,7 @@ public void delete() { FlwMemoryTracker._freeCpuMemory(capacity); } - private MemoryBlock getScratch(long size) { + public MemoryBlock getScratch(long size) { if (scratch == null) { scratch = MemoryBlock.malloc(size); } else if (scratch.size() < size) { From 5a75fe972f418dc5b86edc7bae8cffcbca47b2fe Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Wed, 18 Sep 2024 23:37:19 -0700 Subject: [PATCH 24/26] First try - Implement instance hiding by deleting/stealing - Work around instancer persistence by storing a recreation supplier in the instance handle - Rework instancer ctors to just take an InstancerKey - Parameterize InstanceHandle by I extends Instance so the steal method and the supplier can be safely assigned --- .../flywheel/api/instance/InstanceHandle.java | 4 ++ .../backend/engine/AbstractInstancer.java | 44 ++++++++++++------- .../flywheel/backend/engine/DrawManager.java | 15 ++++--- .../backend/engine/InstanceHandleImpl.java | 41 ++++++++++++++--- .../engine/indirect/IndirectDrawManager.java | 2 +- .../engine/indirect/IndirectInstancer.java | 11 +++-- .../instancing/InstancedDrawManager.java | 2 +- .../engine/instancing/InstancedInstancer.java | 8 ++-- 8 files changed, 87 insertions(+), 40 deletions(-) diff --git a/common/src/api/java/dev/engine_room/flywheel/api/instance/InstanceHandle.java b/common/src/api/java/dev/engine_room/flywheel/api/instance/InstanceHandle.java index ea2065dd0..3815f5f7e 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/instance/InstanceHandle.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/instance/InstanceHandle.java @@ -7,4 +7,8 @@ public interface InstanceHandle { void setChanged(); void setDeleted(); + + void setVisible(boolean visible); + + boolean isVisible(); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java index 16aa88b64..8938fe080 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/AbstractInstancer.java @@ -1,6 +1,7 @@ package dev.engine_room.flywheel.backend.engine; import java.util.ArrayList; +import java.util.function.Supplier; import org.jetbrains.annotations.Nullable; @@ -13,26 +14,32 @@ public abstract class AbstractInstancer implements Instancer { public final InstanceType type; public final Environment environment; + private final Supplier> recreate; // Lock for all instances, only needs to be used in methods that may run on the TaskExecutor. protected final Object lock = new Object(); protected final ArrayList instances = new ArrayList<>(); - protected final ArrayList handles = new ArrayList<>(); + protected final ArrayList> handles = new ArrayList<>(); protected final AtomicBitSet changed = new AtomicBitSet(); protected final AtomicBitSet deleted = new AtomicBitSet(); - protected AbstractInstancer(InstanceType type, Environment environment) { - this.type = type; - this.environment = environment; + protected AbstractInstancer(InstancerKey key, Supplier> recreate) { + this.type = key.type(); + this.environment = key.environment(); + this.recreate = recreate; } @Override public I createInstance() { synchronized (lock) { var i = instances.size(); - var handle = new InstanceHandleImpl(this, i); + var handle = new InstanceHandleImpl(); + handle.instancer = this; + handle.recreate = recreate; + handle.index = i; I instance = type.create(handle); + handle.instance = instance; addLocked(instance, handle); return instance; @@ -47,12 +54,15 @@ public void stealInstance(@Nullable I instance) { var instanceHandle = instance.handle(); - if (!(instanceHandle instanceof InstanceHandleImpl handle)) { + if (!(instanceHandle instanceof InstanceHandleImpl)) { // UB: do nothing return; } - if (handle.instancer == this) { + // Should InstanceType have an isInstance method? + var handle = (InstanceHandleImpl) instanceHandle; + + if (handle.instancer == this && handle.visible) { return; } @@ -65,19 +75,23 @@ public void stealInstance(@Nullable I instance) { // is filtering deleted instances later, so is safe. handle.setDeleted(); - // Only lock now that we'll be mutating our state. - synchronized (lock) { - // Add the instance to this instancer. - handle.instancer = this; - handle.index = instances.size(); - addLocked(instance, handle); + // Add the instance to this instancer. + handle.instancer = this; + handle.recreate = recreate; + + if (handle.visible) { + // Only lock now that we'll be mutating our state. + synchronized (lock) { + handle.index = instances.size(); + addLocked(instance, handle); + } } } /** * Calls must be synchronized on {@link #lock}. */ - private void addLocked(I instance, InstanceHandleImpl handle) { + private void addLocked(I instance, InstanceHandleImpl handle) { instances.add(instance); handles.add(handle); changed.set(handle.index); @@ -163,7 +177,7 @@ protected void setRangeChanged(int start, int end) { * Clear all instances without freeing resources. */ public void clear() { - for (InstanceHandleImpl handle : handles) { + for (InstanceHandleImpl handle : handles) { // Only clear instances that belong to this instancer. // If one of these handles was stolen by another instancer, // clearing it here would cause significant visual artifacts and instance leaks. diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java index 476a9abc4..993046659 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java @@ -11,7 +11,6 @@ import dev.engine_room.flywheel.api.backend.Engine; import dev.engine_room.flywheel.api.instance.Instance; import dev.engine_room.flywheel.api.instance.InstanceType; -import dev.engine_room.flywheel.api.instance.Instancer; import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.api.visualization.VisualType; import dev.engine_room.flywheel.backend.FlwBackend; @@ -36,9 +35,13 @@ public abstract class DrawManager> { */ protected final Queue> initializationQueue = new ConcurrentLinkedQueue<>(); + public AbstractInstancer getInstancer(Environment environment, InstanceType type, Model model, VisualType visualType, int bias) { + return getInstancer(new InstancerKey<>(environment, type, model, visualType, bias)); + } + @SuppressWarnings("unchecked") - public Instancer getInstancer(Environment environment, InstanceType type, Model model, VisualType visualType, int bias) { - return (Instancer) instancers.computeIfAbsent(new InstancerKey<>(environment, type, model, visualType, bias), this::createAndDeferInit); + public AbstractInstancer getInstancer(InstancerKey key) { + return (AbstractInstancer) instancers.computeIfAbsent(key, this::createAndDeferInit); } public void flush(LightStorage lightStorage, EnvironmentStorage environmentStorage) { @@ -94,8 +97,8 @@ private static boolean checkAndWarnEmptyModel(Model model) { return false; } - protected static > Map, Int2ObjectMap>>> doCrumblingSort(Class clazz, List crumblingBlocks) { - Map, Int2ObjectMap>>> byType = new HashMap<>(); + protected static > Map, Int2ObjectMap>>>> doCrumblingSort(Class clazz, List crumblingBlocks) { + Map, Int2ObjectMap>>>> byType = new HashMap<>(); for (Engine.CrumblingBlock block : crumblingBlocks) { int progress = block.progress(); @@ -107,7 +110,7 @@ protected static > Map, Int2ObjectMap // Filter out instances that weren't created by this engine. // If all is well, we probably shouldn't take the `continue` // branches but better to do checked casts. - if (!(instance.handle() instanceof InstanceHandleImpl impl)) { + if (!(instance.handle() instanceof InstanceHandleImpl impl)) { continue; } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/InstanceHandleImpl.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/InstanceHandleImpl.java index 3a1de3200..5641cb96c 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/InstanceHandleImpl.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/InstanceHandleImpl.java @@ -1,16 +1,22 @@ package dev.engine_room.flywheel.backend.engine; +import java.util.function.Supplier; + +import org.jetbrains.annotations.UnknownNullability; + +import dev.engine_room.flywheel.api.instance.Instance; import dev.engine_room.flywheel.api.instance.InstanceHandle; -public class InstanceHandleImpl implements InstanceHandle { - public AbstractInstancer instancer; +public class InstanceHandleImpl implements InstanceHandle { + @UnknownNullability + public AbstractInstancer instancer; + @UnknownNullability + public I instance; + @UnknownNullability + public Supplier> recreate; + public boolean visible = true; public int index; - public InstanceHandleImpl(AbstractInstancer instancer, int index) { - this.instancer = instancer; - this.index = index; - } - @Override public void setChanged() { instancer.notifyDirty(index); @@ -23,6 +29,27 @@ public void setDeleted() { clear(); } + @Override + public void setVisible(boolean visible) { + if (this.visible == visible) { + return; + } + + this.visible = visible; + + if (visible) { + recreate.get().stealInstance(instance); + } else { + instancer.notifyRemoval(index); + clear(); + } + } + + @Override + public boolean isVisible() { + return visible; + } + public void clear() { index = -1; } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java index 99ff35707..cb029600e 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java @@ -66,7 +66,7 @@ public IndirectDrawManager(IndirectPrograms programs) { @Override protected IndirectInstancer create(InstancerKey key) { - return new IndirectInstancer<>(key.type(), key.environment(), key.model()); + return new IndirectInstancer<>(key, () -> getInstancer(key)); } @SuppressWarnings("unchecked") 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 7e3ef62c3..26ada98bb 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 @@ -2,17 +2,16 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; import org.jetbrains.annotations.UnknownNullability; import org.joml.Vector4fc; import org.lwjgl.system.MemoryUtil; import dev.engine_room.flywheel.api.instance.Instance; -import dev.engine_room.flywheel.api.instance.InstanceType; import dev.engine_room.flywheel.api.instance.InstanceWriter; -import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.backend.engine.AbstractInstancer; -import dev.engine_room.flywheel.backend.engine.embed.Environment; +import dev.engine_room.flywheel.backend.engine.InstancerKey; import dev.engine_room.flywheel.backend.util.AtomicBitSet; import dev.engine_room.flywheel.lib.math.MoreMath; @@ -29,12 +28,12 @@ public class IndirectInstancer extends AbstractInstancer private int modelIndex = -1; private int baseInstance = -1; - public IndirectInstancer(InstanceType type, Environment environment, Model model) { - super(type, environment); + public IndirectInstancer(InstancerKey key, Supplier> recreate) { + super(key, recreate); instanceStride = MoreMath.align4(type.layout() .byteSize()); writer = this.type.writer(); - boundingSphere = model.boundingSphere(); + boundingSphere = key.model().boundingSphere(); } @Override diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java index 8b241f6c0..f7c55d43f 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java @@ -128,7 +128,7 @@ public void delete() { @Override protected InstancedInstancer create(InstancerKey key) { - return new InstancedInstancer<>(key.type(), key.environment()); + return new InstancedInstancer<>(key, () -> getInstancer(key)); } @Override diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedInstancer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedInstancer.java index a5dff27d9..d72117e7d 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedInstancer.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedInstancer.java @@ -2,14 +2,14 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Supplier; import org.jetbrains.annotations.Nullable; import dev.engine_room.flywheel.api.instance.Instance; -import dev.engine_room.flywheel.api.instance.InstanceType; import dev.engine_room.flywheel.api.instance.InstanceWriter; import dev.engine_room.flywheel.backend.engine.AbstractInstancer; -import dev.engine_room.flywheel.backend.engine.embed.Environment; +import dev.engine_room.flywheel.backend.engine.InstancerKey; import dev.engine_room.flywheel.backend.gl.TextureBuffer; import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer; import dev.engine_room.flywheel.backend.gl.buffer.GlBufferUsage; @@ -25,8 +25,8 @@ public class InstancedInstancer extends AbstractInstancer private final List draws = new ArrayList<>(); - public InstancedInstancer(InstanceType type, Environment environment) { - super(type, environment); + public InstancedInstancer(InstancerKey key, Supplier> recreate) { + super(key, recreate); var layout = type.layout(); // Align to one texel in the texture buffer instanceStride = MoreMath.align16(layout.byteSize()); From 62a09543812597901456710ac51188c2a3f60ffa Mon Sep 17 00:00:00 2001 From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> Date: Thu, 19 Sep 2024 11:40:41 -0700 Subject: [PATCH 25/26] Home alone - Remove LoweringVisitor - Move functionality of four main static methods in LoweringVisitor to new ModelTrees class - Return ModelTree directly - Accept Material instead of TextureAtlasSprite for efficiency, so visuals don't need to look up the sprite to get the ModelTree - Use ResourceReloadCache for MeshTree.CACHE --- .../lib/model/part/LoweringVisitor.java | 128 ------------------ .../flywheel/lib/model/part/MeshTree.java | 25 +--- .../flywheel/lib/model/part/ModelTree.java | 102 ++++++-------- .../lib/model/part/ModelTreeCache.java | 15 -- .../flywheel/lib/model/part/ModelTrees.java | 83 ++++++++++++ .../flywheel/vanilla/BellVisual.java | 9 +- .../flywheel/vanilla/ChestVisual.java | 12 +- .../flywheel/vanilla/MinecartVisual.java | 6 +- .../flywheel/vanilla/ShulkerBoxVisual.java | 22 ++- .../flywheel/impl/FlywheelFabric.java | 5 - .../flywheel/impl/FlywheelForge.java | 4 - 11 files changed, 145 insertions(+), 266 deletions(-) delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/part/LoweringVisitor.java delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTreeCache.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTrees.java diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/LoweringVisitor.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/LoweringVisitor.java deleted file mode 100644 index 29800551b..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/LoweringVisitor.java +++ /dev/null @@ -1,128 +0,0 @@ -package dev.engine_room.flywheel.lib.model.part; - -import java.util.ArrayList; -import java.util.Set; - -import org.jetbrains.annotations.Nullable; - -import dev.engine_room.flywheel.api.material.Material; -import dev.engine_room.flywheel.api.model.Mesh; -import dev.engine_room.flywheel.api.model.Model; -import dev.engine_room.flywheel.lib.model.RetexturedMesh; -import dev.engine_room.flywheel.lib.model.SingleMeshModel; -import net.minecraft.client.model.geom.PartPose; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; - -/** - * A tree walking visitor that lowers a MeshTree to a ModelTree. - */ -public interface LoweringVisitor { - static ModelTree leaf(Model model) { - return leaf(model, PartPose.ZERO); - } - - static ModelTree leaf(Model model, PartPose initialPose) { - return ModelTree.create(model, initialPose, new ModelTree[0], new String[0]); - } - - static LoweringVisitor create(Material material) { - return (path, mesh) -> new SingleMeshModel(mesh, material); - } - - static LoweringVisitor create(Material material, TextureAtlasSprite sprite) { - return (path, mesh) -> new SingleMeshModel(new RetexturedMesh(mesh, sprite), material); - } - - static LoweringVisitor pruning(Set prune, Material material) { - return new LoweringVisitor() { - @Override - public @Nullable ModelTree visit(String path, MeshTree meshTree) { - if (prune.contains(path)) { - return null; - } - return LoweringVisitor.super.visit(path, meshTree); - } - - @Override - public Model visit(String path, Mesh mesh) { - return new SingleMeshModel(mesh, material); - } - }; - } - - static LoweringVisitor pruning(Set prune, Material material, TextureAtlasSprite sprite) { - return new LoweringVisitor() { - @Override - public @Nullable ModelTree visit(String path, MeshTree meshTree) { - if (prune.contains(path)) { - return null; - } - return LoweringVisitor.super.visit(path, meshTree); - } - - @Override - public Model visit(String path, Mesh mesh) { - return new SingleMeshModel(new RetexturedMesh(mesh, sprite), material); - } - }; - } - - static String append(String path, String child) { - if (path.isEmpty()) { - return child; - } - - return path + "/" + child; - } - - /** - * Walk the given MeshTree, lowering its Mesh to a Model, and lowering all children using the given visitor. - * - * @param path The absolute path to the MeshTree node. - * @param meshTree The MeshTree to walk. - * @param loweringVisitor The visitor to use to lower the Mesh and MeshTree nodes. - * @return The lowered ModelTree. - */ - static ModelTree walk(String path, MeshTree meshTree, LoweringVisitor loweringVisitor) { - Model out = null; - - if (meshTree.mesh() != null) { - out = loweringVisitor.visit(path, meshTree.mesh()); - } - - ArrayList children = new ArrayList<>(); - ArrayList childNames = new ArrayList<>(); - - for (int i = 0; i < meshTree.childCount(); i++) { - var child = loweringVisitor.visit(append(path, meshTree.childName(i)), meshTree.child(i)); - - if (child != null) { - children.add(child); - childNames.add(meshTree.childName(i)); - } - } - - return ModelTree.create(out, meshTree.initialPose(), children.toArray(new ModelTree[0]), childNames.toArray(new String[0])); - } - - /** - * Visit the given Mesh, converting it to a Model. - * - * @param path The absolute path to the MeshTree node containing the Mesh. - * @param mesh The Mesh to lower. - * @return The lowered Model, or null if the Model should be omitted. - */ - @Nullable Model visit(String path, Mesh mesh); - - /** - * Visit the given MeshTree, converting it to a ModelTree. - * - * @param path The absolute path to the MeshTree node. - * @param meshTree The MeshTree to lower. - * @return The lowered ModelTree, or null if the ModelTree should be omitted. - */ - @Nullable - default ModelTree visit(String path, MeshTree meshTree) { - return walk(path, meshTree, this); - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java index 6ec94921e..f9d0bd38c 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java @@ -1,12 +1,8 @@ package dev.engine_room.flywheel.lib.model.part; import java.util.Arrays; -import java.util.Map; import java.util.NoSuchElementException; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import com.mojang.blaze3d.vertex.PoseStack; @@ -14,6 +10,7 @@ import dev.engine_room.flywheel.api.model.Mesh; import dev.engine_room.flywheel.lib.internal.FlwLibLink; import dev.engine_room.flywheel.lib.memory.MemoryBlock; +import dev.engine_room.flywheel.lib.model.ResourceReloadCache; import dev.engine_room.flywheel.lib.model.SimpleQuadMesh; import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView; import dev.engine_room.flywheel.lib.vertex.VertexView; @@ -28,7 +25,7 @@ public final class MeshTree { private static final ThreadLocal THREAD_LOCAL_OBJECTS = ThreadLocal.withInitial(ThreadLocalObjects::new); private static final PoseStack.Pose IDENTITY_POSE = new PoseStack().last(); - private static final Map CACHE = new ConcurrentHashMap<>(); + private static final ResourceReloadCache CACHE = new ResourceReloadCache<>(MeshTree::convert); @Nullable private final Mesh mesh; @@ -44,7 +41,7 @@ private MeshTree(@Nullable Mesh mesh, PartPose initialPose, MeshTree[] children, } public static MeshTree of(ModelLayerLocation layer) { - return CACHE.computeIfAbsent(layer, MeshTree::convert); + return CACHE.get(layer); } private static MeshTree convert(ModelLayerLocation layer) { @@ -58,9 +55,9 @@ private static MeshTree convert(ModelLayerLocation layer) { private static MeshTree convert(ModelPart modelPart, ThreadLocalObjects objects) { var modelPartChildren = FlwLibLink.INSTANCE.getModelPartChildren(modelPart); - // Freeze the ordering here. Maybe we want to sort this? String[] childNames = modelPartChildren.keySet() .toArray(String[]::new); + Arrays.sort(childNames); MeshTree[] children = new MeshTree[childNames.length]; for (int i = 0; i < childNames.length; i++) { @@ -135,20 +132,6 @@ public MeshTree childOrThrow(String name) { return child; } - public void traverse(Consumer consumer) { - if (mesh != null) { - consumer.accept(mesh); - } - for (MeshTree child : children) { - child.traverse(consumer); - } - } - - @ApiStatus.Internal - public static void onEndClientResourceReload() { - CACHE.clear(); - } - private static class ThreadLocalObjects { public final VertexWriter vertexWriter = new VertexWriter(); } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTree.java index 293394bef..5774ebf44 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTree.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTree.java @@ -2,74 +2,50 @@ import java.util.Arrays; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.NoSuchElementException; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; import dev.engine_room.flywheel.api.model.Model; -import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.model.geom.PartPose; -public class ModelTree { - private static final Map CACHE = new ConcurrentHashMap<>(); - +public final class ModelTree { @Nullable private final Model model; private final PartPose initialPose; private final ModelTree[] children; private final String[] childNames; - private ModelTree(@Nullable Model model, PartPose initialPose, ModelTree[] children, String[] childNames) { - this.model = model; - this.initialPose = initialPose; - this.children = children; - this.childNames = childNames; - } - - /** - * Create and memoize a ModelTree root. - * - *

This method is intended for use by Visuals. - * - * @param modelLayerLocation The model location to lower. - * @param loweringVisitor The visitor to use to lower the model. - * @return The cached ModelTree root. - */ - public static ModelTree of(ModelLayerLocation modelLayerLocation, LoweringVisitor loweringVisitor) { - return CACHE.computeIfAbsent(new ModelTreeKey(modelLayerLocation, loweringVisitor), k -> { - var meshTree = MeshTree.of(k.modelLayerLocation()); - - var out = k.loweringVisitor() - .visit("", meshTree); - - if (out == null) { - // Should this be an error, or a missing model? - return ModelTree.create(null, PartPose.ZERO, new ModelTree[0], new String[0]); - } - - return out; - }); - } - /** * Create a new ModelTree node. * - *

This method is intended for use by {@link LoweringVisitor} implementations. - * * @param model The model to associate with this node, or null if this node does not render. * @param initialPose The initial pose of this node. * @param children The children of this node. - * @param childNames The names of the children of this node. - * @return A new ModelTree node. - * @throws IllegalArgumentException if children and childNames have different lengths. */ - public static ModelTree create(@Nullable Model model, PartPose initialPose, ModelTree[] children, String[] childNames) { - if (children.length != childNames.length) { - throw new IllegalArgumentException("children and childNames must have the same length (%s != %s)".formatted(children.length, childNames.length)); + public ModelTree(@Nullable Model model, PartPose initialPose, Map children) { + this.model = model; + this.initialPose = initialPose; + + String[] childNames = children.keySet().toArray(String[]::new); + Arrays.sort(childNames); + + ModelTree[] childArray = new ModelTree[childNames.length]; + for (int i = 0; i < childNames.length; i++) { + childArray[i] = children.get(childNames[i]); } - return new ModelTree(model, initialPose, children, childNames); + this.children = childArray; + this.childNames = childNames; + } + + @Nullable + public Model model() { + return model; + } + + public PartPose initialPose() { + return initialPose; } public int childCount() { @@ -84,24 +60,32 @@ public String childName(int index) { return childNames[index]; } - public PartPose initialPose() { - return initialPose; + public int childIndex(String name) { + return Arrays.binarySearch(childNames, name); } - @Nullable - public Model model() { - return model; + public boolean hasChild(String name) { + return childIndex(name) >= 0; } - public int childIndex(String name) { - return Arrays.binarySearch(childNames, name); - } + @Nullable + public ModelTree child(String name) { + int index = childIndex(name); - @ApiStatus.Internal - public static void onEndClientResourceReload() { - CACHE.clear(); + if (index < 0) { + return null; + } + + return child(index); } - private record ModelTreeKey(ModelLayerLocation modelLayerLocation, LoweringVisitor loweringVisitor) { + public ModelTree childOrThrow(String name) { + ModelTree child = child(name); + + if (child == null) { + throw new NoSuchElementException("Can't find part " + name); + } + + return child; } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTreeCache.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTreeCache.java deleted file mode 100644 index bf9274965..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTreeCache.java +++ /dev/null @@ -1,15 +0,0 @@ -package dev.engine_room.flywheel.lib.model.part; - -import java.util.function.Function; - -import dev.engine_room.flywheel.lib.model.ResourceReloadCache; - -/** - * If the lookup to create your model tree directly through {@link ModelTree#of} is particularly expensive, - * you can memoize the arguments here to hide the cost. - */ -public class ModelTreeCache extends ResourceReloadCache { - public ModelTreeCache(Function factory) { - super(factory); - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTrees.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTrees.java new file mode 100644 index 000000000..7c87c6275 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTrees.java @@ -0,0 +1,83 @@ +package dev.engine_room.flywheel.lib.model.part; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.jetbrains.annotations.Nullable; + +import dev.engine_room.flywheel.api.material.Material; +import dev.engine_room.flywheel.api.model.Mesh; +import dev.engine_room.flywheel.api.model.Model; +import dev.engine_room.flywheel.lib.model.ResourceReloadCache; +import dev.engine_room.flywheel.lib.model.RetexturedMesh; +import dev.engine_room.flywheel.lib.model.SingleMeshModel; +import net.minecraft.client.model.geom.ModelLayerLocation; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; + +public final class ModelTrees { + private static final ResourceReloadCache CACHE = new ResourceReloadCache<>(k -> { + ModelTree tree = convert("", MeshTree.of(k.layer), k.pathsToPrune, k.texture != null ? k.texture.sprite() : null, k.material); + + if (tree == null) { + throw new IllegalArgumentException("Cannot prune root node!"); + } + + return tree; + }); + + private ModelTrees() { + } + + public static ModelTree of(ModelLayerLocation layer, Material material) { + return CACHE.get(new ModelTreeKey(layer, Collections.emptySet(), null, material)); + } + + public static ModelTree of(ModelLayerLocation layer, net.minecraft.client.resources.model.Material texture, Material material) { + return CACHE.get(new ModelTreeKey(layer, Collections.emptySet(), texture, material)); + } + + public static ModelTree of(ModelLayerLocation layer, Set pathsToPrune, Material material) { + return CACHE.get(new ModelTreeKey(layer, Set.copyOf(pathsToPrune), null, material)); + } + + public static ModelTree of(ModelLayerLocation layer, Set pathsToPrune, net.minecraft.client.resources.model.Material texture, Material material) { + return CACHE.get(new ModelTreeKey(layer, Set.copyOf(pathsToPrune), texture, material)); + } + + @Nullable + private static ModelTree convert(String path, MeshTree meshTree, Set pathsToPrune, @Nullable TextureAtlasSprite sprite, Material material) { + if (pathsToPrune.contains(path)) { + return null; + } + + Model model = null; + Mesh mesh = meshTree.mesh(); + + if (mesh != null) { + if (sprite != null) { + mesh = new RetexturedMesh(mesh, sprite); + } + + model = new SingleMeshModel(mesh, material); + } + + Map children = new HashMap<>(); + String pathSlash = path + "/"; + + for (int i = 0; i < meshTree.childCount(); i++) { + String childName = meshTree.childName(i); + var child = convert(pathSlash + childName, meshTree.child(i), pathsToPrune, sprite, material); + + if (child != null) { + children.put(childName, child); + } + } + + return new ModelTree(model, meshTree.initialPose(), children); + } + + private record ModelTreeKey(ModelLayerLocation layer, Set pathsToPrune, @Nullable net.minecraft.client.resources.model.Material texture, Material material) { + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java index 080bc2005..c1ebae568 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/BellVisual.java @@ -9,10 +9,8 @@ import dev.engine_room.flywheel.api.material.Material; import dev.engine_room.flywheel.api.visualization.VisualizationContext; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.ResourceReloadHolder; import dev.engine_room.flywheel.lib.model.part.InstanceTree; -import dev.engine_room.flywheel.lib.model.part.LoweringVisitor; -import dev.engine_room.flywheel.lib.model.part.ModelTree; +import dev.engine_room.flywheel.lib.model.part.ModelTrees; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import net.minecraft.client.model.geom.ModelLayers; @@ -26,9 +24,6 @@ public class BellVisual extends AbstractBlockEntityVisual imple .mipmap(false) .build(); - // Need to hold the visitor in a ResourceReloadHolder to ensure we have a valid sprite. - private static final ResourceReloadHolder VISITOR = new ResourceReloadHolder<>(() -> LoweringVisitor.create(MATERIAL, BellRenderer.BELL_RESOURCE_LOCATION.sprite())); - private final InstanceTree instances; private final InstanceTree bellBody; @@ -39,7 +34,7 @@ public class BellVisual extends AbstractBlockEntityVisual imple public BellVisual(VisualizationContext ctx, BellBlockEntity blockEntity, float partialTick) { super(ctx, blockEntity, partialTick); - instances = InstanceTree.create(instancerProvider(), ModelTree.of(ModelLayers.BELL, VISITOR.get())); + instances = InstanceTree.create(instancerProvider(), ModelTrees.of(ModelLayers.BELL, BellRenderer.BELL_RESOURCE_LOCATION, MATERIAL)); bellBody = instances.childOrThrow("bell_body"); BlockPos visualPos = getVisualPosition(); diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java index 39af485fc..ebf0d2456 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ChestVisual.java @@ -4,7 +4,6 @@ import java.util.EnumMap; import java.util.Map; import java.util.function.Consumer; -import java.util.function.Function; import org.jetbrains.annotations.Nullable; import org.joml.Matrix4f; @@ -15,10 +14,8 @@ import dev.engine_room.flywheel.api.visualization.VisualizationContext; import dev.engine_room.flywheel.lib.material.CutoutShaders; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.ResourceReloadCache; import dev.engine_room.flywheel.lib.model.part.InstanceTree; -import dev.engine_room.flywheel.lib.model.part.LoweringVisitor; -import dev.engine_room.flywheel.lib.model.part.ModelTree; +import dev.engine_room.flywheel.lib.model.part.ModelTrees; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import it.unimi.dsi.fastutil.floats.Float2FloatFunction; @@ -28,7 +25,6 @@ import net.minecraft.client.renderer.LevelRenderer; import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.Sheets; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.util.Mth; @@ -55,8 +51,6 @@ public class ChestVisual extends Abstrac LAYER_LOCATIONS.put(ChestType.RIGHT, ModelLayers.DOUBLE_CHEST_RIGHT); } - private static final Function VISITOR = new ResourceReloadCache<>(s -> LoweringVisitor.create(MATERIAL, s)); - @Nullable private final InstanceTree instances; @Nullable @@ -80,8 +74,8 @@ public ChestVisual(VisualizationContext ctx, T blockEntity, float partialTick) { Block block = blockState.getBlock(); if (block instanceof AbstractChestBlock chestBlock) { ChestType chestType = blockState.hasProperty(ChestBlock.TYPE) ? blockState.getValue(ChestBlock.TYPE) : ChestType.SINGLE; - TextureAtlasSprite sprite = Sheets.chooseMaterial(blockEntity, chestType, isChristmas()).sprite(); - instances = InstanceTree.create(instancerProvider(), ModelTree.of(LAYER_LOCATIONS.get(chestType), VISITOR.apply(sprite))); + net.minecraft.client.resources.model.Material texture = Sheets.chooseMaterial(blockEntity, chestType, isChristmas()); + instances = InstanceTree.create(instancerProvider(), ModelTrees.of(LAYER_LOCATIONS.get(chestType), texture, MATERIAL)); lid = instances.childOrThrow("lid"); lock = instances.childOrThrow("lock"); diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java index d8348fbf1..b34b86e84 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java @@ -14,8 +14,7 @@ import dev.engine_room.flywheel.lib.material.SimpleMaterial; import dev.engine_room.flywheel.lib.model.Models; import dev.engine_room.flywheel.lib.model.part.InstanceTree; -import dev.engine_room.flywheel.lib.model.part.LoweringVisitor; -import dev.engine_room.flywheel.lib.model.part.ModelTree; +import dev.engine_room.flywheel.lib.model.part.ModelTrees; import dev.engine_room.flywheel.lib.visual.ComponentEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import dev.engine_room.flywheel.lib.visual.SimpleTickableVisual; @@ -36,7 +35,6 @@ public class MinecartVisual extends ComponentEntityV .texture(TEXTURE) .mipmap(false) .build(); - private static final LoweringVisitor VISITOR = LoweringVisitor.create(MATERIAL); private final InstanceTree instances; @Nullable @@ -50,7 +48,7 @@ public class MinecartVisual extends ComponentEntityV public MinecartVisual(VisualizationContext ctx, T entity, float partialTick, ModelLayerLocation layerLocation) { super(ctx, entity, partialTick); - instances = InstanceTree.create(instancerProvider(), ModelTree.of(layerLocation, VISITOR)); + instances = InstanceTree.create(instancerProvider(), ModelTrees.of(layerLocation, MATERIAL)); blockState = entity.getDisplayBlockState(); contents = createContentsInstance(); diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java index f3554e39e..0c7abb853 100644 --- a/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java +++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/ShulkerBoxVisual.java @@ -2,23 +2,20 @@ import java.util.Set; import java.util.function.Consumer; -import java.util.function.Function; import org.joml.Matrix4f; import dev.engine_room.flywheel.api.instance.Instance; +import dev.engine_room.flywheel.api.material.Material; import dev.engine_room.flywheel.api.visualization.VisualizationContext; import dev.engine_room.flywheel.lib.material.CutoutShaders; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.ResourceReloadCache; import dev.engine_room.flywheel.lib.model.part.InstanceTree; -import dev.engine_room.flywheel.lib.model.part.LoweringVisitor; -import dev.engine_room.flywheel.lib.model.part.ModelTree; +import dev.engine_room.flywheel.lib.model.part.ModelTrees; import dev.engine_room.flywheel.lib.visual.AbstractBlockEntityVisual; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import net.minecraft.client.model.geom.ModelLayers; import net.minecraft.client.renderer.Sheets; -import net.minecraft.client.resources.model.Material; import net.minecraft.core.Direction; import net.minecraft.util.Mth; import net.minecraft.world.item.DyeColor; @@ -26,15 +23,13 @@ import net.minecraft.world.level.block.entity.ShulkerBoxBlockEntity; public class ShulkerBoxVisual extends AbstractBlockEntityVisual implements SimpleDynamicVisual { - private static final dev.engine_room.flywheel.api.material.Material MATERIAL = SimpleMaterial.builder() + private static final Material MATERIAL = SimpleMaterial.builder() .cutout(CutoutShaders.ONE_TENTH) .texture(Sheets.SHULKER_SHEET) .mipmap(false) .backfaceCulling(false) .build(); - - - private static final Function VISITORS = new ResourceReloadCache<>(m -> LoweringVisitor.pruning(Set.of("head"), MATERIAL, m.sprite())); + private static final Set PATHS_TO_PRUNE = Set.of("/head"); private final InstanceTree instances; private final InstanceTree lid; @@ -47,23 +42,22 @@ public ShulkerBoxVisual(VisualizationContext ctx, ShulkerBoxBlockEntity blockEnt super(ctx, blockEntity, partialTick); DyeColor color = blockEntity.getColor(); - Material texture; + net.minecraft.client.resources.model.Material texture; if (color == null) { texture = Sheets.DEFAULT_SHULKER_TEXTURE_LOCATION; } else { texture = Sheets.SHULKER_TEXTURE_LOCATION.get(color.getId()); } - instances = InstanceTree.create(instancerProvider(), ModelTree.of(ModelLayers.SHULKER, VISITORS.apply(texture))); + instances = InstanceTree.create(instancerProvider(), ModelTrees.of(ModelLayers.SHULKER, PATHS_TO_PRUNE, texture, MATERIAL)); + lid = instances.childOrThrow("lid"); initialPose = createInitialPose(); - - lid = instances.childOrThrow("lid"); } private Matrix4f createInitialPose() { - var rotation = getDirection().getRotation(); var visualPosition = getVisualPosition(); + var rotation = getDirection().getRotation(); return new Matrix4f().translate(visualPosition.getX(), visualPosition.getY(), visualPosition.getZ()) .translate(0.5f, 0.5f, 0.5f) .scale(0.9995f) diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java index 46c561fa8..4e33b3145 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java @@ -12,8 +12,6 @@ import dev.engine_room.flywheel.lib.model.ResourceReloadCache; import dev.engine_room.flywheel.lib.model.ResourceReloadHolder; import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler; -import dev.engine_room.flywheel.lib.model.part.MeshTree; -import dev.engine_room.flywheel.lib.model.part.ModelTree; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; @@ -71,9 +69,6 @@ private static void setupImpl() { private static void setupLib() { EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> ResourceReloadCache.onEndClientResourceReload()); EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> ResourceReloadHolder.onEndClientResourceReload()); - EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> - MeshTree.onEndClientResourceReload()); - EndClientResourceReloadCallback.EVENT.register((minecraft, resourceManager, initialReload, error) -> ModelTree.onEndClientResourceReload()); ModelLoadingPlugin.register(ctx -> { ctx.addModels(PartialModelEventHandler.onRegisterAdditional()); diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java b/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java index 763324819..11acf03f8 100644 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java @@ -13,8 +13,6 @@ import dev.engine_room.flywheel.lib.model.ResourceReloadCache; import dev.engine_room.flywheel.lib.model.ResourceReloadHolder; import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler; -import dev.engine_room.flywheel.lib.model.part.MeshTree; -import dev.engine_room.flywheel.lib.model.part.ModelTree; import dev.engine_room.flywheel.lib.util.LevelAttached; import net.minecraft.client.Minecraft; import net.minecraft.commands.synchronization.ArgumentTypeInfos; @@ -113,8 +111,6 @@ private static void registerLibEventListeners(IEventBus forgeEventBus, IEventBus modEventBus.addListener((EndClientResourceReloadEvent e) -> ResourceReloadCache.onEndClientResourceReload()); modEventBus.addListener((EndClientResourceReloadEvent e) -> ResourceReloadHolder.onEndClientResourceReload()); - modEventBus.addListener((EndClientResourceReloadEvent e) -> MeshTree.onEndClientResourceReload()); - modEventBus.addListener((EndClientResourceReloadEvent e) -> ModelTree.onEndClientResourceReload()); modEventBus.addListener(PartialModelEventHandler::onRegisterAdditional); modEventBus.addListener(PartialModelEventHandler::onBakingCompleted); From 5b6463b8f10587bddd6d32017515c9575d493aa2 Mon Sep 17 00:00:00 2001 From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:24:58 -0700 Subject: [PATCH 26/26] Regularly scheduled maintenance - Remove ModelHolder and ModelCache - Remove lib/util.FlwUtil - Remove lib/util.Pair and replace usages with com.mojang.datafixers.util.Pair - Remove lib/util.Unit and replace usages with net.minecraft.util.Unit - Make ResourceReloadHolder and ResourceReloadCache final and move to util - Clean up code in backend/glsl - Move LightSmoothnessArgument to impl --- .../flywheel/backend/BackendConfig.java | 2 +- .../flywheel/backend/FlwBackend.java | 10 +++- .../flywheel/backend/FlwBackendXplat.java | 2 - .../BufferTextureInstanceComponent.java | 4 +- .../component/InstanceStructComponent.java | 2 +- .../component/SsboInstanceComponent.java | 2 +- .../flywheel/backend/engine/DrawManager.java | 3 +- .../engine/indirect/IndirectDrawManager.java | 4 +- .../instancing/InstancedDrawManager.java | 4 +- .../backend/gl/array/GlVertexArrayDSA.java | 5 +- .../backend/gl/array/GlVertexArrayGL3.java | 5 +- .../GlVertexArraySeparateAttributes.java | 5 +- .../flywheel/backend/glsl/LoadError.java | 7 +-- .../flywheel/backend/glsl/SourceFile.java | 31 ++++++------ .../flywheel/backend/glsl/SourceLines.java | 4 -- .../backend/glsl/error/ErrorBuilder.java | 1 - .../backend/glsl/error/lines/ErrorLine.java | 2 +- .../backend/glsl/error/lines/FileLine.java | 1 - .../backend/glsl/error/lines/HeaderLine.java | 1 - .../backend/glsl/error/lines/SourceLine.java | 1 - .../glsl/error/lines/SpanHighlightLine.java | 5 -- .../backend/glsl/error/lines/TextLine.java | 1 - .../backend/glsl/generate/FnSignature.java | 15 +++--- .../backend/glsl/generate/GlslBuilder.java | 50 +++++++++++-------- .../backend/glsl/generate/GlslExpr.java | 6 --- .../backend/glsl/generate/GlslFn.java | 11 ++-- .../backend/glsl/generate/GlslStruct.java | 20 ++++---- .../backend/glsl/generate/GlslSwitch.java | 10 ++-- .../backend/glsl/generate/GlslUniform.java | 21 ++++++++ .../glsl/generate/GlslUniformBlock.java | 31 ++++++------ .../glsl/generate/GlslVertexInput.java | 1 - .../flywheel/backend/glsl/parse/Import.java | 10 ++-- .../backend/glsl/parse/ShaderField.java | 16 +++--- .../backend/glsl/parse/ShaderFunction.java | 41 +++------------ .../backend/glsl/parse/ShaderStruct.java | 39 ++++++--------- .../backend/glsl/parse/ShaderVariable.java | 16 +++--- .../backend/glsl/parse/StructField.java | 16 ++---- .../backend/glsl/span/StringSpan.java | 1 - .../flywheel/lib/model/ModelCache.java | 11 ---- .../flywheel/lib/model/ModelHolder.java | 11 ---- .../flywheel/lib/model/Models.java | 7 +-- .../flywheel/lib/model/part/MeshTree.java | 2 +- .../flywheel/lib/model/part/ModelTrees.java | 2 +- .../flywheel/lib/util/FlwUtil.java | 21 -------- .../engine_room/flywheel/lib/util/Pair.java | 36 ------------- .../{model => util}/ResourceReloadCache.java | 10 ++-- .../{model => util}/ResourceReloadHolder.java | 10 ++-- .../flywheel/lib/util/ResourceUtil.java | 9 ++-- .../flywheel/lib/util/StringUtil.java | 6 --- .../engine_room/flywheel/lib/util/Unit.java | 5 -- .../lib/visual/component/FireComponent.java | 4 +- .../engine_room/flywheel/impl/FlwConfig.java | 3 ++ .../engine_room/flywheel/impl/FlwImpl.java | 2 +- .../impl}/LightSmoothnessArgument.java | 2 +- .../flywheel/backend/glsl/TestBase.java | 4 +- .../backend/glsl/TestShaderSourceLoading.java | 4 +- .../flywheel/impl/task/PlanExecutionTest.java | 2 +- .../lib/task/PlanCompositionTest.java | 2 +- .../flywheel/backend/FlwBackendXplatImpl.java | 10 ---- .../flywheel/impl/FabricFlwConfig.java | 14 +++--- .../flywheel/impl/FlwCommands.java | 1 - .../flywheel/impl/FlywheelFabric.java | 5 +- .../flywheel/backend/FlwBackendXplatImpl.java | 10 ---- .../flywheel/impl/FlwCommands.java | 1 - .../flywheel/impl/FlywheelForge.java | 5 +- .../flywheel/impl/ForgeFlwConfig.java | 10 ++-- 66 files changed, 251 insertions(+), 364 deletions(-) create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslUniform.java delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelCache.java delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelHolder.java delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/util/FlwUtil.java delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/util/Pair.java rename common/src/lib/java/dev/engine_room/flywheel/lib/{model => util}/ResourceReloadCache.java (72%) rename common/src/lib/java/dev/engine_room/flywheel/lib/{model => util}/ResourceReloadHolder.java (76%) delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/util/Unit.java rename common/src/{backend/java/dev/engine_room/flywheel/backend => main/java/dev/engine_room/flywheel/impl}/LightSmoothnessArgument.java (93%) diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/BackendConfig.java b/common/src/backend/java/dev/engine_room/flywheel/backend/BackendConfig.java index 779ab2f9c..b61b3bef5 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/BackendConfig.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/BackendConfig.java @@ -3,7 +3,7 @@ import dev.engine_room.flywheel.backend.compile.LightSmoothness; public interface BackendConfig { - BackendConfig INSTANCE = FlwBackendXplat.INSTANCE.getConfig(); + BackendConfig INSTANCE = FlwBackend.config(); /** * How smooth/accurate our flw_light impl is. diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackend.java b/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackend.java index a51878268..6e1dac124 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackend.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackend.java @@ -1,5 +1,6 @@ package dev.engine_room.flywheel.backend; +import org.jetbrains.annotations.UnknownNullability; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -7,11 +8,18 @@ public final class FlwBackend { public static final Logger LOGGER = LoggerFactory.getLogger(Flywheel.ID + "/backend"); + @UnknownNullability + private static BackendConfig config; private FlwBackend() { } - public static void init() { + public static BackendConfig config() { + return config; + } + + public static void init(BackendConfig config) { + FlwBackend.config = config; Backends.init(); } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplat.java b/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplat.java index 8cd216c59..6acb2832a 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplat.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplat.java @@ -9,6 +9,4 @@ public interface FlwBackendXplat { FlwBackendXplat INSTANCE = DependencyInjection.load(FlwBackendXplat.class, "dev.engine_room.flywheel.backend.FlwBackendXplatImpl"); int getLightEmission(BlockState state, BlockGetter level, BlockPos pos); - - BackendConfig getConfig(); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/BufferTextureInstanceComponent.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/BufferTextureInstanceComponent.java index 9a6a02012..262f051b8 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/BufferTextureInstanceComponent.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/BufferTextureInstanceComponent.java @@ -44,7 +44,9 @@ protected void generateUnpacking(GlslBuilder builder) { fnBody.ret(GlslExpr.call(STRUCT_NAME, unpackArgs)); - builder._addRaw("uniform usamplerBuffer _flw_instances;"); + builder.uniform() + .type("usamplerBuffer") + .name("_flw_instances"); builder.blankLine(); builder.function() .signature(FnSignature.create() diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/InstanceStructComponent.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/InstanceStructComponent.java index 76d56e755..ec875e095 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/InstanceStructComponent.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/InstanceStructComponent.java @@ -34,7 +34,7 @@ public String source() { var builder = new GlslBuilder(); var instance = builder.struct(); - instance.setName(STRUCT_NAME); + instance.name(STRUCT_NAME); for (var element : layout.elements()) { instance.addField(LayoutInterpreter.typeName(element.type()), element.name()); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/SsboInstanceComponent.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/SsboInstanceComponent.java index c8902b2d4..2b7df8af8 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/SsboInstanceComponent.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/component/SsboInstanceComponent.java @@ -43,7 +43,7 @@ protected void generateUnpacking(GlslBuilder builder) { fnBody.ret(GlslExpr.call(STRUCT_NAME, unpackArgs)); - builder._addRaw("layout(std430, binding = " + BufferBindings.INSTANCE + ") restrict readonly buffer InstanceBuffer {\n" + builder._raw("layout(std430, binding = " + BufferBindings.INSTANCE + ") restrict readonly buffer InstanceBuffer {\n" + " uint _flw_instances[];\n" + "};"); builder.blankLine(); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java index 993046659..19bdfeae1 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/DrawManager.java @@ -8,6 +8,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import com.mojang.datafixers.util.Pair; + import dev.engine_room.flywheel.api.backend.Engine; import dev.engine_room.flywheel.api.instance.Instance; import dev.engine_room.flywheel.api.instance.InstanceType; @@ -16,7 +18,6 @@ import dev.engine_room.flywheel.backend.FlwBackend; import dev.engine_room.flywheel.backend.engine.embed.Environment; import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage; -import dev.engine_room.flywheel.lib.util.Pair; import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import net.minecraft.client.resources.model.ModelBakery; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java index cb029600e..e19999b5c 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java @@ -219,8 +219,8 @@ public void renderCrumbling(List crumblingBlocks) { TextureBinder.bind(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey())); for (var instanceHandlePair : progressEntry.getValue()) { - IndirectInstancer instancer = instanceHandlePair.first(); - int instanceIndex = instanceHandlePair.second().index; + IndirectInstancer instancer = instanceHandlePair.getFirst(); + int instanceIndex = instanceHandlePair.getSecond().index; for (IndirectDraw draw : instancer.draws()) { // Transform the material to be suited for crumbling. diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java index f7c55d43f..400dae79c 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java @@ -180,8 +180,8 @@ public void renderCrumbling(List crumblingBlocks) { TextureBinder.bind(ModelBakery.BREAKING_LOCATIONS.get(progressEntry.getIntKey())); for (var instanceHandlePair : progressEntry.getValue()) { - InstancedInstancer instancer = instanceHandlePair.first(); - var index = instanceHandlePair.second().index; + InstancedInstancer instancer = instanceHandlePair.getFirst(); + var index = instanceHandlePair.getSecond().index; program.setInt("_flw_baseInstance", index); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayDSA.java b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayDSA.java index 4dca28b58..a3c67b0d2 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayDSA.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayDSA.java @@ -1,5 +1,6 @@ package dev.engine_room.flywheel.backend.gl.array; +import java.util.Arrays; import java.util.BitSet; import java.util.List; @@ -7,13 +8,13 @@ import org.lwjgl.system.Checks; import dev.engine_room.flywheel.backend.gl.GlCompat; -import dev.engine_room.flywheel.lib.util.FlwUtil; +import net.minecraft.Util; public class GlVertexArrayDSA extends GlVertexArray { public static final boolean SUPPORTED = isSupported(); private final BitSet attributeEnabled = new BitSet(MAX_ATTRIBS); private final VertexAttribute[] attributes = new VertexAttribute[MAX_ATTRIBS]; - private final int[] attributeBindings = FlwUtil.initArray(MAX_ATTRIBS, -1); + private final int[] attributeBindings = Util.make(new int[MAX_ATTRIBS], a -> Arrays.fill(a, -1)); private final int[] bindingBuffers = new int[MAX_ATTRIB_BINDINGS]; private final long[] bindingOffsets = new long[MAX_ATTRIB_BINDINGS]; private final int[] bindingStrides = new int[MAX_ATTRIB_BINDINGS]; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayGL3.java b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayGL3.java index bdf048ff4..785b17fca 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayGL3.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArrayGL3.java @@ -1,5 +1,6 @@ package dev.engine_room.flywheel.backend.gl.array; +import java.util.Arrays; import java.util.BitSet; import java.util.List; @@ -12,13 +13,13 @@ import dev.engine_room.flywheel.backend.gl.GlCompat; import dev.engine_room.flywheel.backend.gl.buffer.GlBufferType; -import dev.engine_room.flywheel.lib.util.FlwUtil; +import net.minecraft.Util; public abstract class GlVertexArrayGL3 extends GlVertexArray { private final BitSet attributeDirty = new BitSet(MAX_ATTRIBS); private final int[] attributeOffsets = new int[MAX_ATTRIBS]; private final VertexAttribute[] attributes = new VertexAttribute[MAX_ATTRIBS]; - private final int[] attributeBindings = FlwUtil.initArray(MAX_ATTRIBS, -1); + private final int[] attributeBindings = Util.make(new int[MAX_ATTRIBS], a -> Arrays.fill(a, -1)); private final int[] bindingBuffers = new int[MAX_ATTRIB_BINDINGS]; private final long[] bindingOffsets = new long[MAX_ATTRIB_BINDINGS]; private final int[] bindingStrides = new int[MAX_ATTRIB_BINDINGS]; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArraySeparateAttributes.java b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArraySeparateAttributes.java index a7bdbf978..6488ea03d 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArraySeparateAttributes.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/array/GlVertexArraySeparateAttributes.java @@ -1,5 +1,6 @@ package dev.engine_room.flywheel.backend.gl.array; +import java.util.Arrays; import java.util.BitSet; import java.util.List; @@ -9,13 +10,13 @@ import dev.engine_room.flywheel.backend.gl.GlCompat; import dev.engine_room.flywheel.backend.gl.GlStateTracker; import dev.engine_room.flywheel.backend.gl.buffer.GlBufferType; -import dev.engine_room.flywheel.lib.util.FlwUtil; +import net.minecraft.Util; public class GlVertexArraySeparateAttributes extends GlVertexArray { public static final boolean SUPPORTED = isSupported(); private final BitSet attributeEnabled = new BitSet(MAX_ATTRIBS); private final VertexAttribute[] attributes = new VertexAttribute[MAX_ATTRIBS]; - private final int[] attributeBindings = FlwUtil.initArray(MAX_ATTRIBS, -1); + private final int[] attributeBindings = Util.make(new int[MAX_ATTRIBS], a -> Arrays.fill(a, -1)); private final int[] bindingBuffers = new int[MAX_ATTRIB_BINDINGS]; private final long[] bindingOffsets = new long[MAX_ATTRIB_BINDINGS]; private final int[] bindingStrides = new int[MAX_ATTRIB_BINDINGS]; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/LoadError.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/LoadError.java index 0cfd3058b..5d4347ad4 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/LoadError.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/LoadError.java @@ -5,9 +5,10 @@ import java.util.List; import java.util.stream.Collectors; +import com.mojang.datafixers.util.Pair; + import dev.engine_room.flywheel.backend.glsl.error.ErrorBuilder; import dev.engine_room.flywheel.backend.glsl.span.Span; -import dev.engine_room.flywheel.lib.util.Pair; import net.minecraft.ResourceLocationException; import net.minecraft.resources.ResourceLocation; @@ -38,9 +39,9 @@ public ErrorBuilder generateMessage() { .pointAtFile(location); for (var innerError : innerErrors) { - var err = innerError.second() + var err = innerError.getSecond() .generateMessage(); - out.pointAt(innerError.first()) + out.pointAt(innerError.getFirst()) .nested(err); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceFile.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceFile.java index 020b85692..1f9646c56 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceFile.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceFile.java @@ -4,13 +4,13 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; import org.jetbrains.annotations.Nullable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.mojang.datafixers.util.Pair; import dev.engine_room.flywheel.backend.glsl.parse.Import; import dev.engine_room.flywheel.backend.glsl.parse.ShaderField; @@ -18,7 +18,6 @@ import dev.engine_room.flywheel.backend.glsl.parse.ShaderStruct; import dev.engine_room.flywheel.backend.glsl.span.Span; import dev.engine_room.flywheel.backend.glsl.span.StringSpan; -import dev.engine_room.flywheel.lib.util.Pair; import dev.engine_room.flywheel.lib.util.ResourceUtil; import net.minecraft.ResourceLocationException; import net.minecraft.resources.ResourceLocation; @@ -172,44 +171,48 @@ public Span getLineSpanMatching(int line, @Nullable String match) { * @param name The name of the struct to find. * @return null if no definition matches the name. */ - public Optional findStructByName(String name) { + @Nullable + public ShaderStruct findStruct(String name) { ShaderStruct struct = structs.get(name); if (struct != null) { - return Optional.of(struct); + return struct; } for (var include : included) { - var external = include.structs.get(name); + var external = include.findStruct(name); if (external != null) { - return Optional.of(external); + return external; } } - return Optional.empty(); + return null; } /** * Search this file and recursively search all imports to find a function definition matching the given name. * * @param name The name of the function to find. - * @return Optional#empty() if no definition matches the name. + * @return null if no definition matches the name. */ - public Optional findFunction(String name) { - ShaderFunction local = functions.get(name); + @Nullable + public ShaderFunction findFunction(String name) { + ShaderFunction function = functions.get(name); - if (local != null) return Optional.of(local); + if (function != null) { + return function; + } for (var include : included) { - var external = include.functions.get(name); + var external = include.findFunction(name); if (external != null) { - return Optional.of(external); + return external; } } - return Optional.empty(); + return null; } @Override diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceLines.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceLines.java index ebc40e19e..934c40865 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceLines.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/SourceLines.java @@ -128,10 +128,6 @@ public int length() { return raw.length(); } - public int lineStartCol(int spanLine) { - return 0; - } - public int lineWidth(int spanLine) { return lines.get(spanLine) .length(); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/ErrorBuilder.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/ErrorBuilder.java index f4e5fe023..b23a05954 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/ErrorBuilder.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/ErrorBuilder.java @@ -29,7 +29,6 @@ public class ErrorBuilder { private final List lines = new ArrayList<>(); private ErrorBuilder() { - } public static ErrorBuilder create() { diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/ErrorLine.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/ErrorLine.java index d2622ffcd..3e7811a04 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/ErrorLine.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/ErrorLine.java @@ -1,7 +1,6 @@ package dev.engine_room.flywheel.backend.glsl.error.lines; public interface ErrorLine { - default int neededMargin() { return left().length(); } @@ -17,6 +16,7 @@ default String build() { default String left() { return ""; } + default String right() { return ""; } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/FileLine.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/FileLine.java index e9f77a090..d7571cec8 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/FileLine.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/FileLine.java @@ -1,7 +1,6 @@ package dev.engine_room.flywheel.backend.glsl.error.lines; public record FileLine(String fileName) implements ErrorLine { - @Override public String left() { return "-"; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/HeaderLine.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/HeaderLine.java index dc4f24703..d6af1f811 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/HeaderLine.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/HeaderLine.java @@ -1,7 +1,6 @@ package dev.engine_room.flywheel.backend.glsl.error.lines; public record HeaderLine(String level, CharSequence message) implements ErrorLine { - @Override public int neededMargin() { return -1; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SourceLine.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SourceLine.java index 8015f5abe..96a2c7a93 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SourceLine.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SourceLine.java @@ -1,7 +1,6 @@ package dev.engine_room.flywheel.backend.glsl.error.lines; public record SourceLine(String number, String line) implements ErrorLine { - public static SourceLine numbered(int number, String line) { return new SourceLine(Integer.toString(number), line); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SpanHighlightLine.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SpanHighlightLine.java index 1d1e41a72..1fe6c1b28 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SpanHighlightLine.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/SpanHighlightLine.java @@ -7,11 +7,6 @@ public SpanHighlightLine(int firstCol, int lastCol) { line = generateUnderline(firstCol, lastCol); } - @Override - public String left() { - return ""; - } - @Override public String right() { return line; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/TextLine.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/TextLine.java index e957106db..5880d8566 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/TextLine.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/error/lines/TextLine.java @@ -1,7 +1,6 @@ package dev.engine_room.flywheel.backend.glsl.error.lines; public record TextLine(String msg) implements ErrorLine { - @Override public String build() { return msg; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/FnSignature.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/FnSignature.java index dcfea176c..6dd81ff65 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/FnSignature.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/FnSignature.java @@ -3,12 +3,12 @@ import java.util.Collection; import java.util.stream.Collectors; -import com.google.common.collect.ImmutableList; +import org.jetbrains.annotations.Nullable; -import dev.engine_room.flywheel.lib.util.Pair; +import com.google.common.collect.ImmutableList; +import com.mojang.datafixers.util.Pair; public record FnSignature(String returnType, String name, ImmutableList> args) { - public static Builder create() { return new Builder(); } @@ -26,7 +26,7 @@ public static FnSignature ofVoid(String name) { public Collection createArgExpressions() { return args.stream() - .map(Pair::second) + .map(Pair::getSecond) .map(GlslExpr::variable) .collect(Collectors.toList()); } @@ -37,18 +37,20 @@ public boolean isVoid() { public String fullDeclaration() { return returnType + ' ' + name + '(' + args.stream() - .map(p -> p.first() + ' ' + p.second()) + .map(p -> p.getFirst() + ' ' + p.getSecond()) .collect(Collectors.joining(", ")) + ')'; } public String signatureDeclaration() { return returnType + ' ' + name + '(' + args.stream() - .map(Pair::first) + .map(Pair::getFirst) .collect(Collectors.joining(", ")) + ')'; } public static class Builder { + @Nullable private String returnType; + @Nullable private String name; private final ImmutableList.Builder> args = ImmutableList.builder(); @@ -77,5 +79,4 @@ public FnSignature build() { return new FnSignature(returnType, name, args.build()); } } - } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslBuilder.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslBuilder.java index 74f95b389..ba28d74ab 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslBuilder.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslBuilder.java @@ -19,29 +19,33 @@ public GlslStruct struct() { return add(new GlslStruct()); } - public GlslFn function() { - return add(new GlslFn()); - } - public GlslVertexInput vertexInput() { return add(new GlslVertexInput()); } + public GlslUniform uniform() { + return add(new GlslUniform()); + } + public GlslUniformBlock uniformBlock() { return add(new GlslUniformBlock()); } - public T add(T element) { - elements.add(element); - return element; + public GlslFn function() { + return add(new GlslFn()); } public void blankLine() { - elements.add(Separators.BLANK_LINE); + add(Separators.BLANK_LINE); + } + + public void _raw(String sourceString) { + add(new Raw(sourceString)); } - public void _addRaw(String sourceString) { - elements.add(() -> sourceString); + public T add(T element) { + elements.add(element); + return element; } public String build() { @@ -54,6 +58,20 @@ public interface Declaration { String prettyPrint(); } + public record Define(String name, String value) implements Declaration { + @Override + public String prettyPrint() { + return "#define " + name + " " + value; + } + } + + public record Undef(String name) implements Declaration { + @Override + public String prettyPrint() { + return "#undef " + name; + } + } + public enum Separators implements Declaration { BLANK_LINE(""), ; @@ -70,18 +88,10 @@ public String prettyPrint() { } } - public record Define(String name, String value) implements Declaration { - @Override - public String prettyPrint() { - return "#define " + name + " " + value; - } - } - - public record Undef(String name) implements Declaration { + public record Raw(String sourceString) implements Declaration { @Override public String prettyPrint() { - return "#undef " + name; + return sourceString; } } - } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslExpr.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslExpr.java index c9f15ea73..0d4d36b86 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslExpr.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslExpr.java @@ -8,7 +8,6 @@ import com.google.common.collect.ImmutableList; public interface GlslExpr { - /** * Create a glsl variable with the given name. * @@ -129,7 +128,6 @@ record Variable(String name) implements GlslExpr { public String prettyPrint() { return name; } - } record FunctionCall(String name, Collection args) implements GlslExpr { @@ -144,7 +142,6 @@ public String prettyPrint() { .collect(Collectors.joining(", ")); return name + "(" + args + ")"; } - } record FunctionCall0(String name) implements GlslExpr { @@ -152,7 +149,6 @@ record FunctionCall0(String name) implements GlslExpr { public String prettyPrint() { return name + "()"; } - } record Swizzle(GlslExpr target, String selection) implements GlslExpr { @@ -160,7 +156,6 @@ record Swizzle(GlslExpr target, String selection) implements GlslExpr { public String prettyPrint() { return target.prettyPrint() + "." + selection; } - } record Access(GlslExpr target, String argName) implements GlslExpr { @@ -168,7 +163,6 @@ record Access(GlslExpr target, String argName) implements GlslExpr { public String prettyPrint() { return target.prettyPrint() + "." + argName; } - } record Clamp(GlslExpr value, GlslExpr from, GlslExpr to) implements GlslExpr { diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslFn.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslFn.java index cd6bbea68..f56735a52 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslFn.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslFn.java @@ -5,24 +5,25 @@ import dev.engine_room.flywheel.lib.util.StringUtil; public class GlslFn implements GlslBuilder.Declaration { - private GlslBlock body = new GlslBlock(); private FnSignature signature; + private GlslBlock body = new GlslBlock(); public GlslFn signature(FnSignature signature) { this.signature = signature; return this; } - public GlslFn body(Consumer f) { - f.accept(body); + public GlslFn body(GlslBlock block) { + body = block; return this; } - public GlslFn body(GlslBlock block) { - body = block; + public GlslFn body(Consumer f) { + f.accept(body); return this; } + @Override public String prettyPrint() { return """ %s { diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslStruct.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslStruct.java index 5da578f56..f60c514d2 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslStruct.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslStruct.java @@ -4,15 +4,15 @@ import java.util.List; import java.util.stream.Collectors; -import dev.engine_room.flywheel.lib.util.Pair; +import com.mojang.datafixers.util.Pair; + import dev.engine_room.flywheel.lib.util.StringUtil; public class GlslStruct implements GlslBuilder.Declaration { - - private final List> fields = new ArrayList<>(); private String name; + private final List> fields = new ArrayList<>(); - public GlslStruct setName(String name) { + public GlslStruct name(String name) { this.name = name; return this; } @@ -22,12 +22,6 @@ public GlslStruct addField(String type, String name) { return this; } - private String buildFields() { - return fields.stream() - .map(p -> p.first() + ' ' + p.second() + ';') - .collect(Collectors.joining("\n")); - } - @Override public String prettyPrint() { return """ @@ -35,4 +29,10 @@ public String prettyPrint() { %s };""".formatted(name, StringUtil.indent(buildFields(), 4)); } + + private String buildFields() { + return fields.stream() + .map(p -> p.getFirst() + ' ' + p.getSecond() + ';') + .collect(Collectors.joining("\n")); + } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslSwitch.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslSwitch.java index 37ef474b8..5112165cb 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslSwitch.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslSwitch.java @@ -4,13 +4,17 @@ import java.util.List; import java.util.stream.Collectors; -import dev.engine_room.flywheel.lib.util.Pair; +import org.jetbrains.annotations.Nullable; + +import com.mojang.datafixers.util.Pair; + import dev.engine_room.flywheel.lib.util.StringUtil; public class GlslSwitch implements GlslStmt { private final GlslExpr on; private final List> cases = new ArrayList<>(); + @Nullable private GlslBlock defaultCase = null; private GlslSwitch(GlslExpr on) { @@ -52,9 +56,9 @@ private String formatCases() { } private static String prettyPrintCase(Pair p) { - var variant = p.first() + var variant = p.getFirst() .prettyPrint(); - var block = p.second() + var block = p.getSecond() .prettyPrint(); return """ case %s: diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslUniform.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslUniform.java new file mode 100644 index 000000000..bc87770b6 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslUniform.java @@ -0,0 +1,21 @@ +package dev.engine_room.flywheel.backend.glsl.generate; + +public class GlslUniform implements GlslBuilder.Declaration { + private String type; + private String name; + + public GlslUniform type(String typeName) { + type = typeName; + return this; + } + + public GlslUniform name(String name) { + this.name = name; + return this; + } + + @Override + public String prettyPrint() { + return "uniform " + type + " " + name + ";"; + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslUniformBlock.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslUniformBlock.java index 011cfe822..f1edcbebc 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslUniformBlock.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslUniformBlock.java @@ -4,7 +4,8 @@ import java.util.List; import java.util.stream.Collectors; -import dev.engine_room.flywheel.lib.util.Pair; +import com.mojang.datafixers.util.Pair; + import dev.engine_room.flywheel.lib.util.StringUtil; public class GlslUniformBlock implements GlslBuilder.Declaration { @@ -12,20 +13,6 @@ public class GlslUniformBlock implements GlslBuilder.Declaration { private String name; private final List> members = new ArrayList<>(); - @Override - public String prettyPrint() { - return """ - layout(%s) uniform %s { - %s - };""".formatted(qualifier, name, StringUtil.indent(formatMembers(), 4)); - } - - private String formatMembers() { - return members.stream() - .map(p -> p.first() + " " + p.second() + ";") - .collect(Collectors.joining("\n")); - } - public GlslUniformBlock layout(String qualifier) { this.qualifier = qualifier; return this; @@ -40,4 +27,18 @@ public GlslUniformBlock member(String typeName, String variableName) { members.add(Pair.of(typeName, variableName)); return this; } + + @Override + public String prettyPrint() { + return """ + layout(%s) uniform %s { + %s + };""".formatted(qualifier, name, StringUtil.indent(formatMembers(), 4)); + } + + private String formatMembers() { + return members.stream() + .map(p -> p.getFirst() + " " + p.getSecond() + ";") + .collect(Collectors.joining("\n")); + } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslVertexInput.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslVertexInput.java index c33f11c12..9763026b4 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslVertexInput.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/generate/GlslVertexInput.java @@ -1,7 +1,6 @@ package dev.engine_room.flywheel.backend.glsl.generate; public class GlslVertexInput implements GlslBuilder.Declaration { - private int binding; private String type; private String name; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/Import.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/Import.java index 159009b21..006338c6f 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/Import.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/Import.java @@ -12,17 +12,17 @@ public record Import(Span self, Span file) { public static final Pattern PATTERN = Pattern.compile("^\\s*#\\s*include\\s+\"(.*)\"", Pattern.MULTILINE); /** - * Scan the source for {@code #use "..."} directives. + * Scan the source for {@code #include "..."} directives. * Records the contents of the directive into an {@link Import} object, and marks the directive for elision. */ public static ImmutableList parseImports(SourceLines source) { - Matcher uses = PATTERN.matcher(source); + Matcher matcher = PATTERN.matcher(source); var imports = ImmutableList.builder(); - while (uses.find()) { - Span use = Span.fromMatcher(source, uses); - Span file = Span.fromMatcher(source, uses, 1); + while (matcher.find()) { + Span use = Span.fromMatcher(source, matcher); + Span file = Span.fromMatcher(source, matcher, 1); imports.add(new Import(use, file)); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderField.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderField.java index 5ed22b77a..3f7871d43 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderField.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderField.java @@ -13,17 +13,19 @@ public class ShaderField { public static final Pattern PATTERN = Pattern.compile("layout\\s*\\(location\\s*=\\s*(\\d+)\\)\\s+(in|out)\\s+([\\w\\d]+)\\s+" + "([\\w\\d]+)"); + public final Span self; public final Span location; - public final @Nullable Decoration decoration; + public final Span qualifierSpan; + @Nullable + public final Qualifier qualifier; public final Span type; public final Span name; - public final Span self; - public ShaderField(Span self, Span location, Span inOut, Span type, Span name) { + public ShaderField(Span self, Span location, Span qualifier, Span type, Span name) { this.self = self; - this.location = location; - this.decoration = Decoration.fromSpan(inOut); + this.qualifierSpan = qualifier; + this.qualifier = Qualifier.fromSpan(qualifier); this.type = type; this.name = name; } @@ -48,14 +50,14 @@ public static ImmutableMap parseFields(SourceLines source) return fields.build(); } - public enum Decoration { + public enum Qualifier { IN, OUT, FLAT, ; @Nullable - public static Decoration fromSpan(Span span) { + public static Qualifier fromSpan(Span span) { return switch (span.toString()) { case "in" -> IN; case "out" -> OUT; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderFunction.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderFunction.java index 5d6c06bbf..4ddec0e01 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderFunction.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderFunction.java @@ -17,17 +17,16 @@ public class ShaderFunction { // https://regexr.com/60n3d public static final Pattern PATTERN = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(([\\w,\\s]*)\\)\\s*\\{"); + public static final Pattern ARGUMENT_PATTERN = Pattern.compile("(?:(inout|in|out) )?(\\w+)\\s+(\\w+)"); + public static final Pattern ASSIGNMENT_PATTERN = Pattern.compile("(\\w+)\\s*="); - public static final Pattern argument = Pattern.compile("(?:(inout|in|out) )?(\\w+)\\s+(\\w+)"); - public static final Pattern assignment = Pattern.compile("(\\w+)\\s*="); public final Span self; + public final Span type; + public final Span name; + public final Span args; + public final Span body; - private final Span type; - private final Span name; - private final Span args; - private final Span body; - - private final ImmutableList parameters; + public final ImmutableList parameters; public ShaderFunction(Span self, Span type, Span name, Span args, Span body) { this.self = self; @@ -95,22 +94,6 @@ private static int findEndOfBlock(CharSequence source, int start) { return -1; } - public Span getType() { - return type; - } - - public Span getName() { - return name; - } - - public Span getArgs() { - return args; - } - - public Span getBody() { - return body; - } - public String call(String... args) { return name + "(" + String.join(", ", args) + ")"; } @@ -119,18 +102,10 @@ public Span getParameterType(int index) { return parameters.get(index).type; } - public ImmutableList getParameters() { - return parameters; - } - - public String returnTypeName() { - return type.get(); - } - protected ImmutableList parseArguments() { if (args.isErr() || args.isEmpty()) return ImmutableList.of(); - Matcher arguments = argument.matcher(args.get()); + Matcher arguments = ARGUMENT_PATTERN.matcher(args.get()); ImmutableList.Builder builder = ImmutableList.builder(); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderStruct.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderStruct.java index d1af3342e..3b4a7fe7a 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderStruct.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderStruct.java @@ -13,19 +13,20 @@ public class ShaderStruct { // https://regexr.com/61rpe public static final Pattern PATTERN = Pattern.compile("struct\\s+([\\w_]*)\\s*\\{(.*?)}\\s*([\\w_]*)?\\s*;\\s", Pattern.DOTALL); + public final Span self; public final Span name; public final Span body; - public final Span self; public final Span variableName; - private final ImmutableList fields; - private final ImmutableMap fields2Types; + public final ImmutableList fields; + public final ImmutableMap fields2Types; public ShaderStruct(Span self, Span name, Span body, Span variableName) { this.self = self; this.name = name; this.body = body; this.variableName = variableName; + this.fields = parseFields(); this.fields2Types = createTypeLookup(); } @@ -51,29 +52,8 @@ public static ImmutableMap parseStructs(SourceLines source return structs.build(); } - public Span getName() { - return name; - } - - public Span getBody() { - return body; - } - - public ImmutableList getFields() { - return fields; - } - - private ImmutableMap createTypeLookup() { - ImmutableMap.Builder lookup = ImmutableMap.builder(); - for (StructField field : fields) { - lookup.put(field.name.get(), field.type); - } - - return lookup.build(); - } - private ImmutableList parseFields() { - Matcher matcher = StructField.fieldPattern.matcher(body); + Matcher matcher = StructField.PATTERN.matcher(body); ImmutableList.Builder fields = ImmutableList.builder(); @@ -88,6 +68,15 @@ private ImmutableList parseFields() { return fields.build(); } + private ImmutableMap createTypeLookup() { + ImmutableMap.Builder lookup = ImmutableMap.builder(); + for (StructField field : fields) { + lookup.put(field.name.get(), field.type); + } + + return lookup.build(); + } + @Override public String toString() { return "struct " + name; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderVariable.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderVariable.java index 707c92830..6eee86538 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderVariable.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/ShaderVariable.java @@ -1,21 +1,23 @@ package dev.engine_room.flywheel.backend.glsl.parse; +import org.jetbrains.annotations.Nullable; + import dev.engine_room.flywheel.backend.glsl.span.Span; public class ShaderVariable { - + public final Span self; public final Span qualifierSpan; + @Nullable + public final Qualifier qualifier; public final Span type; public final Span name; - public final Qualifier qualifier; - public final Span self; public ShaderVariable(Span self, Span qualifier, Span type, Span name) { this.self = self; this.qualifierSpan = qualifier; + this.qualifier = Qualifier.fromSpan(qualifierSpan); this.type = type; this.name = name; - this.qualifier = Qualifier.fromSpan(qualifierSpan); } @Override @@ -27,9 +29,9 @@ public enum Qualifier { NONE, IN, OUT, - INOUT, - ERROR; + INOUT; + @Nullable public static Qualifier fromSpan(Span s) { String span = s.toString(); @@ -38,7 +40,7 @@ public static Qualifier fromSpan(Span s) { case "in" -> IN; case "inout" -> INOUT; case "out" -> OUT; - default -> ERROR; + default -> null; }; } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/StructField.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/StructField.java index b0b3f7448..016494607 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/StructField.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/parse/StructField.java @@ -5,11 +5,11 @@ import dev.engine_room.flywheel.backend.glsl.span.Span; public class StructField { - public static final Pattern fieldPattern = Pattern.compile("(\\S+)\\s*(\\S+);"); - public final Span self; + public static final Pattern PATTERN = Pattern.compile("(\\S+)\\s*(\\S+);"); - public Span type; - public Span name; + public final Span self; + public final Span type; + public final Span name; public StructField(Span self, Span type, Span name) { this.self = self; @@ -17,14 +17,6 @@ public StructField(Span self, Span type, Span name) { this.name = name; } - public Span getType() { - return type; - } - - public Span getName() { - return name; - } - @Override public String toString() { return type + " " + name; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/span/StringSpan.java b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/span/StringSpan.java index 547ffbb0c..75d50165b 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/span/StringSpan.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/glsl/span/StringSpan.java @@ -3,7 +3,6 @@ import dev.engine_room.flywheel.backend.glsl.SourceLines; public class StringSpan extends Span { - public StringSpan(SourceLines in, int start, int end) { super(in, start, end); } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelCache.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelCache.java deleted file mode 100644 index 2b99cde80..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelCache.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.engine_room.flywheel.lib.model; - -import java.util.function.Function; - -import dev.engine_room.flywheel.api.model.Model; - -public final class ModelCache extends ResourceReloadCache { - public ModelCache(Function factory) { - super(factory); - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelHolder.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelHolder.java deleted file mode 100644 index 7e838bf70..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ModelHolder.java +++ /dev/null @@ -1,11 +0,0 @@ -package dev.engine_room.flywheel.lib.model; - -import java.util.function.Supplier; - -import dev.engine_room.flywheel.api.model.Model; - -public final class ModelHolder extends ResourceReloadHolder { - public ModelHolder(Supplier factory) { - super(factory); - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/Models.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/Models.java index 93a4849a5..9e0e3a813 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/Models.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/Models.java @@ -9,6 +9,7 @@ import dev.engine_room.flywheel.lib.model.baked.BlockModelBuilder; import dev.engine_room.flywheel.lib.model.baked.PartialModel; import dev.engine_room.flywheel.lib.transform.TransformStack; +import dev.engine_room.flywheel.lib.util.ResourceReloadCache; import net.minecraft.core.Direction; import net.minecraft.world.level.block.state.BlockState; @@ -19,11 +20,11 @@ * method with the same parameters will return the same object. */ public final class Models { - private static final ModelCache BLOCK_STATE = new ModelCache<>(it -> BlockModelBuilder.create(it) + private static final ResourceReloadCache BLOCK_STATE = new ResourceReloadCache<>(it -> BlockModelBuilder.create(it) .build()); - private static final ModelCache PARTIAL = new ModelCache<>(it -> BakedModelBuilder.create(it.get()) + private static final ResourceReloadCache PARTIAL = new ResourceReloadCache<>(it -> BakedModelBuilder.create(it.get()) .build()); - private static final ModelCache> TRANSFORMED_PARTIAL = new ModelCache<>(TransformedPartial::create); + private static final ResourceReloadCache, Model> TRANSFORMED_PARTIAL = new ResourceReloadCache<>(TransformedPartial::create); private Models() { } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java index f9d0bd38c..98c10fc0c 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/MeshTree.java @@ -10,8 +10,8 @@ import dev.engine_room.flywheel.api.model.Mesh; import dev.engine_room.flywheel.lib.internal.FlwLibLink; import dev.engine_room.flywheel.lib.memory.MemoryBlock; -import dev.engine_room.flywheel.lib.model.ResourceReloadCache; import dev.engine_room.flywheel.lib.model.SimpleQuadMesh; +import dev.engine_room.flywheel.lib.util.ResourceReloadCache; import dev.engine_room.flywheel.lib.vertex.PosTexNormalVertexView; import dev.engine_room.flywheel.lib.vertex.VertexView; import net.minecraft.client.Minecraft; diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTrees.java b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTrees.java index 7c87c6275..7b9599f82 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTrees.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/model/part/ModelTrees.java @@ -10,9 +10,9 @@ import dev.engine_room.flywheel.api.material.Material; import dev.engine_room.flywheel.api.model.Mesh; import dev.engine_room.flywheel.api.model.Model; -import dev.engine_room.flywheel.lib.model.ResourceReloadCache; import dev.engine_room.flywheel.lib.model.RetexturedMesh; import dev.engine_room.flywheel.lib.model.SingleMeshModel; +import dev.engine_room.flywheel.lib.util.ResourceReloadCache; import net.minecraft.client.model.geom.ModelLayerLocation; import net.minecraft.client.renderer.texture.TextureAtlasSprite; diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/FlwUtil.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/FlwUtil.java deleted file mode 100644 index c00d10d97..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/util/FlwUtil.java +++ /dev/null @@ -1,21 +0,0 @@ -package dev.engine_room.flywheel.lib.util; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Set; -import java.util.WeakHashMap; - -public final class FlwUtil { - private FlwUtil() { - } - - public static int[] initArray(int size, int fill) { - var out = new int[size]; - Arrays.fill(out, fill); - return out; - } - - public static Set createWeakHashSet() { - return Collections.newSetFromMap(new WeakHashMap<>()); - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/Pair.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/Pair.java deleted file mode 100644 index a54e9e297..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/util/Pair.java +++ /dev/null @@ -1,36 +0,0 @@ -package dev.engine_room.flywheel.lib.util; - -import java.util.Objects; - -public record Pair(F first, S second) { - public static Pair of(F first, S second) { - return new Pair<>(first, second); - } - - public Pair swap() { - return Pair.of(second, first); - } - - public Pair copy() { - return Pair.of(first, second); - } - - @Override - public boolean equals(final Object obj) { - if (obj == this) return true; - if (obj instanceof final Pair other) { - return Objects.equals(first, other.first) && Objects.equals(second, other.second); - } - return false; - } - - @Override - public int hashCode() { - return (Objects.hashCode(first) * 31) ^ Objects.hashCode(second); - } - - @Override - public String toString() { - return "(" + first + ", " + second + ")"; - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ResourceReloadCache.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceReloadCache.java similarity index 72% rename from common/src/lib/java/dev/engine_room/flywheel/lib/model/ResourceReloadCache.java rename to common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceReloadCache.java index 363cf61b2..dcacb38d3 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ResourceReloadCache.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceReloadCache.java @@ -1,16 +1,16 @@ -package dev.engine_room.flywheel.lib.model; +package dev.engine_room.flywheel.lib.util; +import java.util.Collections; import java.util.Map; import java.util.Set; +import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; import org.jetbrains.annotations.ApiStatus; -import dev.engine_room.flywheel.lib.util.FlwUtil; - -public class ResourceReloadCache implements Function { - private static final Set> ALL = FlwUtil.createWeakHashSet(); +public final class ResourceReloadCache implements Function { + private static final Set> ALL = Collections.newSetFromMap(new WeakHashMap<>()); private final Function factory; private final Map map = new ConcurrentHashMap<>(); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ResourceReloadHolder.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceReloadHolder.java similarity index 76% rename from common/src/lib/java/dev/engine_room/flywheel/lib/model/ResourceReloadHolder.java rename to common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceReloadHolder.java index dd00c5e2a..9ff36d40c 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/model/ResourceReloadHolder.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceReloadHolder.java @@ -1,15 +1,15 @@ -package dev.engine_room.flywheel.lib.model; +package dev.engine_room.flywheel.lib.util; +import java.util.Collections; import java.util.Set; +import java.util.WeakHashMap; import java.util.function.Supplier; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; -import dev.engine_room.flywheel.lib.util.FlwUtil; - -public class ResourceReloadHolder implements Supplier { - private static final Set> ALL = FlwUtil.createWeakHashSet(); +public final class ResourceReloadHolder implements Supplier { + private static final Set> ALL = Collections.newSetFromMap(new WeakHashMap<>()); private final Supplier factory; @Nullable private volatile T obj; diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceUtil.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceUtil.java index 9eb0f1419..8aca2b9be 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceUtil.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/util/ResourceUtil.java @@ -53,10 +53,11 @@ public static ResourceLocation readFlywheelDefault(StringReader reader) throws C } } + /** + * Same as {@link ResourceLocation#toDebugFileName()}, but also removes the file extension. + */ public static String toDebugFileNameNoExtension(ResourceLocation resourceLocation) { - var stringLoc = resourceLocation.toString(); - return stringLoc.substring(0, stringLoc.lastIndexOf('.')) - .replace('/', '_') - .replace(':', '_'); + var stringLoc = resourceLocation.toDebugFileName(); + return stringLoc.substring(0, stringLoc.lastIndexOf('.')); } } diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/StringUtil.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/StringUtil.java index 93364d43a..6cf5bfcbd 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/util/StringUtil.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/util/StringUtil.java @@ -57,12 +57,6 @@ public static String formatAddress(long address) { return "0x" + Long.toHexString(address); } - public static String args(String functionName, Object... args) { - return functionName + '(' + Arrays.stream(args) - .map(Object::toString) - .collect(Collectors.joining(", ")) + ')'; - } - public static String trimPrefix(String s, String prefix) { if (s.startsWith(prefix)) { return s.substring(prefix.length()); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/util/Unit.java b/common/src/lib/java/dev/engine_room/flywheel/lib/util/Unit.java deleted file mode 100644 index 2544e4286..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/util/Unit.java +++ /dev/null @@ -1,5 +0,0 @@ -package dev.engine_room.flywheel.lib.util; - -public enum Unit { - INSTANCE; -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/component/FireComponent.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/component/FireComponent.java index 13d60a58d..0e0cb8301 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/component/FireComponent.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/component/FireComponent.java @@ -15,9 +15,9 @@ import dev.engine_room.flywheel.lib.instance.TransformedInstance; import dev.engine_room.flywheel.lib.material.Materials; import dev.engine_room.flywheel.lib.material.SimpleMaterial; -import dev.engine_room.flywheel.lib.model.ModelCache; import dev.engine_room.flywheel.lib.model.QuadMesh; import dev.engine_room.flywheel.lib.model.SingleMeshModel; +import dev.engine_room.flywheel.lib.util.ResourceReloadCache; import dev.engine_room.flywheel.lib.visual.util.SmartRecycler; import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.texture.TextureAtlasSprite; @@ -36,7 +36,7 @@ public final class FireComponent implements EntityComponent { // Parameterize by the material instead of the sprite // because Material#sprite is a surprisingly heavy operation // and because sprites are invalidated after a resource reload. - private static final ModelCache FIRE_MODELS = new ModelCache<>(texture -> { + private static final ResourceReloadCache FIRE_MODELS = new ResourceReloadCache<>(texture -> { return new SingleMeshModel(new FireMesh(texture.sprite()), FIRE_MATERIAL); }); diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/FlwConfig.java b/common/src/main/java/dev/engine_room/flywheel/impl/FlwConfig.java index ba661d25c..760711c0f 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/FlwConfig.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/FlwConfig.java @@ -1,6 +1,7 @@ package dev.engine_room.flywheel.impl; import dev.engine_room.flywheel.api.backend.Backend; +import dev.engine_room.flywheel.backend.BackendConfig; public interface FlwConfig { FlwConfig INSTANCE = FlwImplXplat.INSTANCE.getConfig(); @@ -10,4 +11,6 @@ public interface FlwConfig { boolean limitUpdates(); int workerThreads(); + + BackendConfig backendConfig(); } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/FlwImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/FlwImpl.java index d1eafb15e..2b33206ac 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/FlwImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/FlwImpl.java @@ -35,7 +35,7 @@ public static void init() { StandardMaterialShaders.init(); // backend - FlwBackend.init(); + FlwBackend.init(FlwConfig.INSTANCE.backendConfig()); // vanilla VanillaVisuals.init(); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/LightSmoothnessArgument.java b/common/src/main/java/dev/engine_room/flywheel/impl/LightSmoothnessArgument.java similarity index 93% rename from common/src/backend/java/dev/engine_room/flywheel/backend/LightSmoothnessArgument.java rename to common/src/main/java/dev/engine_room/flywheel/impl/LightSmoothnessArgument.java index 871184d5d..da04731c5 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/LightSmoothnessArgument.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/LightSmoothnessArgument.java @@ -1,4 +1,4 @@ -package dev.engine_room.flywheel.backend; +package dev.engine_room.flywheel.impl; import dev.engine_room.flywheel.backend.compile.LightSmoothness; import net.minecraft.commands.arguments.StringRepresentableArgument; diff --git a/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestBase.java b/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestBase.java index fe6be7887..5c159daf8 100644 --- a/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestBase.java +++ b/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestBase.java @@ -30,10 +30,10 @@ static E assertSimpleNestedErrorsToDepth(Class finalErr var pair = assertSingletonList(includeError.innerErrors()); for (int i = 1; i < depth; i++) { - includeError = assertInstanceOf(LoadError.IncludeError.class, pair.second()); + includeError = assertInstanceOf(LoadError.IncludeError.class, pair.getSecond()); pair = assertSingletonList(includeError.innerErrors()); } - return assertInstanceOf(finalErrType, pair.second()); + return assertInstanceOf(finalErrType, pair.getSecond()); } public static SourceFile findAndAssertSuccess(MockShaderSources sources, ResourceLocation loc) { diff --git a/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestShaderSourceLoading.java b/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestShaderSourceLoading.java index 51bcccf91..3acf1ba2d 100644 --- a/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestShaderSourceLoading.java +++ b/common/src/test/java/dev/engine_room/flywheel/backend/glsl/TestShaderSourceLoading.java @@ -129,11 +129,11 @@ void testSelfInclude() { var shouldBeRecursiveIncludePair = assertSingletonList(aErr.innerErrors()); - var circularDependency = assertInstanceOf(LoadError.CircularDependency.class, shouldBeRecursiveIncludePair.second()); + var circularDependency = assertInstanceOf(LoadError.CircularDependency.class, shouldBeRecursiveIncludePair.getSecond()); assertEquals(ImmutableList.of(FLW_A, FLW_A), circularDependency.stack()); assertEquals(FLW_A, circularDependency.offender()); - assertEquals(FLW_A.toString(), shouldBeRecursiveIncludePair.first() + assertEquals(FLW_A.toString(), shouldBeRecursiveIncludePair.getFirst() .toString()); } diff --git a/common/src/test/java/dev/engine_room/flywheel/impl/task/PlanExecutionTest.java b/common/src/test/java/dev/engine_room/flywheel/impl/task/PlanExecutionTest.java index 90991fbd8..9a967baac 100644 --- a/common/src/test/java/dev/engine_room/flywheel/impl/task/PlanExecutionTest.java +++ b/common/src/test/java/dev/engine_room/flywheel/impl/task/PlanExecutionTest.java @@ -21,8 +21,8 @@ import dev.engine_room.flywheel.lib.task.Synchronizer; import dev.engine_room.flywheel.lib.task.UnitPlan; import dev.engine_room.flywheel.lib.task.functional.RunnableWithContext; -import dev.engine_room.flywheel.lib.util.Unit; import it.unimi.dsi.fastutil.ints.IntArrayList; +import net.minecraft.util.Unit; class PlanExecutionTest { protected static ParallelTaskExecutor EXECUTOR; diff --git a/common/src/test/java/dev/engine_room/flywheel/lib/task/PlanCompositionTest.java b/common/src/test/java/dev/engine_room/flywheel/lib/task/PlanCompositionTest.java index 2569b5848..d35b7ac26 100644 --- a/common/src/test/java/dev/engine_room/flywheel/lib/task/PlanCompositionTest.java +++ b/common/src/test/java/dev/engine_room/flywheel/lib/task/PlanCompositionTest.java @@ -4,7 +4,7 @@ import org.junit.jupiter.api.Test; import dev.engine_room.flywheel.api.task.Plan; -import dev.engine_room.flywheel.lib.util.Unit; +import net.minecraft.util.Unit; public class PlanCompositionTest { public static final Plan SIMPLE = SimplePlan.of(() -> { diff --git a/fabric/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java b/fabric/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java index 23a08243e..661f58bc7 100644 --- a/fabric/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java +++ b/fabric/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java @@ -1,22 +1,12 @@ package dev.engine_room.flywheel.backend; -import org.jetbrains.annotations.UnknownNullability; - import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.block.state.BlockState; public class FlwBackendXplatImpl implements FlwBackendXplat { - @UnknownNullability - public static BackendConfig CONFIG; - @Override public int getLightEmission(BlockState state, BlockGetter level, BlockPos pos) { return state.getLightEmission(); } - - @Override - public BackendConfig getConfig() { - return CONFIG; - } } diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/FabricFlwConfig.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/FabricFlwConfig.java index aa04433b5..cf51ddf48 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/FabricFlwConfig.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/FabricFlwConfig.java @@ -16,7 +16,6 @@ import dev.engine_room.flywheel.api.backend.BackendManager; import dev.engine_room.flywheel.backend.BackendConfig; import dev.engine_room.flywheel.backend.FlwBackend; -import dev.engine_room.flywheel.backend.FlwBackendXplatImpl; import dev.engine_room.flywheel.backend.compile.LightSmoothness; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.ResourceLocationException; @@ -39,16 +38,14 @@ public class FabricFlwConfig implements FlwConfig { private final File file; - public final FabricBackendConfig backendConfig = new FabricBackendConfig(); - public Backend backend = BackendManager.defaultBackend(); public boolean limitUpdates = LIMIT_UPDATES_DEFAULT; public int workerThreads = WORKER_THREADS_DEFAULT; + public final FabricBackendConfig backendConfig = new FabricBackendConfig(); + public FabricFlwConfig(File file) { this.file = file; - - FlwBackendXplatImpl.CONFIG = backendConfig; } @Override @@ -66,6 +63,11 @@ public int workerThreads() { return workerThreads; } + @Override + public BackendConfig backendConfig() { + return backendConfig; + } + public void load() { if (file.exists()) { try (FileReader reader = new FileReader(file)) { @@ -177,7 +179,7 @@ public JsonObject toJson() { object.addProperty("backend", Backend.REGISTRY.getIdOrThrow(backend).toString()); object.addProperty("limitUpdates", limitUpdates); object.addProperty("workerThreads", workerThreads); - object.add("flw_backend", backendConfig.toJson()); + object.add("flw_backends", backendConfig.toJson()); return object; } diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java index 6bf004116..72ff04be9 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java @@ -8,7 +8,6 @@ import dev.engine_room.flywheel.api.backend.Backend; import dev.engine_room.flywheel.api.backend.BackendManager; -import dev.engine_room.flywheel.backend.LightSmoothnessArgument; import dev.engine_room.flywheel.backend.compile.LightSmoothness; import dev.engine_room.flywheel.backend.engine.uniform.DebugMode; import dev.engine_room.flywheel.backend.engine.uniform.FrameUniforms; diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java index 4e33b3145..50d5129a9 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlywheelFabric.java @@ -5,13 +5,12 @@ import dev.engine_room.flywheel.api.Flywheel; import dev.engine_room.flywheel.api.event.EndClientResourceReloadCallback; import dev.engine_room.flywheel.api.event.ReloadLevelRendererCallback; -import dev.engine_room.flywheel.backend.LightSmoothnessArgument; import dev.engine_room.flywheel.backend.compile.FlwProgramsReloader; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; import dev.engine_room.flywheel.impl.visualization.VisualizationEventHandler; -import dev.engine_room.flywheel.lib.model.ResourceReloadCache; -import dev.engine_room.flywheel.lib.model.ResourceReloadHolder; import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler; +import dev.engine_room.flywheel.lib.util.ResourceReloadCache; +import dev.engine_room.flywheel.lib.util.ResourceReloadHolder; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientEntityEvents; diff --git a/forge/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java b/forge/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java index c8eb18fb7..f49549f3e 100644 --- a/forge/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java +++ b/forge/src/backend/java/dev/engine_room/flywheel/backend/FlwBackendXplatImpl.java @@ -1,22 +1,12 @@ package dev.engine_room.flywheel.backend; -import org.jetbrains.annotations.UnknownNullability; - import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.block.state.BlockState; public class FlwBackendXplatImpl implements FlwBackendXplat { - @UnknownNullability - public static BackendConfig CONFIG; - @Override public int getLightEmission(BlockState state, BlockGetter level, BlockPos pos) { return state.getLightEmission(level, pos); } - - @Override - public BackendConfig getConfig() { - return CONFIG; - } } diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java b/forge/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java index 870680a66..ee46048d5 100644 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java @@ -6,7 +6,6 @@ import dev.engine_room.flywheel.api.backend.Backend; import dev.engine_room.flywheel.api.backend.BackendManager; -import dev.engine_room.flywheel.backend.LightSmoothnessArgument; import dev.engine_room.flywheel.backend.compile.LightSmoothness; import dev.engine_room.flywheel.backend.engine.uniform.DebugMode; import dev.engine_room.flywheel.backend.engine.uniform.FrameUniforms; diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java b/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java index 11acf03f8..205db0497 100644 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/FlywheelForge.java @@ -6,14 +6,13 @@ import dev.engine_room.flywheel.api.Flywheel; import dev.engine_room.flywheel.api.event.EndClientResourceReloadEvent; import dev.engine_room.flywheel.api.event.ReloadLevelRendererEvent; -import dev.engine_room.flywheel.backend.LightSmoothnessArgument; import dev.engine_room.flywheel.backend.compile.FlwProgramsReloader; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; import dev.engine_room.flywheel.impl.visualization.VisualizationEventHandler; -import dev.engine_room.flywheel.lib.model.ResourceReloadCache; -import dev.engine_room.flywheel.lib.model.ResourceReloadHolder; import dev.engine_room.flywheel.lib.model.baked.PartialModelEventHandler; import dev.engine_room.flywheel.lib.util.LevelAttached; +import dev.engine_room.flywheel.lib.util.ResourceReloadCache; +import dev.engine_room.flywheel.lib.util.ResourceReloadHolder; import net.minecraft.client.Minecraft; import net.minecraft.commands.synchronization.ArgumentTypeInfos; import net.minecraftforge.api.distmarker.Dist; diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/ForgeFlwConfig.java b/forge/src/main/java/dev/engine_room/flywheel/impl/ForgeFlwConfig.java index b19ae9fea..dbf03d087 100644 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/ForgeFlwConfig.java +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/ForgeFlwConfig.java @@ -6,7 +6,6 @@ import dev.engine_room.flywheel.api.backend.Backend; import dev.engine_room.flywheel.api.backend.BackendManager; import dev.engine_room.flywheel.backend.BackendConfig; -import dev.engine_room.flywheel.backend.FlwBackendXplatImpl; import dev.engine_room.flywheel.backend.compile.LightSmoothness; import net.minecraft.ResourceLocationException; import net.minecraft.resources.ResourceLocation; @@ -24,8 +23,6 @@ private ForgeFlwConfig() { Pair clientPair = new ForgeConfigSpec.Builder().configure(ClientConfig::new); this.client = clientPair.getLeft(); clientSpec = clientPair.getRight(); - - FlwBackendXplatImpl.CONFIG = client.backendConfig; } @Override @@ -68,6 +65,11 @@ public int workerThreads() { return client.workerThreads.get(); } + @Override + public BackendConfig backendConfig() { + return client.backendConfig; + } + public void registerSpecs(ModLoadingContext context) { context.registerConfig(ModConfig.Type.CLIENT, clientSpec); } @@ -90,7 +92,7 @@ private ClientConfig(ForgeConfigSpec.Builder builder) { .defineInRange("workerThreads", -1, -1, Runtime.getRuntime() .availableProcessors()); - builder.comment("Config options for flywheel's build-in backends.") + builder.comment("Config options for Flywheel's built-in backends.") .push("flw_backends"); backendConfig = new ForgeBackendConfig(builder);