From 21471b117865348b2f3e5e80db974d078c3bd325 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Mon, 27 May 2024 18:06:17 -0500 Subject: [PATCH 01/18] Little light lut - Commit entire chunks of light at a time - Share all light data between embeddings --- .../api/visualization/VisualEmbedding.java | 27 +-- .../flywheel/backend/Backends.java | 4 +- .../flywheel/backend/engine/EngineImpl.java | 11 +- .../backend/engine/EnvironmentStorage.java | 17 -- .../embed/AbstractEmbeddedEnvironment.java | 15 +- .../flywheel/backend/engine/embed/Arena.java | 50 +++++ .../engine/embed/EmbeddedLightTexture.java | 87 -------- .../engine/embed/EmbeddedLightVolume.java | 187 ----------------- .../backend/engine/embed/LightLut.java | 139 +++++++++++++ .../backend/engine/embed/LightStorage.java | 195 ++++++++++++++++++ .../embed/NestedEmbeddedEnvironment.java | 11 +- .../embed/TopLevelEmbeddedEnvironment.java | 52 +---- .../backend/engine/indirect/LightBuffers.java | 29 +++ .../flywheel/flywheel/internal/common.vert | 9 +- .../flywheel/internal/indirect/main.vert | 71 +++++++ .../flywheel/internal/instancing/main.vert | 6 + 16 files changed, 524 insertions(+), 386 deletions(-) create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Arena.java delete mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedLightTexture.java delete mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedLightVolume.java create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightLut.java create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java diff --git a/common/src/api/java/dev/engine_room/flywheel/api/visualization/VisualEmbedding.java b/common/src/api/java/dev/engine_room/flywheel/api/visualization/VisualEmbedding.java index f935ab006..e0b6d4f81 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/visualization/VisualEmbedding.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/visualization/VisualEmbedding.java @@ -4,7 +4,7 @@ import org.joml.Matrix4fc; import dev.engine_room.flywheel.api.BackendImplemented; -import net.minecraft.world.level.BlockAndTintGetter; +import it.unimi.dsi.fastutil.longs.LongSet; @BackendImplemented public interface VisualEmbedding extends VisualizationContext { @@ -16,30 +16,7 @@ public interface VisualEmbedding extends VisualizationContext { */ void transforms(Matrix4fc pose, Matrix3fc normal); - /** - * Collect light information from the given level for the given box. - * - *

Call this method on as many or as few boxes as you need to - * encompass all child visuals of this embedding.

- * - *

After this method is called, instances rendered from this - * embedding within the given box will be lit as if they were in - * the given level.

- * - * @param level The level to collect light information from. - * @param minX The minimum x coordinate of the box. - * @param minY The minimum y coordinate of the box. - * @param minZ The minimum z coordinate of the box. - * @param sizeX The size of the box in the x direction. - * @param sizeY The size of the box in the y direction. - * @param sizeZ The size of the box in the z direction. - */ - void collectLight(BlockAndTintGetter level, int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ); - - /** - * Reset any collected lighting information. - */ - void invalidateLight(); + void lightChunks(LongSet chunks); /** * Delete this embedding. diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/Backends.java b/common/src/backend/java/dev/engine_room/flywheel/backend/Backends.java index 001f8f39b..59e255cd5 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/Backends.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/Backends.java @@ -16,7 +16,7 @@ public final class Backends { * Use GPU instancing to render everything. */ public static final Backend INSTANCING = SimpleBackend.builder() - .engineFactory(level -> new EngineImpl(new InstancedDrawManager(InstancingPrograms.get()), 256)) + .engineFactory(level -> new EngineImpl(level, new InstancedDrawManager(InstancingPrograms.get()), 256)) .supported(() -> GlCompat.SUPPORTS_INSTANCING && InstancingPrograms.allLoaded() && !ShadersModHandler.isShaderPackInUse()) .register(Flywheel.rl("instancing")); @@ -24,7 +24,7 @@ public final class Backends { * Use Compute shaders to cull instances. */ public static final Backend INDIRECT = SimpleBackend.builder() - .engineFactory(level -> new EngineImpl(new IndirectDrawManager(IndirectPrograms.get()), 256)) + .engineFactory(level -> new EngineImpl(level, new IndirectDrawManager(IndirectPrograms.get()), 256)) .fallback(() -> Backends.INSTANCING) .supported(() -> GlCompat.SUPPORTS_INDIRECT && IndirectPrograms.allLoaded() && !ShadersModHandler.isShaderPackInUse()) .register(Flywheel.rl("indirect")); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java index 3e7dd9d61..87349d721 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java @@ -15,6 +15,7 @@ import dev.engine_room.flywheel.api.visualization.VisualEmbedding; import dev.engine_room.flywheel.api.visualization.VisualizationContext; import dev.engine_room.flywheel.backend.engine.embed.Environment; +import dev.engine_room.flywheel.backend.engine.embed.LightStorage; import dev.engine_room.flywheel.backend.engine.embed.TopLevelEmbeddedEnvironment; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; import dev.engine_room.flywheel.backend.gl.GlStateTracker; @@ -24,19 +25,22 @@ import net.minecraft.client.Camera; import net.minecraft.core.BlockPos; import net.minecraft.core.Vec3i; +import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.phys.Vec3; public class EngineImpl implements Engine { private final int sqrMaxOriginDistance; private final DrawManager> drawManager; private final EnvironmentStorage environmentStorage = new EnvironmentStorage(); + private final LightStorage lightStorage; private final Flag flushFlag = new NamedFlag("flushed"); private BlockPos renderOrigin = BlockPos.ZERO; - public EngineImpl(DrawManager> drawManager, int maxOriginDistance) { + public EngineImpl(LevelAccessor level, DrawManager> drawManager, int maxOriginDistance) { this.drawManager = drawManager; sqrMaxOriginDistance = maxOriginDistance * maxOriginDistance; + lightStorage = new LightStorage(level); } @Override @@ -93,6 +97,7 @@ public Vec3i renderOrigin() { public void delete() { drawManager.delete(); environmentStorage.delete(); + lightStorage.delete(); } public Instancer instancer(Environment environment, InstanceType type, Model model, RenderStage stage) { @@ -113,6 +118,10 @@ public EnvironmentStorage environmentStorage() { return environmentStorage; } + public LightStorage lightStorage() { + return lightStorage; + } + private class VisualizationContextImpl implements VisualizationContext { private final InstancerProviderImpl instancerProvider; private final RenderStage stage; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java index c6500069f..5a260eb04 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java @@ -1,8 +1,5 @@ package dev.engine_room.flywheel.backend.engine; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; - import dev.engine_room.flywheel.backend.engine.embed.AbstractEmbeddedEnvironment; import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; import it.unimi.dsi.fastutil.objects.ReferenceSet; @@ -10,30 +7,16 @@ public class EnvironmentStorage { protected final ReferenceSet environments = ReferenceSets.synchronize(new ReferenceLinkedOpenHashSet<>()); - private final Queue forDeletion = new ConcurrentLinkedQueue<>(); public void track(AbstractEmbeddedEnvironment environment) { environments.add(environment); } - public void enqueueDeletion(AbstractEmbeddedEnvironment environment) { - environments.remove(environment); - - forDeletion.add(environment); - } - public void flush() { - AbstractEmbeddedEnvironment env; - - while ((env = forDeletion.poll()) != null) { - env.actuallyDelete(); - } - environments.forEach(AbstractEmbeddedEnvironment::flush); } public void delete() { - environments.forEach(AbstractEmbeddedEnvironment::actuallyDelete); environments.clear(); } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java index 07adb4805..f2a7912be 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java @@ -24,7 +24,7 @@ public abstract class AbstractEmbeddedEnvironment extends AtomicReferenceCounted private final Matrix4f poseComposed = new Matrix4f(); private final Matrix3f normalComposed = new Matrix3f(); private final InstancerProvider instancerProvider; - private final EngineImpl engine; + protected final EngineImpl engine; private final RenderStage renderStage; public AbstractEmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) { @@ -105,20 +105,7 @@ public void delete() { release(); } - /** - * Called when referenceCount goes to 0 - */ - @Override - public void _delete() { - engine.environmentStorage().enqueueDeletion(this); - } - public abstract void setupLight(GlProgram program); public abstract void composeMatrices(Matrix4f pose, Matrix3f normal); - - /** - * Called in EnvironmentStorage#flush - */ - public abstract void actuallyDelete(); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Arena.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Arena.java new file mode 100644 index 000000000..31dbc77cd --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Arena.java @@ -0,0 +1,50 @@ +package dev.engine_room.flywheel.backend.engine.embed; + +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; + // List of free indices. + private final IntList freeStack = new IntArrayList(); + + public Arena(long elementSizeBytes, int initialCapacity) { + this.elementSizeBytes = elementSizeBytes; + + memoryBlock = MemoryBlock.malloc(elementSizeBytes * initialCapacity); + } + + public int alloc() { + // First re-use freed elements. + if (!freeStack.isEmpty()) { + return freeStack.removeInt(freeStack.size() - 1); + } + + // Make sure there's room to increment top. + if (top * elementSizeBytes >= memoryBlock.size()) { + memoryBlock = memoryBlock.realloc(memoryBlock.size() * 2); + } + + // Return the top index and increment. + return top++; + } + + public void free(int i) { + // That's it! Now pls don't try to use it. + freeStack.add(i); + } + + public long indexToPointer(int i) { + return memoryBlock.ptr() + i * elementSizeBytes; + } + + public void delete() { + memoryBlock.free(); + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedLightTexture.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedLightTexture.java deleted file mode 100644 index 7e4325fb9..000000000 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedLightTexture.java +++ /dev/null @@ -1,87 +0,0 @@ -package dev.engine_room.flywheel.backend.engine.embed; - -import static org.lwjgl.opengl.GL11.GL_LINEAR; -import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER; -import static org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER; -import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_S; -import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_T; -import static org.lwjgl.opengl.GL11.GL_UNPACK_ALIGNMENT; -import static org.lwjgl.opengl.GL11.GL_UNPACK_ROW_LENGTH; -import static org.lwjgl.opengl.GL11.GL_UNPACK_SKIP_PIXELS; -import static org.lwjgl.opengl.GL11.GL_UNPACK_SKIP_ROWS; -import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE; -import static org.lwjgl.opengl.GL11.glPixelStorei; -import static org.lwjgl.opengl.GL11.glTexParameteri; -import static org.lwjgl.opengl.GL12.GL_TEXTURE_3D; -import static org.lwjgl.opengl.GL12.GL_TEXTURE_WRAP_R; -import static org.lwjgl.opengl.GL12.GL_UNPACK_IMAGE_HEIGHT; -import static org.lwjgl.opengl.GL12.GL_UNPACK_SKIP_IMAGES; -import static org.lwjgl.opengl.GL12.glTexImage3D; -import static org.lwjgl.opengl.GL12.glTexSubImage3D; -import static org.lwjgl.opengl.GL14.GL_MIRRORED_REPEAT; - -import org.jetbrains.annotations.Nullable; -import org.lwjgl.opengl.GL30; - -import dev.engine_room.flywheel.backend.gl.GlTexture; -import net.minecraft.util.Mth; - -public class EmbeddedLightTexture { - @Nullable - private GlTexture texture; - - public int sizeX; - public int sizeY; - public int sizeZ; - - public void bind() { - - texture().bind(); - } - - private GlTexture texture() { - if (texture == null) { - texture = new GlTexture(GL_TEXTURE_3D); - } - return texture; - } - - public void ensureCapacity(int sizeX, int sizeY, int sizeZ) { - sizeX = Mth.smallestEncompassingPowerOfTwo(sizeX); - sizeY = Mth.smallestEncompassingPowerOfTwo(sizeY); - sizeZ = Mth.smallestEncompassingPowerOfTwo(sizeZ); - - if (sizeX > this.sizeX || sizeY > this.sizeY || sizeZ > this.sizeZ) { - this.sizeX = sizeX; - this.sizeY = sizeY; - this.sizeZ = sizeZ; - - glTexImage3D(GL_TEXTURE_3D, 0, GL30.GL_RG8, sizeX, sizeY, sizeZ, 0, GL30.GL_RG, GL_UNSIGNED_BYTE, 0); - - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT); - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); - } - } - - public void upload(long ptr, int sizeX, int sizeY, int sizeZ) { - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0); - glPixelStorei(GL_UNPACK_SKIP_ROWS, 0); - glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0); - glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0); - glPixelStorei(GL_UNPACK_ALIGNMENT, (int) EmbeddedLightVolume.STRIDE); - - glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, sizeX, sizeY, sizeZ, GL30.GL_RG, GL_UNSIGNED_BYTE, ptr); - - glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4 is the default - } - - public void delete() { - if (texture != null) { - texture.delete(); - } - } -} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedLightVolume.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedLightVolume.java deleted file mode 100644 index c815ae3ea..000000000 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedLightVolume.java +++ /dev/null @@ -1,187 +0,0 @@ -package dev.engine_room.flywheel.backend.engine.embed; - -import org.jetbrains.annotations.Nullable; -import org.lwjgl.system.MemoryUtil; - -import dev.engine_room.flywheel.lib.memory.MemoryBlock; -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.BlockAndTintGetter; -import net.minecraft.world.level.LightLayer; - -public class EmbeddedLightVolume { - public static final long STRIDE = Short.BYTES; - - private int minX; - private int minY; - private int minZ; - private int maxX; - private int maxY; - private int maxZ; - - private final BlockPos.MutableBlockPos scratchPos = new BlockPos.MutableBlockPos(); - - @Nullable - protected MemoryBlock memoryBlock; - protected boolean empty = true; - - public boolean empty() { - return empty; - } - - public void collect(BlockAndTintGetter level, int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) { - maybeExpandForBox(minX, minY, minZ, sizeX, sizeY, sizeZ); - - empty = false; - - for (int z = minZ; z < minZ + sizeZ; z++) { - for (int y = minY; y < minY + sizeY; y++) { - for (int x = minX; x < minX + sizeX; x++) { - paintLight(level, x, y, z); - } - } - } - } - - private void paintLight(BlockAndTintGetter level, int x, int y, int z) { - scratchPos.set(x, y, z); - - int block = level.getBrightness(LightLayer.BLOCK, scratchPos); - int sky = level.getBrightness(LightLayer.SKY, scratchPos); - - long ptr = this.memoryBlock.ptr() + offset(x - x(), y - y(), z - z(), sizeX(), sizeY()); - MemoryUtil.memPutShort(ptr, (short) ((block << 4) | sky << 12)); - } - - private void maybeExpandForBox(int x, int y, int z, int sizeX, int sizeY, int sizeZ) { - if (empty || memoryBlock == null) { - // We're either brand new or recently #clear'd, - // so none of the previous min/max values have any meaning. - this.minX = x; - this.minY = y; - this.minZ = z; - this.maxX = x + sizeX; - this.maxY = y + sizeY; - this.maxZ = z + sizeZ; - - int volume = sizeX * sizeY * sizeZ; - long neededSize = volume * STRIDE; - - if (memoryBlock == null) { - memoryBlock = MemoryBlock.malloc(neededSize); - } else if (memoryBlock.size() < neededSize) { - // There's some memory left over from before the last #clear, - // but not enough to hold this initial box. Need to grow the block. - memoryBlock.realloc(neededSize); - } - // else: we have enough memory left over to hold this box, nothing to do! - return; - } - - int oldMinX = this.minX; - int oldMinY = this.minY; - int oldMinZ = this.minZ; - int oldSizeX = this.sizeX(); - int oldSizeY = this.sizeY(); - int oldSizeZ = this.sizeZ(); - boolean changed = false; - - if (x < this.minX) { - this.minX = x; - changed = true; - } - if (y < this.minY) { - this.minY = y; - changed = true; - } - if (z < this.minZ) { - this.minZ = z; - changed = true; - } - if (x + sizeX > this.maxX) { - this.maxX = x + sizeX; - changed = true; - } - if (y + sizeY > this.maxY) { - this.maxY = y + sizeY; - changed = true; - } - if (z + sizeZ > this.maxZ) { - this.maxZ = z + sizeZ; - changed = true; - } - - if (!changed) { - return; - } - - int volume = volume(); - - memoryBlock = memoryBlock.realloc(volume * STRIDE); - - int xOff = oldMinX - minX; - int yOff = oldMinY - minY; - int zOff = oldMinZ - minZ; - - blit(memoryBlock.ptr(), 0, 0, 0, oldSizeX, oldSizeY, memoryBlock.ptr(), xOff, yOff, zOff, sizeX(), sizeY(), oldSizeX, oldSizeY, oldSizeZ); - } - - public static void blit(long src, int srcX, int srcY, int srcZ, int srcSizeX, int srcSizeY, long dst, int dstX, int dstY, int dstZ, int dstSizeX, int dstSizeY, int sizeX, int sizeY, int sizeZ) { - for (int z = 0; z < sizeZ; z++) { - for (int y = 0; y < sizeY; y++) { - for (int x = 0; x < sizeX; x++) { - long srcPtr = src + offset(x + srcX, y + srcY, z + srcZ, srcSizeX, srcSizeY); - long dstPtr = dst + offset(x + dstX, y + dstY, z + dstZ, dstSizeX, dstSizeY); - - MemoryUtil.memPutShort(dstPtr, MemoryUtil.memGetShort(srcPtr)); - } - } - } - } - - public static long offset(int x, int y, int z, int sizeX, int sizeY) { - return (x + sizeX * (y + sizeY * z)) * STRIDE; - } - - public void clear() { - empty = true; - } - - public void delete() { - if (memoryBlock != null) { - memoryBlock.free(); - memoryBlock = null; - } - } - - public long ptr() { - return memoryBlock.ptr(); - } - - public int x() { - return minX; - } - - public int y() { - return minY; - } - - public int z() { - return minZ; - } - - public int sizeX() { - return maxX - minX; - } - - public int sizeY() { - return maxY - minY; - } - - public int sizeZ() { - return maxZ - minZ; - } - - public int volume() { - return sizeX() * sizeY() * sizeZ(); - } -} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightLut.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightLut.java new file mode 100644 index 000000000..cfe4ced2f --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightLut.java @@ -0,0 +1,139 @@ +package dev.engine_room.flywheel.backend.engine.embed; + +import org.jetbrains.annotations.NotNull; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntObjectImmutablePair; +import it.unimi.dsi.fastutil.ints.IntObjectPair; +import it.unimi.dsi.fastutil.longs.Long2IntMap; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.longs.LongComparator; +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import net.minecraft.core.SectionPos; + +public class LightLut { + public static final LongComparator SECTION_X_THEN_Y_THEN_Z = (long a, long b) -> { + final var xComp = Integer.compare(SectionPos.x(a), SectionPos.x(b)); + if (xComp != 0) { + return xComp; + } + var yComp = Integer.compare(SectionPos.y(a), SectionPos.y(b)); + if (yComp != 0) { + return yComp; + } + return Integer.compare(SectionPos.z(a), SectionPos.z(b)); + }; + + + // Massive kudos to RogueLogix for figuring out this LUT scheme. + // TODO: switch to y x z or x z y ordering + // DATA LAYOUT + // [0] : base chunk X, X index count, followed by linear indices of y blocks + // [yBlockIndex] : baseChunk Y, Y index count, followed by linear indices of z blocks for this x + // [zBlockIndex] : baseChunk Z, Z index count, followed by linear indices of lighting chunks + // this data layout allows a single buffer to represent the lighting volume, without requiring the entire 3d lookup volume to be allocated + public static IntArrayList buildLut(Long2IntMap sectionIndicesMaps) { + if (sectionIndicesMaps.isEmpty()) { + return new IntArrayList(); + } + final var positions = sortedKeys(sectionIndicesMaps); + final var baseX = SectionPos.x(positions.getLong(0)); + + return buildLut(baseX, buildIndices(sectionIndicesMaps, positions, baseX)); + } + + private static ReferenceArrayList>> buildIndices(Long2IntMap sectionIndicesMaps, LongArrayList positions, int baseX) { + final var indices = new ReferenceArrayList>>(); + for (long position : positions) { + final var x = SectionPos.x(position); + final var y = SectionPos.y(position); + final var z = SectionPos.z(position); + + final var xIndex = x - baseX; + if (indices.size() <= xIndex) { + indices.ensureCapacity(xIndex + 1); + indices.size(xIndex + 1); + } + var yLookup = indices.get(xIndex); + if (yLookup == null) { + //noinspection SuspiciousNameCombination + yLookup = new IntObjectImmutablePair<>(y, new ReferenceArrayList<>()); + indices.set(xIndex, yLookup); + } + + final var yIndices = yLookup.right(); + final var yIndex = y - yLookup.leftInt(); + if (yIndices.size() <= yIndex) { + yIndices.ensureCapacity(yIndex + 1); + yIndices.size(yIndex + 1); + } + var zLookup = yIndices.get(yIndex); + if (zLookup == null) { + zLookup = new IntArrayList(); + zLookup.add(z); + zLookup.add(0); // this value will be filled in later + yIndices.set(yIndex, zLookup); + } + + final var zIndex = z - zLookup.getInt(0); + if ((zLookup.size() - 2) <= zIndex) { + zLookup.ensureCapacity(zIndex + 3); + zLookup.size(zIndex + 3); + } + zLookup.set(zIndex + 2, sectionIndicesMaps.get(position)); + } + return indices; + } + + private static @NotNull LongArrayList sortedKeys(Long2IntMap sectionIndicesMaps) { + final var out = new LongArrayList(sectionIndicesMaps.keySet()); + out.unstableSort(SECTION_X_THEN_Y_THEN_Z); + return out; + } + + private static IntArrayList buildLut(int baseX, ReferenceArrayList>> indices) { + final var out = new IntArrayList(); + out.add(baseX); + out.add(indices.size()); + for (int i = 0; i < indices.size(); i++) { + out.add(0); + } + for (int x = 0; x < indices.size(); x++) { + final var yLookup = indices.get(x); + if (yLookup == null) { + out.set(x + 2, 0); + continue; + } + // ensure that the base position and size dont cross a (64 byte) cache line + if ((out.size() & 0xF) == 0xF) { + out.add(0); + } + + final var baseYIndex = out.size(); + out.set(x + 2, baseYIndex); + + final var yIndices = yLookup.right(); + out.add(yLookup.leftInt()); + out.add(yIndices.size()); + for (int i = 0; i < indices.size(); i++) { + out.add(0); + } + + for (int y = 0; y < yIndices.size(); y++) { + final var zLookup = yIndices.get(y); + if (zLookup == null) { + out.set(baseYIndex + y + 2, 0); + continue; + } + // ensure that the base position and size dont cross a (64 byte) cache line + if ((out.size() & 0xF) == 0xF) { + out.add(0); + } + out.set(baseYIndex + y + 2, out.size()); + zLookup.set(1, zLookup.size() - 2); + out.addAll(zLookup); + } + } + return out; + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java new file mode 100644 index 000000000..c1edb2fcf --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java @@ -0,0 +1,195 @@ +package dev.engine_room.flywheel.backend.engine.embed; + +import java.util.BitSet; + +import org.lwjgl.system.MemoryUtil; + +import dev.engine_room.flywheel.backend.engine.indirect.StagingBuffer; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.longs.Long2IntMap; +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LightLayer; + +/** + * TODO: AO data + * A managed arena of light sections for uploading to the GPU. + * + *

Each section represents an 18x18x18 block volume of light data. + * The "edges" are taken from the neighboring sections, so that each + * shader invocation only needs to access a single section of data. + * Even still, neighboring shader invocations may need to access other sections. + * + *

Sections are logically stored as a 9x9x9 array of longs, + * where each long holds a 2x2x2 array of light data. + *
Both the greater array and the longs are packed in x, z, y order. + * + *

Thus, each section occupies 5832 bytes. + */ +public class LightStorage { + private final long SECTION_SIZE_BYTES = 9 * 9 * 9 * 8; + private final int DEFAULT_ARENA_CAPACITY_SECTIONS = 64; + private final int INVALID_SECTION = -1; + + private final LevelAccessor level; + + private final Arena arena; + private final Long2IntMap section2ArenaIndex = new Long2IntOpenHashMap(); + { + section2ArenaIndex.defaultReturnValue(INVALID_SECTION); + } + + private final BitSet changed = new BitSet(); + private boolean newSections = false; + + public LightStorage(LevelAccessor level) { + this.level = level; + + arena = new Arena(SECTION_SIZE_BYTES, DEFAULT_ARENA_CAPACITY_SECTIONS); + } + + public void addSection(long section) { + var lightEngine = level.getLightEngine(); + + var blockLight = lightEngine.getLayerListener(LightLayer.BLOCK); + var skyLight = lightEngine.getLayerListener(LightLayer.SKY); + + var blockPos = new BlockPos.MutableBlockPos(); + + int xMin = SectionPos.sectionToBlockCoord(SectionPos.x(section)); + int yMin = SectionPos.sectionToBlockCoord(SectionPos.y(section)); + int zMin = SectionPos.sectionToBlockCoord(SectionPos.z(section)); + + int index = indexForSection(section); + + changed.set(index); + + long ptr = arena.indexToPointer(index); + + for (int y = -1; y < 17; y++) { + for (int z = -1; z < 17; z++) { + for (int x = -1; x < 17; x++) { + blockPos.set(xMin + x, yMin + y, zMin + z); + var block = blockLight.getLightValue(blockPos); + var sky = skyLight.getLightValue(blockPos); + + write(ptr, x, y, z, block, sky); + } + } + } + } + + void addSectionFast(long section) { + // TODO: get this to work. it should be MUCH faster to read directly from the data layer + // though it's more complicated to manage which section datas we fetch + var lightEngine = level.getLightEngine(); + + var blockLight = lightEngine.getLayerListener(LightLayer.BLOCK); + var skyLight = lightEngine.getLayerListener(LightLayer.SKY); + + var sectionPos = SectionPos.of(section); + var blockData = blockLight.getDataLayerData(sectionPos); + var skyData = skyLight.getDataLayerData(sectionPos); + + if (blockData == null || skyData == null) { + return; + } + + long ptr = ptrForSection(section); + + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + var block = blockData.get(x, y, z); + var sky = skyData.get(x, y, z); + + write(ptr, x, y, z, block, sky); + } + } + } + } + + /** + * Write to the given section. + * @param ptr Pointer to the base of a section's data. + * @param x X coordinate in the section, from [-1, 16]. + * @param y Y coordinate in the section, from [-1, 16]. + * @param z Z coordinate in the section, from [-1, 16]. + * @param block The block light level, from [0, 15]. + * @param sky The sky light level, from [0, 15]. + */ + private void write(long ptr, int x, int y, int z, int block, int sky) { + int x1 = x + 1; + int y1 = y + 1; + int z1 = z + 1; + + int offset = x1 + z1 * 18 + y1 * 18 * 18; + + long packedByte = (block & 0xF) | ((sky & 0xF) << 4); + + MemoryUtil.memPutByte(ptr + offset, (byte) packedByte); + } + + private void writeFor2Cubed(long ptr, int x, int y, int z, int block, int sky) { + int x1 = x + 1; + int y1 = y + 1; + int z1 = z + 1; + + int longIndex = (x1 >> 1) + (z1 >> 1) * 9 + (y1 >> 1) * 9 * 9; + int byteIndexInLong = (x1 & 1) + ((z1 & 1) << 1) + ((y1 & 1) << 2); + + long packedByte = (block & 0xF) | ((sky & 0xF) << 4); + + MemoryUtil.memPutByte(ptr + longIndex * 8L + byteIndexInLong, (byte) packedByte); + } + + /** + * Get a pointer to the base of the given section. + *

If the section is not yet reserved, allocate a chunk in the arena. + * @param section The section to write to. + * @return A raw pointer to the base of the section. + */ + private long ptrForSection(long section) { + return arena.indexToPointer(indexForSection(section)); + } + + private int indexForSection(long section) { + int out = section2ArenaIndex.get(section); + + // Need to allocate. + if (out == INVALID_SECTION) { + out = arena.alloc(); + section2ArenaIndex.put(section, out); + newSections = true; + } + return out; + } + + public void removeSection(long section) { + + } + + public void delete() { + arena.delete(); + } + + public boolean hasChanges() { + return !changed.isEmpty(); + } + + public boolean hasNewSections() { + return newSections; + } + + public void uploadChangedSections(StagingBuffer staging, int dstVbo) { + for (int i = changed.nextSetBit(0); i >= 0; i = changed.nextSetBit(i + 1)) { + staging.enqueueCopy(arena.indexToPointer(i), SECTION_SIZE_BYTES, dstVbo, i * SECTION_SIZE_BYTES); + } + } + + public IntArrayList createLut() { + return LightLut.buildLut(section2ArenaIndex); + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java index f957a7e43..82afd22fa 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java @@ -6,7 +6,7 @@ import dev.engine_room.flywheel.api.event.RenderStage; import dev.engine_room.flywheel.backend.engine.EngineImpl; import dev.engine_room.flywheel.backend.gl.shader.GlProgram; -import net.minecraft.world.level.BlockAndTintGetter; +import it.unimi.dsi.fastutil.longs.LongSet; public class NestedEmbeddedEnvironment extends AbstractEmbeddedEnvironment { private final AbstractEmbeddedEnvironment parent; @@ -18,11 +18,8 @@ public NestedEmbeddedEnvironment(AbstractEmbeddedEnvironment parent, EngineImpl } @Override - public void collectLight(BlockAndTintGetter level, int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) { - } - - @Override - public void invalidateLight() { + public void lightChunks(LongSet chunks) { + // noop } @Override @@ -38,7 +35,7 @@ public void composeMatrices(Matrix4f pose, Matrix3f normal) { } @Override - public void actuallyDelete() { + public void _delete() { parent.release(); } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java index d5a8aa9f9..926d246b9 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java @@ -4,14 +4,13 @@ import org.joml.Matrix4f; import dev.engine_room.flywheel.api.event.RenderStage; -import dev.engine_room.flywheel.backend.Samplers; import dev.engine_room.flywheel.backend.engine.EngineImpl; import dev.engine_room.flywheel.backend.gl.shader.GlProgram; -import net.minecraft.world.level.BlockAndTintGetter; +import it.unimi.dsi.fastutil.longs.LongArraySet; +import it.unimi.dsi.fastutil.longs.LongSet; public class TopLevelEmbeddedEnvironment extends AbstractEmbeddedEnvironment { - private final EmbeddedLightVolume lightVolume = new EmbeddedLightVolume(); - private final EmbeddedLightTexture lightTexture = new EmbeddedLightTexture(); + private final LongSet lightSections = new LongArraySet(); public TopLevelEmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) { super(engine, renderStage); @@ -21,45 +20,18 @@ public TopLevelEmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) { public void flush() { super.flush(); - if (lightVolume.empty()) { - return; - } - Samplers.EMBEDDED_LIGHT.makeActive(); - - lightTexture.bind(); - - lightTexture.ensureCapacity(lightVolume.sizeX(), lightVolume.sizeY(), lightVolume.sizeZ()); - - lightTexture.upload(lightVolume.ptr(), lightVolume.sizeX(), lightVolume.sizeY(), lightVolume.sizeZ()); - } - - @Override - public void collectLight(BlockAndTintGetter level, int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) { - lightVolume.collect(level, minX, minY, minZ, sizeX, sizeY, sizeZ); + lightSections.forEach(engine.lightStorage()::addSection); } @Override - public void invalidateLight() { - lightVolume.clear(); + public void lightChunks(LongSet chunks) { + lightSections.clear(); + lightSections.addAll(chunks); } @Override public void setupLight(GlProgram program) { - if (!lightVolume.empty()) { - Samplers.EMBEDDED_LIGHT.makeActive(); - - lightTexture.bind(); - - float oneOverSizeX = 1f / (float) lightTexture.sizeX; - float oneOverSizeY = 1f / (float) lightTexture.sizeY; - float oneOverSizeZ = 1f / (float) lightTexture.sizeZ; - - program.setVec3(EmbeddingUniforms.ONE_OVER_LIGHT_BOX_SIZE, oneOverSizeX, oneOverSizeY, oneOverSizeZ); - program.setVec3(EmbeddingUniforms.LIGHT_VOLUME_MIN, lightVolume.x(), lightVolume.y(), lightVolume.z()); - program.setBool(EmbeddingUniforms.USE_LIGHT_VOLUME, true); - } else { - program.setBool(EmbeddingUniforms.USE_LIGHT_VOLUME, false); - } + program.setBool(EmbeddingUniforms.USE_LIGHT_VOLUME, !lightSections.isEmpty()); } @Override @@ -69,11 +41,7 @@ public void composeMatrices(Matrix4f pose, Matrix3f normal) { } @Override - public void actuallyDelete() { - // We could technically free the light volume right away in _delete, but - // the control flow here is so convoluted that it's probably best to do - // everything in one place. - lightVolume.delete(); - lightTexture.delete(); + protected void _delete() { + } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java new file mode 100644 index 000000000..9a0a37a57 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java @@ -0,0 +1,29 @@ +package dev.engine_room.flywheel.backend.engine.indirect; + +import org.lwjgl.system.MemoryUtil; + +import dev.engine_room.flywheel.backend.engine.embed.LightStorage; + +public class LightBuffers { + private final ResizableStorageBuffer lightArena = new ResizableStorageBuffer(); + private final ResizableStorageArray lut = new ResizableStorageArray(4); + + public LightBuffers() { + } + + public void flush(StagingBuffer staging, LightStorage light) { + light.uploadChangedSections(staging, lightArena.handle()); + + if (light.hasNewSections()) { + var lut = light.createLut(); + + this.lut.ensureCapacity(lut.size()); + + staging.enqueueCopy((long) lut.size() * Integer.BYTES, this.lut.handle(), 0, ptr -> { + for (int i = 0; i < lut.size(); i++) { + MemoryUtil.memPutInt(ptr + (long) i * Integer.BYTES, lut.getInt(i)); + } + }); + } + } +} diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert index ec009e414..fae9fd91c 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert @@ -67,12 +67,10 @@ vec2 getCrumblingTexCoord() { #endif #ifdef _FLW_EMBEDDED -uniform vec3 _flw_oneOverLightBoxSize; -uniform vec3 _flw_lightVolumeMin; uniform mat4 _flw_modelMatrix; uniform mat3 _flw_normalMatrix; -out vec3 _flw_lightVolumeCoord; +bool _flw_embeddedLight(vec3 worldPos, vec3 normal, out vec2 lightCoord); #endif flat out uint _flw_instanceID; @@ -90,7 +88,10 @@ void _flw_main(in FlwInstance instance, in uint stableInstanceID) { flw_vertexPos = _flw_modelMatrix * flw_vertexPos; flw_vertexNormal = _flw_normalMatrix * flw_vertexNormal; - _flw_lightVolumeCoord = (flw_vertexPos.xyz - _flw_lightVolumeMin) * _flw_oneOverLightBoxSize; + vec2 embeddedLight; + if (_flw_embeddedLight(flw_vertexPos, flw_vertexNormal, embeddedLight)) { + flw_vertexLight = max(flw_vertexLight, embeddedLight); + } #endif flw_vertexNormal = normalize(flw_vertexNormal); 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 55bd8fc0f..de5493d3c 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 @@ -11,6 +11,77 @@ layout(std430, binding = _FLW_DRAW_BUFFER_BINDING) restrict readonly buffer Draw MeshDrawCommand _flw_drawCommands[]; }; +#ifdef _FLW_EMBEDDED + +layout(std430, binding = 8) restrict readonly buffer EmbeddingLut { + uint _flw_embeddingLut[]; +}; + +const uint _FLW_LIGHT_SECTION_SIZE_BYTES = 18 * 18 * 18; +const uint _FLW_LIGHT_SECTION_SIZE_INTS = _FLW_LIGHT_SECTION_SIZE_BYTES / 4; + +layout(std430, binding = 9) restrict readonly buffer LightSections { + uint _flw_lightSections[]; +}; + +bool _flw_nextLut(uint base, int coord, out uint next) { + int start = int(_flw_embeddingLut[base]); + uint size = _flw_embeddingLut[base + 1]; + + int i = coord - start; + + if (i < 0 || i >= size) { + return true; + } + + next = _flw_embeddingLut[base + 2 + i]; + + return false; +} + +bool _flw_chunkCoordToSectionIndex(ivec3 sectionPos, out uint index) { + uint y; + if (_flw_nextLut(0, sectionPos.x, y)) { + return true; + } + + uint z; + if (_flw_nextLut(y, sectionPos.y, z)) { + return true; + } + return _flw_nextLut(z, sectionPos.z, index); +} + +vec2 _flw_lightAt(uint sectionOffset, ivec3 blockInSectionPos) { + uint byteOffset = blockInSectionPos.x + blockInSectionPos.z * 18u + blockInSectionPos.y * 18u * 18u; + + uint uintOffset = byteOffset >> 2u; + uint bitOffset = (byteOffset & 3u) << 3; + + uint packed = _flw_lightSections[sectionOffset + uintOffset]; + uint block = (packed >> bitOffset) & 0xFu; + uint sky = (packed >> (bitOffset + 4u)) & 0xFu; + + return vec2(block, sky); +} + +bool _flw_embeddedLight(vec3 worldPos, vec3 normal, out vec2 lightCoord) { + ivec3 sectionPos = blockPos >> 4; + uvec3 blockInSectionPos = (blockPos & 0xF) + 1; + + uint lightSectionIndex; + if (_flw_chunkCoordToSectionIndex(sectionPos, lightSectionIndex)) { + return false; + } + + uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS; + + lightCoord = _flw_lightAt(sectionOffset, blockInSectionPos); + return true; +} + +#endif + uniform uint _flw_baseDraw; flat out uvec3 _flw_packedMaterial; diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert index 8c4b9d6a3..ad3e9f9e3 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert @@ -4,6 +4,12 @@ uniform uvec4 _flw_packedMaterial; uniform int _flw_baseInstance = 0; +#ifdef _FLW_EMBEDDED +bool _flw_embeddedLight(vec3 worldPos, vec3 normal, out vec2 lightCoord) { + return true; +} +#endif + void main() { _flw_uberMaterialVertexIndex = _flw_packedMaterial.x; _flw_unpackMaterialProperties(_flw_packedMaterial.w, flw_material); From 8e1a95073ee60b78f60756e06c757c5087c65d84 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Fri, 21 Jun 2024 15:26:48 -0700 Subject: [PATCH 02/18] Indirectly lit - "Functional" arena based lighting for indirect - Strip out most of the reference counting stuffs for embeddings - Naively re-buffer all tracked light sections every frame --- .../flywheel/backend/Samplers.java | 1 - .../backend/compile/ContextShader.java | 2 +- .../component/SsboInstanceComponent.java | 4 +-- .../backend/engine/AbstractInstancer.java | 6 +---- .../flywheel/backend/engine/DrawManager.java | 3 ++- .../flywheel/backend/engine/EngineImpl.java | 10 +++---- .../backend/engine/EnvironmentStorage.java | 9 +++++-- .../embed/AbstractEmbeddedEnvironment.java | 22 ++++++++------- .../flywheel/backend/engine/embed/Arena.java | 4 +++ .../backend/engine/embed/Environment.java | 4 --- .../engine/embed/GlobalEnvironment.java | 10 ------- .../backend/engine/embed/LightStorage.java | 26 +++++++++++++++--- .../embed/NestedEmbeddedEnvironment.java | 6 ----- .../embed/TopLevelEmbeddedEnvironment.java | 17 ++++-------- .../engine/indirect/BufferBindings.java | 14 ++++++++++ .../engine/indirect/IndirectBuffers.java | 11 ++------ .../engine/indirect/IndirectDrawManager.java | 13 ++++++--- .../engine/indirect/IndirectInstancer.java | 2 -- .../backend/engine/indirect/LightBuffers.java | 19 ++++++++++++- .../instancing/InstancedDrawManager.java | 5 ++-- .../engine/instancing/InstancedInstancer.java | 2 -- .../flywheel/flywheel/internal/common.frag | 19 ------------- .../flywheel/flywheel/internal/common.vert | 4 +-- .../internal/indirect/buffer_bindings.glsl | 2 ++ .../flywheel/internal/indirect/main.vert | 27 ++++++++++++++----- .../flywheel/internal/instancing/main.vert | 2 +- 26 files changed, 134 insertions(+), 110 deletions(-) create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/BufferBindings.java diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/Samplers.java b/common/src/backend/java/dev/engine_room/flywheel/backend/Samplers.java index d8d2975dc..cbdde0166 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/Samplers.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/Samplers.java @@ -8,5 +8,4 @@ public class Samplers { public static final GlTextureUnit LIGHT = GlTextureUnit.T2; public static final GlTextureUnit CRUMBLING = GlTextureUnit.T3; public static final GlTextureUnit INSTANCE_BUFFER = GlTextureUnit.T4; - public static final GlTextureUnit EMBEDDED_LIGHT = GlTextureUnit.T5; } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/ContextShader.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/ContextShader.java index bcc7832e5..6d3fede7a 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/ContextShader.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/ContextShader.java @@ -13,7 +13,7 @@ public enum ContextShader { DEFAULT(null, $ -> { }), CRUMBLING("_FLW_CRUMBLING", program -> program.setSamplerBinding("_flw_crumblingTex", Samplers.CRUMBLING)), - EMBEDDED("_FLW_EMBEDDED", program -> program.setSamplerBinding("_flw_lightVolume", Samplers.EMBEDDED_LIGHT)); + EMBEDDED("_FLW_EMBEDDED", $ -> {}); @Nullable private final String define; 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 756ab202f..92fb61099 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 @@ -5,7 +5,7 @@ import dev.engine_room.flywheel.api.Flywheel; import dev.engine_room.flywheel.api.instance.InstanceType; import dev.engine_room.flywheel.api.layout.Layout; -import dev.engine_room.flywheel.backend.engine.indirect.IndirectBuffers; +import dev.engine_room.flywheel.backend.engine.indirect.BufferBindings; import dev.engine_room.flywheel.backend.glsl.generate.FnSignature; import dev.engine_room.flywheel.backend.glsl.generate.GlslBlock; import dev.engine_room.flywheel.backend.glsl.generate.GlslBuilder; @@ -43,7 +43,7 @@ protected void generateUnpacking(GlslBuilder builder) { fnBody.ret(GlslExpr.call(STRUCT_NAME, unpackArgs)); - builder._addRaw("layout(std430, binding = " + IndirectBuffers.INSTANCE_INDEX + ") restrict readonly buffer InstanceBuffer {\n" + builder._addRaw("layout(std430, binding = " + BufferBindings.INSTANCE_BUFFER_BINDING + ") restrict readonly buffer InstanceBuffer {\n" + " uint _flw_instances[];\n" + "};"); builder.blankLine(); 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 56fbfcf9d..6cbadf9c9 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 @@ -25,8 +25,6 @@ public abstract class AbstractInstancer implements Instancer protected AbstractInstancer(InstanceType type, Environment environment) { this.type = type; this.environment = environment; - - environment.acquire(); } @Override @@ -177,9 +175,7 @@ public void clear() { deleted.clear(); } - public void delete() { - environment.release(); - } + public abstract void delete(); @Override public String toString() { 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 014d2ccdf..8f8def6e0 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 @@ -16,6 +16,7 @@ import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.backend.FlwBackend; import dev.engine_room.flywheel.backend.engine.embed.Environment; +import dev.engine_room.flywheel.backend.engine.embed.LightStorage; import dev.engine_room.flywheel.lib.util.Pair; import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; @@ -45,7 +46,7 @@ public void delete() { initializationQueue.clear(); } - public void flush() { + public void flush(LightStorage lightStorage) { // Thread safety: flush is called from the render thread after all visual updates have been made, // so there are no:tm: threads we could be racing with. for (var instancer : initializationQueue) { diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java index 87349d721..4b17aa5c8 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java @@ -31,7 +31,7 @@ public class EngineImpl implements Engine { private final int sqrMaxOriginDistance; private final DrawManager> drawManager; - private final EnvironmentStorage environmentStorage = new EnvironmentStorage(); + private final EnvironmentStorage environmentStorage; private final LightStorage lightStorage; private final Flag flushFlag = new NamedFlag("flushed"); @@ -40,12 +40,13 @@ public class EngineImpl implements Engine { public EngineImpl(LevelAccessor level, DrawManager> drawManager, int maxOriginDistance) { this.drawManager = drawManager; sqrMaxOriginDistance = maxOriginDistance * maxOriginDistance; - lightStorage = new LightStorage(level); + environmentStorage = new EnvironmentStorage(); + lightStorage = new LightStorage(level, environmentStorage); } @Override public Plan createFramePlan() { - return SyncedPlan.of(this::flush); + return lightStorage.createFramePlan().then(SyncedPlan.of(this::flush)); } @Override @@ -96,7 +97,6 @@ public Vec3i renderOrigin() { @Override public void delete() { drawManager.delete(); - environmentStorage.delete(); lightStorage.delete(); } @@ -107,8 +107,8 @@ public Instancer instancer(Environment environment, Inst private void flush(RenderContext ctx) { try (var state = GlStateTracker.getRestoreState()) { Uniforms.update(ctx); - drawManager.flush(); environmentStorage.flush(); + drawManager.flush(lightStorage); } flushFlag.raise(); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java index 5a260eb04..66e3a9dd5 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java @@ -1,6 +1,8 @@ package dev.engine_room.flywheel.backend.engine; import dev.engine_room.flywheel.backend.engine.embed.AbstractEmbeddedEnvironment; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; import it.unimi.dsi.fastutil.objects.ReferenceSet; import it.unimi.dsi.fastutil.objects.ReferenceSets; @@ -13,10 +15,13 @@ public void track(AbstractEmbeddedEnvironment environment) { } public void flush() { + environments.removeIf(AbstractEmbeddedEnvironment::isDeleted); environments.forEach(AbstractEmbeddedEnvironment::flush); } - public void delete() { - environments.clear(); + public LongSet allLightSections() { + var out = new LongOpenHashSet(); + environments.forEach(e -> e.addLightSections(out)); + return out; } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java index f2a7912be..5d6c573d1 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java @@ -15,10 +15,10 @@ import dev.engine_room.flywheel.backend.compile.ContextShader; import dev.engine_room.flywheel.backend.engine.EngineImpl; import dev.engine_room.flywheel.backend.gl.shader.GlProgram; -import dev.engine_room.flywheel.backend.util.AtomicReferenceCounted; +import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.core.Vec3i; -public abstract class AbstractEmbeddedEnvironment extends AtomicReferenceCounted implements Environment, VisualEmbedding { +public abstract class AbstractEmbeddedEnvironment implements Environment, VisualEmbedding { protected final Matrix4f pose = new Matrix4f(); protected final Matrix3f normal = new Matrix3f(); private final Matrix4f poseComposed = new Matrix4f(); @@ -27,6 +27,8 @@ public abstract class AbstractEmbeddedEnvironment extends AtomicReferenceCounted protected final EngineImpl engine; private final RenderStage renderStage; + private boolean deleted = false; + public AbstractEmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) { this.engine = engine; this.renderStage = renderStage; @@ -38,9 +40,6 @@ public Instancer instancer(InstanceType type, Model m return engine.instancer(AbstractEmbeddedEnvironment.this, type, model, renderStage); } }; - - // Acquire the reference owned by the visual that created this. - acquire(); } @Override @@ -94,15 +93,20 @@ public VisualEmbedding createEmbedding() { return out; } + public boolean isDeleted() { + return deleted; + } + + public void addLightSections(LongSet out) { + + } + /** * Called by visuals */ @Override public void delete() { - // Release the reference owned by the visual that created this. - // Note that visuals don't explicitly call acquire, instead the - // storage acquired a reference when this was constructed. - release(); + deleted = true; } public abstract void setupLight(GlProgram program); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Arena.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Arena.java index 31dbc77cd..a43d6cbe5 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Arena.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Arena.java @@ -47,4 +47,8 @@ public long indexToPointer(int i) { public void delete() { memoryBlock.free(); } + + public int capacity() { + return top; + } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Environment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Environment.java index 91da73062..626f103e5 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Environment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Environment.java @@ -9,8 +9,4 @@ public interface Environment { void setupDraw(GlProgram drawProgram); void setupCull(GlProgram cullProgram); - - void acquire(); - - void release(); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/GlobalEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/GlobalEnvironment.java index b4c852280..c6e50acbd 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/GlobalEnvironment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/GlobalEnvironment.java @@ -23,14 +23,4 @@ public void setupDraw(GlProgram drawProgram) { public void setupCull(GlProgram cullProgram) { cullProgram.setBool(EmbeddingUniforms.USE_MODEL_MATRIX, false); } - - @Override - public void acquire() { - - } - - @Override - public void release() { - - } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java index c1edb2fcf..242c595f3 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java @@ -4,7 +4,11 @@ import org.lwjgl.system.MemoryUtil; +import dev.engine_room.flywheel.api.event.RenderContext; +import dev.engine_room.flywheel.api.task.Plan; +import dev.engine_room.flywheel.backend.engine.EnvironmentStorage; import dev.engine_room.flywheel.backend.engine.indirect.StagingBuffer; +import dev.engine_room.flywheel.lib.task.SimplePlan; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.longs.Long2IntMap; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; @@ -29,11 +33,12 @@ *

Thus, each section occupies 5832 bytes. */ public class LightStorage { - private final long SECTION_SIZE_BYTES = 9 * 9 * 9 * 8; - private final int DEFAULT_ARENA_CAPACITY_SECTIONS = 64; - private final int INVALID_SECTION = -1; + public static final long SECTION_SIZE_BYTES = 9 * 9 * 9 * 8; + private static final int DEFAULT_ARENA_CAPACITY_SECTIONS = 64; + private static final int INVALID_SECTION = -1; private final LevelAccessor level; + private final EnvironmentStorage environmentStorage; private final Arena arena; private final Long2IntMap section2ArenaIndex = new Long2IntOpenHashMap(); @@ -44,12 +49,24 @@ public class LightStorage { private final BitSet changed = new BitSet(); private boolean newSections = false; - public LightStorage(LevelAccessor level) { + public LightStorage(LevelAccessor level, EnvironmentStorage environmentStorage) { this.level = level; + this.environmentStorage = environmentStorage; arena = new Arena(SECTION_SIZE_BYTES, DEFAULT_ARENA_CAPACITY_SECTIONS); } + public Plan createFramePlan() { + return SimplePlan.of(() -> { + var longs = environmentStorage.allLightSections(); + longs.forEach(this::addSection); + }); + } + + public int capacity() { + return arena.capacity(); + } + public void addSection(long section) { var lightEngine = level.getLightEngine(); @@ -187,6 +204,7 @@ public void uploadChangedSections(StagingBuffer staging, int dstVbo) { for (int i = changed.nextSetBit(0); i >= 0; i = changed.nextSetBit(i + 1)) { staging.enqueueCopy(arena.indexToPointer(i), SECTION_SIZE_BYTES, dstVbo, i * SECTION_SIZE_BYTES); } + changed.clear(); } public IntArrayList createLut() { diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java index 82afd22fa..e2b727476 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java @@ -14,7 +14,6 @@ public class NestedEmbeddedEnvironment extends AbstractEmbeddedEnvironment { public NestedEmbeddedEnvironment(AbstractEmbeddedEnvironment parent, EngineImpl engine, RenderStage renderStage) { super(engine, renderStage); this.parent = parent; - parent.acquire(); } @Override @@ -33,9 +32,4 @@ public void composeMatrices(Matrix4f pose, Matrix3f normal) { pose.mul(this.pose); normal.mul(this.normal); } - - @Override - public void _delete() { - parent.release(); - } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java index 926d246b9..a79ce7ae8 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java @@ -16,19 +16,17 @@ public TopLevelEmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) { super(engine, renderStage); } - @Override - public void flush() { - super.flush(); - - lightSections.forEach(engine.lightStorage()::addSection); - } - @Override public void lightChunks(LongSet chunks) { lightSections.clear(); lightSections.addAll(chunks); } + @Override + public void addLightSections(LongSet out) { + out.addAll(lightSections); + } + @Override public void setupLight(GlProgram program) { program.setBool(EmbeddingUniforms.USE_LIGHT_VOLUME, !lightSections.isEmpty()); @@ -39,9 +37,4 @@ public void composeMatrices(Matrix4f pose, Matrix3f normal) { pose.set(this.pose); normal.set(this.normal); } - - @Override - protected void _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 new file mode 100644 index 000000000..eba0afbfd --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/BufferBindings.java @@ -0,0 +1,14 @@ +package dev.engine_room.flywheel.backend.engine.indirect; + +public class BufferBindings { + public static final int INSTANCE_BUFFER_BINDING = 0; + public static final int TARGET_BUFFER_BINDING = 1; + public static final int MODEL_INDEX_BUFFER_BINDING = 2; + public static final int MODEL_BUFFER_BINDING = 3; + public static final int DRAW_BUFFER_BINDING = 4; + public static final int EMBEDDING_LUT_BINDING = 5; + public static final int EMBEDDING_LIGHT_BINDING = 6; + + private BufferBindings() { + } +} 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 fff5d9ea1..68034d16a 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 @@ -22,13 +22,6 @@ public class IndirectBuffers { public static final long DRAW_COMMAND_STRIDE = 40; public static final long DRAW_COMMAND_OFFSET = 0; - public static final int INSTANCE_INDEX = 0; - public static final int TARGET_INDEX = 1; - public static final int MODEL_INDEX_INDEX = 2; - public static final int MODEL_INDEX = 3; - public static final int DRAW_INDEX = 4; - - // Offsets to the 3 segments private static final long HANDLE_OFFSET = 0; private static final long OFFSET_OFFSET = BUFFER_COUNT * INT_SIZE; @@ -117,7 +110,7 @@ public void bindForDraw() { private void multiBind() { final long ptr = multiBindBlock.ptr(); - nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, IndirectBuffers.BUFFER_COUNT, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET); + nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE_BUFFER_BINDING, IndirectBuffers.BUFFER_COUNT, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET); } /** @@ -125,7 +118,7 @@ private void multiBind() { */ public void bindForCrumbling() { final long ptr = multiBindBlock.ptr(); - nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, 4, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET); + nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE_BUFFER_BINDING, 4, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET); } 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 8da0093f9..acc555013 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 @@ -23,6 +23,7 @@ import dev.engine_room.flywheel.backend.engine.MaterialRenderState; import dev.engine_room.flywheel.backend.engine.MeshPool; import dev.engine_room.flywheel.backend.engine.TextureBinder; +import dev.engine_room.flywheel.backend.engine.embed.LightStorage; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; import dev.engine_room.flywheel.backend.gl.GlStateTracker; import dev.engine_room.flywheel.backend.gl.array.GlVertexArray; @@ -40,6 +41,8 @@ public class IndirectDrawManager extends DrawManager> { private final Map, IndirectCullingGroup> cullingGroups = new HashMap<>(); private final GlBuffer crumblingDrawBuffer = new GlBuffer(); + private final LightBuffers lightBuffers; + public IndirectDrawManager(IndirectPrograms programs) { this.programs = programs; programs.acquire(); @@ -50,6 +53,7 @@ public IndirectDrawManager(IndirectPrograms programs) { vertexArray = GlVertexArray.create(); meshPool.bind(vertexArray); + lightBuffers = new LightBuffers(); } @Override @@ -83,6 +87,7 @@ public void renderStage(RenderStage stage) { TextureBinder.bindLightAndOverlay(); vertexArray.bindForDraw(); + lightBuffers.bind(); Uniforms.bindAll(); for (var group : cullingGroups.values()) { @@ -95,8 +100,8 @@ public void renderStage(RenderStage stage) { } @Override - public void flush() { - super.flush(); + public void flush(LightStorage lightStorage) { + super.flush(lightStorage); for (var group : cullingGroups.values()) { group.flushInstancers(); @@ -108,6 +113,8 @@ public void flush() { stagingBuffer.reclaim(); + lightBuffers.flush(stagingBuffer, lightStorage); + for (var group : cullingGroups.values()) { group.upload(stagingBuffer); } @@ -159,7 +166,7 @@ public void renderCrumbling(List crumblingBlocks) { var block = MemoryBlock.malloc(IndirectBuffers.DRAW_COMMAND_STRIDE); GlBufferType.DRAW_INDIRECT_BUFFER.bind(crumblingDrawBuffer.handle()); - glBindBufferRange(GL_SHADER_STORAGE_BUFFER, IndirectBuffers.DRAW_INDEX, crumblingDrawBuffer.handle(), 0, IndirectBuffers.DRAW_COMMAND_STRIDE); + glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.DRAW_BUFFER_BINDING, crumblingDrawBuffer.handle(), 0, IndirectBuffers.DRAW_COMMAND_STRIDE); for (var groupEntry : byType.entrySet()) { var byProgress = groupEntry.getValue(); 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 38099c85d..b0ac9bc98 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 @@ -125,8 +125,6 @@ private void uploadAllModelIndices(StagingBuffer stagingBuffer, long modelIndexB @Override public void delete() { - super.delete(); - for (IndirectDraw draw : draws()) { draw.delete(); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java index 9a0a37a57..76e3d200c 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java @@ -1,17 +1,25 @@ package dev.engine_room.flywheel.backend.engine.indirect; +import org.lwjgl.opengl.GL46; import org.lwjgl.system.MemoryUtil; import dev.engine_room.flywheel.backend.engine.embed.LightStorage; public class LightBuffers { - private final ResizableStorageBuffer lightArena = new ResizableStorageBuffer(); + private final ResizableStorageArray lightArena = new ResizableStorageArray(LightStorage.SECTION_SIZE_BYTES); private final ResizableStorageArray lut = new ResizableStorageArray(4); public LightBuffers() { } public void flush(StagingBuffer staging, LightStorage light) { + var capacity = light.capacity(); + + if (capacity == 0) { + return; + } + + lightArena.ensureCapacity(capacity); light.uploadChangedSections(staging, lightArena.handle()); if (light.hasNewSections()) { @@ -26,4 +34,13 @@ public void flush(StagingBuffer staging, LightStorage light) { }); } } + + public void bind() { + if (lightArena.capacity() == 0) { + return; + } + + GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.EMBEDDING_LUT_BINDING, lut.handle(), 0, lut.byteCapacity()); + GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.EMBEDDING_LIGHT_BINDING, lightArena.handle(), 0, lightArena.byteCapacity()); + } } 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 b532823ab..bba7a716a 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 @@ -22,6 +22,7 @@ import dev.engine_room.flywheel.backend.engine.MaterialRenderState; import dev.engine_room.flywheel.backend.engine.MeshPool; import dev.engine_room.flywheel.backend.engine.TextureBinder; +import dev.engine_room.flywheel.backend.engine.embed.LightStorage; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; import dev.engine_room.flywheel.backend.gl.GlStateTracker; import dev.engine_room.flywheel.backend.gl.TextureBuffer; @@ -55,8 +56,8 @@ public InstancedDrawManager(InstancingPrograms programs) { } @Override - public void flush() { - super.flush(); + public void flush(LightStorage lightStorage) { + super.flush(lightStorage); var instancers = this.instancers.values(); instancers.removeIf(instancer -> { 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 9ca3d5cc8..a5dff27d9 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 @@ -116,8 +116,6 @@ public boolean needsToGrow(long capacity) { } public void delete() { - super.delete(); - if (vbo == null) { return; } diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag index f73846dea..23df5b7e9 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag @@ -13,14 +13,6 @@ uniform sampler2D _flw_crumblingTex; in vec2 _flw_crumblingTexCoord; #endif -#ifdef _FLW_EMBEDDED -uniform sampler3D _flw_lightVolume; - -uniform bool _flw_useLightVolume; - -in vec3 _flw_lightVolumeCoord; -#endif - flat in uint _flw_instanceID; out vec4 _flw_outputColor; @@ -43,12 +35,6 @@ void _flw_main() { flw_fragOverlay = flw_vertexOverlay; flw_fragLight = flw_vertexLight; - #ifdef _FLW_EMBEDDED - if (_flw_useLightVolume) { - flw_fragLight = max(flw_fragLight, texture(_flw_lightVolume, _flw_lightVolumeCoord).rg); - } - #endif - flw_materialFragment(); #ifdef _FLW_CRUMBLING @@ -98,11 +84,6 @@ void _flw_main() { case 6u: color = vec4(vec3(diffuseFactor), 1.); break; - #ifdef _FLW_EMBEDDED - case 7u: - color = vec4(_flw_lightVolumeCoord, 1.); - break; - #endif } _flw_outputColor = flw_fogFilter(color); diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert index fae9fd91c..e65cacd7b 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert @@ -70,7 +70,7 @@ vec2 getCrumblingTexCoord() { uniform mat4 _flw_modelMatrix; uniform mat3 _flw_normalMatrix; -bool _flw_embeddedLight(vec3 worldPos, vec3 normal, out vec2 lightCoord); +bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord); #endif flat out uint _flw_instanceID; @@ -89,7 +89,7 @@ void _flw_main(in FlwInstance instance, in uint stableInstanceID) { flw_vertexNormal = _flw_normalMatrix * flw_vertexNormal; vec2 embeddedLight; - if (_flw_embeddedLight(flw_vertexPos, flw_vertexNormal, embeddedLight)) { + if (_flw_embeddedLight(flw_vertexPos.xyz, embeddedLight)) { flw_vertexLight = max(flw_vertexLight, embeddedLight); } #endif 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 4aa9edc56..439c78057 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 @@ -3,3 +3,5 @@ #define _FLW_MODEL_INDEX_BUFFER_BINDING 2 #define _FLW_MODEL_BUFFER_BINDING 3 #define _FLW_DRAW_BUFFER_BINDING 4 +#define _FLW_EMBEDDING_LUT_BINDING 5 +#define _FLW_EMBEDDING_LIGHT_BINDING 6 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 de5493d3c..c9dba65ce 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 @@ -13,24 +13,33 @@ layout(std430, binding = _FLW_DRAW_BUFFER_BINDING) restrict readonly buffer Draw #ifdef _FLW_EMBEDDED -layout(std430, binding = 8) restrict readonly buffer EmbeddingLut { +layout(std430, binding = _FLW_EMBEDDING_LUT_BINDING) restrict readonly buffer EmbeddingLut { uint _flw_embeddingLut[]; }; const uint _FLW_LIGHT_SECTION_SIZE_BYTES = 18 * 18 * 18; const uint _FLW_LIGHT_SECTION_SIZE_INTS = _FLW_LIGHT_SECTION_SIZE_BYTES / 4; -layout(std430, binding = 9) restrict readonly buffer LightSections { +layout(std430, binding = _FLW_EMBEDDING_LIGHT_BINDING) restrict readonly buffer LightSections { uint _flw_lightSections[]; }; +/// Find the index for the next step in the LUT. +/// @param base The base index in the LUT, should point to the start of a coordinate span. +/// @param coord The coordinate to look for. +/// @param next Output. The index of the next step in the LUT. +/// @return true if the coordinate is not in the span. bool _flw_nextLut(uint base, int coord, out uint next) { + // The base coordinate. int start = int(_flw_embeddingLut[base]); + // The width of the coordinate span. uint size = _flw_embeddingLut[base + 1]; + // Index of the coordinate in the span. int i = coord - start; if (i < 0 || i >= size) { + // We missed. return true; } @@ -52,25 +61,29 @@ bool _flw_chunkCoordToSectionIndex(ivec3 sectionPos, out uint index) { return _flw_nextLut(z, sectionPos.z, index); } -vec2 _flw_lightAt(uint sectionOffset, ivec3 blockInSectionPos) { +vec2 _flw_lightAt(uint sectionOffset, uvec3 blockInSectionPos) { uint byteOffset = blockInSectionPos.x + blockInSectionPos.z * 18u + blockInSectionPos.y * 18u * 18u; uint uintOffset = byteOffset >> 2u; uint bitOffset = (byteOffset & 3u) << 3; - uint packed = _flw_lightSections[sectionOffset + uintOffset]; - uint block = (packed >> bitOffset) & 0xFu; - uint sky = (packed >> (bitOffset + 4u)) & 0xFu; + uint raw = _flw_lightSections[sectionOffset + uintOffset]; + uint block = (raw >> bitOffset) & 0xFu; + uint sky = (raw >> (bitOffset + 4u)) & 0xFu; return vec2(block, sky); } -bool _flw_embeddedLight(vec3 worldPos, vec3 normal, out vec2 lightCoord) { +bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord) { + ivec3 blockPos = ivec3(floor(worldPos)); + ivec3 sectionPos = blockPos >> 4; uvec3 blockInSectionPos = (blockPos & 0xF) + 1; uint lightSectionIndex; if (_flw_chunkCoordToSectionIndex(sectionPos, lightSectionIndex)) { + // TODO: useful debug mode for this. + // flw_vertexOverlay = ivec2(0, 3); return false; } diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert index ad3e9f9e3..16d52eb58 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert @@ -5,7 +5,7 @@ uniform uvec4 _flw_packedMaterial; uniform int _flw_baseInstance = 0; #ifdef _FLW_EMBEDDED -bool _flw_embeddedLight(vec3 worldPos, vec3 normal, out vec2 lightCoord) { +bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord) { return true; } #endif From d945332ae4190be5d67532f8b7b3a7bfcad62e9f Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 29 Jun 2024 17:18:06 -0700 Subject: [PATCH 03/18] Pixel perfect - Do embedded lighting in the fragment shader - Fix light coord being saturated --- .../flywheel/flywheel/internal/common.frag | 11 +++ .../flywheel/flywheel/internal/common.vert | 7 -- .../flywheel/internal/indirect/main.frag | 85 +++++++++++++++++++ .../flywheel/internal/indirect/main.vert | 84 ------------------ .../flywheel/internal/instancing/main.frag | 6 ++ .../flywheel/internal/instancing/main.vert | 6 -- 6 files changed, 102 insertions(+), 97 deletions(-) diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag index 23df5b7e9..491df32b3 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag @@ -13,6 +13,10 @@ uniform sampler2D _flw_crumblingTex; in vec2 _flw_crumblingTexCoord; #endif +#ifdef _FLW_EMBEDDED +bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord); +#endif + flat in uint _flw_instanceID; out vec4 _flw_outputColor; @@ -35,6 +39,13 @@ void _flw_main() { flw_fragOverlay = flw_vertexOverlay; flw_fragLight = flw_vertexLight; + #ifdef _FLW_EMBEDDED + vec2 embeddedLight; + if (_flw_embeddedLight(flw_vertexPos.xyz, embeddedLight)) { + flw_fragLight = max(flw_fragLight, embeddedLight); + } + #endif + flw_materialFragment(); #ifdef _FLW_CRUMBLING diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert index e65cacd7b..0ba630c8f 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert @@ -69,8 +69,6 @@ vec2 getCrumblingTexCoord() { #ifdef _FLW_EMBEDDED uniform mat4 _flw_modelMatrix; uniform mat3 _flw_normalMatrix; - -bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord); #endif flat out uint _flw_instanceID; @@ -87,11 +85,6 @@ void _flw_main(in FlwInstance instance, in uint stableInstanceID) { #ifdef _FLW_EMBEDDED flw_vertexPos = _flw_modelMatrix * flw_vertexPos; flw_vertexNormal = _flw_normalMatrix * flw_vertexNormal; - - vec2 embeddedLight; - if (_flw_embeddedLight(flw_vertexPos.xyz, embeddedLight)) { - flw_vertexLight = max(flw_vertexLight, embeddedLight); - } #endif flw_vertexNormal = normalize(flw_vertexNormal); diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag index 334b7b461..1e923fd39 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag @@ -1,7 +1,92 @@ #include "flywheel:internal/common.frag" +#include "flywheel:internal/indirect/buffer_bindings.glsl" flat in uvec3 _flw_packedMaterial; +#ifdef _FLW_EMBEDDED + +layout(std430, binding = _FLW_EMBEDDING_LUT_BINDING) restrict readonly buffer EmbeddingLut { + uint _flw_embeddingLut[]; +}; + +const uint _FLW_LIGHT_SECTION_SIZE_BYTES = 18 * 18 * 18; +const uint _FLW_LIGHT_SECTION_SIZE_INTS = _FLW_LIGHT_SECTION_SIZE_BYTES / 4; + +layout(std430, binding = _FLW_EMBEDDING_LIGHT_BINDING) restrict readonly buffer LightSections { + uint _flw_lightSections[]; +}; + +/// Find the index for the next step in the LUT. +/// @param base The base index in the LUT, should point to the start of a coordinate span. +/// @param coord The coordinate to look for. +/// @param next Output. The index of the next step in the LUT. +/// @return true if the coordinate is not in the span. +bool _flw_nextLut(uint base, int coord, out uint next) { + // The base coordinate. + int start = int(_flw_embeddingLut[base]); + // The width of the coordinate span. + uint size = _flw_embeddingLut[base + 1]; + + // Index of the coordinate in the span. + int i = coord - start; + + if (i < 0 || i >= size) { + // We missed. + return true; + } + + next = _flw_embeddingLut[base + 2 + i]; + + return false; +} + +bool _flw_chunkCoordToSectionIndex(ivec3 sectionPos, out uint index) { + uint y; + if (_flw_nextLut(0, sectionPos.x, y)) { + return true; + } + + uint z; + if (_flw_nextLut(y, sectionPos.y, z)) { + return true; + } + return _flw_nextLut(z, sectionPos.z, index); +} + +vec2 _flw_lightAt(uint sectionOffset, uvec3 blockInSectionPos) { + uint byteOffset = blockInSectionPos.x + blockInSectionPos.z * 18u + blockInSectionPos.y * 18u * 18u; + + uint uintOffset = byteOffset >> 2u; + uint bitOffset = (byteOffset & 3u) << 3; + + uint raw = _flw_lightSections[sectionOffset + uintOffset]; + uint block = (raw >> bitOffset) & 0xFu; + uint sky = (raw >> (bitOffset + 4u)) & 0xFu; + + return vec2(block / 15., sky / 15.); +} + +bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord) { + ivec3 blockPos = ivec3(floor(worldPos)); + + ivec3 sectionPos = blockPos >> 4; + uvec3 blockInSectionPos = (blockPos & 0xF) + 1; + + uint lightSectionIndex; + if (_flw_chunkCoordToSectionIndex(sectionPos, lightSectionIndex)) { + // TODO: useful debug mode for this. + // flw_fragOverlay = ivec2(0, 3); + return false; + } + + uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS; + + lightCoord = _flw_lightAt(sectionOffset, blockInSectionPos); + return true; +} + +#endif + void main() { _flw_uberMaterialFragmentIndex = _flw_packedMaterial.x; _flw_unpackUint2x16(_flw_packedMaterial.y, _flw_uberCutoutIndex, _flw_uberFogIndex); 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 c9dba65ce..55bd8fc0f 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 @@ -11,90 +11,6 @@ layout(std430, binding = _FLW_DRAW_BUFFER_BINDING) restrict readonly buffer Draw MeshDrawCommand _flw_drawCommands[]; }; -#ifdef _FLW_EMBEDDED - -layout(std430, binding = _FLW_EMBEDDING_LUT_BINDING) restrict readonly buffer EmbeddingLut { - uint _flw_embeddingLut[]; -}; - -const uint _FLW_LIGHT_SECTION_SIZE_BYTES = 18 * 18 * 18; -const uint _FLW_LIGHT_SECTION_SIZE_INTS = _FLW_LIGHT_SECTION_SIZE_BYTES / 4; - -layout(std430, binding = _FLW_EMBEDDING_LIGHT_BINDING) restrict readonly buffer LightSections { - uint _flw_lightSections[]; -}; - -/// Find the index for the next step in the LUT. -/// @param base The base index in the LUT, should point to the start of a coordinate span. -/// @param coord The coordinate to look for. -/// @param next Output. The index of the next step in the LUT. -/// @return true if the coordinate is not in the span. -bool _flw_nextLut(uint base, int coord, out uint next) { - // The base coordinate. - int start = int(_flw_embeddingLut[base]); - // The width of the coordinate span. - uint size = _flw_embeddingLut[base + 1]; - - // Index of the coordinate in the span. - int i = coord - start; - - if (i < 0 || i >= size) { - // We missed. - return true; - } - - next = _flw_embeddingLut[base + 2 + i]; - - return false; -} - -bool _flw_chunkCoordToSectionIndex(ivec3 sectionPos, out uint index) { - uint y; - if (_flw_nextLut(0, sectionPos.x, y)) { - return true; - } - - uint z; - if (_flw_nextLut(y, sectionPos.y, z)) { - return true; - } - return _flw_nextLut(z, sectionPos.z, index); -} - -vec2 _flw_lightAt(uint sectionOffset, uvec3 blockInSectionPos) { - uint byteOffset = blockInSectionPos.x + blockInSectionPos.z * 18u + blockInSectionPos.y * 18u * 18u; - - uint uintOffset = byteOffset >> 2u; - uint bitOffset = (byteOffset & 3u) << 3; - - uint raw = _flw_lightSections[sectionOffset + uintOffset]; - uint block = (raw >> bitOffset) & 0xFu; - uint sky = (raw >> (bitOffset + 4u)) & 0xFu; - - return vec2(block, sky); -} - -bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord) { - ivec3 blockPos = ivec3(floor(worldPos)); - - ivec3 sectionPos = blockPos >> 4; - uvec3 blockInSectionPos = (blockPos & 0xF) + 1; - - uint lightSectionIndex; - if (_flw_chunkCoordToSectionIndex(sectionPos, lightSectionIndex)) { - // TODO: useful debug mode for this. - // flw_vertexOverlay = ivec2(0, 3); - return false; - } - - uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS; - - lightCoord = _flw_lightAt(sectionOffset, blockInSectionPos); - return true; -} - -#endif - uniform uint _flw_baseDraw; flat out uvec3 _flw_packedMaterial; diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag index 661c3a769..ef56c1bce 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag @@ -2,6 +2,12 @@ uniform uvec4 _flw_packedMaterial; +#ifdef _FLW_EMBEDDED +bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord) { + return true; +} +#endif + void main() { _flw_uberMaterialFragmentIndex = _flw_packedMaterial.y; _flw_unpackUint2x16(_flw_packedMaterial.z, _flw_uberCutoutIndex, _flw_uberFogIndex); diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert index 16d52eb58..8c4b9d6a3 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert @@ -4,12 +4,6 @@ uniform uvec4 _flw_packedMaterial; uniform int _flw_baseInstance = 0; -#ifdef _FLW_EMBEDDED -bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord) { - return true; -} -#endif - void main() { _flw_uberMaterialVertexIndex = _flw_packedMaterial.x; _flw_unpackMaterialProperties(_flw_packedMaterial.w, flw_material); From 420e94091785912eff2291558362344d233b425b Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 29 Jun 2024 17:58:55 -0700 Subject: [PATCH 04/18] Get lerped - Use naive trilinear interpolation when fetching embedded light --- .../flywheel/internal/indirect/main.frag | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag index 1e923fd39..b51f490a6 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag @@ -63,17 +63,15 @@ vec2 _flw_lightAt(uint sectionOffset, uvec3 blockInSectionPos) { uint block = (raw >> bitOffset) & 0xFu; uint sky = (raw >> (bitOffset + 4u)) & 0xFu; - return vec2(block / 15., sky / 15.); + return vec2(block, sky); } bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord) { - ivec3 blockPos = ivec3(floor(worldPos)); - - ivec3 sectionPos = blockPos >> 4; - uvec3 blockInSectionPos = (blockPos & 0xF) + 1; + // The lowest corner of the 2x2x2 area we'll be trilinear interpolating. + ivec3 basePosition = ivec3(round(worldPos)) - 1; uint lightSectionIndex; - if (_flw_chunkCoordToSectionIndex(sectionPos, lightSectionIndex)) { + if (_flw_chunkCoordToSectionIndex(basePosition >> 4, lightSectionIndex)) { // TODO: useful debug mode for this. // flw_fragOverlay = ivec2(0, 3); return false; @@ -81,7 +79,33 @@ bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord) { uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS; - lightCoord = _flw_lightAt(sectionOffset, blockInSectionPos); + // Adjusted into 18x18x18 space + uvec3 blockInSectionPos = (basePosition & 0xF) + 1; + + // Fetch everything for trilinear interpolation + // Hypothetically we could re-order these and do some calculations in-between fetches + // to help with latency hiding, but the compiler should be able to do that for us. + vec2 light000 = _flw_lightAt(sectionOffset, blockInSectionPos); + vec2 light001 = _flw_lightAt(sectionOffset, blockInSectionPos + uvec3(0, 0, 1)); + vec2 light010 = _flw_lightAt(sectionOffset, blockInSectionPos + uvec3(0, 1, 0)); + vec2 light011 = _flw_lightAt(sectionOffset, blockInSectionPos + uvec3(0, 1, 1)); + vec2 light100 = _flw_lightAt(sectionOffset, blockInSectionPos + uvec3(1, 0, 0)); + vec2 light101 = _flw_lightAt(sectionOffset, blockInSectionPos + uvec3(1, 0, 1)); + vec2 light110 = _flw_lightAt(sectionOffset, blockInSectionPos + uvec3(1, 1, 0)); + vec2 light111 = _flw_lightAt(sectionOffset, blockInSectionPos + uvec3(1, 1, 1)); + + // The distance our fragment is from the center of the lowest corner. + vec3 interpolant = worldPos - vec3(basePosition) - 0.5; + + vec2 light00 = mix(light000, light001, interpolant.z); + vec2 light01 = mix(light010, light011, interpolant.z); + vec2 light10 = mix(light100, light101, interpolant.z); + vec2 light11 = mix(light110, light111, interpolant.z); + + vec2 light0 = mix(light00, light01, interpolant.y); + vec2 light1 = mix(light10, light11, interpolant.y); + + lightCoord = mix(light0, light1, interpolant.x) / 15.; return true; } From 48bf8511764b1adbecc030068a34bf4790c907b7 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 29 Jun 2024 19:01:48 -0700 Subject: [PATCH 05/18] Missing section misdirection - Avoid adding all sections every frame - Remove sections when they are no longer needed - Rebuild the lut when sections are removed - Properly detect missing sections by writing 1-based indices to the lut --- .../flywheel/backend/engine/EngineImpl.java | 3 +- .../backend/engine/embed/LightLut.java | 3 +- .../backend/engine/embed/LightStorage.java | 43 ++++++++++++++----- .../backend/engine/indirect/LightBuffers.java | 2 +- .../flywheel/internal/indirect/main.frag | 15 +++++-- 5 files changed, 50 insertions(+), 16 deletions(-) diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java index 4b17aa5c8..1912bbf23 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java @@ -46,7 +46,8 @@ public EngineImpl(LevelAccessor level, DrawManager createFramePlan() { - return lightStorage.createFramePlan().then(SyncedPlan.of(this::flush)); + return lightStorage.createFramePlan() + .then(SyncedPlan.of(this::flush)); } @Override diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightLut.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightLut.java index cfe4ced2f..736811c5a 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightLut.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightLut.java @@ -80,7 +80,8 @@ private static ReferenceArrayList zLookup.ensureCapacity(zIndex + 3); zLookup.size(zIndex + 3); } - zLookup.set(zIndex + 2, sectionIndicesMaps.get(position)); + // Add 1 to the actual index so that 0 indicates a missing section. + zLookup.set(zIndex + 2, sectionIndicesMaps.get(position) + 1); } return indices; } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java index 242c595f3..8095e7cdb 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java @@ -12,6 +12,7 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.longs.Long2IntMap; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import net.minecraft.world.level.LevelAccessor; @@ -47,7 +48,7 @@ public class LightStorage { } private final BitSet changed = new BitSet(); - private boolean newSections = false; + private boolean needsLutRebuild = false; public LightStorage(LevelAccessor level, EnvironmentStorage environmentStorage) { this.level = level; @@ -58,11 +59,34 @@ public LightStorage(LevelAccessor level, EnvironmentStorage environmentStorage) public Plan createFramePlan() { return SimplePlan.of(() -> { - var longs = environmentStorage.allLightSections(); - longs.forEach(this::addSection); + var allLightSections = environmentStorage.allLightSections(); + + removeUnusedSections(allLightSections); + + // Only add the new sections. + allLightSections.removeAll(section2ArenaIndex.keySet()); + + for (var section : allLightSections) { + addSection(section); + } }); } + private void removeUnusedSections(LongSet allLightSections) { + var entries = section2ArenaIndex.long2IntEntrySet(); + var it = entries.iterator(); + while (it.hasNext()) { + var entry = it.next(); + var section = entry.getLongKey(); + + if (!allLightSections.contains(section)) { + arena.free(entry.getIntValue()); + needsLutRebuild = true; + it.remove(); + } + } + } + public int capacity() { return arena.capacity(); } @@ -179,15 +203,11 @@ private int indexForSection(long section) { if (out == INVALID_SECTION) { out = arena.alloc(); section2ArenaIndex.put(section, out); - newSections = true; + needsLutRebuild = true; } return out; } - public void removeSection(long section) { - - } - public void delete() { arena.delete(); } @@ -196,8 +216,10 @@ public boolean hasChanges() { return !changed.isEmpty(); } - public boolean hasNewSections() { - return newSections; + public boolean checkNeedsLutRebuildAndClear() { + var out = needsLutRebuild; + needsLutRebuild = false; + return out; } public void uploadChangedSections(StagingBuffer staging, int dstVbo) { @@ -208,6 +230,7 @@ public void uploadChangedSections(StagingBuffer staging, int dstVbo) { } public IntArrayList createLut() { + // TODO: incremental lut updates return LightLut.buildLut(section2ArenaIndex); } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java index 76e3d200c..506e453f8 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java @@ -22,7 +22,7 @@ public void flush(StagingBuffer staging, LightStorage light) { lightArena.ensureCapacity(capacity); light.uploadChangedSections(staging, lightArena.handle()); - if (light.hasNewSections()) { + if (light.checkNeedsLutRebuildAndClear()) { var lut = light.createLut(); this.lut.ensureCapacity(lut.size()); diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag index b51f490a6..0179a846f 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag @@ -42,15 +42,24 @@ bool _flw_nextLut(uint base, int coord, out uint next) { bool _flw_chunkCoordToSectionIndex(ivec3 sectionPos, out uint index) { uint y; - if (_flw_nextLut(0, sectionPos.x, y)) { + if (_flw_nextLut(0, sectionPos.x, y) || y == 0) { return true; } uint z; - if (_flw_nextLut(y, sectionPos.y, z)) { + if (_flw_nextLut(y, sectionPos.y, z) || z == 0) { return true; } - return _flw_nextLut(z, sectionPos.z, index); + + uint sectionIndex; + if (_flw_nextLut(z, sectionPos.z, index) || index == 0) { + return true; + } + + // The index is written as 1-based so we can properly detect missing sections. + index = sectionIndex - 1; + + return false; } vec2 _flw_lightAt(uint sectionOffset, uvec3 blockInSectionPos) { From ceed8399a4aa156c27836259be0d02074ef0cdcf Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 30 Jun 2024 17:28:14 -0700 Subject: [PATCH 06/18] Cornered again - Fix light being fetched from a section adjacent to the block a fragment actually resides in - Fix light always being missing --- .../flywheel/internal/indirect/main.frag | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag index 0179a846f..229b7babf 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag @@ -52,7 +52,7 @@ bool _flw_chunkCoordToSectionIndex(ivec3 sectionPos, out uint index) { } uint sectionIndex; - if (_flw_nextLut(z, sectionPos.z, index) || index == 0) { + if (_flw_nextLut(z, sectionPos.z, sectionIndex) || sectionIndex == 0) { return true; } @@ -76,35 +76,41 @@ vec2 _flw_lightAt(uint sectionOffset, uvec3 blockInSectionPos) { } bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord) { - // The lowest corner of the 2x2x2 area we'll be trilinear interpolating. - ivec3 basePosition = ivec3(round(worldPos)) - 1; + // Always use the section of the block we are contained in to ensure accuracy. + // We don't want to interpolate between sections, but also we might not be able + // to rely on the existence neighboring sections, so don't do any extra rounding here. + ivec3 blockPos = ivec3(floor(worldPos)); uint lightSectionIndex; - if (_flw_chunkCoordToSectionIndex(basePosition >> 4, lightSectionIndex)) { + if (_flw_chunkCoordToSectionIndex(blockPos >> 4, lightSectionIndex)) { // TODO: useful debug mode for this. // flw_fragOverlay = ivec2(0, 3); return false; } - + // The offset of the section in the light buffer. uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS; - // Adjusted into 18x18x18 space - uvec3 blockInSectionPos = (basePosition & 0xF) + 1; + // The block's position in the section adjusted into 18x18x18 space + uvec3 blockInSectionPos = (blockPos & 0xF) + 1; + + // The lowest corner of the 2x2x2 area we'll be trilinear interpolating. + // The ugly bit on the end evaluates to -1 or 0 depending on which side of 0.5 we are. + uvec3 lowestCorner = blockInSectionPos + ivec3(floor(fract(worldPos) - 0.5)); + + // The distance our fragment is from the center of the lowest corner. + vec3 interpolant = fract(worldPos - 0.5); // Fetch everything for trilinear interpolation // Hypothetically we could re-order these and do some calculations in-between fetches // to help with latency hiding, but the compiler should be able to do that for us. - vec2 light000 = _flw_lightAt(sectionOffset, blockInSectionPos); - vec2 light001 = _flw_lightAt(sectionOffset, blockInSectionPos + uvec3(0, 0, 1)); - vec2 light010 = _flw_lightAt(sectionOffset, blockInSectionPos + uvec3(0, 1, 0)); - vec2 light011 = _flw_lightAt(sectionOffset, blockInSectionPos + uvec3(0, 1, 1)); - vec2 light100 = _flw_lightAt(sectionOffset, blockInSectionPos + uvec3(1, 0, 0)); - vec2 light101 = _flw_lightAt(sectionOffset, blockInSectionPos + uvec3(1, 0, 1)); - vec2 light110 = _flw_lightAt(sectionOffset, blockInSectionPos + uvec3(1, 1, 0)); - vec2 light111 = _flw_lightAt(sectionOffset, blockInSectionPos + uvec3(1, 1, 1)); - - // The distance our fragment is from the center of the lowest corner. - vec3 interpolant = worldPos - vec3(basePosition) - 0.5; + vec2 light000 = _flw_lightAt(sectionOffset, lowestCorner); + vec2 light001 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 0, 1)); + vec2 light010 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 0)); + vec2 light011 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 1)); + vec2 light100 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 0)); + vec2 light101 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 1)); + vec2 light110 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 0)); + vec2 light111 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 1)); vec2 light00 = mix(light000, light001, interpolant.z); vec2 light01 = mix(light010, light011, interpolant.z); From d5fc6ffc25bbcdb981b8d9ed80f7fba7f1d69c51 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 30 Jun 2024 20:45:52 -0700 Subject: [PATCH 07/18] Lightly observed - Pass light updates to LightStorage so that we don't have to re-upload every tracked section every frame - Slightly optimize light section writing, still room for improvement - Remove dead code in LightStorage --- .../backend/engine/embed/LightStorage.java | 93 +++++++++---------- .../engine/embed/LightUpdateHolder.java | 32 +++++++ .../backend/mixin/ClientChunkCacheMixin.java | 29 ++++++ .../resources/flywheel.backend.mixins.json | 1 + 4 files changed, 105 insertions(+), 50 deletions(-) create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightUpdateHolder.java create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/mixin/ClientChunkCacheMixin.java diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java index 8095e7cdb..8521b1835 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java @@ -63,10 +63,28 @@ public Plan createFramePlan() { removeUnusedSections(allLightSections); + var knownSections = section2ArenaIndex.keySet(); + + var updatedSections = LightUpdateHolder.get(level) + .getUpdatedSections(); + // Only add the new sections. - allLightSections.removeAll(section2ArenaIndex.keySet()); + allLightSections.removeAll(knownSections); + + for (long updatedSection : updatedSections) { + for (int x = -1; x <= 1; x++) { + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + long section = SectionPos.offset(updatedSection, x, y, z); + if (knownSections.contains(section)) { + allLightSections.add(section); + } + } + } + } + } - for (var section : allLightSections) { + for (long section : allLightSections) { addSection(section); } }); @@ -103,50 +121,38 @@ public void addSection(long section) { int yMin = SectionPos.sectionToBlockCoord(SectionPos.y(section)); int zMin = SectionPos.sectionToBlockCoord(SectionPos.z(section)); + var sectionPos = SectionPos.of(section); + var blockData = blockLight.getDataLayerData(sectionPos); + var skyData = skyLight.getDataLayerData(sectionPos); + int index = indexForSection(section); changed.set(index); long ptr = arena.indexToPointer(index); + // Not sure of a way to iterate over the surface of a cube, so branch in the inner loop to take the fast path. + // Adding the fast path is about 8x faster than having only the slow path. + // There's still room for optimization, as the slow path takes about 3x the cpu time as the fast path despite + // being called an order of magnitude less. for (int y = -1; y < 17; y++) { for (int z = -1; z < 17; z++) { for (int x = -1; x < 17; x++) { - blockPos.set(xMin + x, yMin + y, zMin + z); - var block = blockLight.getLightValue(blockPos); - var sky = skyLight.getLightValue(blockPos); - - write(ptr, x, y, z, block, sky); - } - } - } - } - - void addSectionFast(long section) { - // TODO: get this to work. it should be MUCH faster to read directly from the data layer - // though it's more complicated to manage which section datas we fetch - var lightEngine = level.getLightEngine(); - - var blockLight = lightEngine.getLayerListener(LightLayer.BLOCK); - var skyLight = lightEngine.getLayerListener(LightLayer.SKY); - - var sectionPos = SectionPos.of(section); - var blockData = blockLight.getDataLayerData(sectionPos); - var skyData = skyLight.getDataLayerData(sectionPos); - - if (blockData == null || skyData == null) { - return; - } - - long ptr = ptrForSection(section); - - for (int y = 0; y < 16; y++) { - for (int z = 0; z < 16; z++) { - for (int x = 0; x < 16; x++) { - var block = blockData.get(x, y, z); - var sky = skyData.get(x, y, z); - - write(ptr, x, y, z, block, sky); + if (x == -1 || y == -1 || z == -1 || x == 16 || y == 16 || z == 16) { + // Slow path, collect the surface of our section. + blockPos.set(xMin + x, yMin + y, zMin + z); + var block = blockLight.getLightValue(blockPos); + var sky = skyLight.getLightValue(blockPos); + + write(ptr, x, y, z, block, sky); + } else { + // Fast path, read directly from the data layer for the main section. + // Would be nice to move the null check elsewhere. + var block = blockData == null ? 0 : blockData.get(x, y, z); + var sky = skyData == null ? 0 : skyData.get(x, y, z); + + write(ptr, x, y, z, block, sky); + } } } } @@ -173,19 +179,6 @@ private void write(long ptr, int x, int y, int z, int block, int sky) { MemoryUtil.memPutByte(ptr + offset, (byte) packedByte); } - private void writeFor2Cubed(long ptr, int x, int y, int z, int block, int sky) { - int x1 = x + 1; - int y1 = y + 1; - int z1 = z + 1; - - int longIndex = (x1 >> 1) + (z1 >> 1) * 9 + (y1 >> 1) * 9 * 9; - int byteIndexInLong = (x1 & 1) + ((z1 & 1) << 1) + ((y1 & 1) << 2); - - long packedByte = (block & 0xF) | ((sky & 0xF) << 4); - - MemoryUtil.memPutByte(ptr + longIndex * 8L + byteIndexInLong, (byte) packedByte); - } - /** * Get a pointer to the base of the given section. *

If the section is not yet reserved, allocate a chunk in the arena. diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightUpdateHolder.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightUpdateHolder.java new file mode 100644 index 000000000..b61dfc197 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightUpdateHolder.java @@ -0,0 +1,32 @@ +package dev.engine_room.flywheel.backend.engine.embed; + +import dev.engine_room.flywheel.lib.util.LevelAttached; +import it.unimi.dsi.fastutil.longs.LongArraySet; +import it.unimi.dsi.fastutil.longs.LongSet; +import net.minecraft.world.level.LevelAccessor; + +/** + * Stores the set of updates light sections for LightStorage to poll in its frame plan. + */ +public class LightUpdateHolder { + private static final LevelAttached HOLDERS = new LevelAttached<>(level -> new LightUpdateHolder()); + + public static LightUpdateHolder get(LevelAccessor level) { + return HOLDERS.get(level); + } + + private final LongSet updatedSections = new LongArraySet(); + + public LongSet getUpdatedSections() { + var out = new LongArraySet(updatedSections); + updatedSections.clear(); + return out; + } + + public void add(long section) { + updatedSections.add(section); + } + + private LightUpdateHolder() { + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/ClientChunkCacheMixin.java b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/ClientChunkCacheMixin.java new file mode 100644 index 000000000..11ea0cea2 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/ClientChunkCacheMixin.java @@ -0,0 +1,29 @@ +package dev.engine_room.flywheel.backend.mixin; + +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import dev.engine_room.flywheel.backend.engine.embed.LightUpdateHolder; +import net.minecraft.client.multiplayer.ClientChunkCache; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.SectionPos; +import net.minecraft.world.level.LightLayer; + +@Mixin(ClientChunkCache.class) +abstract class ClientChunkCacheMixin { + @Shadow + @Final + ClientLevel level; + + @Inject(method = "onLightUpdate", at = @At("HEAD")) + private void flywheel$backend$onLightUpdate(LightLayer layer, SectionPos pos, CallbackInfo ci) { + // This is duplicated from code in impl, but I'm not sure that it + // makes sense to be generically passed to backends. + LightUpdateHolder.get(level) + .add(pos.asLong()); + } +} diff --git a/common/src/backend/resources/flywheel.backend.mixins.json b/common/src/backend/resources/flywheel.backend.mixins.json index 2d924bef0..e303f7b03 100644 --- a/common/src/backend/resources/flywheel.backend.mixins.json +++ b/common/src/backend/resources/flywheel.backend.mixins.json @@ -6,6 +6,7 @@ "refmap": "backend-flywheel.refmap.json", "client": [ "AbstractClientPlayerAccessor", + "ClientChunkCacheMixin", "GlStateManagerMixin", "LevelRendererAccessor", "OptionsMixin", From 9ac6ee0f27d7ef0c14f92b4734be1da6dd6059e4 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Fri, 5 Jul 2024 17:40:55 -0700 Subject: [PATCH 08/18] Lighting, for instance - Sideport light lut stuffs to instancing engine - Move actual lookup logic to light_lut.glsl, and have backend mains provide functions to index the backing storages for sanity's sake - Standardize naming of lut and sections - Pull in pepper's loom fix, so I can build :lwe: - Allow specifying the internal format of texture buffers so light can be a simple uint array --- .../flywheel/backend/Samplers.java | 2 + .../flywheel/backend/compile/Pipelines.java | 6 +- .../backend/engine/embed/LightStorage.java | 14 +- .../engine/indirect/BufferBindings.java | 4 +- .../backend/engine/indirect/LightBuffers.java | 12 +- .../instancing/InstancedDrawManager.java | 7 + .../engine/instancing/InstancedLight.java | 62 +++++++++ .../flywheel/backend/gl/TextureBuffer.java | 8 +- .../flywheel/flywheel/internal/common.frag | 2 - .../internal/indirect/buffer_bindings.glsl | 4 +- .../flywheel/internal/indirect/main.frag | 122 ++---------------- .../flywheel/internal/instancing/main.frag | 14 +- .../flywheel/flywheel/internal/light_lut.glsl | 115 +++++++++++++++++ 13 files changed, 236 insertions(+), 136 deletions(-) create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedLight.java create mode 100644 common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/Samplers.java b/common/src/backend/java/dev/engine_room/flywheel/backend/Samplers.java index cbdde0166..4441c6246 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/Samplers.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/Samplers.java @@ -8,4 +8,6 @@ public class Samplers { public static final GlTextureUnit LIGHT = GlTextureUnit.T2; public static final GlTextureUnit CRUMBLING = GlTextureUnit.T3; public static final GlTextureUnit INSTANCE_BUFFER = GlTextureUnit.T4; + public static final GlTextureUnit LIGHT_LUT = GlTextureUnit.T5; + public static final GlTextureUnit LIGHT_SECTIONS = GlTextureUnit.T6; } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/Pipelines.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/Pipelines.java index 6b3af1ff9..65f122f4b 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/Pipelines.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/Pipelines.java @@ -11,7 +11,11 @@ public final class Pipelines { .vertexMain(Flywheel.rl("internal/instancing/main.vert")) .fragmentMain(Flywheel.rl("internal/instancing/main.frag")) .assembler(BufferTextureInstanceComponent::new) - .onLink(program -> program.setSamplerBinding("_flw_instances", Samplers.INSTANCE_BUFFER)) + .onLink(program -> { + program.setSamplerBinding("_flw_instances", Samplers.INSTANCE_BUFFER); + program.setSamplerBinding("_flw_lightLut", Samplers.LIGHT_LUT); + program.setSamplerBinding("_flw_lightSections", Samplers.LIGHT_SECTIONS); + }) .build(); public static final Pipeline INDIRECT = Pipeline.builder() diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java index 8521b1835..02cd179e8 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java @@ -8,6 +8,7 @@ import dev.engine_room.flywheel.api.task.Plan; import dev.engine_room.flywheel.backend.engine.EnvironmentStorage; import dev.engine_room.flywheel.backend.engine.indirect.StagingBuffer; +import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer; import dev.engine_room.flywheel.lib.task.SimplePlan; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.longs.Long2IntMap; @@ -205,10 +206,6 @@ public void delete() { arena.delete(); } - public boolean hasChanges() { - return !changed.isEmpty(); - } - public boolean checkNeedsLutRebuildAndClear() { var out = needsLutRebuild; needsLutRebuild = false; @@ -222,6 +219,15 @@ public void uploadChangedSections(StagingBuffer staging, int dstVbo) { changed.clear(); } + public void upload(GlBuffer buffer) { + if (changed.isEmpty()) { + return; + } + + buffer.upload(arena.indexToPointer(0), arena.capacity() * SECTION_SIZE_BYTES); + changed.clear(); + } + public IntArrayList createLut() { // TODO: incremental lut updates return LightLut.buildLut(section2ArenaIndex); 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 eba0afbfd..97970f3b5 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 @@ -6,8 +6,8 @@ public class BufferBindings { public static final int MODEL_INDEX_BUFFER_BINDING = 2; public static final int MODEL_BUFFER_BINDING = 3; public static final int DRAW_BUFFER_BINDING = 4; - public static final int EMBEDDING_LUT_BINDING = 5; - public static final int EMBEDDING_LIGHT_BINDING = 6; + public static final int LIGHT_LUT_BINDING = 5; + public static final int LIGHT_SECTION_BINDING = 6; private BufferBindings() { } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java index 506e453f8..2423ae0fb 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java @@ -6,7 +6,7 @@ import dev.engine_room.flywheel.backend.engine.embed.LightStorage; public class LightBuffers { - private final ResizableStorageArray lightArena = new ResizableStorageArray(LightStorage.SECTION_SIZE_BYTES); + private final ResizableStorageArray sections = new ResizableStorageArray(LightStorage.SECTION_SIZE_BYTES); private final ResizableStorageArray lut = new ResizableStorageArray(4); public LightBuffers() { @@ -19,8 +19,8 @@ public void flush(StagingBuffer staging, LightStorage light) { return; } - lightArena.ensureCapacity(capacity); - light.uploadChangedSections(staging, lightArena.handle()); + sections.ensureCapacity(capacity); + light.uploadChangedSections(staging, sections.handle()); if (light.checkNeedsLutRebuildAndClear()) { var lut = light.createLut(); @@ -36,11 +36,11 @@ public void flush(StagingBuffer staging, LightStorage light) { } public void bind() { - if (lightArena.capacity() == 0) { + if (sections.capacity() == 0) { return; } - GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.EMBEDDING_LUT_BINDING, lut.handle(), 0, lut.byteCapacity()); - GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.EMBEDDING_LIGHT_BINDING, lightArena.handle(), 0, lightArena.byteCapacity()); + GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.LIGHT_LUT_BINDING, lut.handle(), 0, lut.byteCapacity()); + GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.LIGHT_SECTION_BINDING, sections.handle(), 0, sections.byteCapacity()); } } 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 bba7a716a..9130af672 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 @@ -43,6 +43,7 @@ public class InstancedDrawManager extends DrawManager> { private final MeshPool meshPool; private final GlVertexArray vao; private final TextureBuffer instanceTexture; + private final InstancedLight light; public InstancedDrawManager(InstancingPrograms programs) { programs.acquire(); @@ -51,6 +52,7 @@ public InstancedDrawManager(InstancingPrograms programs) { meshPool = new MeshPool(); vao = GlVertexArray.create(); instanceTexture = new TextureBuffer(); + light = new InstancedLight(); meshPool.bind(vao); } @@ -78,6 +80,8 @@ public void flush(LightStorage lightStorage) { } meshPool.flush(); + + light.flush(lightStorage); } @Override @@ -92,6 +96,7 @@ public void renderStage(RenderStage stage) { Uniforms.bindAll(); vao.bindForDraw(); TextureBinder.bindLightAndOverlay(); + light.bind(); drawSet.draw(instanceTexture, programs); @@ -114,6 +119,8 @@ public void delete() { programs.release(); vao.delete(); + light.delete(); + super.delete(); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedLight.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedLight.java new file mode 100644 index 000000000..3d966f0c8 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedLight.java @@ -0,0 +1,62 @@ +package dev.engine_room.flywheel.backend.engine.instancing; + +import org.lwjgl.opengl.GL32; +import org.lwjgl.system.MemoryUtil; + +import dev.engine_room.flywheel.backend.Samplers; +import dev.engine_room.flywheel.backend.engine.embed.LightStorage; +import dev.engine_room.flywheel.backend.gl.TextureBuffer; +import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer; +import dev.engine_room.flywheel.lib.memory.MemoryBlock; + +public class InstancedLight { + private final GlBuffer lut; + private final GlBuffer sections; + private final TextureBuffer lutTexture; + private final TextureBuffer sectionsTexture; + + public InstancedLight() { + sections = new GlBuffer(); + lut = new GlBuffer(); + lutTexture = new TextureBuffer(GL32.GL_R32UI); + sectionsTexture = new TextureBuffer(GL32.GL_R32UI); + } + + public void bind() { + Samplers.LIGHT_LUT.makeActive(); + lutTexture.bind(lut.handle()); + Samplers.LIGHT_SECTIONS.makeActive(); + sectionsTexture.bind(sections.handle()); + } + + public void flush(LightStorage light) { + if (light.capacity() == 0) { + return; + } + + light.upload(sections); + + if (light.checkNeedsLutRebuildAndClear()) { + var lut = light.createLut(); + + var up = MemoryBlock.malloc((long) lut.size() * Integer.BYTES); + + long ptr = up.ptr(); + + for (int i = 0; i < lut.size(); i++) { + MemoryUtil.memPutInt(ptr + (long) Integer.BYTES * i, lut.getInt(i)); + } + + this.lut.upload(up); + + up.free(); + } + } + + public void delete() { + sections.delete(); + lut.delete(); + lutTexture.delete(); + sectionsTexture.delete(); + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/TextureBuffer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/TextureBuffer.java index 21eb9361f..d7bbc9cfc 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/TextureBuffer.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/TextureBuffer.java @@ -5,14 +5,20 @@ public class TextureBuffer extends GlObject { public static final int MAX_TEXELS = GL32.glGetInteger(GL32.GL_MAX_TEXTURE_BUFFER_SIZE); public static final int MAX_BYTES = MAX_TEXELS * 16; // 4 channels * 4 bytes + private final int format; public TextureBuffer() { + this(GL32.GL_RGBA32UI); + } + + public TextureBuffer(int format) { handle(GL32.glGenTextures()); + this.format = format; } public void bind(int buffer) { GL32.glBindTexture(GL32.GL_TEXTURE_BUFFER, handle()); - GL32.glTexBuffer(GL32.GL_TEXTURE_BUFFER, GL32.GL_RGBA32UI, buffer); + GL32.glTexBuffer(GL32.GL_TEXTURE_BUFFER, format, buffer); } @Override diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag index 491df32b3..2cfa92e96 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag @@ -13,9 +13,7 @@ uniform sampler2D _flw_crumblingTex; in vec2 _flw_crumblingTexCoord; #endif -#ifdef _FLW_EMBEDDED bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord); -#endif flat in uint _flw_instanceID; 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 439c78057..604da5269 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 @@ -3,5 +3,5 @@ #define _FLW_MODEL_INDEX_BUFFER_BINDING 2 #define _FLW_MODEL_BUFFER_BINDING 3 #define _FLW_DRAW_BUFFER_BINDING 4 -#define _FLW_EMBEDDING_LUT_BINDING 5 -#define _FLW_EMBEDDING_LIGHT_BINDING 6 +#define _FLW_LIGHT_LUT_BINDING 5 +#define _FLW_LIGHT_SECTIONS_BINDING 6 diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag index 229b7babf..1711390a6 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag @@ -1,131 +1,25 @@ #include "flywheel:internal/common.frag" +#include "flywheel:internal/light_lut.glsl" #include "flywheel:internal/indirect/buffer_bindings.glsl" flat in uvec3 _flw_packedMaterial; -#ifdef _FLW_EMBEDDED - -layout(std430, binding = _FLW_EMBEDDING_LUT_BINDING) restrict readonly buffer EmbeddingLut { - uint _flw_embeddingLut[]; +layout(std430, binding = _FLW_LIGHT_LUT_BINDING) restrict readonly buffer LightLut { + uint _flw_lightLut[]; }; -const uint _FLW_LIGHT_SECTION_SIZE_BYTES = 18 * 18 * 18; -const uint _FLW_LIGHT_SECTION_SIZE_INTS = _FLW_LIGHT_SECTION_SIZE_BYTES / 4; - -layout(std430, binding = _FLW_EMBEDDING_LIGHT_BINDING) restrict readonly buffer LightSections { +layout(std430, binding = _FLW_LIGHT_SECTIONS_BINDING) restrict readonly buffer LightSections { uint _flw_lightSections[]; }; -/// Find the index for the next step in the LUT. -/// @param base The base index in the LUT, should point to the start of a coordinate span. -/// @param coord The coordinate to look for. -/// @param next Output. The index of the next step in the LUT. -/// @return true if the coordinate is not in the span. -bool _flw_nextLut(uint base, int coord, out uint next) { - // The base coordinate. - int start = int(_flw_embeddingLut[base]); - // The width of the coordinate span. - uint size = _flw_embeddingLut[base + 1]; - - // Index of the coordinate in the span. - int i = coord - start; - - if (i < 0 || i >= size) { - // We missed. - return true; - } - - next = _flw_embeddingLut[base + 2 + i]; - - return false; -} - -bool _flw_chunkCoordToSectionIndex(ivec3 sectionPos, out uint index) { - uint y; - if (_flw_nextLut(0, sectionPos.x, y) || y == 0) { - return true; - } - - uint z; - if (_flw_nextLut(y, sectionPos.y, z) || z == 0) { - return true; - } - - uint sectionIndex; - if (_flw_nextLut(z, sectionPos.z, sectionIndex) || sectionIndex == 0) { - return true; - } - - // The index is written as 1-based so we can properly detect missing sections. - index = sectionIndex - 1; - - return false; -} - -vec2 _flw_lightAt(uint sectionOffset, uvec3 blockInSectionPos) { - uint byteOffset = blockInSectionPos.x + blockInSectionPos.z * 18u + blockInSectionPos.y * 18u * 18u; - - uint uintOffset = byteOffset >> 2u; - uint bitOffset = (byteOffset & 3u) << 3; - - uint raw = _flw_lightSections[sectionOffset + uintOffset]; - uint block = (raw >> bitOffset) & 0xFu; - uint sky = (raw >> (bitOffset + 4u)) & 0xFu; - - return vec2(block, sky); +uint _flw_indexLut(uint index) { + return _flw_lightLut[index]; } -bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord) { - // Always use the section of the block we are contained in to ensure accuracy. - // We don't want to interpolate between sections, but also we might not be able - // to rely on the existence neighboring sections, so don't do any extra rounding here. - ivec3 blockPos = ivec3(floor(worldPos)); - - uint lightSectionIndex; - if (_flw_chunkCoordToSectionIndex(blockPos >> 4, lightSectionIndex)) { - // TODO: useful debug mode for this. - // flw_fragOverlay = ivec2(0, 3); - return false; - } - // The offset of the section in the light buffer. - uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS; - - // The block's position in the section adjusted into 18x18x18 space - uvec3 blockInSectionPos = (blockPos & 0xF) + 1; - - // The lowest corner of the 2x2x2 area we'll be trilinear interpolating. - // The ugly bit on the end evaluates to -1 or 0 depending on which side of 0.5 we are. - uvec3 lowestCorner = blockInSectionPos + ivec3(floor(fract(worldPos) - 0.5)); - - // The distance our fragment is from the center of the lowest corner. - vec3 interpolant = fract(worldPos - 0.5); - - // Fetch everything for trilinear interpolation - // Hypothetically we could re-order these and do some calculations in-between fetches - // to help with latency hiding, but the compiler should be able to do that for us. - vec2 light000 = _flw_lightAt(sectionOffset, lowestCorner); - vec2 light001 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 0, 1)); - vec2 light010 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 0)); - vec2 light011 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 1)); - vec2 light100 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 0)); - vec2 light101 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 1)); - vec2 light110 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 0)); - vec2 light111 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 1)); - - vec2 light00 = mix(light000, light001, interpolant.z); - vec2 light01 = mix(light010, light011, interpolant.z); - vec2 light10 = mix(light100, light101, interpolant.z); - vec2 light11 = mix(light110, light111, interpolant.z); - - vec2 light0 = mix(light00, light01, interpolant.y); - vec2 light1 = mix(light10, light11, interpolant.y); - - lightCoord = mix(light0, light1, interpolant.x) / 15.; - return true; +uint _flw_indexLight(uint index) { + return _flw_lightSections[index]; } -#endif - void main() { _flw_uberMaterialFragmentIndex = _flw_packedMaterial.x; _flw_unpackUint2x16(_flw_packedMaterial.y, _flw_uberCutoutIndex, _flw_uberFogIndex); diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag index ef56c1bce..80a4402ea 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag @@ -1,12 +1,18 @@ #include "flywheel:internal/common.frag" +#include "flywheel:internal/light_lut.glsl" uniform uvec4 _flw_packedMaterial; -#ifdef _FLW_EMBEDDED -bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord) { - return true; +uniform usamplerBuffer _flw_lightLut; +uniform usamplerBuffer _flw_lightSections; + +uint _flw_indexLut(uint index) { + return texelFetch(_flw_lightLut, int(index)).r; +} + +uint _flw_indexLight(uint index) { + return texelFetch(_flw_lightSections, int(index)).r; } -#endif void main() { _flw_uberMaterialFragmentIndex = _flw_packedMaterial.y; diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl new file mode 100644 index 000000000..e6abff4fa --- /dev/null +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl @@ -0,0 +1,115 @@ +const uint _FLW_LIGHT_SECTION_SIZE_BYTES = 18 * 18 * 18; +const uint _FLW_LIGHT_SECTION_SIZE_INTS = _FLW_LIGHT_SECTION_SIZE_BYTES / 4; + +uint _flw_indexLut(uint index); + +uint _flw_indexLight(uint index); + +/// Find the index for the next step in the LUT. +/// @param base The base index in the LUT, should point to the start of a coordinate span. +/// @param coord The coordinate to look for. +/// @param next Output. The index of the next step in the LUT. +/// @return true if the coordinate is not in the span. +bool _flw_nextLut(uint base, int coord, out uint next) { + // The base coordinate. + int start = int(_flw_indexLut(base)); + // The width of the coordinate span. + uint size = _flw_indexLut(base + 1); + + // Index of the coordinate in the span. + int i = coord - start; + + if (i < 0 || i >= size) { + // We missed. + return true; + } + + next = _flw_indexLut(base + 2 + i); + + return false; +} + +bool _flw_chunkCoordToSectionIndex(ivec3 sectionPos, out uint index) { + uint y; + if (_flw_nextLut(0, sectionPos.x, y) || y == 0) { + return true; + } + + uint z; + if (_flw_nextLut(y, sectionPos.y, z) || z == 0) { + return true; + } + + uint sectionIndex; + if (_flw_nextLut(z, sectionPos.z, sectionIndex) || sectionIndex == 0) { + return true; + } + + // The index is written as 1-based so we can properly detect missing sections. + index = sectionIndex - 1; + + return false; +} + +vec2 _flw_lightAt(uint sectionOffset, uvec3 blockInSectionPos) { + uint byteOffset = blockInSectionPos.x + blockInSectionPos.z * 18u + blockInSectionPos.y * 18u * 18u; + + uint uintOffset = byteOffset >> 2u; + uint bitOffset = (byteOffset & 3u) << 3; + + uint raw = _flw_indexLight(sectionOffset + uintOffset); + uint block = (raw >> bitOffset) & 0xFu; + uint sky = (raw >> (bitOffset + 4u)) & 0xFu; + + return vec2(block, sky); +} + +bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord) { + // Always use the section of the block we are contained in to ensure accuracy. + // We don't want to interpolate between sections, but also we might not be able + // to rely on the existence neighboring sections, so don't do any extra rounding here. + ivec3 blockPos = ivec3(floor(worldPos)); + + uint lightSectionIndex; + if (_flw_chunkCoordToSectionIndex(blockPos >> 4, lightSectionIndex)) { + // TODO: useful debug mode for this. + // flw_fragOverlay = ivec2(0, 3); + return false; + } + // The offset of the section in the light buffer. + uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS; + + // The block's position in the section adjusted into 18x18x18 space + uvec3 blockInSectionPos = (blockPos & 0xF) + 1; + + // The lowest corner of the 2x2x2 area we'll be trilinear interpolating. + // The ugly bit on the end evaluates to -1 or 0 depending on which side of 0.5 we are. + uvec3 lowestCorner = blockInSectionPos + ivec3(floor(fract(worldPos) - 0.5)); + + // The distance our fragment is from the center of the lowest corner. + vec3 interpolant = fract(worldPos - 0.5); + + // Fetch everything for trilinear interpolation + // Hypothetically we could re-order these and do some calculations in-between fetches + // to help with latency hiding, but the compiler should be able to do that for us. + vec2 light000 = _flw_lightAt(sectionOffset, lowestCorner); + vec2 light001 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 0, 1)); + vec2 light010 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 0)); + vec2 light011 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 1)); + vec2 light100 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 0)); + vec2 light101 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 1)); + vec2 light110 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 0)); + vec2 light111 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 1)); + + vec2 light00 = mix(light000, light001, interpolant.z); + vec2 light01 = mix(light010, light011, interpolant.z); + vec2 light10 = mix(light100, light101, interpolant.z); + vec2 light11 = mix(light110, light111, interpolant.z); + + vec2 light0 = mix(light00, light01, interpolant.y); + vec2 light1 = mix(light10, light11, interpolant.y); + + lightCoord = mix(light0, light1, interpolant.x) / 15.; + return true; +} + From b2686db96865ffccc8a3663d470bab15f8ffd6f5 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Fri, 5 Jul 2024 19:27:26 -0700 Subject: [PATCH 09/18] Light in the shade - Expose light in the shader api - flw_light - for builtin smooth lighting, faster than can be implemented by materials alone - flw_lightFetch - for materials that want to go crazy, access to raw data --- .../backend/compile/ContextShader.java | 3 ++- .../flywheel/flywheel/internal/api_impl.frag | 1 + .../flywheel/flywheel/internal/api_impl.glsl | 7 +++++++ .../flywheel/flywheel/internal/api_impl.vert | 1 + .../flywheel/flywheel/internal/common.frag | 9 --------- .../flywheel/internal/indirect/light.glsl | 17 +++++++++++++++++ .../flywheel/internal/indirect/main.frag | 18 +----------------- .../flywheel/internal/indirect/main.vert | 1 + .../flywheel/internal/instancing/light.glsl | 12 ++++++++++++ .../flywheel/internal/instancing/main.frag | 13 +------------ .../flywheel/internal/instancing/main.vert | 1 + .../flywheel/flywheel/internal/light_lut.glsl | 18 +++++++++++++++--- .../flywheel/flywheel/material/default.frag | 6 ++++++ docs/shader-api/common.glsl | 7 +++++++ docs/shader-api/fragment.glsl | 5 +---- docs/shader-api/vertex.glsl | 5 +---- 16 files changed, 74 insertions(+), 50 deletions(-) create mode 100644 common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.glsl create mode 100644 common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/light.glsl create mode 100644 common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/light.glsl create mode 100644 docs/shader-api/common.glsl diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/ContextShader.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/ContextShader.java index 6d3fede7a..7c8aac5dd 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/ContextShader.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/ContextShader.java @@ -13,7 +13,8 @@ public enum ContextShader { DEFAULT(null, $ -> { }), CRUMBLING("_FLW_CRUMBLING", program -> program.setSamplerBinding("_flw_crumblingTex", Samplers.CRUMBLING)), - EMBEDDED("_FLW_EMBEDDED", $ -> {}); + EMBEDDED("FLW_EMBEDDED", $ -> { + }); @Nullable private final String define; diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.frag index dc8334916..af16b80e2 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.frag +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.frag @@ -1,4 +1,5 @@ #include "flywheel:internal/material.glsl" +#include "flywheel:internal/api_impl.glsl" #include "flywheel:internal/uniforms/uniforms.glsl" in vec4 flw_vertexPos; diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.glsl new file mode 100644 index 000000000..9362c693d --- /dev/null +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.glsl @@ -0,0 +1,7 @@ +/// Get the light at the given world position. +/// This may be interpolated for smooth lighting. +bool flw_light(vec3 worldPos, out vec2 light); + +/// Fetches the light value at the given block position. +/// Returns false if the light for the given block is not available. +bool flw_lightFetch(ivec3 blockPos, out vec2 light); diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.vert index d9934d42e..d1e751929 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.vert +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.vert @@ -1,4 +1,5 @@ #include "flywheel:internal/material.glsl" +#include "flywheel:internal/api_impl.glsl" #include "flywheel:internal/uniforms/uniforms.glsl" out vec4 flw_vertexPos; diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag index 2cfa92e96..23df5b7e9 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag @@ -13,8 +13,6 @@ uniform sampler2D _flw_crumblingTex; in vec2 _flw_crumblingTexCoord; #endif -bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord); - flat in uint _flw_instanceID; out vec4 _flw_outputColor; @@ -37,13 +35,6 @@ void _flw_main() { flw_fragOverlay = flw_vertexOverlay; flw_fragLight = flw_vertexLight; - #ifdef _FLW_EMBEDDED - vec2 embeddedLight; - if (_flw_embeddedLight(flw_vertexPos.xyz, embeddedLight)) { - flw_fragLight = max(flw_fragLight, embeddedLight); - } - #endif - flw_materialFragment(); #ifdef _FLW_CRUMBLING diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/light.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/light.glsl new file mode 100644 index 000000000..bd688ef28 --- /dev/null +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/light.glsl @@ -0,0 +1,17 @@ +#include "flywheel:internal/light_lut.glsl" + +layout(std430, binding = _FLW_LIGHT_LUT_BINDING) restrict readonly buffer LightLut { + uint _flw_lightLut[]; +}; + +layout(std430, binding = _FLW_LIGHT_SECTIONS_BINDING) restrict readonly buffer LightSections { + uint _flw_lightSections[]; +}; + +uint _flw_indexLut(uint index) { + return _flw_lightLut[index]; +} + +uint _flw_indexLight(uint index) { + return _flw_lightSections[index]; +} diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag index 1711390a6..b1dbb0822 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag @@ -1,25 +1,9 @@ #include "flywheel:internal/common.frag" -#include "flywheel:internal/light_lut.glsl" #include "flywheel:internal/indirect/buffer_bindings.glsl" +#include "flywheel:internal/indirect/light.glsl" flat in uvec3 _flw_packedMaterial; -layout(std430, binding = _FLW_LIGHT_LUT_BINDING) restrict readonly buffer LightLut { - uint _flw_lightLut[]; -}; - -layout(std430, binding = _FLW_LIGHT_SECTIONS_BINDING) restrict readonly buffer LightSections { - uint _flw_lightSections[]; -}; - -uint _flw_indexLut(uint index) { - return _flw_lightLut[index]; -} - -uint _flw_indexLight(uint index) { - return _flw_lightSections[index]; -} - void main() { _flw_uberMaterialFragmentIndex = _flw_packedMaterial.x; _flw_unpackUint2x16(_flw_packedMaterial.y, _flw_uberCutoutIndex, _flw_uberFogIndex); 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 55bd8fc0f..a95d31662 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 @@ -2,6 +2,7 @@ #include "flywheel:internal/packed_material.glsl" #include "flywheel:internal/indirect/buffer_bindings.glsl" #include "flywheel:internal/indirect/draw_command.glsl" +#include "flywheel:internal/indirect/light.glsl" layout(std430, binding = _FLW_TARGET_BUFFER_BINDING) restrict readonly buffer TargetBuffer { uint _flw_instanceIndices[]; diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/light.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/light.glsl new file mode 100644 index 000000000..f023322a4 --- /dev/null +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/light.glsl @@ -0,0 +1,12 @@ +#include "flywheel:internal/light_lut.glsl" + +uniform usamplerBuffer _flw_lightLut; +uniform usamplerBuffer _flw_lightSections; + +uint _flw_indexLut(uint index) { + return texelFetch(_flw_lightLut, int(index)).r; +} + +uint _flw_indexLight(uint index) { + return texelFetch(_flw_lightSections, int(index)).r; +} diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag index 80a4402ea..89b09b37f 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag @@ -1,19 +1,8 @@ #include "flywheel:internal/common.frag" -#include "flywheel:internal/light_lut.glsl" +#include "flywheel:internal/instancing/light.glsl" uniform uvec4 _flw_packedMaterial; -uniform usamplerBuffer _flw_lightLut; -uniform usamplerBuffer _flw_lightSections; - -uint _flw_indexLut(uint index) { - return texelFetch(_flw_lightLut, int(index)).r; -} - -uint _flw_indexLight(uint index) { - return texelFetch(_flw_lightSections, int(index)).r; -} - void main() { _flw_uberMaterialFragmentIndex = _flw_packedMaterial.y; _flw_unpackUint2x16(_flw_packedMaterial.z, _flw_uberCutoutIndex, _flw_uberFogIndex); diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert index 8c4b9d6a3..4b6914eae 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert @@ -1,5 +1,6 @@ #include "flywheel:internal/common.vert" #include "flywheel:internal/packed_material.glsl" +#include "flywheel:internal/instancing/light.glsl" uniform uvec4 _flw_packedMaterial; uniform int _flw_baseInstance = 0; diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl index e6abff4fa..f79b5a20a 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl @@ -64,7 +64,21 @@ vec2 _flw_lightAt(uint sectionOffset, uvec3 blockInSectionPos) { return vec2(block, sky); } -bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord) { +bool flw_lightFetch(ivec3 blockPos, out vec2 lightCoord) { + uint lightSectionIndex; + if (_flw_chunkCoordToSectionIndex(blockPos >> 4, lightSectionIndex)) { + return false; + } + // The offset of the section in the light buffer. + uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS; + + uvec3 blockInSectionPos = (blockPos & 0xF) + 1; + + lightCoord = _flw_lightAt(sectionOffset, blockInSectionPos) / 15.; + return true; +} + +bool flw_light(vec3 worldPos, out vec2 lightCoord) { // Always use the section of the block we are contained in to ensure accuracy. // We don't want to interpolate between sections, but also we might not be able // to rely on the existence neighboring sections, so don't do any extra rounding here. @@ -72,8 +86,6 @@ bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord) { uint lightSectionIndex; if (_flw_chunkCoordToSectionIndex(blockPos >> 4, lightSectionIndex)) { - // TODO: useful debug mode for this. - // flw_fragOverlay = ivec2(0, 3); return false; } // The offset of the section in the light buffer. diff --git a/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag b/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag index 68dfe9a5b..42b1ac1fc 100644 --- a/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag +++ b/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag @@ -1,2 +1,8 @@ void flw_materialFragment() { + #ifdef FLW_EMBEDDED + vec2 embeddedLight; + if (flw_light(flw_vertexPos.xyz, embeddedLight)) { + flw_fragLight = max(flw_fragLight, embeddedLight); + } + #endif } diff --git a/docs/shader-api/common.glsl b/docs/shader-api/common.glsl new file mode 100644 index 000000000..9362c693d --- /dev/null +++ b/docs/shader-api/common.glsl @@ -0,0 +1,7 @@ +/// Get the light at the given world position. +/// This may be interpolated for smooth lighting. +bool flw_light(vec3 worldPos, out vec2 light); + +/// Fetches the light value at the given block position. +/// Returns false if the light for the given block is not available. +bool flw_lightFetch(ivec3 blockPos, out vec2 light); diff --git a/docs/shader-api/fragment.glsl b/docs/shader-api/fragment.glsl index a99837c25..e13ec6588 100644 --- a/docs/shader-api/fragment.glsl +++ b/docs/shader-api/fragment.glsl @@ -1,4 +1,5 @@ #include "flywheel:api/material.glsl" +#include "flywheel:api/common.glsl" /*const*/ vec4 flw_vertexPos; /*const*/ vec4 flw_vertexColor; @@ -24,10 +25,6 @@ vec4 flw_fogFilter(vec4 color); // To be implemented by discard shaders. bool flw_discardPredicate(vec4 finalColor); -// To be implemented by the context shader. -void flw_beginFragment(); -void flw_endFragment(); - sampler2D flw_diffuseTex; sampler2D flw_overlayTex; sampler2D flw_lightTex; diff --git a/docs/shader-api/vertex.glsl b/docs/shader-api/vertex.glsl index 338923540..5dcab38e7 100644 --- a/docs/shader-api/vertex.glsl +++ b/docs/shader-api/vertex.glsl @@ -1,4 +1,5 @@ #include "flywheel:api/material.glsl" +#include "flywheel:api/common.glsl" vec4 flw_vertexPos; vec4 flw_vertexColor; @@ -17,7 +18,3 @@ void flw_transformBoundingSphere(in FlwInstance i, inout vec3 center, inout floa // To be implemented by the material vertex shader. void flw_materialVertex(); - -// To be implemented by the context shader. -void flw_beginVertex(); -void flw_endVertex(); From 22aefa32c23b2916481dcbf07b04d4c1e00c31f4 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 6 Jul 2024 13:50:25 -0700 Subject: [PATCH 10/18] Literally unusable - Trim out dead light code from *EmbeddedEnvironment - Fix indirect never deleting empty culling groups - Fix embedded transforms not being applied --- .../engine/embed/AbstractEmbeddedEnvironment.java | 4 ---- .../engine/embed/NestedEmbeddedEnvironment.java | 6 ------ .../engine/embed/TopLevelEmbeddedEnvironment.java | 6 ------ .../backend/engine/indirect/IndirectCullingGroup.java | 10 ++++++++++ .../backend/engine/indirect/IndirectDrawManager.java | 6 +++++- .../engine/instancing/InstancedDrawManager.java | 4 ++-- .../assets/flywheel/flywheel/internal/common.vert | 4 ++-- 7 files changed, 19 insertions(+), 21 deletions(-) diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java index 5d6c573d1..2861dee83 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java @@ -57,8 +57,6 @@ public void flush() { @Override public void setupDraw(GlProgram program) { - setupLight(program); - program.setMat4(EmbeddingUniforms.MODEL_MATRIX, poseComposed); program.setMat3(EmbeddingUniforms.NORMAL_MATRIX, normalComposed); } @@ -109,7 +107,5 @@ public void delete() { deleted = true; } - public abstract void setupLight(GlProgram program); - public abstract void composeMatrices(Matrix4f pose, Matrix3f normal); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java index e2b727476..516d1f358 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java @@ -5,7 +5,6 @@ import dev.engine_room.flywheel.api.event.RenderStage; import dev.engine_room.flywheel.backend.engine.EngineImpl; -import dev.engine_room.flywheel.backend.gl.shader.GlProgram; import it.unimi.dsi.fastutil.longs.LongSet; public class NestedEmbeddedEnvironment extends AbstractEmbeddedEnvironment { @@ -21,11 +20,6 @@ public void lightChunks(LongSet chunks) { // noop } - @Override - public void setupLight(GlProgram program) { - parent.setupLight(program); - } - @Override public void composeMatrices(Matrix4f pose, Matrix3f normal) { parent.composeMatrices(pose, normal); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java index a79ce7ae8..4c58a8660 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java @@ -5,7 +5,6 @@ import dev.engine_room.flywheel.api.event.RenderStage; import dev.engine_room.flywheel.backend.engine.EngineImpl; -import dev.engine_room.flywheel.backend.gl.shader.GlProgram; import it.unimi.dsi.fastutil.longs.LongArraySet; import it.unimi.dsi.fastutil.longs.LongSet; @@ -27,11 +26,6 @@ public void addLightSections(LongSet out) { out.addAll(lightSections); } - @Override - public void setupLight(GlProgram program) { - program.setBool(EmbeddingUniforms.USE_LIGHT_VOLUME, !lightSections.isEmpty()); - } - @Override public void composeMatrices(Matrix4f pose, Matrix3f normal) { pose.set(this.pose); 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 c4d5fb8ea..b79a52edd 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 @@ -280,6 +280,16 @@ public void delete() { buffers.delete(); } + public boolean checkEmptyAndDelete() { + var out = indirectDraws.isEmpty(); + + if (out) { + delete(); + } + + return out; + } + private record MultiDraw(Material material, int start, int end) { private void submit() { if (GlCompat.DRIVER == Driver.INTEL) { 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 acc555013..1aadae5a3 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 @@ -107,7 +107,11 @@ public void flush(LightStorage lightStorage) { group.flushInstancers(); } - instancers.values().removeIf(instancer -> instancer.instanceCount() == 0); + cullingGroups.values() + .removeIf(IndirectCullingGroup::checkEmptyAndDelete); + + instancers.values() + .removeIf(instancer -> instancer.instanceCount() == 0); meshPool.flush(); 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 9130af672..6ea923cc1 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 @@ -61,8 +61,8 @@ public InstancedDrawManager(InstancingPrograms programs) { public void flush(LightStorage lightStorage) { super.flush(lightStorage); - var instancers = this.instancers.values(); - instancers.removeIf(instancer -> { + this.instancers.values() + .removeIf(instancer -> { // Update the instancers and remove any that are empty. instancer.update(); diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert index 0ba630c8f..075dae3f6 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert @@ -66,7 +66,7 @@ vec2 getCrumblingTexCoord() { } #endif -#ifdef _FLW_EMBEDDED +#ifdef FLW_EMBEDDED uniform mat4 _flw_modelMatrix; uniform mat3 _flw_normalMatrix; #endif @@ -82,7 +82,7 @@ void _flw_main(in FlwInstance instance, in uint stableInstanceID) { _flw_crumblingTexCoord = getCrumblingTexCoord(); #endif - #ifdef _FLW_EMBEDDED + #ifdef FLW_EMBEDDED flw_vertexPos = _flw_modelMatrix * flw_vertexPos; flw_vertexNormal = _flw_normalMatrix * flw_vertexNormal; #endif From 39237e1fc838f5f16b184420f762c30b6663cc15 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 6 Jul 2024 14:03:20 -0700 Subject: [PATCH 11/18] Smoothest operator - Implement RogueLogix's normal-dependent smooth lighting function - Uses a lot of fetches, so I imagine occupancy is kinda bad --- .../flywheel/flywheel/internal/api_impl.glsl | 6 + .../flywheel/flywheel/internal/light_lut.glsl | 107 ++++++++++++++++++ .../flywheel/flywheel/material/default.frag | 2 +- 3 files changed, 114 insertions(+), 1 deletion(-) diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.glsl index 9362c693d..a839a81c2 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.glsl @@ -1,3 +1,9 @@ +// TODO: Add config for light smoothness. Should work at a compile flag level + +/// Get the light at the given world position from the given normal. +/// This may be interpolated for smooth lighting. +bool flw_light(vec3 worldPos, vec3 normal, out vec2 light); + /// Get the light at the given world position. /// This may be interpolated for smooth lighting. bool flw_light(vec3 worldPos, out vec2 light); diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl index f79b5a20a..0b80c6714 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl @@ -125,3 +125,110 @@ bool flw_light(vec3 worldPos, out vec2 lightCoord) { return true; } +uint _flw_lightIndex(in uvec3 p) { + return p.x + p.z * 3u + p.y * 9u; +} + +/// Premtively collect all light in a 3x3x3 area centered on our block. +/// Depending on the normal, we won't use all the data, but fetching on demand will have many duplicated fetches. +vec2[27] _flw_lightFetch3x3x3(uint sectionOffset, ivec3 blockInSectionPos) { + vec2[27] lights; + + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + for (int x = -1; x <= 1; x++) { + lights[_flw_lightIndex(uvec3(x + 1, y + 1, z + 1))] = _flw_lightAt(sectionOffset, uvec3(blockInSectionPos + ivec3(x, y, z))); + } + } + } + + return lights; +} + +/// Calculate the light for a direction by averaging the light at the corners of the block. +/// +/// To make this reusable across directions, c00..c11 choose what values relative to each corner to use. +/// e.g. (0, 0, 0) (0, 0, 1) (0, 1, 0) (0, 1, 1) would give you the light coming from -x at each corner. +/// In general, to get the light for a particular direction, you fix the x, y, or z coordinate of the c values, and permutate 0 and 1 for the other two. +/// Fixing the x coordinate to 0 gives you the light from -x, 1 gives you the light from +x. +/// +/// @param lights The light data for the 3x3x3 area. +/// @param interpolant The position within the center block. +/// @param c00..c11 4 offsets to determine which "direction" we are averaging. +vec2 _flw_lightForDirection(in vec2[27] lights, in vec3 interpolant, in uvec3 c00, in uvec3 c01, in uvec3 c10, in uvec3 c11) { + + vec2 light000 = lights[_flw_lightIndex(c00 + uvec3(0u, 0u, 0u))] + lights[_flw_lightIndex(c01 + uvec3(0u, 0u, 0u))] + lights[_flw_lightIndex(c10 + uvec3(0u, 0u, 0u))] + lights[_flw_lightIndex(c11 + uvec3(0u, 0u, 0u))]; + vec2 light001 = lights[_flw_lightIndex(c00 + uvec3(0u, 0u, 1u))] + lights[_flw_lightIndex(c01 + uvec3(0u, 0u, 1u))] + lights[_flw_lightIndex(c10 + uvec3(0u, 0u, 1u))] + lights[_flw_lightIndex(c11 + uvec3(0u, 0u, 1u))]; + vec2 light010 = lights[_flw_lightIndex(c00 + uvec3(0u, 1u, 0u))] + lights[_flw_lightIndex(c01 + uvec3(0u, 1u, 0u))] + lights[_flw_lightIndex(c10 + uvec3(0u, 1u, 0u))] + lights[_flw_lightIndex(c11 + uvec3(0u, 1u, 0u))]; + vec2 light011 = lights[_flw_lightIndex(c00 + uvec3(0u, 1u, 1u))] + lights[_flw_lightIndex(c01 + uvec3(0u, 1u, 1u))] + lights[_flw_lightIndex(c10 + uvec3(0u, 1u, 1u))] + lights[_flw_lightIndex(c11 + uvec3(0u, 1u, 1u))]; + vec2 light100 = lights[_flw_lightIndex(c00 + uvec3(1u, 0u, 0u))] + lights[_flw_lightIndex(c01 + uvec3(1u, 0u, 0u))] + lights[_flw_lightIndex(c10 + uvec3(1u, 0u, 0u))] + lights[_flw_lightIndex(c11 + uvec3(1u, 0u, 0u))]; + vec2 light101 = lights[_flw_lightIndex(c00 + uvec3(1u, 0u, 1u))] + lights[_flw_lightIndex(c01 + uvec3(1u, 0u, 1u))] + lights[_flw_lightIndex(c10 + uvec3(1u, 0u, 1u))] + lights[_flw_lightIndex(c11 + uvec3(1u, 0u, 1u))]; + vec2 light110 = lights[_flw_lightIndex(c00 + uvec3(1u, 1u, 0u))] + lights[_flw_lightIndex(c01 + uvec3(1u, 1u, 0u))] + lights[_flw_lightIndex(c10 + uvec3(1u, 1u, 0u))] + lights[_flw_lightIndex(c11 + uvec3(1u, 1u, 0u))]; + vec2 light111 = lights[_flw_lightIndex(c00 + uvec3(1u, 1u, 1u))] + lights[_flw_lightIndex(c01 + uvec3(1u, 1u, 1u))] + lights[_flw_lightIndex(c10 + uvec3(1u, 1u, 1u))] + lights[_flw_lightIndex(c11 + uvec3(1u, 1u, 1u))]; + + vec2 light00 = mix(light000, light001, interpolant.z); + vec2 light01 = mix(light010, light011, interpolant.z); + vec2 light10 = mix(light100, light101, interpolant.z); + vec2 light11 = mix(light110, light111, interpolant.z); + + vec2 light0 = mix(light00, light01, interpolant.y); + vec2 light1 = mix(light10, light11, interpolant.y); + + // Divide by 60 (15 * 4) to normalize. + return mix(light0, light1, interpolant.x) / 60.; +} + +bool flw_light(vec3 worldPos, vec3 normal, out vec2 lightCoord) { + // Always use the section of the block we are contained in to ensure accuracy. + // We don't want to interpolate between sections, but also we might not be able + // to rely on the existence neighboring sections, so don't do any extra rounding here. + ivec3 blockPos = ivec3(floor(worldPos)); + + uint lightSectionIndex; + if (_flw_chunkCoordToSectionIndex(blockPos >> 4, lightSectionIndex)) { + return false; + } + // The offset of the section in the light buffer. + uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS; + + // The block's position in the section adjusted into 18x18x18 space + ivec3 blockInSectionPos = (blockPos & 0xF) + 1; + + // Fetch everything in a 3x3x3 area centered around the block. + vec2[27] lights = _flw_lightFetch3x3x3(sectionOffset, blockInSectionPos); + + vec3 interpolant = fract(worldPos); + + vec2 lightX; + if (normal.x > 0) { + lightX = _flw_lightForDirection(lights, interpolant, uvec3(1u, 0u, 0u), uvec3(1u, 0u, 1u), uvec3(1u, 1u, 0u), uvec3(1u, 1u, 1u)); + } else if (normal.x < 0) { + lightX = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 0u), uvec3(0u, 0u, 1u), uvec3(0u, 1u, 0u), uvec3(0u, 1u, 1u)); + } else { + lightX = vec2(0.); + } + + vec2 lightZ; + if (normal.z > 0) { + lightZ = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 1u), uvec3(0u, 1u, 1u), uvec3(1u, 0u, 1u), uvec3(1u, 1u, 1u)); + } else if (normal.z < 0) { + lightZ = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 0u), uvec3(0u, 1u, 0u), uvec3(1u, 0u, 0u), uvec3(1u, 1u, 0u)); + } else { + lightZ = vec2(0.); + } + + vec2 lightY; + // Average the light in relevant directions at each corner. + if (normal.y > 0.) { + lightY = _flw_lightForDirection(lights, interpolant, uvec3(0u, 1u, 0u), uvec3(0u, 1u, 1u), uvec3(1u, 1u, 0u), uvec3(1u, 1u, 1u)); + } else if (normal.y < 0.) { + lightY = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 0u), uvec3(0u, 0u, 1u), uvec3(1u, 0u, 0u), uvec3(1u, 0u, 1u)); + } else { + lightY = vec2(0.); + } + + lightCoord = (lightX * abs(normal.x) + lightY * abs(normal.y) + lightZ * abs(normal.z)); + + return true; +} + diff --git a/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag b/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag index 42b1ac1fc..f072c1849 100644 --- a/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag +++ b/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag @@ -1,7 +1,7 @@ void flw_materialFragment() { #ifdef FLW_EMBEDDED vec2 embeddedLight; - if (flw_light(flw_vertexPos.xyz, embeddedLight)) { + if (flw_light(flw_vertexPos.xyz, flw_vertexNormal, embeddedLight)) { flw_fragLight = max(flw_fragLight, embeddedLight); } #endif From 4b04d747bd6e1c380b8a3f2f64c021131ff93235 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 6 Jul 2024 22:19:47 -0700 Subject: [PATCH 12/18] SmoothLit joins the battle - Not attached to the name - Add SmoothLitVisual opt in interface, allowing any visuals to contribute light sections to the arena - Remove lightChunks from VisualEmbedding, it has been usurped - Pass total collected light sections from BEs, Es, and effects to the engine interface. It seemed the most proper way to hand off information from the impl to the backend - Add SmoothLitVisualStorage to maintain the set of collected sections, though at the moment it is very naive and simply unions everything upon request, which is also naively done every frame --- .../flywheel/api/backend/Engine.java | 8 ++++ .../flywheel/api/visual/LitVisual.java | 3 ++ .../flywheel/api/visual/SmoothLitVisual.java | 24 ++++++++++ .../api/visualization/VisualEmbedding.java | 3 -- .../flywheel/backend/engine/EngineImpl.java | 8 +++- .../backend/engine/EnvironmentStorage.java | 8 ---- .../embed/AbstractEmbeddedEnvironment.java | 5 --- .../backend/engine/embed/LightStorage.java | 25 +++++++---- .../embed/NestedEmbeddedEnvironment.java | 6 --- .../embed/TopLevelEmbeddedEnvironment.java | 15 ------- .../VisualizationManagerImpl.java | 9 ++++ .../manager/VisualManagerImpl.java | 5 +++ .../storage/SmoothLitVisualStorage.java | 45 +++++++++++++++++++ .../impl/visualization/storage/Storage.java | 15 +++++++ 14 files changed, 132 insertions(+), 47 deletions(-) create mode 100644 common/src/api/java/dev/engine_room/flywheel/api/visual/SmoothLitVisual.java create mode 100644 common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SmoothLitVisualStorage.java diff --git a/common/src/api/java/dev/engine_room/flywheel/api/backend/Engine.java b/common/src/api/java/dev/engine_room/flywheel/api/backend/Engine.java index ab7d41ee9..64e960240 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/backend/Engine.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/backend/Engine.java @@ -9,6 +9,7 @@ import dev.engine_room.flywheel.api.task.Plan; import dev.engine_room.flywheel.api.task.TaskExecutor; import dev.engine_room.flywheel.api.visualization.VisualizationContext; +import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.client.Camera; import net.minecraft.core.BlockPos; import net.minecraft.core.Vec3i; @@ -68,6 +69,13 @@ public interface Engine { */ void delete(); + /** + * Assign the set of sections that visuals have requested GPU light for. + * + * @param sections The set of sections. + */ + void lightSections(LongSet sections); + /** * A block to be rendered as a crumbling overlay. * @param progress The progress of the crumbling animation in the range [0, 10). diff --git a/common/src/api/java/dev/engine_room/flywheel/api/visual/LitVisual.java b/common/src/api/java/dev/engine_room/flywheel/api/visual/LitVisual.java index 4f05e7687..9891962ea 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/visual/LitVisual.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/visual/LitVisual.java @@ -2,6 +2,8 @@ import java.util.function.LongConsumer; +import org.jetbrains.annotations.ApiStatus; + import net.minecraft.core.SectionPos; /** @@ -51,6 +53,7 @@ public interface LitVisual extends Visual { * A notifier object that can be used to indicate to the impl * that the sections a visual is contained in have changed. */ + @ApiStatus.NonExtendable interface Notifier { /** * Invoke this to indicate to the impl that your visual has moved to a different set of sections. diff --git a/common/src/api/java/dev/engine_room/flywheel/api/visual/SmoothLitVisual.java b/common/src/api/java/dev/engine_room/flywheel/api/visual/SmoothLitVisual.java new file mode 100644 index 000000000..43daf3757 --- /dev/null +++ b/common/src/api/java/dev/engine_room/flywheel/api/visual/SmoothLitVisual.java @@ -0,0 +1,24 @@ +package dev.engine_room.flywheel.api.visual; + +import org.jetbrains.annotations.ApiStatus; + +import it.unimi.dsi.fastutil.longs.LongSet; + +public interface SmoothLitVisual extends Visual { + /** + * Set the section property object. + * + *

This method is only called once, upon visual creation, + * + * @param property The property. + */ + void setSectionProperty(SectionProperty property); + + @ApiStatus.NonExtendable + interface SectionProperty { + /** + * Invoke this to indicate to the impl that your visual has moved to a different set of sections. + */ + void lightSections(LongSet sections); + } +} diff --git a/common/src/api/java/dev/engine_room/flywheel/api/visualization/VisualEmbedding.java b/common/src/api/java/dev/engine_room/flywheel/api/visualization/VisualEmbedding.java index e0b6d4f81..baf35844c 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/visualization/VisualEmbedding.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/visualization/VisualEmbedding.java @@ -4,7 +4,6 @@ import org.joml.Matrix4fc; import dev.engine_room.flywheel.api.BackendImplemented; -import it.unimi.dsi.fastutil.longs.LongSet; @BackendImplemented public interface VisualEmbedding extends VisualizationContext { @@ -16,8 +15,6 @@ public interface VisualEmbedding extends VisualizationContext { */ void transforms(Matrix4fc pose, Matrix3fc normal); - void lightChunks(LongSet chunks); - /** * Delete this embedding. * diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java index 1912bbf23..fa36cd21f 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java @@ -22,6 +22,7 @@ import dev.engine_room.flywheel.lib.task.Flag; import dev.engine_room.flywheel.lib.task.NamedFlag; import dev.engine_room.flywheel.lib.task.SyncedPlan; +import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.client.Camera; import net.minecraft.core.BlockPos; import net.minecraft.core.Vec3i; @@ -41,7 +42,7 @@ public EngineImpl(LevelAccessor level, DrawManager Instancer instancer(Environment environment, InstanceType type, Model model, RenderStage stage) { return drawManager.getInstancer(environment, type, model, stage); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java index 66e3a9dd5..e68ea5250 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java @@ -1,8 +1,6 @@ package dev.engine_room.flywheel.backend.engine; import dev.engine_room.flywheel.backend.engine.embed.AbstractEmbeddedEnvironment; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; import it.unimi.dsi.fastutil.objects.ReferenceSet; import it.unimi.dsi.fastutil.objects.ReferenceSets; @@ -18,10 +16,4 @@ public void flush() { environments.removeIf(AbstractEmbeddedEnvironment::isDeleted); environments.forEach(AbstractEmbeddedEnvironment::flush); } - - public LongSet allLightSections() { - var out = new LongOpenHashSet(); - environments.forEach(e -> e.addLightSections(out)); - return out; - } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java index 2861dee83..6bf89450c 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java @@ -15,7 +15,6 @@ import dev.engine_room.flywheel.backend.compile.ContextShader; import dev.engine_room.flywheel.backend.engine.EngineImpl; import dev.engine_room.flywheel.backend.gl.shader.GlProgram; -import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.core.Vec3i; public abstract class AbstractEmbeddedEnvironment implements Environment, VisualEmbedding { @@ -95,10 +94,6 @@ public boolean isDeleted() { return deleted; } - public void addLightSections(LongSet out) { - - } - /** * Called by visuals */ diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java index 02cd179e8..c9dd9b179 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java @@ -2,11 +2,11 @@ import java.util.BitSet; +import org.jetbrains.annotations.Nullable; import org.lwjgl.system.MemoryUtil; import dev.engine_room.flywheel.api.event.RenderContext; import dev.engine_room.flywheel.api.task.Plan; -import dev.engine_room.flywheel.backend.engine.EnvironmentStorage; import dev.engine_room.flywheel.backend.engine.indirect.StagingBuffer; import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer; import dev.engine_room.flywheel.lib.task.SimplePlan; @@ -40,7 +40,6 @@ public class LightStorage { private static final int INVALID_SECTION = -1; private final LevelAccessor level; - private final EnvironmentStorage environmentStorage; private final Arena arena; private final Long2IntMap section2ArenaIndex = new Long2IntOpenHashMap(); @@ -51,18 +50,26 @@ public class LightStorage { private final BitSet changed = new BitSet(); private boolean needsLutRebuild = false; - public LightStorage(LevelAccessor level, EnvironmentStorage environmentStorage) { + @Nullable + private LongSet requestedSections; + + public LightStorage(LevelAccessor level) { this.level = level; - this.environmentStorage = environmentStorage; arena = new Arena(SECTION_SIZE_BYTES, DEFAULT_ARENA_CAPACITY_SECTIONS); } + public void sections(LongSet sections) { + requestedSections = sections; + } + public Plan createFramePlan() { return SimplePlan.of(() -> { - var allLightSections = environmentStorage.allLightSections(); + if (requestedSections == null) { + return; + } - removeUnusedSections(allLightSections); + removeUnusedSections(requestedSections); var knownSections = section2ArenaIndex.keySet(); @@ -70,7 +77,7 @@ public Plan createFramePlan() { .getUpdatedSections(); // Only add the new sections. - allLightSections.removeAll(knownSections); + requestedSections.removeAll(knownSections); for (long updatedSection : updatedSections) { for (int x = -1; x <= 1; x++) { @@ -78,14 +85,14 @@ public Plan createFramePlan() { for (int z = -1; z <= 1; z++) { long section = SectionPos.offset(updatedSection, x, y, z); if (knownSections.contains(section)) { - allLightSections.add(section); + requestedSections.add(section); } } } } } - for (long section : allLightSections) { + for (long section : requestedSections) { addSection(section); } }); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java index 516d1f358..fb4bfbb49 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java @@ -5,7 +5,6 @@ import dev.engine_room.flywheel.api.event.RenderStage; import dev.engine_room.flywheel.backend.engine.EngineImpl; -import it.unimi.dsi.fastutil.longs.LongSet; public class NestedEmbeddedEnvironment extends AbstractEmbeddedEnvironment { private final AbstractEmbeddedEnvironment parent; @@ -15,11 +14,6 @@ public NestedEmbeddedEnvironment(AbstractEmbeddedEnvironment parent, EngineImpl this.parent = parent; } - @Override - public void lightChunks(LongSet chunks) { - // noop - } - @Override public void composeMatrices(Matrix4f pose, Matrix3f normal) { parent.composeMatrices(pose, normal); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java index 4c58a8660..297c3dd6b 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java @@ -5,27 +5,12 @@ import dev.engine_room.flywheel.api.event.RenderStage; import dev.engine_room.flywheel.backend.engine.EngineImpl; -import it.unimi.dsi.fastutil.longs.LongArraySet; -import it.unimi.dsi.fastutil.longs.LongSet; public class TopLevelEmbeddedEnvironment extends AbstractEmbeddedEnvironment { - private final LongSet lightSections = new LongArraySet(); - public TopLevelEmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) { super(engine, renderStage); } - @Override - public void lightChunks(LongSet chunks) { - lightSections.clear(); - lightSections.addAll(chunks); - } - - @Override - public void addLightSections(LongSet out) { - out.addAll(lightSections); - } - @Override public void composeMatrices(Matrix4f pose, Matrix3f normal) { pose.set(this.pose); diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java index e0accaf2d..7b2f9920e 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java @@ -45,6 +45,7 @@ import dev.engine_room.flywheel.lib.task.SimplePlan; import dev.engine_room.flywheel.lib.util.LevelAttached; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import net.minecraft.client.Minecraft; import net.minecraft.core.Vec3i; import net.minecraft.server.level.BlockDestructionProgress; @@ -104,6 +105,14 @@ private VisualizationManagerImpl(LevelAccessor level) { .ifTrue(recreate) .ifFalse(update) .plan() + .then(SimplePlan.of(() -> { + // TODO: Lazily re-evaluate the union'd set + var out = new LongOpenHashSet(); + out.addAll(blockEntities.lightSections()); + out.addAll(entities.lightSections()); + out.addAll(effects.lightSections()); + engine.lightSections(out); + })) .then(RaisePlan.raise(frameVisualsFlag)) .then(engine.createFramePlan()) .then(RaisePlan.raise(frameFlag)); diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/manager/VisualManagerImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/manager/VisualManagerImpl.java index 3abc3c7ac..213b9b436 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/manager/VisualManagerImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/manager/VisualManagerImpl.java @@ -10,6 +10,7 @@ import dev.engine_room.flywheel.impl.visualization.storage.Storage; import dev.engine_room.flywheel.impl.visualization.storage.Transaction; import dev.engine_room.flywheel.lib.task.SimplePlan; +import it.unimi.dsi.fastutil.longs.LongSet; public class VisualManagerImpl> implements VisualManager { private final Queue> queue = new ConcurrentLinkedQueue<>(); @@ -74,4 +75,8 @@ public Plan tickPlan() { return SimplePlan.of(context -> processQueue(1)) .then(storage.tickPlan()); } + + public LongSet lightSections() { + return getStorage().lightSections(); + } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SmoothLitVisualStorage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SmoothLitVisualStorage.java new file mode 100644 index 000000000..2dbe51d3b --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SmoothLitVisualStorage.java @@ -0,0 +1,45 @@ +package dev.engine_room.flywheel.impl.visualization.storage; + +import java.util.Map; + +import dev.engine_room.flywheel.api.visual.SmoothLitVisual; +import it.unimi.dsi.fastutil.longs.LongArraySet; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; + +public class SmoothLitVisualStorage { + private final Map visuals = new Reference2ObjectOpenHashMap<>(); + + public LongSet sections() { + var out = new LongOpenHashSet(); + for (SectionProperty value : visuals.values()) { + out.addAll(value.sections); + } + return out; + } + + public void remove(SmoothLitVisual smoothLit) { + visuals.remove(smoothLit); + } + + public void add(SmoothLitVisual smoothLit) { + var sections = new SectionProperty(); + visuals.put(smoothLit, sections); + smoothLit.setSectionProperty(sections); + } + + public void clear() { + visuals.clear(); + } + + private static final class SectionProperty implements SmoothLitVisual.SectionProperty { + private final LongSet sections = new LongArraySet(); + + @Override + public void lightSections(LongSet sections) { + this.sections.clear(); + this.sections.addAll(sections); + } + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java index b599dc26e..e2445ddd9 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java @@ -11,6 +11,7 @@ import dev.engine_room.flywheel.api.task.Plan; import dev.engine_room.flywheel.api.visual.DynamicVisual; import dev.engine_room.flywheel.api.visual.LitVisual; +import dev.engine_room.flywheel.api.visual.SmoothLitVisual; import dev.engine_room.flywheel.api.visual.TickableVisual; import dev.engine_room.flywheel.api.visual.Visual; import dev.engine_room.flywheel.api.visualization.VisualizationContext; @@ -19,6 +20,7 @@ import dev.engine_room.flywheel.lib.task.PlanMap; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import dev.engine_room.flywheel.lib.visual.SimpleTickableVisual; +import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; public abstract class Storage { @@ -28,6 +30,7 @@ public abstract class Storage { protected final List simpleDynamicVisuals = new ArrayList<>(); protected final List simpleTickableVisuals = new ArrayList<>(); protected final LitVisualStorage litVisuals = new LitVisualStorage(); + protected final SmoothLitVisualStorage smoothLitVisuals = new SmoothLitVisualStorage(); private final Map visuals = new Reference2ObjectOpenHashMap<>(); @@ -71,6 +74,9 @@ public void remove(T obj) { if (visual instanceof LitVisual lit) { litVisuals.remove(lit); } + if (visual instanceof SmoothLitVisual smoothLit) { + smoothLitVisuals.remove(smoothLit); + } visual.delete(); } @@ -90,6 +96,7 @@ public void recreateAll(float partialTick) { simpleTickableVisuals.clear(); simpleDynamicVisuals.clear(); litVisuals.clear(); + smoothLitVisuals.clear(); visuals.replaceAll((obj, visual) -> { visual.delete(); @@ -156,6 +163,10 @@ private void setup(Visual visual) { if (visual instanceof LitVisual lit) { litVisuals.setNotifierAndAdd(lit); } + + if (visual instanceof SmoothLitVisual smoothLit) { + smoothLitVisuals.add(smoothLit); + } } /** @@ -164,4 +175,8 @@ private void setup(Visual visual) { * @return true if the object is currently capable of being visualized. */ public abstract boolean willAccept(T obj); + + public LongSet lightSections() { + return smoothLitVisuals.sections(); + } } From d63e59245635989c7a2fac4dd32f75af11e83c6a Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 13 Jul 2024 13:09:47 -0700 Subject: [PATCH 13/18] Lazy as can be - Only push light sections to the engine when the set of sections requested by visuals changes - Clean up light storage plan and comment code - Remove LIGHT_VOLUME debug mode as it's no longer used --- .../flywheel/api/backend/Engine.java | 2 + .../flywheel/api/visual/SmoothLitVisual.java | 13 ++++- .../backend/engine/embed/LightStorage.java | 57 +++++++++++++------ .../engine/embed/LightUpdateHolder.java | 6 +- .../backend/engine/uniform/DebugMode.java | 1 - .../flywheel/flywheel/internal/light_lut.glsl | 5 +- .../VisualizationManagerImpl.java | 13 +++-- .../manager/VisualManagerImpl.java | 8 ++- .../storage/SmoothLitVisualStorage.java | 19 +++++-- .../impl/visualization/storage/Storage.java | 5 +- 10 files changed, 93 insertions(+), 36 deletions(-) diff --git a/common/src/api/java/dev/engine_room/flywheel/api/backend/Engine.java b/common/src/api/java/dev/engine_room/flywheel/api/backend/Engine.java index 64e960240..86865e33b 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/backend/Engine.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/backend/Engine.java @@ -72,6 +72,8 @@ public interface Engine { /** * Assign the set of sections that visuals have requested GPU light for. * + *

This will be called at most once per frame, and not necessarily every frame. + * * @param sections The set of sections. */ void lightSections(LongSet sections); diff --git a/common/src/api/java/dev/engine_room/flywheel/api/visual/SmoothLitVisual.java b/common/src/api/java/dev/engine_room/flywheel/api/visual/SmoothLitVisual.java index 43daf3757..c22d6dfdf 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/visual/SmoothLitVisual.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/visual/SmoothLitVisual.java @@ -4,11 +4,20 @@ import it.unimi.dsi.fastutil.longs.LongSet; +/** + * An interface allowing visuals to request light data on the GPU for a set of sections. + * + *

Sections passed into {@link SectionProperty#lightSections} will have their light data handed to the + * backend and queryable by {@code flw_light*} functions in shaders. + *
+ * Note that the queryable light data is shared across all visuals, so even if one specific visual does not + * request a given section, the data will be available if another visual does. + */ public interface SmoothLitVisual extends Visual { /** * Set the section property object. * - *

This method is only called once, upon visual creation, + *

This method is only called once, upon visual creation. * * @param property The property. */ @@ -17,7 +26,7 @@ public interface SmoothLitVisual extends Visual { @ApiStatus.NonExtendable interface SectionProperty { /** - * Invoke this to indicate to the impl that your visual has moved to a different set of sections. + * Assign the set of sections this visual wants to have light data for. */ void lightSections(LongSet sections); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java index c9dd9b179..cfacd116e 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java @@ -13,6 +13,7 @@ import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.longs.Long2IntMap; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArraySet; import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; @@ -59,53 +60,77 @@ public LightStorage(LevelAccessor level) { arena = new Arena(SECTION_SIZE_BYTES, DEFAULT_ARENA_CAPACITY_SECTIONS); } + /** + * Set the set of requested sections. + *

When set, this will be processed in the next frame plan. It may not be set every frame. + * + * @param sections The set of sections requested by the impl. + */ public void sections(LongSet sections) { requestedSections = sections; } public Plan createFramePlan() { return SimplePlan.of(() -> { - if (requestedSections == null) { + var updatedSections = LightUpdateHolder.get(level) + .getAndClearUpdatedSections(); + + if (updatedSections.isEmpty() && requestedSections == null) { return; } - removeUnusedSections(requestedSections); - - var knownSections = section2ArenaIndex.keySet(); - - var updatedSections = LightUpdateHolder.get(level) - .getUpdatedSections(); + removeUnusedSections(); - // Only add the new sections. - requestedSections.removeAll(knownSections); + // Start building the set of sections we need to collect this frame. + LongSet sectionsToCollect; + if (requestedSections == null) { + // If none were requested, then we need to collect all sections that received updates. + sectionsToCollect = new LongArraySet(); + } else { + // If we did receive a new set of requested sections, we only + // need to collect the sections that weren't yet tracked. + sectionsToCollect = requestedSections; + sectionsToCollect.removeAll(section2ArenaIndex.keySet()); + } + // updatedSections contains all sections than received light updates, + // but we only care about its intersection with our tracked sections. for (long updatedSection : updatedSections) { + // Since sections contain the border light of their neighbors, we need to collect the neighbors as well. for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { for (int z = -1; z <= 1; z++) { long section = SectionPos.offset(updatedSection, x, y, z); - if (knownSections.contains(section)) { - requestedSections.add(section); + if (section2ArenaIndex.containsKey(section)) { + sectionsToCollect.add(section); } } } } } - for (long section : requestedSections) { - addSection(section); + // Now actually do the collection. + // TODO: Can this be done in parallel? + for (long section : sectionsToCollect) { + collectSection(section); } + + requestedSections = null; }); } - private void removeUnusedSections(LongSet allLightSections) { + private void removeUnusedSections() { + if (requestedSections == null) { + return; + } + var entries = section2ArenaIndex.long2IntEntrySet(); var it = entries.iterator(); while (it.hasNext()) { var entry = it.next(); var section = entry.getLongKey(); - if (!allLightSections.contains(section)) { + if (!this.requestedSections.contains(section)) { arena.free(entry.getIntValue()); needsLutRebuild = true; it.remove(); @@ -117,7 +142,7 @@ public int capacity() { return arena.capacity(); } - public void addSection(long section) { + public void collectSection(long section) { var lightEngine = level.getLightEngine(); var blockLight = lightEngine.getLayerListener(LightLayer.BLOCK); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightUpdateHolder.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightUpdateHolder.java index b61dfc197..52c5c09f7 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightUpdateHolder.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightUpdateHolder.java @@ -17,7 +17,11 @@ public static LightUpdateHolder get(LevelAccessor level) { private final LongSet updatedSections = new LongArraySet(); - public LongSet getUpdatedSections() { + public LongSet getAndClearUpdatedSections() { + if (updatedSections.isEmpty()) { + return LongSet.of(); + } + var out = new LongArraySet(updatedSections); updatedSections.clear(); return out; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/DebugMode.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/DebugMode.java index 8eae9c292..dcc13dc4c 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/DebugMode.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/uniform/DebugMode.java @@ -14,7 +14,6 @@ public enum DebugMode implements StringRepresentable { LIGHT_COLOR, OVERLAY, DIFFUSE, - LIGHT_VOLUME, ; public static final Codec CODEC = StringRepresentable.fromEnum(DebugMode::values); diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl index 0b80c6714..4d864928c 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl @@ -175,7 +175,7 @@ vec2 _flw_lightForDirection(in vec2[27] lights, in vec3 interpolant, in uvec3 c0 vec2 light1 = mix(light10, light11, interpolant.y); // Divide by 60 (15 * 4) to normalize. - return mix(light0, light1, interpolant.x) / 60.; + return mix(light0, light1, interpolant.x) / 63.; } bool flw_light(vec3 worldPos, vec3 normal, out vec2 lightCoord) { @@ -227,7 +227,8 @@ bool flw_light(vec3 worldPos, vec3 normal, out vec2 lightCoord) { lightY = vec2(0.); } - lightCoord = (lightX * abs(normal.x) + lightY * abs(normal.y) + lightZ * abs(normal.z)); + vec3 n2 = normal * normal; + lightCoord = lightX * n2.x + lightY * n2.y + lightZ * n2.z; return true; } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java index 7b2f9920e..efd5c6820 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java @@ -106,12 +106,13 @@ private VisualizationManagerImpl(LevelAccessor level) { .ifFalse(update) .plan() .then(SimplePlan.of(() -> { - // TODO: Lazily re-evaluate the union'd set - var out = new LongOpenHashSet(); - out.addAll(blockEntities.lightSections()); - out.addAll(entities.lightSections()); - out.addAll(effects.lightSections()); - engine.lightSections(out); + if (blockEntities.lightSectionsDirty() || entities.lightSectionsDirty() || effects.lightSectionsDirty()) { + var out = new LongOpenHashSet(); + out.addAll(blockEntities.lightSections()); + out.addAll(entities.lightSections()); + out.addAll(effects.lightSections()); + engine.lightSections(out); + } })) .then(RaisePlan.raise(frameVisualsFlag)) .then(engine.createFramePlan()) diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/manager/VisualManagerImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/manager/VisualManagerImpl.java index 213b9b436..e8c0b88c9 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/manager/VisualManagerImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/manager/VisualManagerImpl.java @@ -76,7 +76,13 @@ public Plan tickPlan() { .then(storage.tickPlan()); } + public boolean lightSectionsDirty() { + return getStorage().smoothLitStorage() + .sectionsDirty(); + } + public LongSet lightSections() { - return getStorage().lightSections(); + return getStorage().smoothLitStorage() + .sections(); } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SmoothLitVisualStorage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SmoothLitVisualStorage.java index 2dbe51d3b..09c66b833 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SmoothLitVisualStorage.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SmoothLitVisualStorage.java @@ -2,6 +2,8 @@ import java.util.Map; +import org.jetbrains.annotations.Nullable; + import dev.engine_room.flywheel.api.visual.SmoothLitVisual; import it.unimi.dsi.fastutil.longs.LongArraySet; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; @@ -11,12 +13,19 @@ public class SmoothLitVisualStorage { private final Map visuals = new Reference2ObjectOpenHashMap<>(); + @Nullable + private LongSet cachedSections; + + public boolean sectionsDirty() { + return cachedSections == null; + } + public LongSet sections() { - var out = new LongOpenHashSet(); + cachedSections = new LongOpenHashSet(); for (SectionProperty value : visuals.values()) { - out.addAll(value.sections); + cachedSections.addAll(value.sections); } - return out; + return cachedSections; } public void remove(SmoothLitVisual smoothLit) { @@ -33,13 +42,15 @@ public void clear() { visuals.clear(); } - private static final class SectionProperty implements SmoothLitVisual.SectionProperty { + private final class SectionProperty implements SmoothLitVisual.SectionProperty { private final LongSet sections = new LongArraySet(); @Override public void lightSections(LongSet sections) { this.sections.clear(); this.sections.addAll(sections); + + SmoothLitVisualStorage.this.cachedSections = null; } } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java index e2445ddd9..84505dcb7 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java @@ -20,7 +20,6 @@ import dev.engine_room.flywheel.lib.task.PlanMap; import dev.engine_room.flywheel.lib.visual.SimpleDynamicVisual; import dev.engine_room.flywheel.lib.visual.SimpleTickableVisual; -import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; public abstract class Storage { @@ -176,7 +175,7 @@ private void setup(Visual visual) { */ public abstract boolean willAccept(T obj); - public LongSet lightSections() { - return smoothLitVisuals.sections(); + public SmoothLitVisualStorage smoothLitStorage() { + return smoothLitVisuals; } } From 1ccc4d4baa2d9ea5291bc4a6b77a7f57bd045961 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 13 Jul 2024 15:17:48 -0700 Subject: [PATCH 14/18] It's not a phase! - Optimize collecting light section edges - Kinda an absurd amount of code, but I'm not sure how to parameterize by an axis without having capturing lambdas - Around 3-4x faster --- .../backend/engine/embed/LightStorage.java | 189 ++++++++++++++---- 1 file changed, 153 insertions(+), 36 deletions(-) diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java index cfacd116e..90f551e9a 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java @@ -19,6 +19,7 @@ import net.minecraft.core.SectionPos; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.lighting.LayerLightEventListener; /** * TODO: AO data @@ -110,10 +111,8 @@ public Plan createFramePlan() { } // Now actually do the collection. - // TODO: Can this be done in parallel? - for (long section : sectionsToCollect) { - collectSection(section); - } + // TODO: Should this be done in parallel? + sectionsToCollect.forEach(this::collectSection); requestedSections = null; }); @@ -148,44 +147,137 @@ public void collectSection(long section) { var blockLight = lightEngine.getLayerListener(LightLayer.BLOCK); var skyLight = lightEngine.getLayerListener(LightLayer.SKY); - var blockPos = new BlockPos.MutableBlockPos(); - - int xMin = SectionPos.sectionToBlockCoord(SectionPos.x(section)); - int yMin = SectionPos.sectionToBlockCoord(SectionPos.y(section)); - int zMin = SectionPos.sectionToBlockCoord(SectionPos.z(section)); - - var sectionPos = SectionPos.of(section); - var blockData = blockLight.getDataLayerData(sectionPos); - var skyData = skyLight.getDataLayerData(sectionPos); - int index = indexForSection(section); changed.set(index); long ptr = arena.indexToPointer(index); - // Not sure of a way to iterate over the surface of a cube, so branch in the inner loop to take the fast path. - // Adding the fast path is about 8x faster than having only the slow path. - // There's still room for optimization, as the slow path takes about 3x the cpu time as the fast path despite - // being called an order of magnitude less. - for (int y = -1; y < 17; y++) { - for (int z = -1; z < 17; z++) { - for (int x = -1; x < 17; x++) { - if (x == -1 || y == -1 || z == -1 || x == 16 || y == 16 || z == 16) { - // Slow path, collect the surface of our section. - blockPos.set(xMin + x, yMin + y, zMin + z); - var block = blockLight.getLightValue(blockPos); - var sky = skyLight.getLightValue(blockPos); - - write(ptr, x, y, z, block, sky); - } else { - // Fast path, read directly from the data layer for the main section. - // Would be nice to move the null check elsewhere. - var block = blockData == null ? 0 : blockData.get(x, y, z); - var sky = skyData == null ? 0 : skyData.get(x, y, z); - - write(ptr, x, y, z, block, sky); - } + // Zero it out first. This is basically free and makes it easier to handle missing sections later. + MemoryUtil.memSet(ptr, 0, SECTION_SIZE_BYTES); + + collectCenter(blockLight, skyLight, ptr, section); + + for (SectionEdge i : SectionEdge.values()) { + collectYZPlane(blockLight, skyLight, ptr, SectionPos.offset(section, i.sectionOffset, 0, 0), i); + collectXZPlane(blockLight, skyLight, ptr, SectionPos.offset(section, 0, i.sectionOffset, 0), i); + collectXYPlane(blockLight, skyLight, ptr, SectionPos.offset(section, 0, 0, i.sectionOffset), i); + + for (SectionEdge j : SectionEdge.values()) { + collectXStrip(blockLight, skyLight, ptr, SectionPos.offset(section, 0, i.sectionOffset, j.sectionOffset), i, j); + collectYStrip(blockLight, skyLight, ptr, SectionPos.offset(section, i.sectionOffset, 0, j.sectionOffset), i, j); + collectZStrip(blockLight, skyLight, ptr, SectionPos.offset(section, i.sectionOffset, j.sectionOffset, 0), i, j); + } + } + + collectCorners(blockLight, skyLight, ptr, section); + } + + private void collectXStrip(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section, SectionEdge y, SectionEdge z) { + var pos = SectionPos.of(section); + var blockData = blockLight.getDataLayerData(pos); + var skyData = skyLight.getDataLayerData(pos); + if (blockData == null || skyData == null) { + return; + } + for (int x = 0; x < 16; x++) { + write(ptr, x, y.relative, z.relative, blockData.get(x, y.pos, z.pos), skyData.get(x, y.pos, z.pos)); + } + } + + private void collectYStrip(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section, SectionEdge x, SectionEdge z) { + var pos = SectionPos.of(section); + var blockData = blockLight.getDataLayerData(pos); + var skyData = skyLight.getDataLayerData(pos); + if (blockData == null || skyData == null) { + return; + } + for (int y = 0; y < 16; y++) { + write(ptr, x.relative, y, z.relative, blockData.get(x.pos, y, z.pos), skyData.get(x.pos, y, z.pos)); + } + } + + private void collectZStrip(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section, SectionEdge x, SectionEdge y) { + var pos = SectionPos.of(section); + var blockData = blockLight.getDataLayerData(pos); + var skyData = skyLight.getDataLayerData(pos); + if (blockData == null || skyData == null) { + return; + } + for (int z = 0; z < 16; z++) { + write(ptr, x.relative, y.relative, z, blockData.get(x.pos, y.pos, z), skyData.get(x.pos, y.pos, z)); + } + } + + private void collectYZPlane(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section, SectionEdge x) { + var pos = SectionPos.of(section); + var blockData = blockLight.getDataLayerData(pos); + var skyData = skyLight.getDataLayerData(pos); + if (blockData == null || skyData == null) { + return; + } + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + write(ptr, x.relative, y, z, blockData.get(x.pos, y, z), skyData.get(x.pos, y, z)); + } + } + } + + private void collectXZPlane(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section, SectionEdge y) { + var pos = SectionPos.of(section); + var blockData = blockLight.getDataLayerData(pos); + var skyData = skyLight.getDataLayerData(pos); + if (blockData == null || skyData == null) { + return; + } + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + write(ptr, x, y.relative, z, blockData.get(x, y.pos, z), skyData.get(x, y.pos, z)); + } + } + } + + private void collectXYPlane(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section, SectionEdge z) { + var pos = SectionPos.of(section); + var blockData = blockLight.getDataLayerData(pos); + var skyData = skyLight.getDataLayerData(pos); + if (blockData == null || skyData == null) { + return; + } + for (int y = 0; y < 16; y++) { + for (int x = 0; x < 16; x++) { + write(ptr, x, y, z.relative, blockData.get(x, y, z.pos), skyData.get(x, y, z.pos)); + } + } + } + + private void collectCenter(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section) { + var pos = SectionPos.of(section); + var blockData = blockLight.getDataLayerData(pos); + var skyData = skyLight.getDataLayerData(pos); + if (blockData == null || skyData == null) { + return; + } + for (int y = 0; y < 16; y++) { + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + write(ptr, x, y, z, blockData.get(x, y, z), skyData.get(x, y, z)); + } + } + } + } + + private void collectCorners(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section) { + var blockPos = new BlockPos.MutableBlockPos(); + int xMin = SectionPos.sectionToBlockCoord(SectionPos.x(section)); + int yMin = SectionPos.sectionToBlockCoord(SectionPos.y(section)); + int zMin = SectionPos.sectionToBlockCoord(SectionPos.z(section)); + + for (SectionEdge x : SectionEdge.values()) { + for (SectionEdge y : SectionEdge.values()) { + for (SectionEdge z : SectionEdge.values()) { + blockPos.set(x.relative + xMin, y.relative + yMin, z.relative + zMin); + write(ptr, x.relative, y.relative, z.relative, blockLight.getLightValue(blockPos), skyLight.getLightValue(blockPos)); } } } @@ -264,4 +356,29 @@ public IntArrayList createLut() { // TODO: incremental lut updates return LightLut.buildLut(section2ArenaIndex); } + + private enum SectionEdge { + LOW(15, -1, -1), + HIGH(0, 16, 1), + ; + + /** + * The position in the section to collect. + */ + private final int pos; + /** + * The position relative to the main section. + */ + private final int relative; + /** + * The offset to the neighboring section. + */ + private final int sectionOffset; + + SectionEdge(int pos, int relative, int sectionOffset) { + this.pos = pos; + this.relative = relative; + this.sectionOffset = sectionOffset; + } + } } From b498e2cbc1c0eeff7456c034197d0f04ba0a218b Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sat, 13 Jul 2024 21:36:15 -0700 Subject: [PATCH 15/18] Sunset box - Unused in flywheel and not a productive utility for others with the new features --- .../dev/engine_room/flywheel/lib/box/Box.java | 142 -------- .../flywheel/lib/box/MutableBox.java | 329 ------------------ .../flywheel/lib/light/LightPacking.java | 26 -- .../flywheel/lib/light/LightVolume.java | 217 ------------ 4 files changed, 714 deletions(-) delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/box/Box.java delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/box/MutableBox.java delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/light/LightPacking.java delete mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/light/LightVolume.java diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/box/Box.java b/common/src/lib/java/dev/engine_room/flywheel/lib/box/Box.java deleted file mode 100644 index 3955cde1b..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/box/Box.java +++ /dev/null @@ -1,142 +0,0 @@ -package dev.engine_room.flywheel.lib.box; - -import net.minecraft.util.Mth; -import net.minecraft.world.phys.AABB; - -public interface Box { - int getMinX(); - - int getMinY(); - - int getMinZ(); - - int getMaxX(); - - int getMaxY(); - - int getMaxZ(); - - default int sizeX() { - return getMaxX() - getMinX(); - } - - default int sizeY() { - return getMaxY() - getMinY(); - } - - default int sizeZ() { - return getMaxZ() - getMinZ(); - } - - default int volume() { - return sizeX() * sizeY() * sizeZ(); - } - - default boolean isEmpty() { - // if any dimension has side length 0 this box contains no volume - return getMinX() == getMaxX() || getMinY() == getMaxY() || getMinZ() == getMaxZ(); - } - - default boolean sameAs(Box other) { - return getMinX() == other.getMinX() && getMinY() == other.getMinY() && getMinZ() == other.getMinZ() && getMaxX() == other.getMaxX() && getMaxY() == other.getMaxY() && getMaxZ() == other.getMaxZ(); - } - - default boolean sameAs(Box other, int margin) { - return getMinX() == other.getMinX() - margin && - getMinY() == other.getMinY() - margin && - getMinZ() == other.getMinZ() - margin && - getMaxX() == other.getMaxX() + margin && - getMaxY() == other.getMaxY() + margin && - getMaxZ() == other.getMaxZ() + margin; - } - - default boolean sameAs(AABB other) { - return getMinX() == Math.floor(other.minX) - && getMinY() == Math.floor(other.minY) - && getMinZ() == Math.floor(other.minZ) - && getMaxX() == Math.ceil(other.maxX) - && getMaxY() == Math.ceil(other.maxY) - && getMaxZ() == Math.ceil(other.maxZ); - } - - default boolean intersects(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { - return this.getMinX() < maxX && this.getMaxX() > minX && this.getMinY() < maxY && this.getMaxY() > minY && this.getMinZ() < maxZ && this.getMaxZ() > minZ; - } - - default boolean intersects(Box other) { - return this.intersects(other.getMinX(), other.getMinY(), other.getMinZ(), other.getMaxX(), other.getMaxY(), other.getMaxZ()); - } - - default boolean contains(int x, int y, int z) { - return x >= getMinX() - && x <= getMaxX() - && y >= getMinY() - && y <= getMaxY() - && z >= getMinZ() - && z <= getMaxZ(); - } - - default boolean contains(Box other) { - return other.getMinX() >= this.getMinX() - && other.getMaxX() <= this.getMaxX() - && other.getMinY() >= this.getMinY() - && other.getMaxY() <= this.getMaxY() - && other.getMinZ() >= this.getMinZ() - && other.getMaxZ() <= this.getMaxZ(); - } - - default void forEachContained(CoordinateConsumer func) { - int minX = getMinX(); - int minY = getMinY(); - int minZ = getMinZ(); - int maxX = getMaxX(); - int maxY = getMaxY(); - int maxZ = getMaxZ(); - - for (int x = minX; x < maxX; x++) { - for (int y = minY; y < maxY; y++) { - for (int z = minZ; z < maxZ; z++) { - func.accept(x, y, z); - } - } - } - } - - default boolean hasPowerOf2Sides() { - // this is only true if all individual side lengths are powers of 2 - return Mth.isPowerOfTwo(volume()); - } - - default MutableBox union(Box other) { - int minX = Math.min(this.getMinX(), other.getMinX()); - int minY = Math.min(this.getMinY(), other.getMinY()); - int minZ = Math.min(this.getMinZ(), other.getMinZ()); - int maxX = Math.max(this.getMaxX(), other.getMaxX()); - int maxY = Math.max(this.getMaxY(), other.getMaxY()); - int maxZ = Math.max(this.getMaxZ(), other.getMaxZ()); - return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ); - } - - default MutableBox intersect(Box other) { - int minX = Math.max(this.getMinX(), other.getMinX()); - int minY = Math.max(this.getMinY(), other.getMinY()); - int minZ = Math.max(this.getMinZ(), other.getMinZ()); - int maxX = Math.min(this.getMaxX(), other.getMaxX()); - int maxY = Math.min(this.getMaxY(), other.getMaxY()); - int maxZ = Math.min(this.getMaxZ(), other.getMaxZ()); - return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ); - } - - default AABB toAABB() { - return new AABB(getMinX(), getMinY(), getMinZ(), getMaxX(), getMaxY(), getMaxZ()); - } - - default MutableBox copy() { - return new MutableBox(getMinX(), getMinY(), getMinZ(), getMaxX(), getMaxY(), getMaxZ()); - } - - @FunctionalInterface - interface CoordinateConsumer { - void accept(int x, int y, int z); - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/box/MutableBox.java b/common/src/lib/java/dev/engine_room/flywheel/lib/box/MutableBox.java deleted file mode 100644 index db69a3883..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/box/MutableBox.java +++ /dev/null @@ -1,329 +0,0 @@ -package dev.engine_room.flywheel.lib.box; - -import java.util.Collection; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.SectionPos; -import net.minecraft.core.Vec3i; -import net.minecraft.util.Mth; -import net.minecraft.world.phys.AABB; - -public class MutableBox implements Box { - protected int minX; - protected int minY; - protected int minZ; - protected int maxX; - protected int maxY; - protected int maxZ; - - public MutableBox() { - } - - public MutableBox(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) { - this.minX = minX; - this.minY = minY; - this.minZ = minZ; - this.maxX = maxX; - this.maxY = maxY; - this.maxZ = maxZ; - } - - public static MutableBox from(AABB aabb) { - int minX = (int) Math.floor(aabb.minX); - int minY = (int) Math.floor(aabb.minY); - int minZ = (int) Math.floor(aabb.minZ); - int maxX = (int) Math.ceil(aabb.maxX); - int maxY = (int) Math.ceil(aabb.maxY); - int maxZ = (int) Math.ceil(aabb.maxZ); - return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ); - } - - public static MutableBox from(Vec3i pos) { - return new MutableBox(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1); - } - - public static MutableBox from(SectionPos pos) { - return new MutableBox(pos.minBlockX(), pos.minBlockY(), pos.minBlockZ(), pos.maxBlockX() + 1, pos.maxBlockY() + 1, pos.maxBlockZ() + 1); - } - - public static MutableBox from(Vec3i start, Vec3i end) { - return new MutableBox(start.getX(), start.getY(), start.getZ(), end.getX() + 1, end.getY() + 1, end.getZ() + 1); - } - - public static MutableBox ofRadius(int radius) { - return new MutableBox(-radius, -radius, -radius, radius + 1, radius + 1, radius + 1); - } - - public static Box containingAll(Collection positions) { - if (positions.isEmpty()) { - return new MutableBox(); - } - int minX = Integer.MAX_VALUE; - int minY = Integer.MAX_VALUE; - int minZ = Integer.MAX_VALUE; - int maxX = Integer.MIN_VALUE; - int maxY = Integer.MIN_VALUE; - int maxZ = Integer.MIN_VALUE; - for (BlockPos pos : positions) { - minX = Math.min(minX, pos.getX()); - minY = Math.min(minY, pos.getY()); - minZ = Math.min(minZ, pos.getZ()); - maxX = Math.max(maxX, pos.getX()); - maxY = Math.max(maxY, pos.getY()); - maxZ = Math.max(maxZ, pos.getZ()); - } - return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ); - } - - @Override - public int getMinX() { - return minX; - } - - @Override - public int getMinY() { - return minY; - } - - @Override - public int getMinZ() { - return minZ; - } - - @Override - public int getMaxX() { - return maxX; - } - - @Override - public int getMaxY() { - return maxY; - } - - @Override - public int getMaxZ() { - return maxZ; - } - - public void setMinX(int minX) { - this.minX = minX; - } - - public void setMinY(int minY) { - this.minY = minY; - } - - public MutableBox setMinZ(int minZ) { - this.minZ = minZ; - return this; - } - - public void setMaxX(int maxX) { - this.maxX = maxX; - } - - public void setMaxY(int maxY) { - this.maxY = maxY; - } - - public void setMaxZ(int maxZ) { - this.maxZ = maxZ; - } - - public void setMin(int x, int y, int z) { - minX = x; - minY = y; - minZ = z; - } - - public void setMax(int x, int y, int z) { - maxX = x; - maxY = y; - maxZ = z; - } - - public void setMin(Vec3i v) { - setMin(v.getX(), v.getY(), v.getZ()); - } - - public void setMax(Vec3i v) { - setMax(v.getX(), v.getY(), v.getZ()); - } - - public void assign(Box other) { - minX = other.getMinX(); - minY = other.getMinY(); - minZ = other.getMinZ(); - maxX = other.getMaxX(); - maxY = other.getMaxY(); - maxZ = other.getMaxZ(); - } - - public void assign(AABB other) { - minX = (int) Math.floor(other.minX); - minY = (int) Math.floor(other.minY); - minZ = (int) Math.floor(other.minZ); - maxX = (int) Math.ceil(other.maxX); - maxY = (int) Math.ceil(other.maxY); - maxZ = (int) Math.ceil(other.maxZ); - } - - public void assign(Vec3i start, Vec3i end) { - minX = start.getX(); - minY = start.getY(); - minZ = start.getZ(); - maxX = end.getX() + 1; - maxY = end.getY() + 1; - maxZ = end.getZ() + 1; - } - - public void unionAssign(Box other) { - minX = Math.min(this.minX, other.getMinX()); - minY = Math.min(this.minY, other.getMinY()); - minZ = Math.min(this.minZ, other.getMinZ()); - maxX = Math.max(this.maxX, other.getMaxX()); - maxY = Math.max(this.maxY, other.getMaxY()); - maxZ = Math.max(this.maxZ, other.getMaxZ()); - } - - public void unionAssign(AABB other) { - minX = Math.min(this.minX, (int) Math.floor(other.minX)); - minY = Math.min(this.minY, (int) Math.floor(other.minY)); - minZ = Math.min(this.minZ, (int) Math.floor(other.minZ)); - maxX = Math.max(this.maxX, (int) Math.ceil(other.maxX)); - maxY = Math.max(this.maxY, (int) Math.ceil(other.maxY)); - maxZ = Math.max(this.maxZ, (int) Math.ceil(other.maxZ)); - } - - public void intersectAssign(Box other) { - minX = Math.max(this.minX, other.getMinX()); - minY = Math.max(this.minY, other.getMinY()); - minZ = Math.max(this.minZ, other.getMinZ()); - maxX = Math.min(this.maxX, other.getMaxX()); - maxY = Math.min(this.maxY, other.getMaxY()); - maxZ = Math.min(this.maxZ, other.getMaxZ()); - } - - public void fixMinMax() { - int minX = Math.min(this.minX, this.maxX); - int minY = Math.min(this.minY, this.maxY); - int minZ = Math.min(this.minZ, this.maxZ); - int maxX = Math.max(this.minX, this.maxX); - int maxY = Math.max(this.minY, this.maxY); - int maxZ = Math.max(this.minZ, this.maxZ); - - this.minX = minX; - this.minY = minY; - this.minZ = minZ; - this.maxX = maxX; - this.maxY = maxY; - this.maxZ = maxZ; - } - - public void translate(int x, int y, int z) { - minX = minX + x; - maxX = maxX + x; - minY = minY + y; - maxY = maxY + y; - minZ = minZ + z; - maxZ = maxZ + z; - } - - public void translate(Vec3i by) { - translate(by.getX(), by.getY(), by.getZ()); - } - - public void grow(int x, int y, int z) { - minX = minX - x; - minY = minY - y; - minZ = minZ - z; - maxX = maxX + x; - maxY = maxY + y; - maxZ = maxZ + z; - } - - public void grow(int s) { - this.grow(s, s, s); - } - - /** - * Grow this box to have power of 2 side lengths, scaling from the minimum coords. - */ - public void nextPowerOf2() { - int sizeX = Mth.smallestEncompassingPowerOfTwo(sizeX()); - int sizeY = Mth.smallestEncompassingPowerOfTwo(sizeY()); - int sizeZ = Mth.smallestEncompassingPowerOfTwo(sizeZ()); - - maxX = minX + sizeX; - maxY = minY + sizeY; - maxZ = minZ + sizeZ; - } - - /** - * Grow this box to have power of 2 side length, scaling from the center. - */ - public void nextPowerOf2Centered() { - int sizeX = sizeX(); - int sizeY = sizeY(); - int sizeZ = sizeZ(); - - int newSizeX = Mth.smallestEncompassingPowerOfTwo(sizeX); - int newSizeY = Mth.smallestEncompassingPowerOfTwo(sizeY); - int newSizeZ = Mth.smallestEncompassingPowerOfTwo(sizeZ); - - int diffX = newSizeX - sizeX; - int diffY = newSizeY - sizeY; - int diffZ = newSizeZ - sizeZ; - - minX = minX - diffX / 2; // floor division for the minimums - minY = minY - diffY / 2; - minZ = minZ - diffZ / 2; - maxX = maxX + (diffX + 1) / 2; // ceiling divison for the maximums - maxY = maxY + (diffY + 1) / 2; - maxZ = maxZ + (diffZ + 1) / 2; - } - - public void mirrorAbout(Direction.Axis axis) { - Vec3i axisVec = Direction.get(Direction.AxisDirection.POSITIVE, axis) - .getNormal(); - int flipX = axisVec.getX() - 1; - int flipY = axisVec.getY() - 1; - int flipZ = axisVec.getZ() - 1; - - int maxX = this.maxX * flipX; - int maxY = this.maxY * flipY; - int maxZ = this.maxZ * flipZ; - this.maxX = this.minX * flipX; - this.maxY = this.minY * flipY; - this.maxZ = this.minZ * flipZ; - this.minX = maxX; - this.minY = maxY; - this.minZ = maxZ; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null) return false; - if (!(o instanceof Box that)) return false; - - return this.sameAs(that); - } - - @Override - public int hashCode() { - int result = minX; - result = 31 * result + minY; - result = 31 * result + minZ; - result = 31 * result + maxX; - result = 31 * result + maxY; - result = 31 * result + maxZ; - return result; - } - - @Override - public String toString() { - return "(" + minX + ", " + minY + ", " + minZ + ")->(" + maxX + ", " + maxY + ", " + maxZ + ')'; - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/light/LightPacking.java b/common/src/lib/java/dev/engine_room/flywheel/lib/light/LightPacking.java deleted file mode 100644 index 7cd9ce37e..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/light/LightPacking.java +++ /dev/null @@ -1,26 +0,0 @@ -package dev.engine_room.flywheel.lib.light; - -/** - * Utility class for bit-twiddling light. - */ -public class LightPacking { - public static int getBlock(short packed) { - return (packed >> 4) & 0xF; - } - - public static int getSky(short packed) { - return (packed >> 12) & 0xF; - } - - public static byte packLightNibbles(byte block, byte sky) { - return (byte) (block | (sky << 4)); - } - - public static int getBlock(byte packed) { - return packed & 0xF; - } - - public static int getSky(byte packed) { - return (packed >> 4) & 0xF; - } -} diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/light/LightVolume.java b/common/src/lib/java/dev/engine_room/flywheel/lib/light/LightVolume.java deleted file mode 100644 index ab71bcae3..000000000 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/light/LightVolume.java +++ /dev/null @@ -1,217 +0,0 @@ -package dev.engine_room.flywheel.lib.light; - -import org.lwjgl.system.MemoryUtil; - -import dev.engine_room.flywheel.lib.box.Box; -import dev.engine_room.flywheel.lib.box.MutableBox; -import dev.engine_room.flywheel.lib.memory.MemoryBlock; -import net.minecraft.core.BlockPos; -import net.minecraft.core.SectionPos; -import net.minecraft.world.level.BlockAndTintGetter; -import net.minecraft.world.level.LightLayer; - -public class LightVolume implements Box { - protected final BlockAndTintGetter level; - protected final MutableBox box = new MutableBox(); - protected MemoryBlock lightData; - - public LightVolume(BlockAndTintGetter level, Box sampleVolume) { - this.level = level; - this.setBox(sampleVolume); - - this.lightData = MemoryBlock.malloc(this.box.volume() * 2); - } - - public Box getVolume() { - return box; - } - - @Override - public int getMinX() { - return box.getMinX(); - } - - @Override - public int getMinY() { - return box.getMinY(); - } - - @Override - public int getMinZ() { - return box.getMinZ(); - } - - @Override - public int getMaxX() { - return box.getMaxX(); - } - - @Override - public int getMaxY() { - return box.getMaxY(); - } - - @Override - public int getMaxZ() { - return box.getMaxZ(); - } - - public boolean isInvalid() { - return lightData == null; - } - - protected void setBox(Box box) { - this.box.assign(box); - } - - public short getPackedLight(int x, int y, int z) { - if (box.contains(x, y, z)) { - return MemoryUtil.memGetShort(levelPosToPtr(x, y, z)); - } else { - return 0; - } - } - - public void move(Box newSampleVolume) { - if (lightData == null) return; - - setBox(newSampleVolume); - int neededCapacity = box.volume() * 2; - if (neededCapacity > lightData.size()) { - lightData = lightData.realloc(neededCapacity); - } - initialize(); - } - - /** - * Completely (re)populate this volume with block and sky lighting data. - * This is expensive and should be avoided. - */ - public void initialize() { - if (lightData == null) return; - - copyLight(getVolume()); - markDirty(); - } - - protected void markDirty() { - // noop - } - - public void delete() { - lightData.free(); - lightData = null; - } - - /** - * Copy all light from the level into this volume. - * - * @param levelVolume the region in the level to copy data from. - */ - public void copyLight(Box levelVolume) { - BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); - - int xShift = box.getMinX(); - int yShift = box.getMinY(); - int zShift = box.getMinZ(); - - levelVolume.forEachContained((x, y, z) -> { - pos.set(x, y, z); - - int block = this.level.getBrightness(LightLayer.BLOCK, pos); - int sky = this.level.getBrightness(LightLayer.SKY, pos); - - writeLight(x - xShift, y - yShift, z - zShift, block, sky); - }); - } - - protected void writeLight(int x, int y, int z, int block, int sky) { - byte b = (byte) ((block & 0xF) << 4); - byte s = (byte) ((sky & 0xF) << 4); - - long ptr = boxPosToPtr(x, y, z); - MemoryUtil.memPutByte(ptr, b); - MemoryUtil.memPutByte(ptr + 1, s); - } - - /** - * Copy block light from the level into this volume. - * - * @param levelVolume the region in the level to copy data from. - */ - public void copyBlock(Box levelVolume) { - var pos = new BlockPos.MutableBlockPos(); - - int xShift = box.getMinX(); - int yShift = box.getMinY(); - int zShift = box.getMinZ(); - - levelVolume.forEachContained((x, y, z) -> { - int light = this.level.getBrightness(LightLayer.BLOCK, pos.set(x, y, z)); - - writeBlock(x - xShift, y - yShift, z - zShift, light); - }); - } - - protected void writeBlock(int x, int y, int z, int block) { - byte b = (byte) ((block & 0xF) << 4); - - MemoryUtil.memPutByte(boxPosToPtr(x, y, z), b); - } - - /** - * Copy sky light from the level into this volume. - * - * @param levelVolume the region in the level to copy data from. - */ - public void copySky(Box levelVolume) { - var pos = new BlockPos.MutableBlockPos(); - - int xShift = box.getMinX(); - int yShift = box.getMinY(); - int zShift = box.getMinZ(); - - levelVolume.forEachContained((x, y, z) -> { - int light = this.level.getBrightness(LightLayer.SKY, pos.set(x, y, z)); - - writeSky(x - xShift, y - yShift, z - zShift, light); - }); - } - - protected void writeSky(int x, int y, int z, int sky) { - byte s = (byte) ((sky & 0xF) << 4); - - MemoryUtil.memPutByte(boxPosToPtr(x, y, z) + 1, s); - } - - protected long levelPosToPtr(int x, int y, int z) { - return lightData.ptr() + levelPosToPtrOffset(x, y, z); - } - - protected long boxPosToPtr(int x, int y, int z) { - return lightData.ptr() + boxPosToPtrOffset(x, y, z); - } - - protected int levelPosToPtrOffset(int x, int y, int z) { - x -= box.getMinX(); - y -= box.getMinY(); - z -= box.getMinZ(); - return boxPosToPtrOffset(x, y, z); - } - - protected int boxPosToPtrOffset(int x, int y, int z) { - return (x + box.sizeX() * (y + z * box.sizeY())) * 2; - } - - public void onLightUpdate(LightLayer type, SectionPos pos) { - if (lightData == null) return; - - MutableBox vol = MutableBox.from(pos); - if (!vol.intersects(getVolume())) return; - vol.intersectAssign(getVolume()); // compute the region contained by us that has dirty lighting data. - - if (type == LightLayer.BLOCK) copyBlock(vol); - else if (type == LightLayer.SKY) copySky(vol); - markDirty(); - } -} From 1253024d9e3f69a5ae9ec230e7a0c223aebdd523 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 14 Jul 2024 16:25:39 -0700 Subject: [PATCH 16/18] Lighter tracking - Deduplicate section tracking logic between Lit and SmoothLit - Now there is one SectionPropertyImpl which the 2 storages add listeners to --- .../flywheel/api/visual/LitVisual.java | 45 +--------------- .../api/visual/SectionTrackedVisual.java | 26 ++++++++++ .../flywheel/api/visual/SmoothLitVisual.java | 23 +-------- .../lib/visual/AbstractBlockEntityVisual.java | 15 ++---- .../storage/LitVisualStorage.java | 51 ++++++++----------- .../storage/SectionPropertyImpl.java | 26 ++++++++++ .../storage/SmoothLitVisualStorage.java | 32 +++++------- .../impl/visualization/storage/Storage.java | 18 +++++-- 8 files changed, 110 insertions(+), 126 deletions(-) create mode 100644 common/src/api/java/dev/engine_room/flywheel/api/visual/SectionTrackedVisual.java create mode 100644 common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionPropertyImpl.java diff --git a/common/src/api/java/dev/engine_room/flywheel/api/visual/LitVisual.java b/common/src/api/java/dev/engine_room/flywheel/api/visual/LitVisual.java index 9891962ea..bd01158cd 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/visual/LitVisual.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/visual/LitVisual.java @@ -1,40 +1,13 @@ package dev.engine_room.flywheel.api.visual; -import java.util.function.LongConsumer; - -import org.jetbrains.annotations.ApiStatus; - -import net.minecraft.core.SectionPos; - /** * A visual that listens to light updates. * *

If your visual moves around in the level at all, you should use {@link TickableVisual} or {@link DynamicVisual}, * and poll for light yourself along with listening for updates. When your visual moves to a different section, call - * {@link Notifier#notifySectionsChanged}.

+ * {@link SectionProperty#lightSections}.

*/ -public interface LitVisual extends Visual { - /** - * Set the notifier object. - * - *

This method is only called once right after the visual - * is created and before {@link #collectLightSections}.

- * - * @param notifier The notifier. - */ - void setLightSectionNotifier(Notifier notifier); - - /** - * Collect the sections that this visual is contained in. - * - *

This method is called upon visual creation, and the frame after - * {@link Notifier#notifySectionsChanged} is called.

- * - * @param consumer The consumer to provide the sections to. - * @see SectionPos#asLong - */ - void collectLightSections(LongConsumer consumer); - +public non-sealed interface LitVisual extends SectionTrackedVisual { /** * Called when a section this visual is contained in receives a light update. * @@ -48,18 +21,4 @@ public interface LitVisual extends Visual { *

This method not is invoked automatically after visual creation.

*/ void updateLight(float partialTick); - - /** - * A notifier object that can be used to indicate to the impl - * that the sections a visual is contained in have changed. - */ - @ApiStatus.NonExtendable - interface Notifier { - /** - * Invoke this to indicate to the impl that your visual has moved to a different set of sections. - *
- * The next frame, the impl will call {@link LitVisual#collectLightSections} again. - */ - void notifySectionsChanged(); - } } diff --git a/common/src/api/java/dev/engine_room/flywheel/api/visual/SectionTrackedVisual.java b/common/src/api/java/dev/engine_room/flywheel/api/visual/SectionTrackedVisual.java new file mode 100644 index 000000000..92bcca70d --- /dev/null +++ b/common/src/api/java/dev/engine_room/flywheel/api/visual/SectionTrackedVisual.java @@ -0,0 +1,26 @@ +package dev.engine_room.flywheel.api.visual; + +import org.jetbrains.annotations.ApiStatus; + +import it.unimi.dsi.fastutil.longs.LongSet; + +public sealed interface SectionTrackedVisual extends Visual permits SmoothLitVisual, LitVisual { + /** + * Set the section property object. + * + *

This method is only called once, upon visual creation. + *
If the property is assigned to in this method, the + * visual will immediately be tracked in the given sections. + * + * @param property The property. + */ + void setSectionProperty(SectionProperty property); + + @ApiStatus.NonExtendable + interface SectionProperty { + /** + * Assign the set of sections this visual wants to track itself in. + */ + void lightSections(LongSet sections); + } +} diff --git a/common/src/api/java/dev/engine_room/flywheel/api/visual/SmoothLitVisual.java b/common/src/api/java/dev/engine_room/flywheel/api/visual/SmoothLitVisual.java index c22d6dfdf..23b09d197 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/visual/SmoothLitVisual.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/visual/SmoothLitVisual.java @@ -1,11 +1,7 @@ package dev.engine_room.flywheel.api.visual; -import org.jetbrains.annotations.ApiStatus; - -import it.unimi.dsi.fastutil.longs.LongSet; - /** - * An interface allowing visuals to request light data on the GPU for a set of sections. + * A marker interface allowing visuals to request light data on the GPU for a set of sections. * *

Sections passed into {@link SectionProperty#lightSections} will have their light data handed to the * backend and queryable by {@code flw_light*} functions in shaders. @@ -13,21 +9,6 @@ * Note that the queryable light data is shared across all visuals, so even if one specific visual does not * request a given section, the data will be available if another visual does. */ -public interface SmoothLitVisual extends Visual { - /** - * Set the section property object. - * - *

This method is only called once, upon visual creation. - * - * @param property The property. - */ - void setSectionProperty(SectionProperty property); +public non-sealed interface SmoothLitVisual extends SectionTrackedVisual { - @ApiStatus.NonExtendable - interface SectionProperty { - /** - * Assign the set of sections this visual wants to have light data for. - */ - void lightSections(LongSet sections); - } } 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 59722198e..1aab4baa6 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 @@ -1,7 +1,5 @@ package dev.engine_room.flywheel.lib.visual; -import java.util.function.LongConsumer; - import org.jetbrains.annotations.Nullable; import org.joml.FrustumIntersection; @@ -13,6 +11,7 @@ import dev.engine_room.flywheel.api.visualization.VisualizationContext; import dev.engine_room.flywheel.lib.instance.FlatLit; import dev.engine_room.flywheel.lib.math.MoreMath; +import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.client.renderer.LevelRenderer; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; @@ -40,7 +39,7 @@ public abstract class AbstractBlockEntityVisual extends A protected final BlockPos visualPos; protected final BlockState blockState; @Nullable - protected LitVisual.Notifier notifier; + protected SectionProperty lightSections; public AbstractBlockEntityVisual(VisualizationContext ctx, T blockEntity, float partialTick) { super(ctx, blockEntity.getLevel(), partialTick); @@ -51,13 +50,9 @@ public AbstractBlockEntityVisual(VisualizationContext ctx, T blockEntity, float } @Override - public void setLightSectionNotifier(Notifier notifier) { - this.notifier = notifier; - } - - @Override - public void collectLightSections(LongConsumer consumer) { - consumer.accept(SectionPos.asLong(pos)); + public void setSectionProperty(SectionProperty property) { + this.lightSections = property; + lightSections.lightSections(LongSet.of(SectionPos.asLong(pos))); } /** diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LitVisualStorage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LitVisualStorage.java index 3651fe278..c7cf7b916 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LitVisualStorage.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LitVisualStorage.java @@ -31,7 +31,7 @@ public class LitVisualStorage { private final Map visuals2Sections = new WeakHashMap<>(); private final Long2ObjectMap> sections2Visuals = new Long2ObjectOpenHashMap<>(); - private final Queue movedVisuals = new ConcurrentLinkedQueue<>(); + private final Queue movedVisuals = new ConcurrentLinkedQueue<>(); private final LongSet sectionsUpdatedThisFrame = new LongOpenHashSet(); private long updateId = INITIAL_UPDATE_ID; @@ -65,11 +65,11 @@ public Plan plan() { } private void processMoved() { - LitVisual visual; - while ((visual = movedVisuals.poll()) != null) { + MovedVisual moved; + while ((moved = movedVisuals.poll()) != null) { // If the visual isn't there when we try to remove it that means it was deleted before we got to it. - if (remove(visual)) { - add(visual); + if (remove(moved.visual)) { + updateTracking(moved.tracker, moved.visual); } } } @@ -90,24 +90,27 @@ public boolean isEmpty() { return visuals2Sections.isEmpty(); } - public void setNotifierAndAdd(LitVisual visual) { - visual.setLightSectionNotifier(new LitVisualNotifierImpl(visual)); - add(visual); - } - - private void add(LitVisual visual) { - LongSet sections = new LongArraySet(); + public void add(SectionPropertyImpl tracker, LitVisual visual) { + var moved = new MovedVisual(tracker, visual); + tracker.addListener(() -> movedVisuals.add(moved)); - visual.collectLightSections(sections::add); + updateTracking(tracker, visual); + } - // Add the visual to the map even if sections is empty, this way we can distinguish from deleted visuals - visuals2Sections.put(visual, sections); + public void updateTracking(SectionPropertyImpl tracker, LitVisual visual) { + if (tracker.sections.isEmpty()) { + // Add the visual to the map even if sections is empty, this way we can distinguish from deleted visuals + visuals2Sections.put(visual, LongSet.of()); - // Don't bother creating an updater if the visual isn't in any sections. - if (sections.isEmpty()) { + // Don't bother creating an updater if the visual isn't in any sections. return; } + // Create a copy of the array, so we know what section to remove the visual from later. + var sections = new LongArraySet(tracker.sections); + + visuals2Sections.put(visual, sections); + var updater = createUpdater(visual, sections.size()); for (long section : sections) { @@ -198,16 +201,6 @@ record Context(long updateId, float partialTick) { } } - private final class LitVisualNotifierImpl implements LitVisual.Notifier { - private final LitVisual litVisual; - - private LitVisualNotifierImpl(LitVisual litVisual) { - this.litVisual = litVisual; - } - - @Override - public void notifySectionsChanged() { - movedVisuals.add(litVisual); - } - } + private record MovedVisual(SectionPropertyImpl tracker, LitVisual visual) { + } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionPropertyImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionPropertyImpl.java new file mode 100644 index 000000000..88c5bf0d7 --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionPropertyImpl.java @@ -0,0 +1,26 @@ +package dev.engine_room.flywheel.impl.visualization.storage; + +import java.util.ArrayList; +import java.util.List; + +import dev.engine_room.flywheel.api.visual.SmoothLitVisual; +import it.unimi.dsi.fastutil.longs.LongArraySet; +import it.unimi.dsi.fastutil.longs.LongSet; + +public class SectionPropertyImpl implements SmoothLitVisual.SectionProperty { + public final LongSet sections = new LongArraySet(); + + private final List listeners = new ArrayList<>(2); + + @Override + public void lightSections(LongSet sections) { + this.sections.clear(); + this.sections.addAll(sections); + + listeners.forEach(Runnable::run); + } + + public void addListener(Runnable listener) { + listeners.add(listener); + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SmoothLitVisualStorage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SmoothLitVisualStorage.java index 09c66b833..dc6b0fbac 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SmoothLitVisualStorage.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SmoothLitVisualStorage.java @@ -5,13 +5,12 @@ import org.jetbrains.annotations.Nullable; import dev.engine_room.flywheel.api.visual.SmoothLitVisual; -import it.unimi.dsi.fastutil.longs.LongArraySet; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; public class SmoothLitVisualStorage { - private final Map visuals = new Reference2ObjectOpenHashMap<>(); + private final Map visuals = new Reference2ObjectOpenHashMap<>(); @Nullable private LongSet cachedSections; @@ -20,9 +19,13 @@ public boolean sectionsDirty() { return cachedSections == null; } + public void markDirty() { + cachedSections = null; + } + public LongSet sections() { cachedSections = new LongOpenHashSet(); - for (SectionProperty value : visuals.values()) { + for (var value : visuals.values()) { cachedSections.addAll(value.sections); } return cachedSections; @@ -32,25 +35,18 @@ public void remove(SmoothLitVisual smoothLit) { visuals.remove(smoothLit); } - public void add(SmoothLitVisual smoothLit) { - var sections = new SectionProperty(); - visuals.put(smoothLit, sections); - smoothLit.setSectionProperty(sections); + public void add(SectionPropertyImpl tracker, SmoothLitVisual smoothLit) { + visuals.put(smoothLit, tracker); + + tracker.addListener(this::markDirty); + + if (!tracker.sections.isEmpty()) { + markDirty(); + } } public void clear() { visuals.clear(); } - private final class SectionProperty implements SmoothLitVisual.SectionProperty { - private final LongSet sections = new LongArraySet(); - - @Override - public void lightSections(LongSet sections) { - this.sections.clear(); - this.sections.addAll(sections); - - SmoothLitVisualStorage.this.cachedSections = null; - } - } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java index 84505dcb7..3f8fab082 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java @@ -11,6 +11,7 @@ import dev.engine_room.flywheel.api.task.Plan; import dev.engine_room.flywheel.api.visual.DynamicVisual; import dev.engine_room.flywheel.api.visual.LitVisual; +import dev.engine_room.flywheel.api.visual.SectionTrackedVisual; import dev.engine_room.flywheel.api.visual.SmoothLitVisual; import dev.engine_room.flywheel.api.visual.TickableVisual; import dev.engine_room.flywheel.api.visual.Visual; @@ -159,12 +160,19 @@ private void setup(Visual visual) { } } - if (visual instanceof LitVisual lit) { - litVisuals.setNotifierAndAdd(lit); - } + if (visual instanceof SectionTrackedVisual tracked) { + SectionPropertyImpl sectionProperty = new SectionPropertyImpl(); - if (visual instanceof SmoothLitVisual smoothLit) { - smoothLitVisuals.add(smoothLit); + // Give the visual a chance to fill in the property. + tracked.setSectionProperty(sectionProperty); + + if (visual instanceof LitVisual lit) { + litVisuals.add(sectionProperty, lit); + } + + if (visual instanceof SmoothLitVisual smoothLit) { + smoothLitVisuals.add(sectionProperty, smoothLit); + } } } From 8a8258e8b7e5e0be034677dbb21a525158a41ab8 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 14 Jul 2024 17:13:53 -0700 Subject: [PATCH 17/18] The name game - Rename the light visuals to have distinct names - Rename SectionProperty -> SectionCollector because it isn't really a property --- ...LitVisual.java => LightUpdatedVisual.java} | 4 +-- .../api/visual/SectionTrackedVisual.java | 8 +++--- ...hLitVisual.java => ShaderLightVisual.java} | 4 +-- .../flywheel/api/visual/Visual.java | 2 +- .../lib/visual/AbstractBlockEntityVisual.java | 15 ++++++----- ...lStorage.java => LightUpdatedStorage.java} | 24 ++++++++--------- ...rtyImpl.java => SectionCollectorImpl.java} | 6 ++--- ...alStorage.java => ShaderLightStorage.java} | 14 +++++----- .../impl/visualization/storage/Storage.java | 26 +++++++++---------- 9 files changed, 53 insertions(+), 50 deletions(-) rename common/src/api/java/dev/engine_room/flywheel/api/visual/{LitVisual.java => LightUpdatedVisual.java} (89%) rename common/src/api/java/dev/engine_room/flywheel/api/visual/{SmoothLitVisual.java => ShaderLightVisual.java} (69%) rename common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/{LitVisualStorage.java => LightUpdatedStorage.java} (87%) rename common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/{SectionPropertyImpl.java => SectionCollectorImpl.java} (72%) rename common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/{SmoothLitVisualStorage.java => ShaderLightStorage.java} (67%) diff --git a/common/src/api/java/dev/engine_room/flywheel/api/visual/LitVisual.java b/common/src/api/java/dev/engine_room/flywheel/api/visual/LightUpdatedVisual.java similarity index 89% rename from common/src/api/java/dev/engine_room/flywheel/api/visual/LitVisual.java rename to common/src/api/java/dev/engine_room/flywheel/api/visual/LightUpdatedVisual.java index bd01158cd..3ac56d93d 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/visual/LitVisual.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/visual/LightUpdatedVisual.java @@ -5,9 +5,9 @@ * *

If your visual moves around in the level at all, you should use {@link TickableVisual} or {@link DynamicVisual}, * and poll for light yourself along with listening for updates. When your visual moves to a different section, call - * {@link SectionProperty#lightSections}.

+ * {@link SectionCollector#sections}.

*/ -public non-sealed interface LitVisual extends SectionTrackedVisual { +public non-sealed interface LightUpdatedVisual extends SectionTrackedVisual { /** * Called when a section this visual is contained in receives a light update. * diff --git a/common/src/api/java/dev/engine_room/flywheel/api/visual/SectionTrackedVisual.java b/common/src/api/java/dev/engine_room/flywheel/api/visual/SectionTrackedVisual.java index 92bcca70d..725d4a4ed 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/visual/SectionTrackedVisual.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/visual/SectionTrackedVisual.java @@ -4,7 +4,7 @@ import it.unimi.dsi.fastutil.longs.LongSet; -public sealed interface SectionTrackedVisual extends Visual permits SmoothLitVisual, LitVisual { +public sealed interface SectionTrackedVisual extends Visual permits ShaderLightVisual, LightUpdatedVisual { /** * Set the section property object. * @@ -14,13 +14,13 @@ public sealed interface SectionTrackedVisual extends Visual permits SmoothLitVis * * @param property The property. */ - void setSectionProperty(SectionProperty property); + void setSectionCollector(SectionCollector property); @ApiStatus.NonExtendable - interface SectionProperty { + interface SectionCollector { /** * Assign the set of sections this visual wants to track itself in. */ - void lightSections(LongSet sections); + void sections(LongSet sections); } } diff --git a/common/src/api/java/dev/engine_room/flywheel/api/visual/SmoothLitVisual.java b/common/src/api/java/dev/engine_room/flywheel/api/visual/ShaderLightVisual.java similarity index 69% rename from common/src/api/java/dev/engine_room/flywheel/api/visual/SmoothLitVisual.java rename to common/src/api/java/dev/engine_room/flywheel/api/visual/ShaderLightVisual.java index 23b09d197..dcd4ed73d 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/visual/SmoothLitVisual.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/visual/ShaderLightVisual.java @@ -3,12 +3,12 @@ /** * A marker interface allowing visuals to request light data on the GPU for a set of sections. * - *

Sections passed into {@link SectionProperty#lightSections} will have their light data handed to the + *

Sections passed into {@link SectionCollector#sections} will have their light data handed to the * backend and queryable by {@code flw_light*} functions in shaders. *
* Note that the queryable light data is shared across all visuals, so even if one specific visual does not * request a given section, the data will be available if another visual does. */ -public non-sealed interface SmoothLitVisual extends SectionTrackedVisual { +public non-sealed interface ShaderLightVisual extends SectionTrackedVisual { } diff --git a/common/src/api/java/dev/engine_room/flywheel/api/visual/Visual.java b/common/src/api/java/dev/engine_room/flywheel/api/visual/Visual.java index 4aed57f59..60b42fd15 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/visual/Visual.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/visual/Visual.java @@ -5,7 +5,7 @@ * * @see DynamicVisual * @see TickableVisual - * @see LitVisual + * @see LightUpdatedVisual */ public interface Visual { /** 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 1aab4baa6..a3fafb59f 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 @@ -5,7 +5,8 @@ import dev.engine_room.flywheel.api.visual.BlockEntityVisual; import dev.engine_room.flywheel.api.visual.DynamicVisual; -import dev.engine_room.flywheel.api.visual.LitVisual; +import dev.engine_room.flywheel.api.visual.LightUpdatedVisual; +import dev.engine_room.flywheel.api.visual.ShaderLightVisual; import dev.engine_room.flywheel.api.visual.TickableVisual; import dev.engine_room.flywheel.api.visualization.VisualManager; import dev.engine_room.flywheel.api.visualization.VisualizationContext; @@ -25,6 +26,8 @@ *

    *
  • {@link DynamicVisual}
  • *
  • {@link TickableVisual}
  • + *
  • {@link LightUpdatedVisual}
  • + *
  • {@link ShaderLightVisual}
  • *
* See the interfaces' documentation for more information about each one. * @@ -33,13 +36,13 @@ * * @param The type of {@link BlockEntity}. */ -public abstract class AbstractBlockEntityVisual extends AbstractVisual implements BlockEntityVisual, LitVisual { +public abstract class AbstractBlockEntityVisual extends AbstractVisual implements BlockEntityVisual, LightUpdatedVisual { protected final T blockEntity; protected final BlockPos pos; protected final BlockPos visualPos; protected final BlockState blockState; @Nullable - protected SectionProperty lightSections; + protected SectionCollector lightSections; public AbstractBlockEntityVisual(VisualizationContext ctx, T blockEntity, float partialTick) { super(ctx, blockEntity.getLevel(), partialTick); @@ -50,9 +53,9 @@ public AbstractBlockEntityVisual(VisualizationContext ctx, T blockEntity, float } @Override - public void setSectionProperty(SectionProperty property) { - this.lightSections = property; - lightSections.lightSections(LongSet.of(SectionPos.asLong(pos))); + public void setSectionCollector(SectionCollector sectionCollector) { + this.lightSections = sectionCollector; + lightSections.sections(LongSet.of(SectionPos.asLong(pos))); } /** diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LitVisualStorage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LightUpdatedStorage.java similarity index 87% rename from common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LitVisualStorage.java rename to common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LightUpdatedStorage.java index c7cf7b916..d2df21868 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LitVisualStorage.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LightUpdatedStorage.java @@ -10,7 +10,7 @@ import dev.engine_room.flywheel.api.task.Plan; import dev.engine_room.flywheel.api.task.TaskExecutor; import dev.engine_room.flywheel.api.visual.DynamicVisual; -import dev.engine_room.flywheel.api.visual.LitVisual; +import dev.engine_room.flywheel.api.visual.LightUpdatedVisual; import dev.engine_room.flywheel.lib.task.Distribute; import dev.engine_room.flywheel.lib.task.SimplyComposedPlan; import dev.engine_room.flywheel.lib.task.Synchronizer; @@ -24,11 +24,11 @@ /** * Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated. */ -public class LitVisualStorage { +public class LightUpdatedStorage { private static final long NEVER_UPDATED = Long.MIN_VALUE; private static final long INITIAL_UPDATE_ID = NEVER_UPDATED + 1; - private final Map visuals2Sections = new WeakHashMap<>(); + private final Map visuals2Sections = new WeakHashMap<>(); private final Long2ObjectMap> sections2Visuals = new Long2ObjectOpenHashMap<>(); private final Queue movedVisuals = new ConcurrentLinkedQueue<>(); @@ -90,14 +90,14 @@ public boolean isEmpty() { return visuals2Sections.isEmpty(); } - public void add(SectionPropertyImpl tracker, LitVisual visual) { + public void add(SectionCollectorImpl tracker, LightUpdatedVisual visual) { var moved = new MovedVisual(tracker, visual); tracker.addListener(() -> movedVisuals.add(moved)); updateTracking(tracker, visual); } - public void updateTracking(SectionPropertyImpl tracker, LitVisual visual) { + public void updateTracking(SectionCollectorImpl tracker, LightUpdatedVisual visual) { if (tracker.sections.isEmpty()) { // Add the visual to the map even if sections is empty, this way we can distinguish from deleted visuals visuals2Sections.put(visual, LongSet.of()); @@ -129,7 +129,7 @@ public void enqueueLightUpdateSection(long section) { * @param visual The visual to remove. * @return {@code true} if the visual was removed, {@code false} otherwise. */ - public boolean remove(LitVisual visual) { + public boolean remove(LightUpdatedVisual visual) { var sections = visuals2Sections.remove(visual); if (sections == null) { @@ -152,7 +152,7 @@ public void clear() { sectionsUpdatedThisFrame.clear(); } - private static int indexOfUpdater(List listeners, LitVisual visual) { + private static int indexOfUpdater(List listeners, LightUpdatedVisual visual) { for (int i = 0; i < listeners.size(); i++) { if (listeners.get(i) .visual() == visual) { @@ -162,7 +162,7 @@ private static int indexOfUpdater(List listeners, LitVisual visual) { return -1; } - private static Updater createUpdater(LitVisual visual, int sectionCount) { + private static Updater createUpdater(LightUpdatedVisual visual, int sectionCount) { if (sectionCount == 1) { return new Updater.Simple(visual); } else { @@ -174,10 +174,10 @@ private static Updater createUpdater(LitVisual visual, int sectionCount) { sealed interface Updater { void updateLight(Context ctx); - LitVisual visual(); + LightUpdatedVisual visual(); // The visual is only in one section. In this case, we can just update the visual directly. - record Simple(LitVisual visual) implements Updater { + record Simple(LightUpdatedVisual visual) implements Updater { @Override public void updateLight(Context ctx) { visual.updateLight(ctx.partialTick); @@ -186,7 +186,7 @@ public void updateLight(Context ctx) { // The visual is in multiple sections. Here we need to make sure that the visual only gets updated once, // even when multiple sections it was contained in are updated at the same time. - record Synced(LitVisual visual, AtomicLong updateId) implements Updater { + record Synced(LightUpdatedVisual visual, AtomicLong updateId) implements Updater { @Override public void updateLight(Context ctx) { // Different update ID means we won, so we can update the visual. @@ -201,6 +201,6 @@ record Context(long updateId, float partialTick) { } } - private record MovedVisual(SectionPropertyImpl tracker, LitVisual visual) { + private record MovedVisual(SectionCollectorImpl tracker, LightUpdatedVisual visual) { } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionPropertyImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionCollectorImpl.java similarity index 72% rename from common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionPropertyImpl.java rename to common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionCollectorImpl.java index 88c5bf0d7..faa1d7b45 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionPropertyImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionCollectorImpl.java @@ -3,17 +3,17 @@ import java.util.ArrayList; import java.util.List; -import dev.engine_room.flywheel.api.visual.SmoothLitVisual; +import dev.engine_room.flywheel.api.visual.SectionTrackedVisual; import it.unimi.dsi.fastutil.longs.LongArraySet; import it.unimi.dsi.fastutil.longs.LongSet; -public class SectionPropertyImpl implements SmoothLitVisual.SectionProperty { +public class SectionCollectorImpl implements SectionTrackedVisual.SectionCollector { public final LongSet sections = new LongArraySet(); private final List listeners = new ArrayList<>(2); @Override - public void lightSections(LongSet sections) { + public void sections(LongSet sections) { this.sections.clear(); this.sections.addAll(sections); diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SmoothLitVisualStorage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/ShaderLightStorage.java similarity index 67% rename from common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SmoothLitVisualStorage.java rename to common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/ShaderLightStorage.java index dc6b0fbac..c51a55467 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SmoothLitVisualStorage.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/ShaderLightStorage.java @@ -4,13 +4,13 @@ import org.jetbrains.annotations.Nullable; -import dev.engine_room.flywheel.api.visual.SmoothLitVisual; +import dev.engine_room.flywheel.api.visual.ShaderLightVisual; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; -public class SmoothLitVisualStorage { - private final Map visuals = new Reference2ObjectOpenHashMap<>(); +public class ShaderLightStorage { + private final Map visuals = new Reference2ObjectOpenHashMap<>(); @Nullable private LongSet cachedSections; @@ -31,12 +31,12 @@ public LongSet sections() { return cachedSections; } - public void remove(SmoothLitVisual smoothLit) { - visuals.remove(smoothLit); + public void remove(ShaderLightVisual visual) { + visuals.remove(visual); } - public void add(SectionPropertyImpl tracker, SmoothLitVisual smoothLit) { - visuals.put(smoothLit, tracker); + public void add(SectionCollectorImpl tracker, ShaderLightVisual visual) { + visuals.put(visual, tracker); tracker.addListener(this::markDirty); diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java index 3f8fab082..f219e9138 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java @@ -10,9 +10,9 @@ import dev.engine_room.flywheel.api.task.Plan; import dev.engine_room.flywheel.api.visual.DynamicVisual; -import dev.engine_room.flywheel.api.visual.LitVisual; +import dev.engine_room.flywheel.api.visual.LightUpdatedVisual; import dev.engine_room.flywheel.api.visual.SectionTrackedVisual; -import dev.engine_room.flywheel.api.visual.SmoothLitVisual; +import dev.engine_room.flywheel.api.visual.ShaderLightVisual; import dev.engine_room.flywheel.api.visual.TickableVisual; import dev.engine_room.flywheel.api.visual.Visual; import dev.engine_room.flywheel.api.visualization.VisualizationContext; @@ -29,8 +29,8 @@ public abstract class Storage { protected final PlanMap tickableVisuals = new PlanMap<>(); protected final List simpleDynamicVisuals = new ArrayList<>(); protected final List simpleTickableVisuals = new ArrayList<>(); - protected final LitVisualStorage litVisuals = new LitVisualStorage(); - protected final SmoothLitVisualStorage smoothLitVisuals = new SmoothLitVisualStorage(); + protected final LightUpdatedStorage litVisuals = new LightUpdatedStorage(); + protected final ShaderLightStorage smoothLitVisuals = new ShaderLightStorage(); private final Map visuals = new Reference2ObjectOpenHashMap<>(); @@ -71,10 +71,10 @@ public void remove(T obj) { dynamicVisuals.remove(dynamic); } } - if (visual instanceof LitVisual lit) { + if (visual instanceof LightUpdatedVisual lit) { litVisuals.remove(lit); } - if (visual instanceof SmoothLitVisual smoothLit) { + if (visual instanceof ShaderLightVisual smoothLit) { smoothLitVisuals.remove(smoothLit); } visual.delete(); @@ -161,17 +161,17 @@ private void setup(Visual visual) { } if (visual instanceof SectionTrackedVisual tracked) { - SectionPropertyImpl sectionProperty = new SectionPropertyImpl(); + SectionCollectorImpl sectionProperty = new SectionCollectorImpl(); // Give the visual a chance to fill in the property. - tracked.setSectionProperty(sectionProperty); + tracked.setSectionCollector(sectionProperty); - if (visual instanceof LitVisual lit) { - litVisuals.add(sectionProperty, lit); + if (visual instanceof LightUpdatedVisual lightUpdated) { + litVisuals.add(sectionProperty, lightUpdated); } - if (visual instanceof SmoothLitVisual smoothLit) { - smoothLitVisuals.add(sectionProperty, smoothLit); + if (visual instanceof ShaderLightVisual shaderLight) { + smoothLitVisuals.add(sectionProperty, shaderLight); } } } @@ -183,7 +183,7 @@ private void setup(Visual visual) { */ public abstract boolean willAccept(T obj); - public SmoothLitVisualStorage smoothLitStorage() { + public ShaderLightStorage smoothLitStorage() { return smoothLitVisuals; } } From ba1dc58532486f4b5289f5b6c386e66570914628 Mon Sep 17 00:00:00 2001 From: PepperCode1 <44146161+PepperCode1@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:36:52 -0600 Subject: [PATCH 18/18] Light clean up - Ensure section set returned by SectionTracker is Unmodifiable to avoid copy in LightUpdatedVisualStorage - Do not recompute section set in ShaderLightVisualStorage if not dirty - Fix BlockEntityStorage not clearing posLookup on recreation or invalidation - Fix Storage.invalidate not clearing everything - Inline TopLevelEmbeddedEnvironment and NestedEmbeddedEnvironment into AbstractEmbeddedEnvironment and rename to EmbeddedEnvironment - Move some classes between packages - Remove unused fields in EmbeddingUniforms - Remove suffix on field names in BufferBindings - Rename enqueueLightUpdateSection methods to onLightUpdate - Rename SectionCollectorImpl to SectionTracker - Rename classes, methods, fields, and parameters and edit javadoc and comments to match previously done renames, new renames, and other existing classes --- .../flywheel/api/backend/Engine.java | 14 +- .../api/visual/LightUpdatedVisual.java | 5 +- .../api/visual/SectionTrackedVisual.java | 8 +- .../flywheel/api/visual/Visual.java | 1 + .../{engine/embed => }/LightUpdateHolder.java | 13 +- .../component/SsboInstanceComponent.java | 2 +- .../backend/engine/{embed => }/Arena.java | 2 +- .../flywheel/backend/engine/DrawManager.java | 1 - .../flywheel/backend/engine/EngineImpl.java | 30 ++-- .../backend/engine/EnvironmentStorage.java | 19 --- .../backend/engine/{embed => }/LightLut.java | 8 +- .../engine/{embed => }/LightStorage.java | 3 +- ...ironment.java => EmbeddedEnvironment.java} | 81 ++++++----- .../engine/embed/EmbeddingUniforms.java | 14 +- .../backend/engine/embed/Environment.java | 4 +- .../engine/embed/EnvironmentStorage.java | 18 +++ .../engine/embed/GlobalEnvironment.java | 7 +- .../embed/NestedEmbeddedEnvironment.java | 23 ---- .../embed/TopLevelEmbeddedEnvironment.java | 19 --- .../engine/indirect/BufferBindings.java | 16 +-- .../engine/indirect/IndirectBuffers.java | 4 +- .../engine/indirect/IndirectDrawManager.java | 9 +- .../backend/engine/indirect/LightBuffers.java | 11 +- .../instancing/InstancedDrawManager.java | 2 +- .../engine/instancing/InstancedLight.java | 6 +- .../backend/mixin/ClientChunkCacheMixin.java | 2 +- .../internal/indirect/buffer_bindings.glsl | 4 +- .../flywheel/internal/indirect/light.glsl | 4 +- .../impl/mixin/ClientChunkCacheMixin.java | 2 +- .../VisualizationManagerImpl.java | 23 ++-- .../manager/BlockEntityStorage.java | 14 +- .../manager/VisualManagerImpl.java | 23 ++-- ...ge.java => LightUpdatedVisualStorage.java} | 73 +++++----- ...CollectorImpl.java => SectionTracker.java} | 19 ++- .../storage/ShaderLightStorage.java | 52 ------- .../storage/ShaderLightVisualStorage.java | 53 +++++++ .../impl/visualization/storage/Storage.java | 129 +++++++++--------- .../flywheel/vanilla/MinecartVisual.java | 2 +- 38 files changed, 356 insertions(+), 364 deletions(-) rename common/src/backend/java/dev/engine_room/flywheel/backend/{engine/embed => }/LightUpdateHolder.java (84%) rename common/src/backend/java/dev/engine_room/flywheel/backend/engine/{embed => }/Arena.java (95%) delete mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java rename common/src/backend/java/dev/engine_room/flywheel/backend/engine/{embed => }/LightLut.java (96%) rename common/src/backend/java/dev/engine_room/flywheel/backend/engine/{embed => }/LightStorage.java (99%) rename common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/{AbstractEmbeddedEnvironment.java => EmbeddedEnvironment.java} (70%) create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EnvironmentStorage.java delete mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java delete mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java rename common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/{LightUpdatedStorage.java => LightUpdatedVisualStorage.java} (74%) rename common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/{SectionCollectorImpl.java => SectionTracker.java} (58%) delete mode 100644 common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/ShaderLightStorage.java create mode 100644 common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/ShaderLightVisualStorage.java diff --git a/common/src/api/java/dev/engine_room/flywheel/api/backend/Engine.java b/common/src/api/java/dev/engine_room/flywheel/api/backend/Engine.java index 86865e33b..b6b7baa25 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/backend/Engine.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/backend/Engine.java @@ -62,13 +62,6 @@ public interface Engine { */ Vec3i renderOrigin(); - /** - * Free all resources associated with this engine. - *
- * This engine will not be used again after this method is called. - */ - void delete(); - /** * Assign the set of sections that visuals have requested GPU light for. * @@ -78,6 +71,13 @@ public interface Engine { */ void lightSections(LongSet sections); + /** + * Free all resources associated with this engine. + *
+ * This engine will not be used again after this method is called. + */ + void delete(); + /** * A block to be rendered as a crumbling overlay. * @param progress The progress of the crumbling animation in the range [0, 10). diff --git a/common/src/api/java/dev/engine_room/flywheel/api/visual/LightUpdatedVisual.java b/common/src/api/java/dev/engine_room/flywheel/api/visual/LightUpdatedVisual.java index 3ac56d93d..52015c436 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/visual/LightUpdatedVisual.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/visual/LightUpdatedVisual.java @@ -9,7 +9,8 @@ */ public non-sealed interface LightUpdatedVisual extends SectionTrackedVisual { /** - * Called when a section this visual is contained in receives a light update. + * Called when a section this visual is contained in receives a light update. It is not called + * unconditionally after visual creation or {@link SectionCollector#sections}. * *

Even if multiple sections are updated at the same time, this method will only be called once.

* @@ -17,8 +18,6 @@ public non-sealed interface LightUpdatedVisual extends SectionTrackedVisual { * returned by {@link DynamicVisual#planFrame} simultaneously. It is safe to query/update light here, * but you must ensure proper synchronization if you want to mutate anything outside this visual or * anything that is also mutated within {@link DynamicVisual#planFrame}.

- * - *

This method not is invoked automatically after visual creation.

*/ void updateLight(float partialTick); } diff --git a/common/src/api/java/dev/engine_room/flywheel/api/visual/SectionTrackedVisual.java b/common/src/api/java/dev/engine_room/flywheel/api/visual/SectionTrackedVisual.java index 725d4a4ed..a2189357a 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/visual/SectionTrackedVisual.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/visual/SectionTrackedVisual.java @@ -6,15 +6,15 @@ public sealed interface SectionTrackedVisual extends Visual permits ShaderLightVisual, LightUpdatedVisual { /** - * Set the section property object. + * Set the section collector object. * *

This method is only called once, upon visual creation. - *
If the property is assigned to in this method, the + *
If the collector is invoked in this method, the * visual will immediately be tracked in the given sections. * - * @param property The property. + * @param collector The collector. */ - void setSectionCollector(SectionCollector property); + void setSectionCollector(SectionCollector collector); @ApiStatus.NonExtendable interface SectionCollector { diff --git a/common/src/api/java/dev/engine_room/flywheel/api/visual/Visual.java b/common/src/api/java/dev/engine_room/flywheel/api/visual/Visual.java index 60b42fd15..f6d44d1de 100644 --- a/common/src/api/java/dev/engine_room/flywheel/api/visual/Visual.java +++ b/common/src/api/java/dev/engine_room/flywheel/api/visual/Visual.java @@ -6,6 +6,7 @@ * @see DynamicVisual * @see TickableVisual * @see LightUpdatedVisual + * @see ShaderLightVisual */ public interface Visual { /** diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightUpdateHolder.java b/common/src/backend/java/dev/engine_room/flywheel/backend/LightUpdateHolder.java similarity index 84% rename from common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightUpdateHolder.java rename to common/src/backend/java/dev/engine_room/flywheel/backend/LightUpdateHolder.java index 52c5c09f7..e6f9e856d 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightUpdateHolder.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/LightUpdateHolder.java @@ -1,7 +1,8 @@ -package dev.engine_room.flywheel.backend.engine.embed; +package dev.engine_room.flywheel.backend; import dev.engine_room.flywheel.lib.util.LevelAttached; import it.unimi.dsi.fastutil.longs.LongArraySet; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.world.level.LevelAccessor; @@ -11,12 +12,15 @@ public class LightUpdateHolder { private static final LevelAttached HOLDERS = new LevelAttached<>(level -> new LightUpdateHolder()); + private final LongSet updatedSections = new LongOpenHashSet(); + + private LightUpdateHolder() { + } + public static LightUpdateHolder get(LevelAccessor level) { return HOLDERS.get(level); } - private final LongSet updatedSections = new LongArraySet(); - public LongSet getAndClearUpdatedSections() { if (updatedSections.isEmpty()) { return LongSet.of(); @@ -30,7 +34,4 @@ public LongSet getAndClearUpdatedSections() { public void add(long section) { updatedSections.add(section); } - - private LightUpdateHolder() { - } } 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 92fb61099..c8902b2d4 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_BUFFER_BINDING + ") restrict readonly buffer InstanceBuffer {\n" + builder._addRaw("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/embed/Arena.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/Arena.java similarity index 95% rename from common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Arena.java rename to common/src/backend/java/dev/engine_room/flywheel/backend/engine/Arena.java index a43d6cbe5..101cc0013 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Arena.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/Arena.java @@ -1,4 +1,4 @@ -package dev.engine_room.flywheel.backend.engine.embed; +package dev.engine_room.flywheel.backend.engine; import dev.engine_room.flywheel.lib.memory.MemoryBlock; import it.unimi.dsi.fastutil.ints.IntArrayList; 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 8f8def6e0..8b78be928 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 @@ -16,7 +16,6 @@ import dev.engine_room.flywheel.api.model.Model; import dev.engine_room.flywheel.backend.FlwBackend; import dev.engine_room.flywheel.backend.engine.embed.Environment; -import dev.engine_room.flywheel.backend.engine.embed.LightStorage; import dev.engine_room.flywheel.lib.util.Pair; import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java index fa36cd21f..46e66c007 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EngineImpl.java @@ -14,9 +14,9 @@ import dev.engine_room.flywheel.api.task.TaskExecutor; import dev.engine_room.flywheel.api.visualization.VisualEmbedding; import dev.engine_room.flywheel.api.visualization.VisualizationContext; +import dev.engine_room.flywheel.backend.engine.embed.EmbeddedEnvironment; import dev.engine_room.flywheel.backend.engine.embed.Environment; -import dev.engine_room.flywheel.backend.engine.embed.LightStorage; -import dev.engine_room.flywheel.backend.engine.embed.TopLevelEmbeddedEnvironment; +import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; import dev.engine_room.flywheel.backend.gl.GlStateTracker; import dev.engine_room.flywheel.lib.task.Flag; @@ -30,11 +30,11 @@ import net.minecraft.world.phys.Vec3; public class EngineImpl implements Engine { - private final int sqrMaxOriginDistance; private final DrawManager> drawManager; + private final int sqrMaxOriginDistance; + private final Flag flushFlag = new NamedFlag("flushed"); private final EnvironmentStorage environmentStorage; private final LightStorage lightStorage; - private final Flag flushFlag = new NamedFlag("flushed"); private BlockPos renderOrigin = BlockPos.ZERO; @@ -45,6 +45,11 @@ public EngineImpl(LevelAccessor level, DrawManager createFramePlan() { return lightStorage.createFramePlan() @@ -69,11 +74,6 @@ public void renderCrumbling(TaskExecutor executor, RenderContext context, List Instancer instancer(Environment environment, InstanceType type, Model model, RenderStage stage) { @@ -150,7 +150,7 @@ public Vec3i renderOrigin() { @Override public VisualEmbedding createEmbedding() { - var out = new TopLevelEmbeddedEnvironment(EngineImpl.this, stage); + var out = new EmbeddedEnvironment(EngineImpl.this, stage); environmentStorage.track(out); return out; } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java deleted file mode 100644 index e68ea5250..000000000 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/EnvironmentStorage.java +++ /dev/null @@ -1,19 +0,0 @@ -package dev.engine_room.flywheel.backend.engine; - -import dev.engine_room.flywheel.backend.engine.embed.AbstractEmbeddedEnvironment; -import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; -import it.unimi.dsi.fastutil.objects.ReferenceSet; -import it.unimi.dsi.fastutil.objects.ReferenceSets; - -public class EnvironmentStorage { - protected final ReferenceSet environments = ReferenceSets.synchronize(new ReferenceLinkedOpenHashSet<>()); - - public void track(AbstractEmbeddedEnvironment environment) { - environments.add(environment); - } - - public void flush() { - environments.removeIf(AbstractEmbeddedEnvironment::isDeleted); - environments.forEach(AbstractEmbeddedEnvironment::flush); - } -} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightLut.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightLut.java similarity index 96% rename from common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightLut.java rename to common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightLut.java index 736811c5a..bb04c7760 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightLut.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightLut.java @@ -1,4 +1,4 @@ -package dev.engine_room.flywheel.backend.engine.embed; +package dev.engine_room.flywheel.backend.engine; import org.jetbrains.annotations.NotNull; @@ -11,8 +11,8 @@ import it.unimi.dsi.fastutil.objects.ReferenceArrayList; import net.minecraft.core.SectionPos; -public class LightLut { - public static final LongComparator SECTION_X_THEN_Y_THEN_Z = (long a, long b) -> { +public final class LightLut { + private static final LongComparator SECTION_X_THEN_Y_THEN_Z = (long a, long b) -> { final var xComp = Integer.compare(SectionPos.x(a), SectionPos.x(b)); if (xComp != 0) { return xComp; @@ -24,6 +24,8 @@ public class LightLut { return Integer.compare(SectionPos.z(a), SectionPos.z(b)); }; + private LightLut() { + } // Massive kudos to RogueLogix for figuring out this LUT scheme. // TODO: switch to y x z or x z y ordering diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java similarity index 99% rename from common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java rename to common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java index 90f551e9a..909dbdb95 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java @@ -1,4 +1,4 @@ -package dev.engine_room.flywheel.backend.engine.embed; +package dev.engine_room.flywheel.backend.engine; import java.util.BitSet; @@ -7,6 +7,7 @@ import dev.engine_room.flywheel.api.event.RenderContext; import dev.engine_room.flywheel.api.task.Plan; +import dev.engine_room.flywheel.backend.LightUpdateHolder; import dev.engine_room.flywheel.backend.engine.indirect.StagingBuffer; import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer; import dev.engine_room.flywheel.lib.task.SimplePlan; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedEnvironment.java similarity index 70% rename from common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java rename to common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedEnvironment.java index 6bf89450c..fef623172 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedEnvironment.java @@ -1,5 +1,6 @@ package dev.engine_room.flywheel.backend.engine.embed; +import org.jetbrains.annotations.Nullable; import org.joml.Matrix3f; import org.joml.Matrix3fc; import org.joml.Matrix4f; @@ -17,54 +18,60 @@ import dev.engine_room.flywheel.backend.gl.shader.GlProgram; import net.minecraft.core.Vec3i; -public abstract class AbstractEmbeddedEnvironment implements Environment, VisualEmbedding { - protected final Matrix4f pose = new Matrix4f(); - protected final Matrix3f normal = new Matrix3f(); +public class EmbeddedEnvironment implements VisualEmbedding, Environment { + private final EngineImpl engine; + private final RenderStage renderStage; + @Nullable + private final EmbeddedEnvironment parent; + private final InstancerProvider instancerProvider; + + private final Matrix4f pose = new Matrix4f(); + private final Matrix3f normal = new Matrix3f(); private final Matrix4f poseComposed = new Matrix4f(); private final Matrix3f normalComposed = new Matrix3f(); - private final InstancerProvider instancerProvider; - protected final EngineImpl engine; - private final RenderStage renderStage; private boolean deleted = false; - public AbstractEmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) { + public EmbeddedEnvironment(EngineImpl engine, RenderStage renderStage, @Nullable EmbeddedEnvironment parent) { this.engine = engine; this.renderStage = renderStage; + this.parent = parent; instancerProvider = new InstancerProvider() { @Override public Instancer instancer(InstanceType type, Model model) { // Kinda cursed usage of anonymous classes here, but it does the job. - return engine.instancer(AbstractEmbeddedEnvironment.this, type, model, renderStage); + return engine.instancer(EmbeddedEnvironment.this, type, model, renderStage); } }; } + public EmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) { + this(engine, renderStage, null); + } + @Override public void transforms(Matrix4fc pose, Matrix3fc normal) { this.pose.set(pose); this.normal.set(normal); } - public void flush() { - poseComposed.identity(); - normalComposed.identity(); - - composeMatrices(poseComposed, normalComposed); + @Override + public InstancerProvider instancerProvider() { + return instancerProvider; } @Override - public void setupDraw(GlProgram program) { - program.setMat4(EmbeddingUniforms.MODEL_MATRIX, poseComposed); - program.setMat3(EmbeddingUniforms.NORMAL_MATRIX, normalComposed); + public Vec3i renderOrigin() { + return Vec3i.ZERO; } @Override - public void setupCull(GlProgram program) { - program.setBool(EmbeddingUniforms.USE_MODEL_MATRIX, true); - - program.setMat4(EmbeddingUniforms.MODEL_MATRIX1, poseComposed); + public VisualEmbedding createEmbedding() { + var out = new EmbeddedEnvironment(engine, renderStage, this); + engine.environmentStorage() + .track(out); + return out; } @Override @@ -73,21 +80,33 @@ public ContextShader contextShader() { } @Override - public InstancerProvider instancerProvider() { - return instancerProvider; + public void setupCull(GlProgram program) { + program.setBool(EmbeddingUniforms.USE_MODEL_MATRIX, true); + program.setMat4(EmbeddingUniforms.MODEL_MATRIX, poseComposed); } @Override - public Vec3i renderOrigin() { - return Vec3i.ZERO; + public void setupDraw(GlProgram program) { + program.setMat4(EmbeddingUniforms.MODEL_MATRIX, poseComposed); + program.setMat3(EmbeddingUniforms.NORMAL_MATRIX, normalComposed); } - @Override - public VisualEmbedding createEmbedding() { - var out = new NestedEmbeddedEnvironment(this, engine, renderStage); - engine.environmentStorage() - .track(out); - return out; + public void flush() { + poseComposed.identity(); + normalComposed.identity(); + + composeMatrices(poseComposed, normalComposed); + } + + private void composeMatrices(Matrix4f pose, Matrix3f normal) { + if (parent != null) { + parent.composeMatrices(pose, normal); + pose.mul(this.pose); + normal.mul(this.normal); + } else { + pose.set(this.pose); + normal.set(this.normal); + } } public boolean isDeleted() { @@ -101,6 +120,4 @@ public boolean isDeleted() { public void delete() { deleted = true; } - - public abstract void composeMatrices(Matrix4f pose, Matrix3f normal); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddingUniforms.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddingUniforms.java index eb0c58bef..e62f0b018 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddingUniforms.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddingUniforms.java @@ -1,11 +1,13 @@ package dev.engine_room.flywheel.backend.engine.embed; -public class EmbeddingUniforms { +public final class EmbeddingUniforms { + /** + * Only used by cull shaders. + */ + public static final String USE_MODEL_MATRIX = "_flw_useModelMatrix"; public static final String MODEL_MATRIX = "_flw_modelMatrix"; public static final String NORMAL_MATRIX = "_flw_normalMatrix"; - public static final String USE_MODEL_MATRIX = "_flw_useModelMatrix"; - public static final String MODEL_MATRIX1 = "_flw_modelMatrix"; - public static final String ONE_OVER_LIGHT_BOX_SIZE = "_flw_oneOverLightBoxSize"; - public static final String LIGHT_VOLUME_MIN = "_flw_lightVolumeMin"; - public static final String USE_LIGHT_VOLUME = "_flw_useLightVolume"; + + private EmbeddingUniforms() { + } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Environment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Environment.java index 626f103e5..83fa60d62 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Environment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Environment.java @@ -6,7 +6,7 @@ public interface Environment { ContextShader contextShader(); - void setupDraw(GlProgram drawProgram); - void setupCull(GlProgram cullProgram); + + void setupDraw(GlProgram drawProgram); } 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 new file mode 100644 index 000000000..9206b9cf3 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EnvironmentStorage.java @@ -0,0 +1,18 @@ +package dev.engine_room.flywheel.backend.engine.embed; + +import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; +import it.unimi.dsi.fastutil.objects.ReferenceSet; +import it.unimi.dsi.fastutil.objects.ReferenceSets; + +public class EnvironmentStorage { + protected final ReferenceSet environments = ReferenceSets.synchronize(new ReferenceLinkedOpenHashSet<>()); + + public void track(EmbeddedEnvironment environment) { + environments.add(environment); + } + + public void flush() { + environments.removeIf(EmbeddedEnvironment::isDeleted); + environments.forEach(EmbeddedEnvironment::flush); + } +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/GlobalEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/GlobalEnvironment.java index c6e50acbd..aaac1ca35 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/GlobalEnvironment.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/GlobalEnvironment.java @@ -15,12 +15,11 @@ public ContextShader contextShader() { } @Override - public void setupDraw(GlProgram drawProgram) { - + public void setupCull(GlProgram cullProgram) { + cullProgram.setBool(EmbeddingUniforms.USE_MODEL_MATRIX, false); } @Override - public void setupCull(GlProgram cullProgram) { - cullProgram.setBool(EmbeddingUniforms.USE_MODEL_MATRIX, false); + public void setupDraw(GlProgram drawProgram) { } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java deleted file mode 100644 index fb4bfbb49..000000000 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java +++ /dev/null @@ -1,23 +0,0 @@ -package dev.engine_room.flywheel.backend.engine.embed; - -import org.joml.Matrix3f; -import org.joml.Matrix4f; - -import dev.engine_room.flywheel.api.event.RenderStage; -import dev.engine_room.flywheel.backend.engine.EngineImpl; - -public class NestedEmbeddedEnvironment extends AbstractEmbeddedEnvironment { - private final AbstractEmbeddedEnvironment parent; - - public NestedEmbeddedEnvironment(AbstractEmbeddedEnvironment parent, EngineImpl engine, RenderStage renderStage) { - super(engine, renderStage); - this.parent = parent; - } - - @Override - public void composeMatrices(Matrix4f pose, Matrix3f normal) { - parent.composeMatrices(pose, normal); - pose.mul(this.pose); - normal.mul(this.normal); - } -} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java deleted file mode 100644 index 297c3dd6b..000000000 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java +++ /dev/null @@ -1,19 +0,0 @@ -package dev.engine_room.flywheel.backend.engine.embed; - -import org.joml.Matrix3f; -import org.joml.Matrix4f; - -import dev.engine_room.flywheel.api.event.RenderStage; -import dev.engine_room.flywheel.backend.engine.EngineImpl; - -public class TopLevelEmbeddedEnvironment extends AbstractEmbeddedEnvironment { - public TopLevelEmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) { - super(engine, renderStage); - } - - @Override - public void composeMatrices(Matrix4f pose, Matrix3f normal) { - pose.set(this.pose); - normal.set(this.normal); - } -} 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 97970f3b5..a0ae93a28 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,13 +1,13 @@ package dev.engine_room.flywheel.backend.engine.indirect; -public class BufferBindings { - public static final int INSTANCE_BUFFER_BINDING = 0; - public static final int TARGET_BUFFER_BINDING = 1; - public static final int MODEL_INDEX_BUFFER_BINDING = 2; - public static final int MODEL_BUFFER_BINDING = 3; - public static final int DRAW_BUFFER_BINDING = 4; - public static final int LIGHT_LUT_BINDING = 5; - public static final int LIGHT_SECTION_BINDING = 6; +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 MODEL = 3; + public static final int DRAW = 4; + public static final int LIGHT_LUT = 5; + public static final int LIGHT_SECTION = 6; private BufferBindings() { } 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 68034d16a..90ecd4149 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 @@ -110,7 +110,7 @@ public void bindForDraw() { private void multiBind() { final long ptr = multiBindBlock.ptr(); - nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE_BUFFER_BINDING, IndirectBuffers.BUFFER_COUNT, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET); + nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE, IndirectBuffers.BUFFER_COUNT, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET); } /** @@ -118,7 +118,7 @@ private void multiBind() { */ public void bindForCrumbling() { final long ptr = multiBindBlock.ptr(); - nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE_BUFFER_BINDING, 4, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET); + nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE, 4, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET); } 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 1aadae5a3..d9ef2254a 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 @@ -20,10 +20,10 @@ import dev.engine_room.flywheel.backend.engine.DrawManager; import dev.engine_room.flywheel.backend.engine.GroupKey; import dev.engine_room.flywheel.backend.engine.InstancerKey; +import dev.engine_room.flywheel.backend.engine.LightStorage; import dev.engine_room.flywheel.backend.engine.MaterialRenderState; import dev.engine_room.flywheel.backend.engine.MeshPool; import dev.engine_room.flywheel.backend.engine.TextureBinder; -import dev.engine_room.flywheel.backend.engine.embed.LightStorage; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; import dev.engine_room.flywheel.backend.gl.GlStateTracker; import dev.engine_room.flywheel.backend.gl.array.GlVertexArray; @@ -40,18 +40,15 @@ public class IndirectDrawManager extends DrawManager> { private final GlVertexArray vertexArray; private final Map, IndirectCullingGroup> cullingGroups = new HashMap<>(); private final GlBuffer crumblingDrawBuffer = new GlBuffer(); - private final LightBuffers lightBuffers; public IndirectDrawManager(IndirectPrograms programs) { this.programs = programs; programs.acquire(); - stagingBuffer = new StagingBuffer(this.programs); + stagingBuffer = new StagingBuffer(this.programs); meshPool = new MeshPool(); - vertexArray = GlVertexArray.create(); - meshPool.bind(vertexArray); lightBuffers = new LightBuffers(); } @@ -170,7 +167,7 @@ public void renderCrumbling(List crumblingBlocks) { var block = MemoryBlock.malloc(IndirectBuffers.DRAW_COMMAND_STRIDE); GlBufferType.DRAW_INDIRECT_BUFFER.bind(crumblingDrawBuffer.handle()); - glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.DRAW_BUFFER_BINDING, crumblingDrawBuffer.handle(), 0, IndirectBuffers.DRAW_COMMAND_STRIDE); + glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.DRAW, crumblingDrawBuffer.handle(), 0, IndirectBuffers.DRAW_COMMAND_STRIDE); for (var groupEntry : byType.entrySet()) { var byProgress = groupEntry.getValue(); diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java index 2423ae0fb..9db63afb0 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java @@ -3,14 +3,11 @@ import org.lwjgl.opengl.GL46; import org.lwjgl.system.MemoryUtil; -import dev.engine_room.flywheel.backend.engine.embed.LightStorage; +import dev.engine_room.flywheel.backend.engine.LightStorage; public class LightBuffers { - private final ResizableStorageArray sections = new ResizableStorageArray(LightStorage.SECTION_SIZE_BYTES); private final ResizableStorageArray lut = new ResizableStorageArray(4); - - public LightBuffers() { - } + private final ResizableStorageArray sections = new ResizableStorageArray(LightStorage.SECTION_SIZE_BYTES); public void flush(StagingBuffer staging, LightStorage light) { var capacity = light.capacity(); @@ -40,7 +37,7 @@ public void bind() { return; } - GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.LIGHT_LUT_BINDING, lut.handle(), 0, lut.byteCapacity()); - GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.LIGHT_SECTION_BINDING, sections.handle(), 0, sections.byteCapacity()); + GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.LIGHT_LUT, lut.handle(), 0, lut.byteCapacity()); + GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.LIGHT_SECTION, sections.handle(), 0, sections.byteCapacity()); } } 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 6ea923cc1..ae231de0b 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 @@ -18,11 +18,11 @@ import dev.engine_room.flywheel.backend.engine.DrawManager; import dev.engine_room.flywheel.backend.engine.GroupKey; import dev.engine_room.flywheel.backend.engine.InstancerKey; +import dev.engine_room.flywheel.backend.engine.LightStorage; import dev.engine_room.flywheel.backend.engine.MaterialEncoder; import dev.engine_room.flywheel.backend.engine.MaterialRenderState; import dev.engine_room.flywheel.backend.engine.MeshPool; import dev.engine_room.flywheel.backend.engine.TextureBinder; -import dev.engine_room.flywheel.backend.engine.embed.LightStorage; import dev.engine_room.flywheel.backend.engine.uniform.Uniforms; import dev.engine_room.flywheel.backend.gl.GlStateTracker; import dev.engine_room.flywheel.backend.gl.TextureBuffer; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedLight.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedLight.java index 3d966f0c8..7831019d1 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedLight.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedLight.java @@ -4,7 +4,7 @@ import org.lwjgl.system.MemoryUtil; import dev.engine_room.flywheel.backend.Samplers; -import dev.engine_room.flywheel.backend.engine.embed.LightStorage; +import dev.engine_room.flywheel.backend.engine.LightStorage; import dev.engine_room.flywheel.backend.gl.TextureBuffer; import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer; import dev.engine_room.flywheel.lib.memory.MemoryBlock; @@ -16,8 +16,8 @@ public class InstancedLight { private final TextureBuffer sectionsTexture; public InstancedLight() { - sections = new GlBuffer(); lut = new GlBuffer(); + sections = new GlBuffer(); lutTexture = new TextureBuffer(GL32.GL_R32UI); sectionsTexture = new TextureBuffer(GL32.GL_R32UI); } @@ -54,8 +54,8 @@ public void flush(LightStorage light) { } public void delete() { - sections.delete(); lut.delete(); + sections.delete(); lutTexture.delete(); sectionsTexture.delete(); } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/ClientChunkCacheMixin.java b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/ClientChunkCacheMixin.java index 11ea0cea2..eb8c974ec 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/ClientChunkCacheMixin.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/ClientChunkCacheMixin.java @@ -7,7 +7,7 @@ import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import dev.engine_room.flywheel.backend.engine.embed.LightUpdateHolder; +import dev.engine_room.flywheel.backend.LightUpdateHolder; import net.minecraft.client.multiplayer.ClientChunkCache; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.core.SectionPos; 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 604da5269..c37db3502 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 @@ -3,5 +3,5 @@ #define _FLW_MODEL_INDEX_BUFFER_BINDING 2 #define _FLW_MODEL_BUFFER_BINDING 3 #define _FLW_DRAW_BUFFER_BINDING 4 -#define _FLW_LIGHT_LUT_BINDING 5 -#define _FLW_LIGHT_SECTIONS_BINDING 6 +#define _FLW_LIGHT_LUT_BUFFER_BINDING 5 +#define _FLW_LIGHT_SECTIONS_BUFFER_BINDING 6 diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/light.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/light.glsl index bd688ef28..48975050e 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/light.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/light.glsl @@ -1,10 +1,10 @@ #include "flywheel:internal/light_lut.glsl" -layout(std430, binding = _FLW_LIGHT_LUT_BINDING) restrict readonly buffer LightLut { +layout(std430, binding = _FLW_LIGHT_LUT_BUFFER_BINDING) restrict readonly buffer LightLut { uint _flw_lightLut[]; }; -layout(std430, binding = _FLW_LIGHT_SECTIONS_BINDING) restrict readonly buffer LightSections { +layout(std430, binding = _FLW_LIGHT_SECTIONS_BUFFER_BINDING) restrict readonly buffer LightSections { uint _flw_lightSections[]; }; diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ClientChunkCacheMixin.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ClientChunkCacheMixin.java index d12415179..f55d6d2e9 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ClientChunkCacheMixin.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ClientChunkCacheMixin.java @@ -24,7 +24,7 @@ abstract class ClientChunkCacheMixin { var manager = VisualizationManagerImpl.get(level); if (manager != null) { - manager.enqueueLightUpdateSection(pos.asLong()); + manager.onLightUpdate(pos.asLong()); } } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java index efd5c6820..ccf69cc0e 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/VisualizationManagerImpl.java @@ -106,11 +106,11 @@ private VisualizationManagerImpl(LevelAccessor level) { .ifFalse(update) .plan() .then(SimplePlan.of(() -> { - if (blockEntities.lightSectionsDirty() || entities.lightSectionsDirty() || effects.lightSectionsDirty()) { + if (blockEntities.areGpuLightSectionsDirty() || entities.areGpuLightSectionsDirty() || effects.areGpuLightSectionsDirty()) { var out = new LongOpenHashSet(); - out.addAll(blockEntities.lightSections()); - out.addAll(entities.lightSections()); - out.addAll(effects.lightSections()); + out.addAll(blockEntities.gpuLightSections()); + out.addAll(entities.gpuLightSections()); + out.addAll(effects.gpuLightSections()); engine.lightSections(out); } })) @@ -302,6 +302,12 @@ public void renderCrumbling(RenderContext context, Long2ObjectMap createRaw(BlockEntity obj, float partialTick) { @Override public void remove(BlockEntity obj) { - super.remove(obj); posLookup.remove(obj.getBlockPos() .asLong()); + super.remove(obj); + } + + @Override + public void recreateAll(float partialTick) { + posLookup.clear(); + super.recreateAll(partialTick); + } + + @Override + public void invalidate() { + posLookup.clear(); + super.invalidate(); } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/manager/VisualManagerImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/manager/VisualManagerImpl.java index e8c0b88c9..f0d3420bb 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/manager/VisualManagerImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/manager/VisualManagerImpl.java @@ -54,10 +54,6 @@ public void queueUpdate(T obj) { queue.add(Transaction.update(obj)); } - public void invalidate() { - getStorage().invalidate(); - } - public void processQueue(float partialTick) { var storage = getStorage(); Transaction transaction; @@ -76,13 +72,22 @@ public Plan tickPlan() { .then(storage.tickPlan()); } - public boolean lightSectionsDirty() { - return getStorage().smoothLitStorage() - .sectionsDirty(); + public void onLightUpdate(long section) { + getStorage().lightUpdatedVisuals() + .onLightUpdate(section); + } + + public boolean areGpuLightSectionsDirty() { + return getStorage().shaderLightVisuals() + .isDirty(); } - public LongSet lightSections() { - return getStorage().smoothLitStorage() + public LongSet gpuLightSections() { + return getStorage().shaderLightVisuals() .sections(); } + + public void invalidate() { + getStorage().invalidate(); + } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LightUpdatedStorage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LightUpdatedVisualStorage.java similarity index 74% rename from common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LightUpdatedStorage.java rename to common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LightUpdatedVisualStorage.java index d2df21868..3f730abc0 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LightUpdatedStorage.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LightUpdatedVisualStorage.java @@ -16,7 +16,6 @@ import dev.engine_room.flywheel.lib.task.Synchronizer; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongArraySet; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.objects.ObjectArrayList; @@ -24,15 +23,15 @@ /** * Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated. */ -public class LightUpdatedStorage { +public class LightUpdatedVisualStorage { private static final long NEVER_UPDATED = Long.MIN_VALUE; private static final long INITIAL_UPDATE_ID = NEVER_UPDATED + 1; - private final Map visuals2Sections = new WeakHashMap<>(); - private final Long2ObjectMap> sections2Visuals = new Long2ObjectOpenHashMap<>(); + private final Map visual2Sections = new WeakHashMap<>(); + private final Long2ObjectMap> section2Updaters = new Long2ObjectOpenHashMap<>(); - private final Queue movedVisuals = new ConcurrentLinkedQueue<>(); private final LongSet sectionsUpdatedThisFrame = new LongOpenHashSet(); + private final Queue movedVisuals = new ConcurrentLinkedQueue<>(); private long updateId = INITIAL_UPDATE_ID; @@ -54,9 +53,9 @@ public Plan plan() { Updater.Context updaterContext = new Updater.Context(updateId, context.partialTick()); for (long section : sectionsUpdatedThisFrame) { - var visuals = sections2Visuals.get(section); - if (visuals != null && !visuals.isEmpty()) { - taskExecutor.execute(() -> Distribute.tasks(taskExecutor, updaterContext, sync, visuals, Updater::updateLight)); + var updaters = section2Updaters.get(section); + if (updaters != null && !updaters.isEmpty()) { + taskExecutor.execute(() -> Distribute.tasks(taskExecutor, updaterContext, sync, updaters, Updater::updateLight)); } else { sync.decrementAndEventuallyRun(); } @@ -69,7 +68,7 @@ private void processMoved() { while ((moved = movedVisuals.poll()) != null) { // If the visual isn't there when we try to remove it that means it was deleted before we got to it. if (remove(moved.visual)) { - updateTracking(moved.tracker, moved.visual); + addInner(moved.visual, moved.tracker); } } } @@ -86,43 +85,32 @@ private long getNextUpdateId() { return out; } - public boolean isEmpty() { - return visuals2Sections.isEmpty(); - } - - public void add(SectionCollectorImpl tracker, LightUpdatedVisual visual) { - var moved = new MovedVisual(tracker, visual); + public void add(LightUpdatedVisual visual, SectionTracker tracker) { + var moved = new MovedVisual(visual, tracker); tracker.addListener(() -> movedVisuals.add(moved)); - updateTracking(tracker, visual); + addInner(visual, tracker); } - public void updateTracking(SectionCollectorImpl tracker, LightUpdatedVisual visual) { - if (tracker.sections.isEmpty()) { + private void addInner(LightUpdatedVisual visual, SectionTracker tracker) { + if (tracker.sections().isEmpty()) { // Add the visual to the map even if sections is empty, this way we can distinguish from deleted visuals - visuals2Sections.put(visual, LongSet.of()); + visual2Sections.put(visual, LongSet.of()); // Don't bother creating an updater if the visual isn't in any sections. return; } - // Create a copy of the array, so we know what section to remove the visual from later. - var sections = new LongArraySet(tracker.sections); - - visuals2Sections.put(visual, sections); - + var sections = tracker.sections(); + visual2Sections.put(visual, sections); var updater = createUpdater(visual, sections.size()); for (long section : sections) { - sections2Visuals.computeIfAbsent(section, $ -> new ObjectArrayList<>()) + section2Updaters.computeIfAbsent(section, $ -> new ObjectArrayList<>()) .add(updater); } } - public void enqueueLightUpdateSection(long section) { - sectionsUpdatedThisFrame.add(section); - } - /** * Remove the visual from this storage. * @@ -130,31 +118,36 @@ public void enqueueLightUpdateSection(long section) { * @return {@code true} if the visual was removed, {@code false} otherwise. */ public boolean remove(LightUpdatedVisual visual) { - var sections = visuals2Sections.remove(visual); + var sections = visual2Sections.remove(visual); if (sections == null) { return false; } for (long section : sections) { - List listeners = sections2Visuals.get(section); - if (listeners != null) { - listeners.remove(indexOfUpdater(listeners, visual)); + List updaters = section2Updaters.get(section); + if (updaters != null) { + updaters.remove(indexOfUpdater(updaters, visual)); } } return true; } + public void onLightUpdate(long section) { + sectionsUpdatedThisFrame.add(section); + } + public void clear() { - visuals2Sections.clear(); - sections2Visuals.clear(); + visual2Sections.clear(); + section2Updaters.clear(); sectionsUpdatedThisFrame.clear(); + movedVisuals.clear(); } - private static int indexOfUpdater(List listeners, LightUpdatedVisual visual) { - for (int i = 0; i < listeners.size(); i++) { - if (listeners.get(i) + private static int indexOfUpdater(List updaters, LightUpdatedVisual visual) { + for (int i = 0; i < updaters.size(); i++) { + if (updaters.get(i) .visual() == visual) { return i; } @@ -171,7 +164,7 @@ private static Updater createUpdater(LightUpdatedVisual visual, int sectionCount } // Breaking this into 2 separate cases allows us to avoid the overhead of atomics in the common case. - sealed interface Updater { + private sealed interface Updater { void updateLight(Context ctx); LightUpdatedVisual visual(); @@ -201,6 +194,6 @@ record Context(long updateId, float partialTick) { } } - private record MovedVisual(SectionCollectorImpl tracker, LightUpdatedVisual visual) { + private record MovedVisual(LightUpdatedVisual visual, SectionTracker tracker) { } } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionCollectorImpl.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionTracker.java similarity index 58% rename from common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionCollectorImpl.java rename to common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionTracker.java index faa1d7b45..9cba32f69 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionCollectorImpl.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionTracker.java @@ -3,20 +3,27 @@ import java.util.ArrayList; import java.util.List; +import org.jetbrains.annotations.Unmodifiable; + import dev.engine_room.flywheel.api.visual.SectionTrackedVisual; import it.unimi.dsi.fastutil.longs.LongArraySet; import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.longs.LongSets; -public class SectionCollectorImpl implements SectionTrackedVisual.SectionCollector { - public final LongSet sections = new LongArraySet(); - +public class SectionTracker implements SectionTrackedVisual.SectionCollector { private final List listeners = new ArrayList<>(2); + @Unmodifiable + private LongSet sections = LongSet.of(); + + @Unmodifiable + public LongSet sections() { + return sections; + } + @Override public void sections(LongSet sections) { - this.sections.clear(); - this.sections.addAll(sections); - + this.sections = LongSets.unmodifiable(new LongArraySet(sections)); listeners.forEach(Runnable::run); } diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/ShaderLightStorage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/ShaderLightStorage.java deleted file mode 100644 index c51a55467..000000000 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/ShaderLightStorage.java +++ /dev/null @@ -1,52 +0,0 @@ -package dev.engine_room.flywheel.impl.visualization.storage; - -import java.util.Map; - -import org.jetbrains.annotations.Nullable; - -import dev.engine_room.flywheel.api.visual.ShaderLightVisual; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.longs.LongSet; -import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; - -public class ShaderLightStorage { - private final Map visuals = new Reference2ObjectOpenHashMap<>(); - - @Nullable - private LongSet cachedSections; - - public boolean sectionsDirty() { - return cachedSections == null; - } - - public void markDirty() { - cachedSections = null; - } - - public LongSet sections() { - cachedSections = new LongOpenHashSet(); - for (var value : visuals.values()) { - cachedSections.addAll(value.sections); - } - return cachedSections; - } - - public void remove(ShaderLightVisual visual) { - visuals.remove(visual); - } - - public void add(SectionCollectorImpl tracker, ShaderLightVisual visual) { - visuals.put(visual, tracker); - - tracker.addListener(this::markDirty); - - if (!tracker.sections.isEmpty()) { - markDirty(); - } - } - - public void clear() { - visuals.clear(); - } - -} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/ShaderLightVisualStorage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/ShaderLightVisualStorage.java new file mode 100644 index 000000000..052a9e06c --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/ShaderLightVisualStorage.java @@ -0,0 +1,53 @@ +package dev.engine_room.flywheel.impl.visualization.storage; + +import java.util.Map; + +import dev.engine_room.flywheel.api.visual.ShaderLightVisual; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; + +public class ShaderLightVisualStorage { + private final Map trackers = new Reference2ReferenceOpenHashMap<>(); + + private final LongSet sections = new LongOpenHashSet(); + private boolean isDirty; + + public LongSet sections() { + if (isDirty) { + sections.clear(); + for (var tracker : trackers.values()) { + sections.addAll(tracker.sections()); + } + isDirty = false; + } + return sections; + } + + public boolean isDirty() { + return isDirty; + } + + public void markDirty() { + isDirty = true; + } + + public void add(ShaderLightVisual visual, SectionTracker tracker) { + trackers.put(visual, tracker); + + tracker.addListener(this::markDirty); + + if (!tracker.sections().isEmpty()) { + markDirty(); + } + } + + public void remove(ShaderLightVisual visual) { + trackers.remove(visual); + } + + public void clear() { + trackers.clear(); + markDirty(); + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java index f219e9138..4e2d1ebaf 100644 --- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java +++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/Storage.java @@ -25,14 +25,14 @@ public abstract class Storage { protected final Supplier visualizationContextSupplier; + + private final Map visuals = new Reference2ObjectOpenHashMap<>(); protected final PlanMap dynamicVisuals = new PlanMap<>(); protected final PlanMap tickableVisuals = new PlanMap<>(); protected final List simpleDynamicVisuals = new ArrayList<>(); protected final List simpleTickableVisuals = new ArrayList<>(); - protected final LightUpdatedStorage litVisuals = new LightUpdatedStorage(); - protected final ShaderLightStorage smoothLitVisuals = new ShaderLightStorage(); - - private final Map visuals = new Reference2ObjectOpenHashMap<>(); + protected final LightUpdatedVisualStorage lightUpdatedVisuals = new LightUpdatedVisualStorage(); + protected final ShaderLightVisualStorage shaderLightVisuals = new ShaderLightVisualStorage(); public Storage(Supplier visualizationContextSupplier) { this.visualizationContextSupplier = visualizationContextSupplier; @@ -42,6 +42,29 @@ public Collection getAllVisuals() { return visuals.values(); } + public Plan framePlan() { + return NestedPlan.of(dynamicVisuals, lightUpdatedVisuals.plan(), ForEachPlan.of(() -> simpleDynamicVisuals, SimpleDynamicVisual::beginFrame)); + } + + public Plan tickPlan() { + return NestedPlan.of(tickableVisuals, ForEachPlan.of(() -> simpleTickableVisuals, SimpleTickableVisual::tick)); + } + + public LightUpdatedVisualStorage lightUpdatedVisuals() { + return lightUpdatedVisuals; + } + + public ShaderLightVisualStorage shaderLightVisuals() { + return shaderLightVisuals; + } + + /** + * Is the given object currently capable of being added? + * + * @return true if the object is currently capable of being visualized. + */ + public abstract boolean willAccept(T obj); + public void add(T obj, float partialTick) { Visual visual = visuals.get(obj); @@ -57,13 +80,6 @@ public void remove(T obj) { return; } - if (visual instanceof TickableVisual tickable) { - if (visual instanceof SimpleTickableVisual simpleTickable) { - simpleTickableVisuals.remove(simpleTickable); - } else { - tickableVisuals.remove(tickable); - } - } if (visual instanceof DynamicVisual dynamic) { if (visual instanceof SimpleDynamicVisual simpleDynamic) { simpleDynamicVisuals.remove(simpleDynamic); @@ -71,12 +87,20 @@ public void remove(T obj) { dynamicVisuals.remove(dynamic); } } - if (visual instanceof LightUpdatedVisual lit) { - litVisuals.remove(lit); + if (visual instanceof TickableVisual tickable) { + if (visual instanceof SimpleTickableVisual simpleTickable) { + simpleTickableVisuals.remove(simpleTickable); + } else { + tickableVisuals.remove(tickable); + } + } + if (visual instanceof LightUpdatedVisual lightUpdated) { + lightUpdatedVisuals.remove(lightUpdated); } - if (visual instanceof ShaderLightVisual smoothLit) { - smoothLitVisuals.remove(smoothLit); + if (visual instanceof ShaderLightVisual shaderLight) { + shaderLightVisuals.remove(shaderLight); } + visual.delete(); } @@ -91,12 +115,13 @@ public void update(T obj, float partialTick) { } public void recreateAll(float partialTick) { - tickableVisuals.clear(); dynamicVisuals.clear(); - simpleTickableVisuals.clear(); + tickableVisuals.clear(); simpleDynamicVisuals.clear(); - litVisuals.clear(); - smoothLitVisuals.clear(); + simpleTickableVisuals.clear(); + lightUpdatedVisuals.clear(); + shaderLightVisuals.clear(); + visuals.replaceAll((obj, visual) -> { visual.delete(); @@ -110,15 +135,6 @@ public void recreateAll(float partialTick) { }); } - public void invalidate() { - tickableVisuals.clear(); - dynamicVisuals.clear(); - litVisuals.clear(); - visuals.values() - .forEach(Visual::delete); - visuals.clear(); - } - private void create(T obj, float partialTick) { var visual = createRaw(obj, partialTick); @@ -131,27 +147,7 @@ private void create(T obj, float partialTick) { @Nullable protected abstract Visual createRaw(T obj, float partialTick); - public Plan framePlan() { - return NestedPlan.of(dynamicVisuals, litVisuals.plan(), ForEachPlan.of(() -> simpleDynamicVisuals, SimpleDynamicVisual::beginFrame)); - } - - public Plan tickPlan() { - return NestedPlan.of(tickableVisuals, ForEachPlan.of(() -> simpleTickableVisuals, SimpleTickableVisual::tick)); - } - - public void enqueueLightUpdateSection(long section) { - litVisuals.enqueueLightUpdateSection(section); - } - private void setup(Visual visual) { - if (visual instanceof TickableVisual tickable) { - if (visual instanceof SimpleTickableVisual simpleTickable) { - simpleTickableVisuals.add(simpleTickable); - } else { - tickableVisuals.add(tickable, tickable.planTick()); - } - } - if (visual instanceof DynamicVisual dynamic) { if (visual instanceof SimpleDynamicVisual simpleDynamic) { simpleDynamicVisuals.add(simpleDynamic); @@ -160,30 +156,39 @@ private void setup(Visual visual) { } } + if (visual instanceof TickableVisual tickable) { + if (visual instanceof SimpleTickableVisual simpleTickable) { + simpleTickableVisuals.add(simpleTickable); + } else { + tickableVisuals.add(tickable, tickable.planTick()); + } + } + if (visual instanceof SectionTrackedVisual tracked) { - SectionCollectorImpl sectionProperty = new SectionCollectorImpl(); + SectionTracker tracker = new SectionTracker(); - // Give the visual a chance to fill in the property. - tracked.setSectionCollector(sectionProperty); + // Give the visual a chance to invoke the collector. + tracked.setSectionCollector(tracker); if (visual instanceof LightUpdatedVisual lightUpdated) { - litVisuals.add(sectionProperty, lightUpdated); + lightUpdatedVisuals.add(lightUpdated, tracker); } if (visual instanceof ShaderLightVisual shaderLight) { - smoothLitVisuals.add(sectionProperty, shaderLight); + shaderLightVisuals.add(shaderLight, tracker); } } } - /** - * Is the given object currently capable of being added? - * - * @return true if the object is currently capable of being visualized. - */ - public abstract boolean willAccept(T obj); - - public ShaderLightStorage smoothLitStorage() { - return smoothLitVisuals; + public void invalidate() { + dynamicVisuals.clear(); + tickableVisuals.clear(); + simpleDynamicVisuals.clear(); + simpleTickableVisuals.clear(); + lightUpdatedVisuals.clear(); + shaderLightVisuals.clear(); + visuals.values() + .forEach(Visual::delete); + visuals.clear(); } } 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 b0a72f0a7..873ff2bce 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 @@ -194,7 +194,7 @@ private void updateInstances(float partialTick) { body.setTransform(stack) .setChanged(); - // TODO: Use LitVisual if possible. + // TODO: Use LightUpdatedVisual/ShaderLightVisual if possible. updateLight(partialTick); }