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;
}
}