Light clean up

- Ensure section set returned by SectionTracker is Unmodifiable to avoid copy in LightUpdatedVisualStorage
- Do not recompute section set in ShaderLightVisualStorage if not dirty
- Fix BlockEntityStorage not clearing posLookup on recreation or invalidation
- Fix Storage.invalidate not clearing everything
- Inline TopLevelEmbeddedEnvironment and NestedEmbeddedEnvironment into AbstractEmbeddedEnvironment and rename to EmbeddedEnvironment
- Move some classes between packages
- Remove unused fields in EmbeddingUniforms
- Remove suffix on field names in BufferBindings
- Rename enqueueLightUpdateSection methods to onLightUpdate
- Rename SectionCollectorImpl to SectionTracker
- Rename classes, methods, fields, and parameters and edit javadoc and comments to match previously done renames, new renames, and other existing classes
This commit is contained in:
PepperCode1 2024-07-15 15:36:52 -06:00 committed by Jozufozu
parent 1ea94a859e
commit fe0eacad8e
38 changed files with 364 additions and 372 deletions

View file

@ -62,13 +62,6 @@ public interface Engine {
*/
Vec3i renderOrigin();
/**
* Free all resources associated with this engine.
* <br>
* This engine will not be used again after this method is called.
*/
void delete();
/**
* Assign the set of sections that visuals have requested GPU light for.
*
@ -78,6 +71,13 @@ public interface Engine {
*/
void lightSections(LongSet sections);
/**
* Free all resources associated with this engine.
* <br>
* This engine will not be used again after this method is called.
*/
void delete();
/**
* A block to be rendered as a crumbling overlay.
* @param progress The progress of the crumbling animation in the range [0, 10).

View file

@ -9,7 +9,8 @@ package dev.engine_room.flywheel.api.visual;
*/
public non-sealed interface LightUpdatedVisual extends SectionTrackedVisual {
/**
* Called when a section this visual is contained in receives a light update.
* Called when a section this visual is contained in receives a light update. It is not called
* unconditionally after visual creation or {@link SectionCollector#sections}.
*
* <p>Even if multiple sections are updated at the same time, this method will only be called once.</p>
*
@ -17,8 +18,6 @@ public non-sealed interface LightUpdatedVisual extends SectionTrackedVisual {
* returned by {@link DynamicVisual#planFrame} simultaneously. It is safe to query/update light here,
* but you must ensure proper synchronization if you want to mutate anything outside this visual or
* anything that is also mutated within {@link DynamicVisual#planFrame}.</p>
*
* <p>This method not is invoked automatically after visual creation.</p>
*/
void updateLight(float partialTick);
}

View file

@ -6,15 +6,15 @@ import it.unimi.dsi.fastutil.longs.LongSet;
public sealed interface SectionTrackedVisual extends Visual permits ShaderLightVisual, LightUpdatedVisual {
/**
* Set the section property object.
* Set the section collector object.
*
* <p>This method is only called once, upon visual creation.
* <br>If the property is assigned to in this method, the
* <br>If the collector is invoked in this method, the
* visual will immediately be tracked in the given sections.
*
* @param property The property.
* @param collector The collector.
*/
void setSectionCollector(SectionCollector property);
void setSectionCollector(SectionCollector collector);
@ApiStatus.NonExtendable
interface SectionCollector {

View file

@ -6,6 +6,7 @@ package dev.engine_room.flywheel.api.visual;
* @see DynamicVisual
* @see TickableVisual
* @see LightUpdatedVisual
* @see ShaderLightVisual
*/
public interface Visual {
/**

View file

@ -1,7 +1,8 @@
package dev.engine_room.flywheel.backend.engine.embed;
package dev.engine_room.flywheel.backend;
import dev.engine_room.flywheel.lib.util.LevelAttached;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.world.level.LevelAccessor;
@ -11,12 +12,15 @@ import net.minecraft.world.level.LevelAccessor;
public class LightUpdateHolder {
private static final LevelAttached<LightUpdateHolder> HOLDERS = new LevelAttached<>(level -> new LightUpdateHolder());
private final LongSet updatedSections = new LongOpenHashSet();
private LightUpdateHolder() {
}
public static LightUpdateHolder get(LevelAccessor level) {
return HOLDERS.get(level);
}
private final LongSet updatedSections = new LongArraySet();
public LongSet getAndClearUpdatedSections() {
if (updatedSections.isEmpty()) {
return LongSet.of();
@ -30,7 +34,4 @@ public class LightUpdateHolder {
public void add(long section) {
updatedSections.add(section);
}
private LightUpdateHolder() {
}
}

View file

@ -43,7 +43,7 @@ public class SsboInstanceComponent extends InstanceAssemblerComponent {
fnBody.ret(GlslExpr.call(STRUCT_NAME, unpackArgs));
builder._addRaw("layout(std430, binding = " + BufferBindings.INSTANCE_BUFFER_BINDING + ") restrict readonly buffer InstanceBuffer {\n"
builder._addRaw("layout(std430, binding = " + BufferBindings.INSTANCE + ") restrict readonly buffer InstanceBuffer {\n"
+ " uint _flw_instances[];\n"
+ "};");
builder.blankLine();

View file

@ -1,4 +1,4 @@
package dev.engine_room.flywheel.backend.engine.embed;
package dev.engine_room.flywheel.backend.engine;
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
import it.unimi.dsi.fastutil.ints.IntArrayList;

View file

@ -16,7 +16,6 @@ import dev.engine_room.flywheel.api.instance.Instancer;
import dev.engine_room.flywheel.api.model.Model;
import dev.engine_room.flywheel.backend.FlwBackend;
import dev.engine_room.flywheel.backend.engine.embed.Environment;
import dev.engine_room.flywheel.backend.engine.embed.LightStorage;
import dev.engine_room.flywheel.lib.util.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;

View file

@ -14,9 +14,9 @@ import dev.engine_room.flywheel.api.task.Plan;
import dev.engine_room.flywheel.api.task.TaskExecutor;
import dev.engine_room.flywheel.api.visualization.VisualEmbedding;
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
import dev.engine_room.flywheel.backend.engine.embed.EmbeddedEnvironment;
import dev.engine_room.flywheel.backend.engine.embed.Environment;
import dev.engine_room.flywheel.backend.engine.embed.LightStorage;
import dev.engine_room.flywheel.backend.engine.embed.TopLevelEmbeddedEnvironment;
import dev.engine_room.flywheel.backend.engine.embed.EnvironmentStorage;
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
import dev.engine_room.flywheel.backend.gl.GlStateTracker;
import dev.engine_room.flywheel.lib.task.Flag;
@ -30,11 +30,11 @@ import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.phys.Vec3;
public class EngineImpl implements Engine {
private final int sqrMaxOriginDistance;
private final DrawManager<? extends AbstractInstancer<?>> drawManager;
private final int sqrMaxOriginDistance;
private final Flag flushFlag = new NamedFlag("flushed");
private final EnvironmentStorage environmentStorage;
private final LightStorage lightStorage;
private final Flag flushFlag = new NamedFlag("flushed");
private BlockPos renderOrigin = BlockPos.ZERO;
@ -45,6 +45,11 @@ public class EngineImpl implements Engine {
lightStorage = new LightStorage(level);
}
@Override
public VisualizationContext createVisualizationContext(RenderStage stage) {
return new VisualizationContextImpl(stage);
}
@Override
public Plan<RenderContext> createFramePlan() {
return lightStorage.createFramePlan()
@ -69,11 +74,6 @@ public class EngineImpl implements Engine {
drawManager.renderCrumbling(crumblingBlocks);
}
@Override
public VisualizationContext createVisualizationContext(RenderStage stage) {
return new VisualizationContextImpl(stage);
}
@Override
public boolean updateRenderOrigin(Camera camera) {
Vec3 cameraPos = camera.getPosition();
@ -97,14 +97,14 @@ public class EngineImpl implements Engine {
}
@Override
public void delete() {
drawManager.delete();
lightStorage.delete();
public void lightSections(LongSet sections) {
lightStorage.sections(sections);
}
@Override
public void lightSections(LongSet sections) {
lightStorage.sections(sections);
public void delete() {
drawManager.delete();
lightStorage.delete();
}
public <I extends Instance> Instancer<I> instancer(Environment environment, InstanceType<I> type, Model model, RenderStage stage) {
@ -150,7 +150,7 @@ public class EngineImpl implements Engine {
@Override
public VisualEmbedding createEmbedding() {
var out = new TopLevelEmbeddedEnvironment(EngineImpl.this, stage);
var out = new EmbeddedEnvironment(EngineImpl.this, stage);
environmentStorage.track(out);
return out;
}

View file

@ -1,19 +0,0 @@
package dev.engine_room.flywheel.backend.engine;
import dev.engine_room.flywheel.backend.engine.embed.AbstractEmbeddedEnvironment;
import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
import it.unimi.dsi.fastutil.objects.ReferenceSet;
import it.unimi.dsi.fastutil.objects.ReferenceSets;
public class EnvironmentStorage {
protected final ReferenceSet<AbstractEmbeddedEnvironment> environments = ReferenceSets.synchronize(new ReferenceLinkedOpenHashSet<>());
public void track(AbstractEmbeddedEnvironment environment) {
environments.add(environment);
}
public void flush() {
environments.removeIf(AbstractEmbeddedEnvironment::isDeleted);
environments.forEach(AbstractEmbeddedEnvironment::flush);
}
}

View file

@ -1,4 +1,4 @@
package dev.engine_room.flywheel.backend.engine.embed;
package dev.engine_room.flywheel.backend.engine;
import org.jetbrains.annotations.NotNull;
@ -11,8 +11,8 @@ import it.unimi.dsi.fastutil.longs.LongComparator;
import it.unimi.dsi.fastutil.objects.ReferenceArrayList;
import net.minecraft.core.SectionPos;
public class LightLut {
public static final LongComparator SECTION_X_THEN_Y_THEN_Z = (long a, long b) -> {
public final class LightLut {
private static final LongComparator SECTION_X_THEN_Y_THEN_Z = (long a, long b) -> {
final var xComp = Integer.compare(SectionPos.x(a), SectionPos.x(b));
if (xComp != 0) {
return xComp;
@ -24,6 +24,8 @@ public class LightLut {
return Integer.compare(SectionPos.z(a), SectionPos.z(b));
};
private LightLut() {
}
// Massive kudos to RogueLogix for figuring out this LUT scheme.
// TODO: switch to y x z or x z y ordering

View file

@ -1,4 +1,4 @@
package dev.engine_room.flywheel.backend.engine.embed;
package dev.engine_room.flywheel.backend.engine;
import java.util.BitSet;
@ -7,6 +7,7 @@ import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.api.event.RenderContext;
import dev.engine_room.flywheel.api.task.Plan;
import dev.engine_room.flywheel.backend.LightUpdateHolder;
import dev.engine_room.flywheel.backend.engine.indirect.StagingBuffer;
import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer;
import dev.engine_room.flywheel.lib.task.SimplePlan;

View file

@ -1,5 +1,6 @@
package dev.engine_room.flywheel.backend.engine.embed;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3f;
import org.joml.Matrix3fc;
import org.joml.Matrix4f;
@ -17,61 +18,44 @@ import dev.engine_room.flywheel.backend.engine.EngineImpl;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
import net.minecraft.core.Vec3i;
public abstract class AbstractEmbeddedEnvironment implements Environment, VisualEmbedding {
protected final Matrix4f pose = new Matrix4f();
protected final Matrix3f normal = new Matrix3f();
public class EmbeddedEnvironment implements VisualEmbedding, Environment {
private final EngineImpl engine;
private final RenderStage renderStage;
@Nullable
private final EmbeddedEnvironment parent;
private final InstancerProvider instancerProvider;
private final Matrix4f pose = new Matrix4f();
private final Matrix3f normal = new Matrix3f();
private final Matrix4f poseComposed = new Matrix4f();
private final Matrix3f normalComposed = new Matrix3f();
private final InstancerProvider instancerProvider;
protected final EngineImpl engine;
private final RenderStage renderStage;
private boolean deleted = false;
public AbstractEmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) {
public EmbeddedEnvironment(EngineImpl engine, RenderStage renderStage, @Nullable EmbeddedEnvironment parent) {
this.engine = engine;
this.renderStage = renderStage;
this.parent = parent;
instancerProvider = new InstancerProvider() {
@Override
public <I extends Instance> Instancer<I> instancer(InstanceType<I> type, Model model) {
// Kinda cursed usage of anonymous classes here, but it does the job.
return engine.instancer(AbstractEmbeddedEnvironment.this, type, model, renderStage);
return engine.instancer(EmbeddedEnvironment.this, type, model, renderStage);
}
};
}
public EmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) {
this(engine, renderStage, null);
}
@Override
public void transforms(Matrix4fc pose, Matrix3fc normal) {
this.pose.set(pose);
this.normal.set(normal);
}
public void flush() {
poseComposed.identity();
normalComposed.identity();
composeMatrices(poseComposed, normalComposed);
}
@Override
public void setupDraw(GlProgram program) {
program.setMat4(EmbeddingUniforms.MODEL_MATRIX, poseComposed);
program.setMat3(EmbeddingUniforms.NORMAL_MATRIX, normalComposed);
}
@Override
public void setupCull(GlProgram program) {
program.setBool(EmbeddingUniforms.USE_MODEL_MATRIX, true);
program.setMat4(EmbeddingUniforms.MODEL_MATRIX1, poseComposed);
}
@Override
public ContextShader contextShader() {
return ContextShader.EMBEDDED;
}
@Override
public InstancerProvider instancerProvider() {
return instancerProvider;
@ -84,12 +68,47 @@ public abstract class AbstractEmbeddedEnvironment implements Environment, Visual
@Override
public VisualEmbedding createEmbedding() {
var out = new NestedEmbeddedEnvironment(this, engine, renderStage);
var out = new EmbeddedEnvironment(engine, renderStage, this);
engine.environmentStorage()
.track(out);
return out;
}
@Override
public ContextShader contextShader() {
return ContextShader.EMBEDDED;
}
@Override
public void setupCull(GlProgram program) {
program.setBool(EmbeddingUniforms.USE_MODEL_MATRIX, true);
program.setMat4(EmbeddingUniforms.MODEL_MATRIX, poseComposed);
}
@Override
public void setupDraw(GlProgram program) {
program.setMat4(EmbeddingUniforms.MODEL_MATRIX, poseComposed);
program.setMat3(EmbeddingUniforms.NORMAL_MATRIX, normalComposed);
}
public void flush() {
poseComposed.identity();
normalComposed.identity();
composeMatrices(poseComposed, normalComposed);
}
private void composeMatrices(Matrix4f pose, Matrix3f normal) {
if (parent != null) {
parent.composeMatrices(pose, normal);
pose.mul(this.pose);
normal.mul(this.normal);
} else {
pose.set(this.pose);
normal.set(this.normal);
}
}
public boolean isDeleted() {
return deleted;
}
@ -101,6 +120,4 @@ public abstract class AbstractEmbeddedEnvironment implements Environment, Visual
public void delete() {
deleted = true;
}
public abstract void composeMatrices(Matrix4f pose, Matrix3f normal);
}

View file

@ -1,11 +1,13 @@
package dev.engine_room.flywheel.backend.engine.embed;
public class EmbeddingUniforms {
public final class EmbeddingUniforms {
/**
* Only used by cull shaders.
*/
public static final String USE_MODEL_MATRIX = "_flw_useModelMatrix";
public static final String MODEL_MATRIX = "_flw_modelMatrix";
public static final String NORMAL_MATRIX = "_flw_normalMatrix";
public static final String USE_MODEL_MATRIX = "_flw_useModelMatrix";
public static final String MODEL_MATRIX1 = "_flw_modelMatrix";
public static final String ONE_OVER_LIGHT_BOX_SIZE = "_flw_oneOverLightBoxSize";
public static final String LIGHT_VOLUME_MIN = "_flw_lightVolumeMin";
public static final String USE_LIGHT_VOLUME = "_flw_useLightVolume";
private EmbeddingUniforms() {
}
}

View file

@ -6,7 +6,7 @@ import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
public interface Environment {
ContextShader contextShader();
void setupDraw(GlProgram drawProgram);
void setupCull(GlProgram cullProgram);
void setupDraw(GlProgram drawProgram);
}

View file

@ -0,0 +1,18 @@
package dev.engine_room.flywheel.backend.engine.embed;
import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet;
import it.unimi.dsi.fastutil.objects.ReferenceSet;
import it.unimi.dsi.fastutil.objects.ReferenceSets;
public class EnvironmentStorage {
protected final ReferenceSet<EmbeddedEnvironment> environments = ReferenceSets.synchronize(new ReferenceLinkedOpenHashSet<>());
public void track(EmbeddedEnvironment environment) {
environments.add(environment);
}
public void flush() {
environments.removeIf(EmbeddedEnvironment::isDeleted);
environments.forEach(EmbeddedEnvironment::flush);
}
}

View file

@ -14,13 +14,12 @@ public class GlobalEnvironment implements Environment {
return ContextShader.DEFAULT;
}
@Override
public void setupDraw(GlProgram drawProgram) {
}
@Override
public void setupCull(GlProgram cullProgram) {
cullProgram.setBool(EmbeddingUniforms.USE_MODEL_MATRIX, false);
}
@Override
public void setupDraw(GlProgram drawProgram) {
}
}

View file

@ -1,23 +0,0 @@
package dev.engine_room.flywheel.backend.engine.embed;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import dev.engine_room.flywheel.api.event.RenderStage;
import dev.engine_room.flywheel.backend.engine.EngineImpl;
public class NestedEmbeddedEnvironment extends AbstractEmbeddedEnvironment {
private final AbstractEmbeddedEnvironment parent;
public NestedEmbeddedEnvironment(AbstractEmbeddedEnvironment parent, EngineImpl engine, RenderStage renderStage) {
super(engine, renderStage);
this.parent = parent;
}
@Override
public void composeMatrices(Matrix4f pose, Matrix3f normal) {
parent.composeMatrices(pose, normal);
pose.mul(this.pose);
normal.mul(this.normal);
}
}

View file

@ -1,19 +0,0 @@
package dev.engine_room.flywheel.backend.engine.embed;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import dev.engine_room.flywheel.api.event.RenderStage;
import dev.engine_room.flywheel.backend.engine.EngineImpl;
public class TopLevelEmbeddedEnvironment extends AbstractEmbeddedEnvironment {
public TopLevelEmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) {
super(engine, renderStage);
}
@Override
public void composeMatrices(Matrix4f pose, Matrix3f normal) {
pose.set(this.pose);
normal.set(this.normal);
}
}

View file

@ -1,13 +1,13 @@
package dev.engine_room.flywheel.backend.engine.indirect;
public class BufferBindings {
public static final int INSTANCE_BUFFER_BINDING = 0;
public static final int TARGET_BUFFER_BINDING = 1;
public static final int MODEL_INDEX_BUFFER_BINDING = 2;
public static final int MODEL_BUFFER_BINDING = 3;
public static final int DRAW_BUFFER_BINDING = 4;
public static final int LIGHT_LUT_BINDING = 5;
public static final int LIGHT_SECTION_BINDING = 6;
public final class BufferBindings {
public static final int INSTANCE = 0;
public static final int TARGET = 1;
public static final int MODEL_INDEX = 2;
public static final int MODEL = 3;
public static final int DRAW = 4;
public static final int LIGHT_LUT = 5;
public static final int LIGHT_SECTION = 6;
private BufferBindings() {
}

View file

@ -110,7 +110,7 @@ public class IndirectBuffers {
private void multiBind() {
final long ptr = multiBindBlock.ptr();
nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE_BUFFER_BINDING, IndirectBuffers.BUFFER_COUNT, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET);
nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE, IndirectBuffers.BUFFER_COUNT, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET);
}
/**
@ -118,7 +118,7 @@ public class IndirectBuffers {
*/
public void bindForCrumbling() {
final long ptr = multiBindBlock.ptr();
nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE_BUFFER_BINDING, 4, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET);
nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.INSTANCE, 4, ptr, ptr + OFFSET_OFFSET, ptr + SIZE_OFFSET);
}
public void delete() {

View file

@ -20,10 +20,10 @@ import dev.engine_room.flywheel.backend.engine.CommonCrumbling;
import dev.engine_room.flywheel.backend.engine.DrawManager;
import dev.engine_room.flywheel.backend.engine.GroupKey;
import dev.engine_room.flywheel.backend.engine.InstancerKey;
import dev.engine_room.flywheel.backend.engine.LightStorage;
import dev.engine_room.flywheel.backend.engine.MaterialRenderState;
import dev.engine_room.flywheel.backend.engine.MeshPool;
import dev.engine_room.flywheel.backend.engine.TextureBinder;
import dev.engine_room.flywheel.backend.engine.embed.LightStorage;
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
import dev.engine_room.flywheel.backend.gl.GlStateTracker;
import dev.engine_room.flywheel.backend.gl.array.GlVertexArray;
@ -40,18 +40,15 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
private final GlVertexArray vertexArray;
private final Map<GroupKey<?>, IndirectCullingGroup<?>> cullingGroups = new HashMap<>();
private final GlBuffer crumblingDrawBuffer = new GlBuffer();
private final LightBuffers lightBuffers;
public IndirectDrawManager(IndirectPrograms programs) {
this.programs = programs;
programs.acquire();
stagingBuffer = new StagingBuffer(this.programs);
meshPool = new MeshPool();
vertexArray = GlVertexArray.create();
meshPool.bind(vertexArray);
lightBuffers = new LightBuffers();
}
@ -170,7 +167,7 @@ public class IndirectDrawManager extends DrawManager<IndirectInstancer<?>> {
var block = MemoryBlock.malloc(IndirectBuffers.DRAW_COMMAND_STRIDE);
GlBufferType.DRAW_INDIRECT_BUFFER.bind(crumblingDrawBuffer.handle());
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.DRAW_BUFFER_BINDING, crumblingDrawBuffer.handle(), 0, IndirectBuffers.DRAW_COMMAND_STRIDE);
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, BufferBindings.DRAW, crumblingDrawBuffer.handle(), 0, IndirectBuffers.DRAW_COMMAND_STRIDE);
for (var groupEntry : byType.entrySet()) {
var byProgress = groupEntry.getValue();

View file

@ -3,14 +3,11 @@ package dev.engine_room.flywheel.backend.engine.indirect;
import org.lwjgl.opengl.GL46;
import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.backend.engine.embed.LightStorage;
import dev.engine_room.flywheel.backend.engine.LightStorage;
public class LightBuffers {
private final ResizableStorageArray sections = new ResizableStorageArray(LightStorage.SECTION_SIZE_BYTES);
private final ResizableStorageArray lut = new ResizableStorageArray(4);
public LightBuffers() {
}
private final ResizableStorageArray sections = new ResizableStorageArray(LightStorage.SECTION_SIZE_BYTES);
public void flush(StagingBuffer staging, LightStorage light) {
var capacity = light.capacity();
@ -40,7 +37,7 @@ public class LightBuffers {
return;
}
GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.LIGHT_LUT_BINDING, lut.handle(), 0, lut.byteCapacity());
GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.LIGHT_SECTION_BINDING, sections.handle(), 0, sections.byteCapacity());
GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.LIGHT_LUT, lut.handle(), 0, lut.byteCapacity());
GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.LIGHT_SECTION, sections.handle(), 0, sections.byteCapacity());
}
}

View file

@ -18,11 +18,11 @@ import dev.engine_room.flywheel.backend.engine.CommonCrumbling;
import dev.engine_room.flywheel.backend.engine.DrawManager;
import dev.engine_room.flywheel.backend.engine.GroupKey;
import dev.engine_room.flywheel.backend.engine.InstancerKey;
import dev.engine_room.flywheel.backend.engine.LightStorage;
import dev.engine_room.flywheel.backend.engine.MaterialEncoder;
import dev.engine_room.flywheel.backend.engine.MaterialRenderState;
import dev.engine_room.flywheel.backend.engine.MeshPool;
import dev.engine_room.flywheel.backend.engine.TextureBinder;
import dev.engine_room.flywheel.backend.engine.embed.LightStorage;
import dev.engine_room.flywheel.backend.engine.uniform.Uniforms;
import dev.engine_room.flywheel.backend.gl.GlStateTracker;
import dev.engine_room.flywheel.backend.gl.TextureBuffer;

View file

@ -4,7 +4,7 @@ import org.lwjgl.opengl.GL32;
import org.lwjgl.system.MemoryUtil;
import dev.engine_room.flywheel.backend.Samplers;
import dev.engine_room.flywheel.backend.engine.embed.LightStorage;
import dev.engine_room.flywheel.backend.engine.LightStorage;
import dev.engine_room.flywheel.backend.gl.TextureBuffer;
import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer;
import dev.engine_room.flywheel.lib.memory.MemoryBlock;
@ -16,8 +16,8 @@ public class InstancedLight {
private final TextureBuffer sectionsTexture;
public InstancedLight() {
sections = new GlBuffer();
lut = new GlBuffer();
sections = new GlBuffer();
lutTexture = new TextureBuffer(GL32.GL_R32UI);
sectionsTexture = new TextureBuffer(GL32.GL_R32UI);
}
@ -54,8 +54,8 @@ public class InstancedLight {
}
public void delete() {
sections.delete();
lut.delete();
sections.delete();
lutTexture.delete();
sectionsTexture.delete();
}

View file

@ -7,7 +7,7 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import dev.engine_room.flywheel.backend.engine.embed.LightUpdateHolder;
import dev.engine_room.flywheel.backend.LightUpdateHolder;
import net.minecraft.client.multiplayer.ClientChunkCache;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.core.SectionPos;

View file

@ -3,5 +3,5 @@
#define _FLW_MODEL_INDEX_BUFFER_BINDING 2
#define _FLW_MODEL_BUFFER_BINDING 3
#define _FLW_DRAW_BUFFER_BINDING 4
#define _FLW_LIGHT_LUT_BINDING 5
#define _FLW_LIGHT_SECTIONS_BINDING 6
#define _FLW_LIGHT_LUT_BUFFER_BINDING 5
#define _FLW_LIGHT_SECTIONS_BUFFER_BINDING 6

View file

@ -1,10 +1,10 @@
#include "flywheel:internal/light_lut.glsl"
layout(std430, binding = _FLW_LIGHT_LUT_BINDING) restrict readonly buffer LightLut {
layout(std430, binding = _FLW_LIGHT_LUT_BUFFER_BINDING) restrict readonly buffer LightLut {
uint _flw_lightLut[];
};
layout(std430, binding = _FLW_LIGHT_SECTIONS_BINDING) restrict readonly buffer LightSections {
layout(std430, binding = _FLW_LIGHT_SECTIONS_BUFFER_BINDING) restrict readonly buffer LightSections {
uint _flw_lightSections[];
};

View file

@ -24,7 +24,7 @@ abstract class ClientChunkCacheMixin {
var manager = VisualizationManagerImpl.get(level);
if (manager != null) {
manager.enqueueLightUpdateSection(pos.asLong());
manager.onLightUpdate(pos.asLong());
}
}
}

View file

@ -106,11 +106,11 @@ public class VisualizationManagerImpl implements VisualizationManager {
.ifFalse(update)
.plan()
.then(SimplePlan.of(() -> {
if (blockEntities.lightSectionsDirty() || entities.lightSectionsDirty() || effects.lightSectionsDirty()) {
if (blockEntities.areGpuLightSectionsDirty() || entities.areGpuLightSectionsDirty() || effects.areGpuLightSectionsDirty()) {
var out = new LongOpenHashSet();
out.addAll(blockEntities.lightSections());
out.addAll(entities.lightSections());
out.addAll(effects.lightSections());
out.addAll(blockEntities.gpuLightSections());
out.addAll(entities.gpuLightSections());
out.addAll(effects.gpuLightSections());
engine.lightSections(out);
}
}))
@ -302,6 +302,12 @@ public class VisualizationManagerImpl implements VisualizationManager {
}
}
public void onLightUpdate(long section) {
blockEntities.onLightUpdate(section);
entities.onLightUpdate(section);
effects.onLightUpdate(section);
}
/**
* Free all acquired resources and delete this manager.
*/
@ -315,13 +321,4 @@ public class VisualizationManagerImpl implements VisualizationManager {
effects.invalidate();
engine.delete();
}
public void enqueueLightUpdateSection(long section) {
blockEntities.getStorage()
.enqueueLightUpdateSection(section);
entities.getStorage()
.enqueueLightUpdateSection(section);
effects.getStorage()
.enqueueLightUpdateSection(section);
}
}

View file

@ -69,8 +69,20 @@ public class BlockEntityStorage extends Storage<BlockEntity> {
@Override
public void remove(BlockEntity obj) {
super.remove(obj);
posLookup.remove(obj.getBlockPos()
.asLong());
super.remove(obj);
}
@Override
public void recreateAll(float partialTick) {
posLookup.clear();
super.recreateAll(partialTick);
}
@Override
public void invalidate() {
posLookup.clear();
super.invalidate();
}
}

View file

@ -54,10 +54,6 @@ public class VisualManagerImpl<T, S extends Storage<T>> implements VisualManager
queue.add(Transaction.update(obj));
}
public void invalidate() {
getStorage().invalidate();
}
public void processQueue(float partialTick) {
var storage = getStorage();
Transaction<T> transaction;
@ -76,13 +72,22 @@ public class VisualManagerImpl<T, S extends Storage<T>> implements VisualManager
.then(storage.tickPlan());
}
public boolean lightSectionsDirty() {
return getStorage().smoothLitStorage()
.sectionsDirty();
public void onLightUpdate(long section) {
getStorage().lightUpdatedVisuals()
.onLightUpdate(section);
}
public LongSet lightSections() {
return getStorage().smoothLitStorage()
public boolean areGpuLightSectionsDirty() {
return getStorage().shaderLightVisuals()
.isDirty();
}
public LongSet gpuLightSections() {
return getStorage().shaderLightVisuals()
.sections();
}
public void invalidate() {
getStorage().invalidate();
}
}

View file

@ -16,7 +16,6 @@ import dev.engine_room.flywheel.lib.task.SimplyComposedPlan;
import dev.engine_room.flywheel.lib.task.Synchronizer;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
@ -24,15 +23,15 @@ import it.unimi.dsi.fastutil.objects.ObjectArrayList;
/**
* Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated.
*/
public class LightUpdatedStorage {
public class LightUpdatedVisualStorage {
private static final long NEVER_UPDATED = Long.MIN_VALUE;
private static final long INITIAL_UPDATE_ID = NEVER_UPDATED + 1;
private final Map<LightUpdatedVisual, LongSet> visuals2Sections = new WeakHashMap<>();
private final Long2ObjectMap<List<Updater>> sections2Visuals = new Long2ObjectOpenHashMap<>();
private final Map<LightUpdatedVisual, LongSet> visual2Sections = new WeakHashMap<>();
private final Long2ObjectMap<List<Updater>> section2Updaters = new Long2ObjectOpenHashMap<>();
private final Queue<MovedVisual> movedVisuals = new ConcurrentLinkedQueue<>();
private final LongSet sectionsUpdatedThisFrame = new LongOpenHashSet();
private final Queue<MovedVisual> movedVisuals = new ConcurrentLinkedQueue<>();
private long updateId = INITIAL_UPDATE_ID;
@ -54,9 +53,9 @@ public class LightUpdatedStorage {
Updater.Context updaterContext = new Updater.Context(updateId, context.partialTick());
for (long section : sectionsUpdatedThisFrame) {
var visuals = sections2Visuals.get(section);
if (visuals != null && !visuals.isEmpty()) {
taskExecutor.execute(() -> Distribute.tasks(taskExecutor, updaterContext, sync, visuals, Updater::updateLight));
var updaters = section2Updaters.get(section);
if (updaters != null && !updaters.isEmpty()) {
taskExecutor.execute(() -> Distribute.tasks(taskExecutor, updaterContext, sync, updaters, Updater::updateLight));
} else {
sync.decrementAndEventuallyRun();
}
@ -69,7 +68,7 @@ public class LightUpdatedStorage {
while ((moved = movedVisuals.poll()) != null) {
// If the visual isn't there when we try to remove it that means it was deleted before we got to it.
if (remove(moved.visual)) {
updateTracking(moved.tracker, moved.visual);
addInner(moved.visual, moved.tracker);
}
}
}
@ -86,43 +85,32 @@ public class LightUpdatedStorage {
return out;
}
public boolean isEmpty() {
return visuals2Sections.isEmpty();
}
public void add(SectionCollectorImpl tracker, LightUpdatedVisual visual) {
var moved = new MovedVisual(tracker, visual);
public void add(LightUpdatedVisual visual, SectionTracker tracker) {
var moved = new MovedVisual(visual, tracker);
tracker.addListener(() -> movedVisuals.add(moved));
updateTracking(tracker, visual);
addInner(visual, tracker);
}
public void updateTracking(SectionCollectorImpl tracker, LightUpdatedVisual visual) {
if (tracker.sections.isEmpty()) {
private void addInner(LightUpdatedVisual visual, SectionTracker tracker) {
if (tracker.sections().isEmpty()) {
// Add the visual to the map even if sections is empty, this way we can distinguish from deleted visuals
visuals2Sections.put(visual, LongSet.of());
visual2Sections.put(visual, LongSet.of());
// Don't bother creating an updater if the visual isn't in any sections.
return;
}
// Create a copy of the array, so we know what section to remove the visual from later.
var sections = new LongArraySet(tracker.sections);
visuals2Sections.put(visual, sections);
var sections = tracker.sections();
visual2Sections.put(visual, sections);
var updater = createUpdater(visual, sections.size());
for (long section : sections) {
sections2Visuals.computeIfAbsent(section, $ -> new ObjectArrayList<>())
section2Updaters.computeIfAbsent(section, $ -> new ObjectArrayList<>())
.add(updater);
}
}
public void enqueueLightUpdateSection(long section) {
sectionsUpdatedThisFrame.add(section);
}
/**
* Remove the visual from this storage.
*
@ -130,31 +118,36 @@ public class LightUpdatedStorage {
* @return {@code true} if the visual was removed, {@code false} otherwise.
*/
public boolean remove(LightUpdatedVisual visual) {
var sections = visuals2Sections.remove(visual);
var sections = visual2Sections.remove(visual);
if (sections == null) {
return false;
}
for (long section : sections) {
List<Updater> listeners = sections2Visuals.get(section);
if (listeners != null) {
listeners.remove(indexOfUpdater(listeners, visual));
List<Updater> updaters = section2Updaters.get(section);
if (updaters != null) {
updaters.remove(indexOfUpdater(updaters, visual));
}
}
return true;
}
public void clear() {
visuals2Sections.clear();
sections2Visuals.clear();
sectionsUpdatedThisFrame.clear();
public void onLightUpdate(long section) {
sectionsUpdatedThisFrame.add(section);
}
private static int indexOfUpdater(List<Updater> listeners, LightUpdatedVisual visual) {
for (int i = 0; i < listeners.size(); i++) {
if (listeners.get(i)
public void clear() {
visual2Sections.clear();
section2Updaters.clear();
sectionsUpdatedThisFrame.clear();
movedVisuals.clear();
}
private static int indexOfUpdater(List<Updater> updaters, LightUpdatedVisual visual) {
for (int i = 0; i < updaters.size(); i++) {
if (updaters.get(i)
.visual() == visual) {
return i;
}
@ -171,7 +164,7 @@ public class LightUpdatedStorage {
}
// Breaking this into 2 separate cases allows us to avoid the overhead of atomics in the common case.
sealed interface Updater {
private sealed interface Updater {
void updateLight(Context ctx);
LightUpdatedVisual visual();
@ -201,6 +194,6 @@ public class LightUpdatedStorage {
}
}
private record MovedVisual(SectionCollectorImpl tracker, LightUpdatedVisual visual) {
private record MovedVisual(LightUpdatedVisual visual, SectionTracker tracker) {
}
}

View file

@ -3,20 +3,27 @@ package dev.engine_room.flywheel.impl.visualization.storage;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.Unmodifiable;
import dev.engine_room.flywheel.api.visual.SectionTrackedVisual;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.longs.LongSets;
public class SectionCollectorImpl implements SectionTrackedVisual.SectionCollector {
public final LongSet sections = new LongArraySet();
public class SectionTracker implements SectionTrackedVisual.SectionCollector {
private final List<Runnable> listeners = new ArrayList<>(2);
@Unmodifiable
private LongSet sections = LongSet.of();
@Unmodifiable
public LongSet sections() {
return sections;
}
@Override
public void sections(LongSet sections) {
this.sections.clear();
this.sections.addAll(sections);
this.sections = LongSets.unmodifiable(new LongArraySet(sections));
listeners.forEach(Runnable::run);
}

View file

@ -1,52 +0,0 @@
package dev.engine_room.flywheel.impl.visualization.storage;
import java.util.Map;
import org.jetbrains.annotations.Nullable;
import dev.engine_room.flywheel.api.visual.ShaderLightVisual;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
public class ShaderLightStorage {
private final Map<ShaderLightVisual, SectionCollectorImpl> visuals = new Reference2ObjectOpenHashMap<>();
@Nullable
private LongSet cachedSections;
public boolean sectionsDirty() {
return cachedSections == null;
}
public void markDirty() {
cachedSections = null;
}
public LongSet sections() {
cachedSections = new LongOpenHashSet();
for (var value : visuals.values()) {
cachedSections.addAll(value.sections);
}
return cachedSections;
}
public void remove(ShaderLightVisual visual) {
visuals.remove(visual);
}
public void add(SectionCollectorImpl tracker, ShaderLightVisual visual) {
visuals.put(visual, tracker);
tracker.addListener(this::markDirty);
if (!tracker.sections.isEmpty()) {
markDirty();
}
}
public void clear() {
visuals.clear();
}
}

View file

@ -0,0 +1,53 @@
package dev.engine_room.flywheel.impl.visualization.storage;
import java.util.Map;
import dev.engine_room.flywheel.api.visual.ShaderLightVisual;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap;
public class ShaderLightVisualStorage {
private final Map<ShaderLightVisual, SectionTracker> trackers = new Reference2ReferenceOpenHashMap<>();
private final LongSet sections = new LongOpenHashSet();
private boolean isDirty;
public LongSet sections() {
if (isDirty) {
sections.clear();
for (var tracker : trackers.values()) {
sections.addAll(tracker.sections());
}
isDirty = false;
}
return sections;
}
public boolean isDirty() {
return isDirty;
}
public void markDirty() {
isDirty = true;
}
public void add(ShaderLightVisual visual, SectionTracker tracker) {
trackers.put(visual, tracker);
tracker.addListener(this::markDirty);
if (!tracker.sections().isEmpty()) {
markDirty();
}
}
public void remove(ShaderLightVisual visual) {
trackers.remove(visual);
}
public void clear() {
trackers.clear();
markDirty();
}
}

View file

@ -25,14 +25,14 @@ import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
public abstract class Storage<T> {
protected final Supplier<VisualizationContext> visualizationContextSupplier;
private final Map<T, Visual> visuals = new Reference2ObjectOpenHashMap<>();
protected final PlanMap<DynamicVisual, DynamicVisual.Context> dynamicVisuals = new PlanMap<>();
protected final PlanMap<TickableVisual, TickableVisual.Context> tickableVisuals = new PlanMap<>();
protected final List<SimpleDynamicVisual> simpleDynamicVisuals = new ArrayList<>();
protected final List<SimpleTickableVisual> simpleTickableVisuals = new ArrayList<>();
protected final LightUpdatedStorage litVisuals = new LightUpdatedStorage();
protected final ShaderLightStorage smoothLitVisuals = new ShaderLightStorage();
private final Map<T, Visual> visuals = new Reference2ObjectOpenHashMap<>();
protected final LightUpdatedVisualStorage lightUpdatedVisuals = new LightUpdatedVisualStorage();
protected final ShaderLightVisualStorage shaderLightVisuals = new ShaderLightVisualStorage();
public Storage(Supplier<VisualizationContext> visualizationContextSupplier) {
this.visualizationContextSupplier = visualizationContextSupplier;
@ -42,6 +42,29 @@ public abstract class Storage<T> {
return visuals.values();
}
public Plan<DynamicVisual.Context> framePlan() {
return NestedPlan.of(dynamicVisuals, lightUpdatedVisuals.plan(), ForEachPlan.of(() -> simpleDynamicVisuals, SimpleDynamicVisual::beginFrame));
}
public Plan<TickableVisual.Context> tickPlan() {
return NestedPlan.of(tickableVisuals, ForEachPlan.of(() -> simpleTickableVisuals, SimpleTickableVisual::tick));
}
public LightUpdatedVisualStorage lightUpdatedVisuals() {
return lightUpdatedVisuals;
}
public ShaderLightVisualStorage shaderLightVisuals() {
return shaderLightVisuals;
}
/**
* Is the given object currently capable of being added?
*
* @return true if the object is currently capable of being visualized.
*/
public abstract boolean willAccept(T obj);
public void add(T obj, float partialTick) {
Visual visual = visuals.get(obj);
@ -57,13 +80,6 @@ public abstract class Storage<T> {
return;
}
if (visual instanceof TickableVisual tickable) {
if (visual instanceof SimpleTickableVisual simpleTickable) {
simpleTickableVisuals.remove(simpleTickable);
} else {
tickableVisuals.remove(tickable);
}
}
if (visual instanceof DynamicVisual dynamic) {
if (visual instanceof SimpleDynamicVisual simpleDynamic) {
simpleDynamicVisuals.remove(simpleDynamic);
@ -71,12 +87,20 @@ public abstract class Storage<T> {
dynamicVisuals.remove(dynamic);
}
}
if (visual instanceof LightUpdatedVisual lit) {
litVisuals.remove(lit);
if (visual instanceof TickableVisual tickable) {
if (visual instanceof SimpleTickableVisual simpleTickable) {
simpleTickableVisuals.remove(simpleTickable);
} else {
tickableVisuals.remove(tickable);
}
}
if (visual instanceof ShaderLightVisual smoothLit) {
smoothLitVisuals.remove(smoothLit);
if (visual instanceof LightUpdatedVisual lightUpdated) {
lightUpdatedVisuals.remove(lightUpdated);
}
if (visual instanceof ShaderLightVisual shaderLight) {
shaderLightVisuals.remove(shaderLight);
}
visual.delete();
}
@ -91,12 +115,13 @@ public abstract class Storage<T> {
}
public void recreateAll(float partialTick) {
tickableVisuals.clear();
dynamicVisuals.clear();
simpleTickableVisuals.clear();
tickableVisuals.clear();
simpleDynamicVisuals.clear();
litVisuals.clear();
smoothLitVisuals.clear();
simpleTickableVisuals.clear();
lightUpdatedVisuals.clear();
shaderLightVisuals.clear();
visuals.replaceAll((obj, visual) -> {
visual.delete();
@ -110,15 +135,6 @@ public abstract class Storage<T> {
});
}
public void invalidate() {
tickableVisuals.clear();
dynamicVisuals.clear();
litVisuals.clear();
visuals.values()
.forEach(Visual::delete);
visuals.clear();
}
private void create(T obj, float partialTick) {
var visual = createRaw(obj, partialTick);
@ -131,27 +147,7 @@ public abstract class Storage<T> {
@Nullable
protected abstract Visual createRaw(T obj, float partialTick);
public Plan<DynamicVisual.Context> framePlan() {
return NestedPlan.of(dynamicVisuals, litVisuals.plan(), ForEachPlan.of(() -> simpleDynamicVisuals, SimpleDynamicVisual::beginFrame));
}
public Plan<TickableVisual.Context> tickPlan() {
return NestedPlan.of(tickableVisuals, ForEachPlan.of(() -> simpleTickableVisuals, SimpleTickableVisual::tick));
}
public void enqueueLightUpdateSection(long section) {
litVisuals.enqueueLightUpdateSection(section);
}
private void setup(Visual visual) {
if (visual instanceof TickableVisual tickable) {
if (visual instanceof SimpleTickableVisual simpleTickable) {
simpleTickableVisuals.add(simpleTickable);
} else {
tickableVisuals.add(tickable, tickable.planTick());
}
}
if (visual instanceof DynamicVisual dynamic) {
if (visual instanceof SimpleDynamicVisual simpleDynamic) {
simpleDynamicVisuals.add(simpleDynamic);
@ -160,30 +156,39 @@ public abstract class Storage<T> {
}
}
if (visual instanceof SectionTrackedVisual tracked) {
SectionCollectorImpl sectionProperty = new SectionCollectorImpl();
if (visual instanceof TickableVisual tickable) {
if (visual instanceof SimpleTickableVisual simpleTickable) {
simpleTickableVisuals.add(simpleTickable);
} else {
tickableVisuals.add(tickable, tickable.planTick());
}
}
// Give the visual a chance to fill in the property.
tracked.setSectionCollector(sectionProperty);
if (visual instanceof SectionTrackedVisual tracked) {
SectionTracker tracker = new SectionTracker();
// Give the visual a chance to invoke the collector.
tracked.setSectionCollector(tracker);
if (visual instanceof LightUpdatedVisual lightUpdated) {
litVisuals.add(sectionProperty, lightUpdated);
lightUpdatedVisuals.add(lightUpdated, tracker);
}
if (visual instanceof ShaderLightVisual shaderLight) {
smoothLitVisuals.add(sectionProperty, shaderLight);
shaderLightVisuals.add(shaderLight, tracker);
}
}
}
/**
* Is the given object currently capable of being added?
*
* @return true if the object is currently capable of being visualized.
*/
public abstract boolean willAccept(T obj);
public ShaderLightStorage smoothLitStorage() {
return smoothLitVisuals;
public void invalidate() {
dynamicVisuals.clear();
tickableVisuals.clear();
simpleDynamicVisuals.clear();
simpleTickableVisuals.clear();
lightUpdatedVisuals.clear();
shaderLightVisuals.clear();
visuals.values()
.forEach(Visual::delete);
visuals.clear();
}
}

View file

@ -194,7 +194,7 @@ public class MinecartVisual<T extends AbstractMinecart> extends SimpleEntityVisu
body.setTransform(stack)
.setChanged();
// TODO: Use LitVisual if possible.
// TODO: Use LightUpdatedVisual/ShaderLightVisual if possible.
updateLight(partialTick);
}