From 6431a84f67593ded757c47c0a3e1f48d74b6c360 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Thu, 14 Nov 2024 16:39:21 -0800 Subject: [PATCH 1/3] Sky's edge - Fix darkness when collecting light sections from high in the air - Add dummy data layer impl to return a constant - Empty sky section returns 15 - Empty block section returns 0 - Copy vanilla's logic for determining which data layer to use for a given position's sky light value - While I'm at it, retrieve light section storage via an accessor to avoid allocating SectionPos objects - Improve lut debug view --- .../SkyLightSectionStorageExtension.java | 9 + .../flywheel/backend/engine/LightStorage.java | 244 +++++++++--------- .../mixin/light/LightEngineAccessor.java | 14 + .../light/SkyDataLayerStorageMapAccessor.java | 15 ++ .../light/SkyLightSectionStorageMixin.java | 46 ++++ .../resources/flywheel.backend.mixins.json | 5 +- 6 files changed, 215 insertions(+), 118 deletions(-) create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/SkyLightSectionStorageExtension.java create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/mixin/light/LightEngineAccessor.java create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/mixin/light/SkyDataLayerStorageMapAccessor.java create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/mixin/light/SkyLightSectionStorageMixin.java diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/SkyLightSectionStorageExtension.java b/common/src/backend/java/dev/engine_room/flywheel/backend/SkyLightSectionStorageExtension.java new file mode 100644 index 000000000..fe3596c1e --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/SkyLightSectionStorageExtension.java @@ -0,0 +1,9 @@ +package dev.engine_room.flywheel.backend; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.world.level.chunk.DataLayer; + +public interface SkyLightSectionStorageExtension { + @Nullable DataLayer flywheel$skyDataLayer(long section); +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java index 9b3f779f7..be9df4981 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java @@ -10,8 +10,10 @@ import dev.engine_room.flywheel.api.visual.Effect; import dev.engine_room.flywheel.api.visual.EffectVisual; import dev.engine_room.flywheel.api.visualization.VisualizationContext; import dev.engine_room.flywheel.api.visualization.VisualizationManager; +import dev.engine_room.flywheel.backend.SkyLightSectionStorageExtension; import dev.engine_room.flywheel.backend.engine.indirect.StagingBuffer; import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer; +import dev.engine_room.flywheel.backend.mixin.light.LightEngineAccessor; import dev.engine_room.flywheel.lib.instance.InstanceTypes; import dev.engine_room.flywheel.lib.instance.TransformedInstance; import dev.engine_room.flywheel.lib.math.MoreMath; @@ -27,9 +29,10 @@ import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.client.renderer.LightTexture; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; +import net.minecraft.core.Vec3i; import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LightLayer; -import net.minecraft.world.level.lighting.LayerLightEventListener; +import net.minecraft.world.level.chunk.DataLayer; /** * A managed arena of light sections for uploading to the GPU. @@ -55,6 +58,9 @@ public class LightStorage implements Effect { private static final int DEFAULT_ARENA_CAPACITY_SECTIONS = 64; private static final int INVALID_SECTION = -1; + private static final ConstantDataLayer EMPTY_BLOCK_DATA = new ConstantDataLayer(0); + private static final ConstantDataLayer EMPTY_SKY_DATA = new ConstantDataLayer(15); + private final LevelAccessor level; private final LightLut lut; private final CpuArena arena; @@ -194,11 +200,6 @@ public class LightStorage implements Effect { } public void collectSection(long section) { - var lightEngine = level.getLightEngine(); - - var blockLight = lightEngine.getLayerListener(LightLayer.BLOCK); - var skyLight = lightEngine.getLayerListener(LightLayer.SKY); - int index = indexForSection(section); changed.set(index); @@ -210,21 +211,21 @@ public class LightStorage implements Effect { collectSolidData(ptr, section); - collectCenter(blockLight, skyLight, ptr, section); + collectCenter(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); + collectYZPlane(ptr, SectionPos.offset(section, i.sectionOffset, 0, 0), i); + collectXZPlane(ptr, SectionPos.offset(section, 0, i.sectionOffset, 0), i); + collectXYPlane(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); + collectXStrip(ptr, SectionPos.offset(section, 0, i.sectionOffset, j.sectionOffset), i, j); + collectYStrip(ptr, SectionPos.offset(section, i.sectionOffset, 0, j.sectionOffset), i, j); + collectZStrip(ptr, SectionPos.offset(section, i.sectionOffset, j.sectionOffset, 0), i, j); } } - collectCorners(blockLight, skyLight, ptr, section); + collectCorners(ptr, section); } private void collectSolidData(long ptr, long section) { @@ -259,64 +260,59 @@ public class LightStorage implements Effect { } } - private void writeSolid(long ptr, int index, boolean blockValid) { - if (!blockValid) { - return; + private DataLayer getSkyData(long section) { + var sky = level.getLightEngine() + .getLayerListener(LightLayer.SKY); + var skyStorage = (SkyLightSectionStorageExtension) ((LightEngineAccessor) sky).flywheel$storage(); + + var out = skyStorage.flywheel$skyDataLayer(section); + + if (out == null) { + return EMPTY_SKY_DATA; } - int intIndex = index / Integer.SIZE; - int bitIndex = index % Integer.SIZE; - long offset = intIndex * Integer.BYTES; - - int bitField = MemoryUtil.memGetInt(ptr + offset); - bitField |= 1 << bitIndex; - - MemoryUtil.memPutInt(ptr + offset, bitField); + return out; } - 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; + private DataLayer getBlockData(long section) { + var out = ((LightEngineAccessor) level.getLightEngine() + .getLayerListener(LightLayer.BLOCK)).flywheel$storage() + .getDataLayerData(section); + + if (out == null) { + return EMPTY_BLOCK_DATA; } + + return out; + } + + private void collectXStrip(long ptr, long section, SectionEdge y, SectionEdge z) { + var blockData = getBlockData(section); + var skyData = getSkyData(section); 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; - } + private void collectYStrip(long ptr, long section, SectionEdge x, SectionEdge z) { + var blockData = getBlockData(section); + var skyData = getSkyData(section); 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; - } + private void collectZStrip(long ptr, long section, SectionEdge x, SectionEdge y) { + var blockData = getBlockData(section); + var skyData = getSkyData(section); 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; - } + private void collectYZPlane(long ptr, long section, SectionEdge x) { + var blockData = getBlockData(section); + var skyData = getSkyData(section); 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)); @@ -324,13 +320,9 @@ public class LightStorage implements Effect { } } - 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; - } + private void collectXZPlane(long ptr, long section, SectionEdge y) { + var blockData = getBlockData(section); + var skyData = getSkyData(section); 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)); @@ -338,13 +330,9 @@ public class LightStorage implements Effect { } } - 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; - } + private void collectXYPlane(long ptr, long section, SectionEdge z) { + var blockData = getBlockData(section); + var skyData = getSkyData(section); 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)); @@ -352,13 +340,9 @@ public class LightStorage implements Effect { } } - 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; - } + private void collectCenter(long ptr, long section) { + var blockData = getBlockData(section); + var skyData = getSkyData(section); for (int y = 0; y < 16; y++) { for (int z = 0; z < 16; z++) { for (int x = 0; x < 16; x++) { @@ -368,7 +352,12 @@ public class LightStorage implements Effect { } } - private void collectCorners(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section) { + private void collectCorners(long ptr, 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)); @@ -485,8 +474,10 @@ public class LightStorage implements Effect { public class DebugVisual implements EffectVisual, SimpleDynamicVisual { private final InstanceRecycler boxes; + private final Vec3i renderOrigin; public DebugVisual(VisualizationContext ctx, float partialTick) { + renderOrigin = ctx.renderOrigin(); boxes = new InstanceRecycler<>(() -> ctx.instancerProvider() .instancer(InstanceTypes.TRANSFORMED, HitboxComponent.BOX_MODEL) .createInstance()); @@ -505,15 +496,15 @@ public class LightStorage implements Effect { private void setupSectionBoxes() { section2ArenaIndex.keySet() .forEach(l -> { - var x = SectionPos.x(l); - var y = SectionPos.y(l); - var z = SectionPos.z(l); + var x = SectionPos.x(l) * 16 - renderOrigin.getX(); + var y = SectionPos.y(l) * 16 - renderOrigin.getY(); + var z = SectionPos.z(l) * 16 - renderOrigin.getZ(); var instance = boxes.get(); instance.setIdentityTransform() - .scale(16) .translate(x, y, z) + .scale(16) .color(255, 255, 0) .light(LightTexture.FULL_BRIGHT) .setChanged(); @@ -526,6 +517,14 @@ public class LightStorage implements Effect { var base1 = first.base(); var size1 = first.size(); + float debug1 = base1 * 16 - renderOrigin.getY(); + + float min2 = Float.POSITIVE_INFINITY; + float max2 = Float.NEGATIVE_INFINITY; + + float min3 = Float.POSITIVE_INFINITY; + float max3 = Float.NEGATIVE_INFINITY; + for (int y = 0; y < size1; y++) { var second = first.getRaw(y); @@ -536,6 +535,16 @@ public class LightStorage implements Effect { var base2 = second.base(); var size2 = second.size(); + float y2 = (base1 + y) * 16 - renderOrigin.getY() + 7.5f; + + min2 = Math.min(min2, base2); + max2 = Math.max(max2, base2 + size2); + + float minLocal3 = Float.POSITIVE_INFINITY; + float maxLocal3 = Float.NEGATIVE_INFINITY; + + float debug2 = base2 * 16 - renderOrigin.getX(); + for (int x = 0; x < size2; x++) { var third = second.getRaw(x); @@ -546,55 +555,43 @@ public class LightStorage implements Effect { var base3 = third.base(); var size3 = third.size(); + float x2 = (base2 + x) * 16 - renderOrigin.getX() + 7.5f; + + min3 = Math.min(min3, base3); + max3 = Math.max(max3, base3 + size3); + + minLocal3 = Math.min(minLocal3, base3); + maxLocal3 = Math.max(maxLocal3, base3 + size3); + + float debug3 = base3 * 16 - renderOrigin.getZ(); + for (int z = 0; z < size3; z++) { - float x1 = base2 * 16; - float y1 = base1 * 16; - float z1 = base3 * 16; - - float x2 = (base2 + x) * 16 + 7.5f; - float y2 = (base1 + y) * 16 + 7.5f; - float z2 = (base3 + z) * 16 + 7.5f; boxes.get() .setIdentityTransform() - .translate(x1, y2, z2) - .scale(size2 * 16, 1, 1) - .color(255, 0, 0) - .light(LightTexture.FULL_BRIGHT) - .setChanged(); - - boxes.get() - .setIdentityTransform() - .translate(x2, y1, z2) - .scale(1, size1 * 16, 1) - .color(0, 255, 0) - .light(LightTexture.FULL_BRIGHT) - .setChanged(); - - boxes.get() - .setIdentityTransform() - .translate(x2, y2, z1) + .translate(x2, y2, debug3) .scale(1, 1, size3 * 16) .color(0, 0, 255) .light(LightTexture.FULL_BRIGHT) .setChanged(); - - if (third.getRaw(z) == 0) { - float x3 = (base2 + x) * 16 + 6f; - float y3 = (base1 + y) * 16 + 6f; - float z3 = (base3 + z) * 16 + 6f; - - // Freely representable section that is not filled. - boxes.get() - .setIdentityTransform() - .translate(x3, y3, z3) - .scale(4) - .color(0, 255, 255) - .light(LightTexture.FULL_BRIGHT) - .setChanged(); - } } } + + boxes.get() + .setIdentityTransform() + .translate(debug2, y2, minLocal3 * 16 - renderOrigin.getZ()) + .scale(size2 * 16, 1, (maxLocal3 - minLocal3) * 16) + .color(255, 0, 0) + .light(LightTexture.FULL_BRIGHT) + .setChanged(); } + + boxes.get() + .setIdentityTransform() + .translate(min2 * 16 - renderOrigin.getX(), debug1, min3 * 16 - renderOrigin.getZ()) + .scale((max2 - min2) * 16, size1 * 16, (max3 - min3) * 16) + .color(0, 255, 0) + .light(LightTexture.FULL_BRIGHT) + .setChanged(); } @Override @@ -607,4 +604,17 @@ public class LightStorage implements Effect { boxes.delete(); } } + + private static class ConstantDataLayer extends DataLayer { + private final int value; + + private ConstantDataLayer(int value) { + this.value = value; + } + + @Override + public int get(int x, int y, int z) { + return value; + } + } } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/light/LightEngineAccessor.java b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/light/LightEngineAccessor.java new file mode 100644 index 000000000..5a6808216 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/light/LightEngineAccessor.java @@ -0,0 +1,14 @@ +package dev.engine_room.flywheel.backend.mixin.light; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.world.level.lighting.DataLayerStorageMap; +import net.minecraft.world.level.lighting.LayerLightSectionStorage; +import net.minecraft.world.level.lighting.LightEngine; + +@Mixin(LightEngine.class) +public interface LightEngineAccessor, S extends LayerLightSectionStorage> { + @Accessor("storage") + S flywheel$storage(); +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/light/SkyDataLayerStorageMapAccessor.java b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/light/SkyDataLayerStorageMapAccessor.java new file mode 100644 index 000000000..b34c0ef31 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/light/SkyDataLayerStorageMapAccessor.java @@ -0,0 +1,15 @@ +package dev.engine_room.flywheel.backend.mixin.light; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; + +@Mixin(targets = "net.minecraft.world.level.lighting.SkyLightSectionStorage.SkyDataLayerStorageMap") +public interface SkyDataLayerStorageMapAccessor { + @Accessor("currentLowestY") + int flywheel$currentLowestY(); + + @Accessor("topSections") + Long2IntOpenHashMap flywheel$topSections(); +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/light/SkyLightSectionStorageMixin.java b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/light/SkyLightSectionStorageMixin.java new file mode 100644 index 000000000..8e178e127 --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/light/SkyLightSectionStorageMixin.java @@ -0,0 +1,46 @@ +package dev.engine_room.flywheel.backend.mixin.light; + +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; + +import dev.engine_room.flywheel.backend.SkyLightSectionStorageExtension; +import net.minecraft.core.Direction; +import net.minecraft.core.SectionPos; +import net.minecraft.world.level.chunk.DataLayer; +import net.minecraft.world.level.lighting.LayerLightSectionStorage; +import net.minecraft.world.level.lighting.SkyLightSectionStorage; + +@Mixin(SkyLightSectionStorage.class) +public abstract class SkyLightSectionStorageMixin extends LayerLightSectionStorage implements SkyLightSectionStorageExtension { + protected SkyLightSectionStorageMixin() { + super(null, null, null); + } + + @Override + @Nullable + public DataLayer flywheel$skyDataLayer(long section) { + // Logic copied from SkyLightSectionStorage#getLightValue, but here we directly return the DataLayer + + long l = section; + int i = SectionPos.y(l); + SkyDataLayerStorageMapAccessor skyDataLayerStorageMap = (SkyDataLayerStorageMapAccessor) this.visibleSectionData; + int j = skyDataLayerStorageMap.flywheel$topSections() + .get(SectionPos.getZeroNode(l)); + if (j != skyDataLayerStorageMap.flywheel$currentLowestY() && i < j) { + DataLayer dataLayer = this.getDataLayerData(l); + if (dataLayer == null) { + for (; dataLayer == null; dataLayer = this.getDataLayerData(l)) { + if (++i >= j) { + return null; + } + + l = SectionPos.offset(l, Direction.UP); + } + } + + return dataLayer; + } else { + return null; + } + } +} diff --git a/common/src/backend/resources/flywheel.backend.mixins.json b/common/src/backend/resources/flywheel.backend.mixins.json index 2d924bef0..b2aa8632a 100644 --- a/common/src/backend/resources/flywheel.backend.mixins.json +++ b/common/src/backend/resources/flywheel.backend.mixins.json @@ -9,7 +9,10 @@ "GlStateManagerMixin", "LevelRendererAccessor", "OptionsMixin", - "RenderSystemMixin" + "RenderSystemMixin", + "light.LightEngineAccessor", + "light.SkyDataLayerStorageMapAccessor", + "light.SkyLightSectionStorageMixin" ], "injectors": { "defaultRequire": 1 From f3845a15fb128b2ccaeac01af56a2ee07240faa8 Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Thu, 14 Nov 2024 17:10:00 -0800 Subject: [PATCH 2/3] Not a lut left - Prune empty layers from the lut to avoid it just getting larger as the player explores the world --- .../flywheel/backend/engine/LightLut.java | 128 ++++++++++++++++++ .../flywheel/backend/engine/LightStorage.java | 8 ++ 2 files changed, 136 insertions(+) diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightLut.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightLut.java index ffe0b2b4d..987ae674b 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightLut.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightLut.java @@ -23,6 +23,11 @@ public final class LightLut { .set(z, index + 1); } + public void prune() { + // Maybe this could be done better incrementally? + indices.prune((middle) -> middle.prune(IntLayer::prune)); + } + public void remove(long section) { final var x = SectionPos.x(section); final var y = SectionPos.y(section); @@ -49,6 +54,11 @@ public final class LightLut { return out; } + @FunctionalInterface + public interface Prune { + boolean prune(T t); + } + public static final class Layer { private boolean hasBase = false; private int base = 0; @@ -135,6 +145,69 @@ public final class LightLut { return (T) out; } + /** + * @return {@code true} if the layer is now empty. + */ + public boolean prune(Prune inner) { + if (!hasBase) { + return true; + } + + // Prune the next layer before checking for leading/trailing zeros. + for (var i = 0; i < nextLayer.length; i++) { + var o = nextLayer[i]; + if (o != null && inner.prune((T) o)) { + nextLayer[i] = null; + } + } + + var leadingZeros = getLeadingZeros(); + + if (leadingZeros == nextLayer.length) { + return true; + } + + var trailingZeros = getTrailingZeros(); + + if (leadingZeros == 0 && trailingZeros == 0) { + return false; + } + + final var newIndices = new Object[nextLayer.length - leadingZeros - trailingZeros]; + + System.arraycopy(nextLayer, leadingZeros, newIndices, 0, newIndices.length); + nextLayer = newIndices; + base += leadingZeros; + + return false; + } + + private int getLeadingZeros() { + int out = 0; + + for (Object index : nextLayer) { + if (index == null) { + out++; + } else { + break; + } + } + return out; + } + + private int getTrailingZeros() { + int out = 0; + + for (int i = nextLayer.length - 1; i >= 0; i--) { + if (nextLayer[i] == null) { + out++; + } else { + break; + } + } + return out; + } + private void resize(int length) { final var newIndices = new Object[length]; System.arraycopy(nextLayer, 0, newIndices, 0, nextLayer.length); @@ -214,6 +287,61 @@ public final class LightLut { indices[offset] = index; } + /** + * @return {@code true} if the layer is now empty. + */ + public boolean prune() { + if (!hasBase) { + return true; + } + + var leadingZeros = getLeadingZeros(); + + if (leadingZeros == indices.length) { + return true; + } + + var trailingZeros = getTrailingZeros(); + + if (leadingZeros == 0 && trailingZeros == 0) { + return false; + } + + final var newIndices = new int[indices.length - leadingZeros - trailingZeros]; + + System.arraycopy(indices, leadingZeros, newIndices, 0, newIndices.length); + indices = newIndices; + base += leadingZeros; + + return false; + } + + private int getTrailingZeros() { + int out = 0; + + for (int i = indices.length - 1; i >= 0; i--) { + if (indices[i] == 0) { + out++; + } else { + break; + } + } + return out; + } + + private int getLeadingZeros() { + int out = 0; + + for (int index : indices) { + if (index == 0) { + out++; + } else { + break; + } + } + return out; + } + public void clear(int i) { if (!hasBase) { return; diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java index be9df4981..060cf6f7d 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java @@ -171,6 +171,8 @@ public class LightStorage implements Effect { return; } + boolean anyRemoved = false; + var entries = section2ArenaIndex.long2IntEntrySet(); var it = entries.iterator(); while (it.hasNext()) { @@ -181,8 +183,14 @@ public class LightStorage implements Effect { arena.free(entry.getIntValue()); endTrackingSection(section); it.remove(); + anyRemoved = true; } } + + if (anyRemoved) { + lut.prune(); + needsLutRebuild = true; + } } private void beginTrackingSection(long section, int index) { From 3811da166c461d5f9344593e5e74d2742d7c891b Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Fri, 15 Nov 2024 18:18:44 -0800 Subject: [PATCH 3/3] Wait a minute - Add debug flag to disable frame/tick plan execution - Should help debugging cases where a visual constructor produces a visual in an invalid state like the weeping shulker bug --- .../flywheel/backend/BackendDebugFlags.java | 5 +++ .../flywheel/backend/engine/LightStorage.java | 9 ++-- .../flywheel/lib/task/ConditionalPlan.java | 44 +++++++++++++++++++ .../flywheel/impl/ImplDebugFlags.java | 8 ++++ .../impl/visualization/storage/Storage.java | 11 ++++- .../flywheel/impl/FlwCommands.java | 18 ++++++-- .../flywheel/impl/FlwCommands.java | 19 ++++++-- 7 files changed, 101 insertions(+), 13 deletions(-) create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/BackendDebugFlags.java create mode 100644 common/src/lib/java/dev/engine_room/flywheel/lib/task/ConditionalPlan.java create mode 100644 common/src/main/java/dev/engine_room/flywheel/impl/ImplDebugFlags.java diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/BackendDebugFlags.java b/common/src/backend/java/dev/engine_room/flywheel/backend/BackendDebugFlags.java new file mode 100644 index 000000000..66704fecc --- /dev/null +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/BackendDebugFlags.java @@ -0,0 +1,5 @@ +package dev.engine_room.flywheel.backend; + +public class BackendDebugFlags { + public static boolean LIGHT_STORAGE_VIEW = false; +} diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java index 060cf6f7d..c9539ca95 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java @@ -10,6 +10,7 @@ import dev.engine_room.flywheel.api.visual.Effect; import dev.engine_room.flywheel.api.visual.EffectVisual; import dev.engine_room.flywheel.api.visualization.VisualizationContext; import dev.engine_room.flywheel.api.visualization.VisualizationManager; +import dev.engine_room.flywheel.backend.BackendDebugFlags; import dev.engine_room.flywheel.backend.SkyLightSectionStorageExtension; import dev.engine_room.flywheel.backend.engine.indirect.StagingBuffer; import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer; @@ -49,8 +50,6 @@ import net.minecraft.world.level.chunk.DataLayer; *

Thus, each section occupies 5832 bytes. */ public class LightStorage implements Effect { - public static boolean DEBUG = false; - public static final int BLOCKS_PER_SECTION = 18 * 18 * 18; public static final int LIGHT_SIZE_BYTES = BLOCKS_PER_SECTION; public static final int SOLID_SIZE_BYTES = MoreMath.ceilingDiv(BLOCKS_PER_SECTION, Integer.SIZE) * Integer.BYTES; @@ -108,12 +107,12 @@ public class LightStorage implements Effect { public Plan createFramePlan() { return SimplePlan.of(() -> { - if (DEBUG != isDebugOn) { + if (BackendDebugFlags.LIGHT_STORAGE_VIEW != isDebugOn) { var visualizationManager = VisualizationManager.get(level); // Really should be non-null, but just in case. if (visualizationManager != null) { - if (DEBUG) { + if (BackendDebugFlags.LIGHT_STORAGE_VIEW) { visualizationManager.effects() .queueAdd(this); } else { @@ -121,7 +120,7 @@ public class LightStorage implements Effect { .queueRemove(this); } } - isDebugOn = DEBUG; + isDebugOn = BackendDebugFlags.LIGHT_STORAGE_VIEW; } if (updatedSections.isEmpty() && requestedSections == null) { diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/task/ConditionalPlan.java b/common/src/lib/java/dev/engine_room/flywheel/lib/task/ConditionalPlan.java new file mode 100644 index 000000000..143bf3939 --- /dev/null +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/task/ConditionalPlan.java @@ -0,0 +1,44 @@ +package dev.engine_room.flywheel.lib.task; + +import dev.engine_room.flywheel.api.task.Plan; +import dev.engine_room.flywheel.api.task.TaskExecutor; +import dev.engine_room.flywheel.lib.task.functional.BooleanSupplierWithContext; + +/** + * Executes one plan or another, depending on a dynamically evaluated condition. + * + * @param condition The condition to branch on. + * @param onTrue The plan to execute if the condition is true. + * @param The type of the context object. + */ +public record ConditionalPlan(BooleanSupplierWithContext condition, + Plan onTrue) implements SimplyComposedPlan { + public static Builder on(BooleanSupplierWithContext condition) { + return new Builder<>(condition); + } + + public static Builder on(BooleanSupplierWithContext.Ignored condition) { + return new Builder<>(condition); + } + + @Override + public void execute(TaskExecutor taskExecutor, C context, Runnable onCompletion) { + if (condition.getAsBoolean(context)) { + onTrue.execute(taskExecutor, context, onCompletion); + } else { + onCompletion.run(); + } + } + + public static final class Builder { + private final BooleanSupplierWithContext condition; + + public Builder(BooleanSupplierWithContext condition) { + this.condition = condition; + } + + public ConditionalPlan then(Plan onTrue) { + return new ConditionalPlan<>(condition, onTrue); + } + } +} diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/ImplDebugFlags.java b/common/src/main/java/dev/engine_room/flywheel/impl/ImplDebugFlags.java new file mode 100644 index 000000000..4072110ae --- /dev/null +++ b/common/src/main/java/dev/engine_room/flywheel/impl/ImplDebugFlags.java @@ -0,0 +1,8 @@ +package dev.engine_room.flywheel.impl; + +public class ImplDebugFlags { + /** + * Debug flag to globally turn beginFrame/tick off. + */ + public static boolean PAUSE_UPDATES = false; +} 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 1cc6667a7..0bef0fd10 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 @@ -15,6 +15,8 @@ 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; +import dev.engine_room.flywheel.impl.ImplDebugFlags; +import dev.engine_room.flywheel.lib.task.ConditionalPlan; import dev.engine_room.flywheel.lib.task.ForEachPlan; import dev.engine_room.flywheel.lib.task.NestedPlan; import dev.engine_room.flywheel.lib.task.PlanMap; @@ -42,11 +44,16 @@ public abstract class Storage { } public Plan framePlan() { - return NestedPlan.of(dynamicVisuals, lightUpdatedVisuals.plan(), ForEachPlan.of(() -> simpleDynamicVisuals, SimpleDynamicVisual::beginFrame)); + var update = ConditionalPlan.on(() -> !ImplDebugFlags.PAUSE_UPDATES) + .then(NestedPlan.of(dynamicVisuals, ForEachPlan.of(() -> simpleDynamicVisuals, SimpleDynamicVisual::beginFrame))); + + // Do light updates regardless. + return NestedPlan.of(lightUpdatedVisuals.plan(), update); } public Plan tickPlan() { - return NestedPlan.of(tickableVisuals, ForEachPlan.of(() -> simpleTickableVisuals, SimpleTickableVisual::tick)); + return ConditionalPlan.on(() -> !ImplDebugFlags.PAUSE_UPDATES) + .then(NestedPlan.of(tickableVisuals, ForEachPlan.of(() -> simpleTickableVisuals, SimpleTickableVisual::tick))); } public LightUpdatedVisualStorage lightUpdatedVisuals() { diff --git a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java index 45e0703d6..f6fa12545 100644 --- a/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java +++ b/fabric/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java @@ -8,9 +8,9 @@ import com.mojang.brigadier.context.CommandContext; import dev.engine_room.flywheel.api.backend.Backend; import dev.engine_room.flywheel.api.backend.BackendManager; +import dev.engine_room.flywheel.backend.BackendDebugFlags; import dev.engine_room.flywheel.backend.compile.LightSmoothness; import dev.engine_room.flywheel.backend.compile.PipelineCompiler; -import dev.engine_room.flywheel.backend.engine.LightStorage; import dev.engine_room.flywheel.backend.engine.uniform.DebugMode; import dev.engine_room.flywheel.backend.engine.uniform.FrameUniforms; import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; @@ -179,12 +179,24 @@ public final class FlwCommands { debug.then(ClientCommandManager.literal("lightSections") .then(ClientCommandManager.literal("on") .executes(context -> { - LightStorage.DEBUG = true; + BackendDebugFlags.LIGHT_STORAGE_VIEW = true; return Command.SINGLE_SUCCESS; })) .then(ClientCommandManager.literal("off") .executes(context -> { - LightStorage.DEBUG = false; + BackendDebugFlags.LIGHT_STORAGE_VIEW = false; + return Command.SINGLE_SUCCESS; + }))); + + debug.then(ClientCommandManager.literal("pauseUpdates") + .then(ClientCommandManager.literal("on") + .executes(context -> { + ImplDebugFlags.PAUSE_UPDATES = true; + return Command.SINGLE_SUCCESS; + })) + .then(ClientCommandManager.literal("off") + .executes(context -> { + ImplDebugFlags.PAUSE_UPDATES = false; return Command.SINGLE_SUCCESS; }))); diff --git a/forge/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java b/forge/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java index 0fcb96d35..8aeb9e45f 100644 --- a/forge/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java +++ b/forge/src/main/java/dev/engine_room/flywheel/impl/FlwCommands.java @@ -6,9 +6,9 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import dev.engine_room.flywheel.api.backend.Backend; import dev.engine_room.flywheel.api.backend.BackendManager; +import dev.engine_room.flywheel.backend.BackendDebugFlags; import dev.engine_room.flywheel.backend.compile.LightSmoothness; import dev.engine_room.flywheel.backend.compile.PipelineCompiler; -import dev.engine_room.flywheel.backend.engine.LightStorage; import dev.engine_room.flywheel.backend.engine.uniform.DebugMode; import dev.engine_room.flywheel.backend.engine.uniform.FrameUniforms; import net.minecraft.client.Minecraft; @@ -172,14 +172,27 @@ public final class FlwCommands { debug.then(Commands.literal("lightSections") .then(Commands.literal("on") .executes(context -> { - LightStorage.DEBUG = true; + BackendDebugFlags.LIGHT_STORAGE_VIEW = true; return Command.SINGLE_SUCCESS; })) .then(Commands.literal("off") .executes(context -> { - LightStorage.DEBUG = false; + BackendDebugFlags.LIGHT_STORAGE_VIEW = false; return Command.SINGLE_SUCCESS; }))); + + debug.then(Commands.literal("pauseUpdates") + .then(Commands.literal("on") + .executes(context -> { + ImplDebugFlags.PAUSE_UPDATES = true; + return Command.SINGLE_SUCCESS; + })) + .then(Commands.literal("off") + .executes(context -> { + ImplDebugFlags.PAUSE_UPDATES = false; + return Command.SINGLE_SUCCESS; + }))); + return debug; }