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 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. + * + *

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 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; +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 class LightStorage { 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); + removeUnusedSections(); - var knownSections = section2ArenaIndex.keySet(); - - var updatedSections = LightUpdateHolder.get(level) - .getUpdatedSections(); - - // 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 class LightStorage { 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 class LightUpdateHolder { 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 @@ public class VisualizationManagerImpl implements VisualizationManager { .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 class VisualManagerImpl> implements VisualManager .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 @@ package dev.engine_room.flywheel.impl.visualization.storage; 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 @@ import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; 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 class SmoothLitVisualStorage { 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.NestedPlan; 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 @@ public abstract class Storage { */ public abstract boolean willAccept(T obj); - public LongSet lightSections() { - return smoothLitVisuals.sections(); + public SmoothLitVisualStorage smoothLitStorage() { + return smoothLitVisuals; } }