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
This commit is contained in:
Jozufozu 2024-07-13 13:09:47 -07:00
parent 2837762cbf
commit 2cced88749
10 changed files with 93 additions and 36 deletions

View file

@ -72,6 +72,8 @@ public interface Engine {
/**
* Assign the set of sections that visuals have requested GPU light for.
*
* <p> This will be called at most once per frame, and not necessarily every frame.
*
* @param sections The set of sections.
*/
void lightSections(LongSet sections);

View file

@ -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.
*
* <p> Sections passed into {@link SectionProperty#lightSections} will have their light data handed to the
* backend and queryable by {@code flw_light*} functions in shaders.
* <br>
* 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.
*
* <p>This method is only called once, upon visual creation,
* <p>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);
}

View file

@ -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.
* <p> 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<RenderContext> 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);

View file

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

View file

@ -14,7 +14,6 @@ public enum DebugMode implements StringRepresentable {
LIGHT_COLOR,
OVERLAY,
DIFFUSE,
LIGHT_VOLUME,
;
public static final Codec<DebugMode> CODEC = StringRepresentable.fromEnum(DebugMode::values);

View file

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

View file

@ -106,12 +106,13 @@ public class VisualizationManagerImpl implements VisualizationManager {
.ifFalse(update)
.plan()
.then(SimplePlan.of(() -> {
// TODO: Lazily re-evaluate the union'd set
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())

View file

@ -76,7 +76,13 @@ public class VisualManagerImpl<T, S extends Storage<T>> implements VisualManager
.then(storage.tickPlan());
}
public boolean lightSectionsDirty() {
return getStorage().smoothLitStorage()
.sectionsDirty();
}
public LongSet lightSections() {
return getStorage().lightSections();
return getStorage().smoothLitStorage()
.sections();
}
}

View file

@ -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<SmoothLitVisual, SectionProperty> visuals = new Reference2ObjectOpenHashMap<>();
public LongSet sections() {
var out = new LongOpenHashSet();
for (SectionProperty value : visuals.values()) {
out.addAll(value.sections);
@Nullable
private LongSet cachedSections;
public boolean sectionsDirty() {
return cachedSections == null;
}
return out;
public LongSet sections() {
cachedSections = new LongOpenHashSet();
for (SectionProperty value : visuals.values()) {
cachedSections.addAll(value.sections);
}
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;
}
}
}

View file

@ -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<T> {
@ -176,7 +175,7 @@ public abstract class Storage<T> {
*/
public abstract boolean willAccept(T obj);
public LongSet lightSections() {
return smoothLitVisuals.sections();
public SmoothLitVisualStorage smoothLitStorage() {
return smoothLitVisuals;
}
}