mirror of
https://github.com/Jozufozu/Flywheel.git
synced 2024-12-26 15:06:28 +01:00
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:
parent
2837762cbf
commit
2cced88749
10 changed files with 93 additions and 36 deletions
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<>();
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue