>> indices) {
+ final var out = new IntArrayList();
+ out.add(baseX);
+ out.add(indices.size());
+ for (int i = 0; i < indices.size(); i++) {
+ out.add(0);
+ }
+ for (int x = 0; x < indices.size(); x++) {
+ final var yLookup = indices.get(x);
+ if (yLookup == null) {
+ out.set(x + 2, 0);
+ continue;
+ }
+ // ensure that the base position and size dont cross a (64 byte) cache line
+ if ((out.size() & 0xF) == 0xF) {
+ out.add(0);
+ }
+
+ final var baseYIndex = out.size();
+ out.set(x + 2, baseYIndex);
+
+ final var yIndices = yLookup.right();
+ out.add(yLookup.leftInt());
+ out.add(yIndices.size());
+ for (int i = 0; i < indices.size(); i++) {
+ out.add(0);
+ }
+
+ for (int y = 0; y < yIndices.size(); y++) {
+ final var zLookup = yIndices.get(y);
+ if (zLookup == null) {
+ out.set(baseYIndex + y + 2, 0);
+ continue;
+ }
+ // ensure that the base position and size dont cross a (64 byte) cache line
+ if ((out.size() & 0xF) == 0xF) {
+ out.add(0);
+ }
+ out.set(baseYIndex + y + 2, out.size());
+ zLookup.set(1, zLookup.size() - 2);
+ out.addAll(zLookup);
+ }
+ }
+ return out;
+ }
+}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java
new file mode 100644
index 000000000..909dbdb95
--- /dev/null
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/LightStorage.java
@@ -0,0 +1,385 @@
+package dev.engine_room.flywheel.backend.engine;
+
+import java.util.BitSet;
+
+import org.jetbrains.annotations.Nullable;
+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;
+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;
+import net.minecraft.world.level.LevelAccessor;
+import net.minecraft.world.level.LightLayer;
+import net.minecraft.world.level.lighting.LayerLightEventListener;
+
+/**
+ * TODO: AO data
+ * A managed arena of light sections for uploading to the GPU.
+ *
+ * Each section represents an 18x18x18 block volume of light data.
+ * The "edges" are taken from the neighboring sections, so that each
+ * shader invocation only needs to access a single section of data.
+ * Even still, neighboring shader invocations may need to access other sections.
+ *
+ *
Sections are logically stored as a 9x9x9 array of longs,
+ * where each long holds a 2x2x2 array of light data.
+ *
Both the greater array and the longs are packed in x, z, y order.
+ *
+ *
Thus, each section occupies 5832 bytes.
+ */
+public class LightStorage {
+ public static final long SECTION_SIZE_BYTES = 9 * 9 * 9 * 8;
+ private static final int DEFAULT_ARENA_CAPACITY_SECTIONS = 64;
+ private static final int INVALID_SECTION = -1;
+
+ private final LevelAccessor level;
+
+ private final Arena arena;
+ private final Long2IntMap section2ArenaIndex = new Long2IntOpenHashMap();
+ {
+ section2ArenaIndex.defaultReturnValue(INVALID_SECTION);
+ }
+
+ private final BitSet changed = new BitSet();
+ private boolean needsLutRebuild = false;
+
+ @Nullable
+ private LongSet requestedSections;
+
+ public LightStorage(LevelAccessor level) {
+ this.level = level;
+
+ 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(() -> {
+ var updatedSections = LightUpdateHolder.get(level)
+ .getAndClearUpdatedSections();
+
+ if (updatedSections.isEmpty() && requestedSections == null) {
+ return;
+ }
+
+ removeUnusedSections();
+
+ // 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 (section2ArenaIndex.containsKey(section)) {
+ sectionsToCollect.add(section);
+ }
+ }
+ }
+ }
+ }
+
+ // Now actually do the collection.
+ // TODO: Should this be done in parallel?
+ sectionsToCollect.forEach(this::collectSection);
+
+ requestedSections = null;
+ });
+ }
+
+ 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 (!this.requestedSections.contains(section)) {
+ arena.free(entry.getIntValue());
+ needsLutRebuild = true;
+ it.remove();
+ }
+ }
+ }
+
+ public int capacity() {
+ return arena.capacity();
+ }
+
+ public void collectSection(long section) {
+ var lightEngine = level.getLightEngine();
+
+ var blockLight = lightEngine.getLayerListener(LightLayer.BLOCK);
+ var skyLight = lightEngine.getLayerListener(LightLayer.SKY);
+
+ int index = indexForSection(section);
+
+ changed.set(index);
+
+ long ptr = arena.indexToPointer(index);
+
+ // Zero it out first. This is basically free and makes it easier to handle missing sections later.
+ MemoryUtil.memSet(ptr, 0, SECTION_SIZE_BYTES);
+
+ collectCenter(blockLight, skyLight, ptr, section);
+
+ for (SectionEdge i : SectionEdge.values()) {
+ collectYZPlane(blockLight, skyLight, ptr, SectionPos.offset(section, i.sectionOffset, 0, 0), i);
+ collectXZPlane(blockLight, skyLight, ptr, SectionPos.offset(section, 0, i.sectionOffset, 0), i);
+ collectXYPlane(blockLight, skyLight, ptr, SectionPos.offset(section, 0, 0, i.sectionOffset), i);
+
+ for (SectionEdge j : SectionEdge.values()) {
+ collectXStrip(blockLight, skyLight, ptr, SectionPos.offset(section, 0, i.sectionOffset, j.sectionOffset), i, j);
+ collectYStrip(blockLight, skyLight, ptr, SectionPos.offset(section, i.sectionOffset, 0, j.sectionOffset), i, j);
+ collectZStrip(blockLight, skyLight, ptr, SectionPos.offset(section, i.sectionOffset, j.sectionOffset, 0), i, j);
+ }
+ }
+
+ collectCorners(blockLight, skyLight, ptr, section);
+ }
+
+ private void collectXStrip(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section, SectionEdge y, SectionEdge z) {
+ var pos = SectionPos.of(section);
+ var blockData = blockLight.getDataLayerData(pos);
+ var skyData = skyLight.getDataLayerData(pos);
+ if (blockData == null || skyData == null) {
+ return;
+ }
+ for (int x = 0; x < 16; x++) {
+ write(ptr, x, y.relative, z.relative, blockData.get(x, y.pos, z.pos), skyData.get(x, y.pos, z.pos));
+ }
+ }
+
+ private void collectYStrip(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section, SectionEdge x, SectionEdge z) {
+ var pos = SectionPos.of(section);
+ var blockData = blockLight.getDataLayerData(pos);
+ var skyData = skyLight.getDataLayerData(pos);
+ if (blockData == null || skyData == null) {
+ return;
+ }
+ for (int y = 0; y < 16; y++) {
+ write(ptr, x.relative, y, z.relative, blockData.get(x.pos, y, z.pos), skyData.get(x.pos, y, z.pos));
+ }
+ }
+
+ private void collectZStrip(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section, SectionEdge x, SectionEdge y) {
+ var pos = SectionPos.of(section);
+ var blockData = blockLight.getDataLayerData(pos);
+ var skyData = skyLight.getDataLayerData(pos);
+ if (blockData == null || skyData == null) {
+ return;
+ }
+ for (int z = 0; z < 16; z++) {
+ write(ptr, x.relative, y.relative, z, blockData.get(x.pos, y.pos, z), skyData.get(x.pos, y.pos, z));
+ }
+ }
+
+ private void collectYZPlane(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section, SectionEdge x) {
+ var pos = SectionPos.of(section);
+ var blockData = blockLight.getDataLayerData(pos);
+ var skyData = skyLight.getDataLayerData(pos);
+ if (blockData == null || skyData == null) {
+ return;
+ }
+ for (int y = 0; y < 16; y++) {
+ for (int z = 0; z < 16; z++) {
+ write(ptr, x.relative, y, z, blockData.get(x.pos, y, z), skyData.get(x.pos, y, z));
+ }
+ }
+ }
+
+ private void collectXZPlane(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section, SectionEdge y) {
+ var pos = SectionPos.of(section);
+ var blockData = blockLight.getDataLayerData(pos);
+ var skyData = skyLight.getDataLayerData(pos);
+ if (blockData == null || skyData == null) {
+ return;
+ }
+ for (int z = 0; z < 16; z++) {
+ for (int x = 0; x < 16; x++) {
+ write(ptr, x, y.relative, z, blockData.get(x, y.pos, z), skyData.get(x, y.pos, z));
+ }
+ }
+ }
+
+ private void collectXYPlane(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section, SectionEdge z) {
+ var pos = SectionPos.of(section);
+ var blockData = blockLight.getDataLayerData(pos);
+ var skyData = skyLight.getDataLayerData(pos);
+ if (blockData == null || skyData == null) {
+ return;
+ }
+ for (int y = 0; y < 16; y++) {
+ for (int x = 0; x < 16; x++) {
+ write(ptr, x, y, z.relative, blockData.get(x, y, z.pos), skyData.get(x, y, z.pos));
+ }
+ }
+ }
+
+ private void collectCenter(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section) {
+ var pos = SectionPos.of(section);
+ var blockData = blockLight.getDataLayerData(pos);
+ var skyData = skyLight.getDataLayerData(pos);
+ if (blockData == null || skyData == null) {
+ return;
+ }
+ for (int y = 0; y < 16; y++) {
+ for (int z = 0; z < 16; z++) {
+ for (int x = 0; x < 16; x++) {
+ write(ptr, x, y, z, blockData.get(x, y, z), skyData.get(x, y, z));
+ }
+ }
+ }
+ }
+
+ private void collectCorners(LayerLightEventListener blockLight, LayerLightEventListener skyLight, long ptr, long section) {
+ var blockPos = new BlockPos.MutableBlockPos();
+ int xMin = SectionPos.sectionToBlockCoord(SectionPos.x(section));
+ int yMin = SectionPos.sectionToBlockCoord(SectionPos.y(section));
+ int zMin = SectionPos.sectionToBlockCoord(SectionPos.z(section));
+
+ for (SectionEdge x : SectionEdge.values()) {
+ for (SectionEdge y : SectionEdge.values()) {
+ for (SectionEdge z : SectionEdge.values()) {
+ blockPos.set(x.relative + xMin, y.relative + yMin, z.relative + zMin);
+ write(ptr, x.relative, y.relative, z.relative, blockLight.getLightValue(blockPos), skyLight.getLightValue(blockPos));
+ }
+ }
+ }
+ }
+
+ /**
+ * Write to the given section.
+ * @param ptr Pointer to the base of a section's data.
+ * @param x X coordinate in the section, from [-1, 16].
+ * @param y Y coordinate in the section, from [-1, 16].
+ * @param z Z coordinate in the section, from [-1, 16].
+ * @param block The block light level, from [0, 15].
+ * @param sky The sky light level, from [0, 15].
+ */
+ private void write(long ptr, int x, int y, int z, int block, int sky) {
+ int x1 = x + 1;
+ int y1 = y + 1;
+ int z1 = z + 1;
+
+ int offset = x1 + z1 * 18 + y1 * 18 * 18;
+
+ long packedByte = (block & 0xF) | ((sky & 0xF) << 4);
+
+ MemoryUtil.memPutByte(ptr + offset, (byte) packedByte);
+ }
+
+ /**
+ * Get a pointer to the base of the given section.
+ * If the section is not yet reserved, allocate a chunk in the arena.
+ * @param section The section to write to.
+ * @return A raw pointer to the base of the section.
+ */
+ private long ptrForSection(long section) {
+ return arena.indexToPointer(indexForSection(section));
+ }
+
+ private int indexForSection(long section) {
+ int out = section2ArenaIndex.get(section);
+
+ // Need to allocate.
+ if (out == INVALID_SECTION) {
+ out = arena.alloc();
+ section2ArenaIndex.put(section, out);
+ needsLutRebuild = true;
+ }
+ return out;
+ }
+
+ public void delete() {
+ arena.delete();
+ }
+
+ public boolean checkNeedsLutRebuildAndClear() {
+ var out = needsLutRebuild;
+ needsLutRebuild = false;
+ return out;
+ }
+
+ public void uploadChangedSections(StagingBuffer staging, int dstVbo) {
+ for (int i = changed.nextSetBit(0); i >= 0; i = changed.nextSetBit(i + 1)) {
+ staging.enqueueCopy(arena.indexToPointer(i), SECTION_SIZE_BYTES, dstVbo, i * SECTION_SIZE_BYTES);
+ }
+ changed.clear();
+ }
+
+ public void upload(GlBuffer buffer) {
+ if (changed.isEmpty()) {
+ return;
+ }
+
+ buffer.upload(arena.indexToPointer(0), arena.capacity() * SECTION_SIZE_BYTES);
+ changed.clear();
+ }
+
+ public IntArrayList createLut() {
+ // TODO: incremental lut updates
+ return LightLut.buildLut(section2ArenaIndex);
+ }
+
+ private enum SectionEdge {
+ LOW(15, -1, -1),
+ HIGH(0, 16, 1),
+ ;
+
+ /**
+ * The position in the section to collect.
+ */
+ private final int pos;
+ /**
+ * The position relative to the main section.
+ */
+ private final int relative;
+ /**
+ * The offset to the neighboring section.
+ */
+ private final int sectionOffset;
+
+ SectionEdge(int pos, int relative, int sectionOffset) {
+ this.pos = pos;
+ this.relative = relative;
+ this.sectionOffset = sectionOffset;
+ }
+ }
+}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedEnvironment.java
similarity index 64%
rename from common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java
rename to common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedEnvironment.java
index 07adb4805..fef623172 100644
--- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/AbstractEmbeddedEnvironment.java
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedEnvironment.java
@@ -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;
@@ -15,32 +16,38 @@
import dev.engine_room.flywheel.backend.compile.ContextShader;
import dev.engine_room.flywheel.backend.engine.EngineImpl;
import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
-import dev.engine_room.flywheel.backend.util.AtomicReferenceCounted;
import net.minecraft.core.Vec3i;
-public abstract class AbstractEmbeddedEnvironment extends AtomicReferenceCounted implements Environment, VisualEmbedding {
- protected final Matrix4f pose = new Matrix4f();
- protected final Matrix3f normal = new Matrix3f();
- private final Matrix4f poseComposed = new Matrix4f();
- private final Matrix3f normalComposed = new Matrix3f();
- private final InstancerProvider instancerProvider;
+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 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 Instancer instancer(InstanceType 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);
}
};
+ }
- // Acquire the reference owned by the visual that created this.
- acquire();
+ public EmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) {
+ this(engine, renderStage, null);
}
@Override
@@ -49,33 +56,6 @@ public void transforms(Matrix4fc pose, Matrix3fc normal) {
this.normal.set(normal);
}
- public void flush() {
- poseComposed.identity();
- normalComposed.identity();
-
- composeMatrices(poseComposed, normalComposed);
- }
-
- @Override
- public void setupDraw(GlProgram program) {
- setupLight(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;
@@ -88,37 +68,56 @@ public Vec3i renderOrigin() {
@Override
public VisualEmbedding createEmbedding() {
- var out = new NestedEmbeddedEnvironment(this, engine, renderStage);
+ var out = new EmbeddedEnvironment(engine, renderStage, this);
engine.environmentStorage()
.track(out);
return out;
}
- /**
- * Called by visuals
- */
@Override
- public void delete() {
- // Release the reference owned by the visual that created this.
- // Note that visuals don't explicitly call acquire, instead the
- // storage acquired a reference when this was constructed.
- release();
+ 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);
}
- /**
- * Called when referenceCount goes to 0
- */
@Override
- public void _delete() {
- engine.environmentStorage().enqueueDeletion(this);
+ 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);
}
- public abstract void setupLight(GlProgram program);
+ 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 abstract void composeMatrices(Matrix4f pose, Matrix3f normal);
+ public boolean isDeleted() {
+ return deleted;
+ }
/**
- * Called in EnvironmentStorage#flush
+ * Called by visuals
*/
- public abstract void actuallyDelete();
+ @Override
+ public void delete() {
+ deleted = true;
+ }
}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedLightTexture.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedLightTexture.java
deleted file mode 100644
index 7e4325fb9..000000000
--- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedLightTexture.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package dev.engine_room.flywheel.backend.engine.embed;
-
-import static org.lwjgl.opengl.GL11.GL_LINEAR;
-import static org.lwjgl.opengl.GL11.GL_TEXTURE_MAG_FILTER;
-import static org.lwjgl.opengl.GL11.GL_TEXTURE_MIN_FILTER;
-import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_S;
-import static org.lwjgl.opengl.GL11.GL_TEXTURE_WRAP_T;
-import static org.lwjgl.opengl.GL11.GL_UNPACK_ALIGNMENT;
-import static org.lwjgl.opengl.GL11.GL_UNPACK_ROW_LENGTH;
-import static org.lwjgl.opengl.GL11.GL_UNPACK_SKIP_PIXELS;
-import static org.lwjgl.opengl.GL11.GL_UNPACK_SKIP_ROWS;
-import static org.lwjgl.opengl.GL11.GL_UNSIGNED_BYTE;
-import static org.lwjgl.opengl.GL11.glPixelStorei;
-import static org.lwjgl.opengl.GL11.glTexParameteri;
-import static org.lwjgl.opengl.GL12.GL_TEXTURE_3D;
-import static org.lwjgl.opengl.GL12.GL_TEXTURE_WRAP_R;
-import static org.lwjgl.opengl.GL12.GL_UNPACK_IMAGE_HEIGHT;
-import static org.lwjgl.opengl.GL12.GL_UNPACK_SKIP_IMAGES;
-import static org.lwjgl.opengl.GL12.glTexImage3D;
-import static org.lwjgl.opengl.GL12.glTexSubImage3D;
-import static org.lwjgl.opengl.GL14.GL_MIRRORED_REPEAT;
-
-import org.jetbrains.annotations.Nullable;
-import org.lwjgl.opengl.GL30;
-
-import dev.engine_room.flywheel.backend.gl.GlTexture;
-import net.minecraft.util.Mth;
-
-public class EmbeddedLightTexture {
- @Nullable
- private GlTexture texture;
-
- public int sizeX;
- public int sizeY;
- public int sizeZ;
-
- public void bind() {
-
- texture().bind();
- }
-
- private GlTexture texture() {
- if (texture == null) {
- texture = new GlTexture(GL_TEXTURE_3D);
- }
- return texture;
- }
-
- public void ensureCapacity(int sizeX, int sizeY, int sizeZ) {
- sizeX = Mth.smallestEncompassingPowerOfTwo(sizeX);
- sizeY = Mth.smallestEncompassingPowerOfTwo(sizeY);
- sizeZ = Mth.smallestEncompassingPowerOfTwo(sizeZ);
-
- if (sizeX > this.sizeX || sizeY > this.sizeY || sizeZ > this.sizeZ) {
- this.sizeX = sizeX;
- this.sizeY = sizeY;
- this.sizeZ = sizeZ;
-
- glTexImage3D(GL_TEXTURE_3D, 0, GL30.GL_RG8, sizeX, sizeY, sizeZ, 0, GL30.GL_RG, GL_UNSIGNED_BYTE, 0);
-
- glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
- glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_MIRRORED_REPEAT);
- glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
- }
- }
-
- public void upload(long ptr, int sizeX, int sizeY, int sizeZ) {
- glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
- glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
- glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
- glPixelStorei(GL_UNPACK_SKIP_IMAGES, 0);
- glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0);
- glPixelStorei(GL_UNPACK_ALIGNMENT, (int) EmbeddedLightVolume.STRIDE);
-
- glTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 0, sizeX, sizeY, sizeZ, GL30.GL_RG, GL_UNSIGNED_BYTE, ptr);
-
- glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // 4 is the default
- }
-
- public void delete() {
- if (texture != null) {
- texture.delete();
- }
- }
-}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedLightVolume.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedLightVolume.java
deleted file mode 100644
index c815ae3ea..000000000
--- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddedLightVolume.java
+++ /dev/null
@@ -1,187 +0,0 @@
-package dev.engine_room.flywheel.backend.engine.embed;
-
-import org.jetbrains.annotations.Nullable;
-import org.lwjgl.system.MemoryUtil;
-
-import dev.engine_room.flywheel.lib.memory.MemoryBlock;
-import net.minecraft.core.BlockPos;
-import net.minecraft.world.level.BlockAndTintGetter;
-import net.minecraft.world.level.LightLayer;
-
-public class EmbeddedLightVolume {
- public static final long STRIDE = Short.BYTES;
-
- private int minX;
- private int minY;
- private int minZ;
- private int maxX;
- private int maxY;
- private int maxZ;
-
- private final BlockPos.MutableBlockPos scratchPos = new BlockPos.MutableBlockPos();
-
- @Nullable
- protected MemoryBlock memoryBlock;
- protected boolean empty = true;
-
- public boolean empty() {
- return empty;
- }
-
- public void collect(BlockAndTintGetter level, int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) {
- maybeExpandForBox(minX, minY, minZ, sizeX, sizeY, sizeZ);
-
- empty = false;
-
- for (int z = minZ; z < minZ + sizeZ; z++) {
- for (int y = minY; y < minY + sizeY; y++) {
- for (int x = minX; x < minX + sizeX; x++) {
- paintLight(level, x, y, z);
- }
- }
- }
- }
-
- private void paintLight(BlockAndTintGetter level, int x, int y, int z) {
- scratchPos.set(x, y, z);
-
- int block = level.getBrightness(LightLayer.BLOCK, scratchPos);
- int sky = level.getBrightness(LightLayer.SKY, scratchPos);
-
- long ptr = this.memoryBlock.ptr() + offset(x - x(), y - y(), z - z(), sizeX(), sizeY());
- MemoryUtil.memPutShort(ptr, (short) ((block << 4) | sky << 12));
- }
-
- private void maybeExpandForBox(int x, int y, int z, int sizeX, int sizeY, int sizeZ) {
- if (empty || memoryBlock == null) {
- // We're either brand new or recently #clear'd,
- // so none of the previous min/max values have any meaning.
- this.minX = x;
- this.minY = y;
- this.minZ = z;
- this.maxX = x + sizeX;
- this.maxY = y + sizeY;
- this.maxZ = z + sizeZ;
-
- int volume = sizeX * sizeY * sizeZ;
- long neededSize = volume * STRIDE;
-
- if (memoryBlock == null) {
- memoryBlock = MemoryBlock.malloc(neededSize);
- } else if (memoryBlock.size() < neededSize) {
- // There's some memory left over from before the last #clear,
- // but not enough to hold this initial box. Need to grow the block.
- memoryBlock.realloc(neededSize);
- }
- // else: we have enough memory left over to hold this box, nothing to do!
- return;
- }
-
- int oldMinX = this.minX;
- int oldMinY = this.minY;
- int oldMinZ = this.minZ;
- int oldSizeX = this.sizeX();
- int oldSizeY = this.sizeY();
- int oldSizeZ = this.sizeZ();
- boolean changed = false;
-
- if (x < this.minX) {
- this.minX = x;
- changed = true;
- }
- if (y < this.minY) {
- this.minY = y;
- changed = true;
- }
- if (z < this.minZ) {
- this.minZ = z;
- changed = true;
- }
- if (x + sizeX > this.maxX) {
- this.maxX = x + sizeX;
- changed = true;
- }
- if (y + sizeY > this.maxY) {
- this.maxY = y + sizeY;
- changed = true;
- }
- if (z + sizeZ > this.maxZ) {
- this.maxZ = z + sizeZ;
- changed = true;
- }
-
- if (!changed) {
- return;
- }
-
- int volume = volume();
-
- memoryBlock = memoryBlock.realloc(volume * STRIDE);
-
- int xOff = oldMinX - minX;
- int yOff = oldMinY - minY;
- int zOff = oldMinZ - minZ;
-
- blit(memoryBlock.ptr(), 0, 0, 0, oldSizeX, oldSizeY, memoryBlock.ptr(), xOff, yOff, zOff, sizeX(), sizeY(), oldSizeX, oldSizeY, oldSizeZ);
- }
-
- public static void blit(long src, int srcX, int srcY, int srcZ, int srcSizeX, int srcSizeY, long dst, int dstX, int dstY, int dstZ, int dstSizeX, int dstSizeY, int sizeX, int sizeY, int sizeZ) {
- for (int z = 0; z < sizeZ; z++) {
- for (int y = 0; y < sizeY; y++) {
- for (int x = 0; x < sizeX; x++) {
- long srcPtr = src + offset(x + srcX, y + srcY, z + srcZ, srcSizeX, srcSizeY);
- long dstPtr = dst + offset(x + dstX, y + dstY, z + dstZ, dstSizeX, dstSizeY);
-
- MemoryUtil.memPutShort(dstPtr, MemoryUtil.memGetShort(srcPtr));
- }
- }
- }
- }
-
- public static long offset(int x, int y, int z, int sizeX, int sizeY) {
- return (x + sizeX * (y + sizeY * z)) * STRIDE;
- }
-
- public void clear() {
- empty = true;
- }
-
- public void delete() {
- if (memoryBlock != null) {
- memoryBlock.free();
- memoryBlock = null;
- }
- }
-
- public long ptr() {
- return memoryBlock.ptr();
- }
-
- public int x() {
- return minX;
- }
-
- public int y() {
- return minY;
- }
-
- public int z() {
- return minZ;
- }
-
- public int sizeX() {
- return maxX - minX;
- }
-
- public int sizeY() {
- return maxY - minY;
- }
-
- public int sizeZ() {
- return maxZ - minZ;
- }
-
- public int volume() {
- return sizeX() * sizeY() * sizeZ();
- }
-}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddingUniforms.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddingUniforms.java
index eb0c58bef..e62f0b018 100644
--- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddingUniforms.java
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EmbeddingUniforms.java
@@ -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() {
+ }
}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Environment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Environment.java
index 91da73062..83fa60d62 100644
--- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Environment.java
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/Environment.java
@@ -6,11 +6,7 @@
public interface Environment {
ContextShader contextShader();
- void setupDraw(GlProgram drawProgram);
-
void setupCull(GlProgram cullProgram);
- void acquire();
-
- void release();
+ void setupDraw(GlProgram drawProgram);
}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EnvironmentStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EnvironmentStorage.java
new file mode 100644
index 000000000..9206b9cf3
--- /dev/null
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/EnvironmentStorage.java
@@ -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 environments = ReferenceSets.synchronize(new ReferenceLinkedOpenHashSet<>());
+
+ public void track(EmbeddedEnvironment environment) {
+ environments.add(environment);
+ }
+
+ public void flush() {
+ environments.removeIf(EmbeddedEnvironment::isDeleted);
+ environments.forEach(EmbeddedEnvironment::flush);
+ }
+}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/GlobalEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/GlobalEnvironment.java
index b4c852280..aaac1ca35 100644
--- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/GlobalEnvironment.java
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/GlobalEnvironment.java
@@ -14,23 +14,12 @@ public ContextShader contextShader() {
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 acquire() {
-
- }
-
- @Override
- public void release() {
-
+ public void setupDraw(GlProgram drawProgram) {
}
}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java
deleted file mode 100644
index f957a7e43..000000000
--- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/NestedEmbeddedEnvironment.java
+++ /dev/null
@@ -1,44 +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;
-import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
-import net.minecraft.world.level.BlockAndTintGetter;
-
-public class NestedEmbeddedEnvironment extends AbstractEmbeddedEnvironment {
- private final AbstractEmbeddedEnvironment parent;
-
- public NestedEmbeddedEnvironment(AbstractEmbeddedEnvironment parent, EngineImpl engine, RenderStage renderStage) {
- super(engine, renderStage);
- this.parent = parent;
- parent.acquire();
- }
-
- @Override
- public void collectLight(BlockAndTintGetter level, int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) {
- }
-
- @Override
- public void invalidateLight() {
- }
-
- @Override
- public void setupLight(GlProgram program) {
- parent.setupLight(program);
- }
-
- @Override
- public void composeMatrices(Matrix4f pose, Matrix3f normal) {
- parent.composeMatrices(pose, normal);
- pose.mul(this.pose);
- normal.mul(this.normal);
- }
-
- @Override
- public void actuallyDelete() {
- parent.release();
- }
-}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java
deleted file mode 100644
index d5a8aa9f9..000000000
--- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/TopLevelEmbeddedEnvironment.java
+++ /dev/null
@@ -1,79 +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.Samplers;
-import dev.engine_room.flywheel.backend.engine.EngineImpl;
-import dev.engine_room.flywheel.backend.gl.shader.GlProgram;
-import net.minecraft.world.level.BlockAndTintGetter;
-
-public class TopLevelEmbeddedEnvironment extends AbstractEmbeddedEnvironment {
- private final EmbeddedLightVolume lightVolume = new EmbeddedLightVolume();
- private final EmbeddedLightTexture lightTexture = new EmbeddedLightTexture();
-
- public TopLevelEmbeddedEnvironment(EngineImpl engine, RenderStage renderStage) {
- super(engine, renderStage);
- }
-
- @Override
- public void flush() {
- super.flush();
-
- if (lightVolume.empty()) {
- return;
- }
- Samplers.EMBEDDED_LIGHT.makeActive();
-
- lightTexture.bind();
-
- lightTexture.ensureCapacity(lightVolume.sizeX(), lightVolume.sizeY(), lightVolume.sizeZ());
-
- lightTexture.upload(lightVolume.ptr(), lightVolume.sizeX(), lightVolume.sizeY(), lightVolume.sizeZ());
- }
-
- @Override
- public void collectLight(BlockAndTintGetter level, int minX, int minY, int minZ, int sizeX, int sizeY, int sizeZ) {
- lightVolume.collect(level, minX, minY, minZ, sizeX, sizeY, sizeZ);
- }
-
- @Override
- public void invalidateLight() {
- lightVolume.clear();
- }
-
- @Override
- public void setupLight(GlProgram program) {
- if (!lightVolume.empty()) {
- Samplers.EMBEDDED_LIGHT.makeActive();
-
- lightTexture.bind();
-
- float oneOverSizeX = 1f / (float) lightTexture.sizeX;
- float oneOverSizeY = 1f / (float) lightTexture.sizeY;
- float oneOverSizeZ = 1f / (float) lightTexture.sizeZ;
-
- program.setVec3(EmbeddingUniforms.ONE_OVER_LIGHT_BOX_SIZE, oneOverSizeX, oneOverSizeY, oneOverSizeZ);
- program.setVec3(EmbeddingUniforms.LIGHT_VOLUME_MIN, lightVolume.x(), lightVolume.y(), lightVolume.z());
- program.setBool(EmbeddingUniforms.USE_LIGHT_VOLUME, true);
- } else {
- program.setBool(EmbeddingUniforms.USE_LIGHT_VOLUME, false);
- }
- }
-
- @Override
- public void composeMatrices(Matrix4f pose, Matrix3f normal) {
- pose.set(this.pose);
- normal.set(this.normal);
- }
-
- @Override
- public void actuallyDelete() {
- // We could technically free the light volume right away in _delete, but
- // the control flow here is so convoluted that it's probably best to do
- // everything in one place.
- lightVolume.delete();
- lightTexture.delete();
- }
-}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/BufferBindings.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/BufferBindings.java
new file mode 100644
index 000000000..a0ae93a28
--- /dev/null
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/BufferBindings.java
@@ -0,0 +1,14 @@
+package dev.engine_room.flywheel.backend.engine.indirect;
+
+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() {
+ }
+}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java
index fff5d9ea1..90ecd4149 100644
--- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectBuffers.java
@@ -22,13 +22,6 @@ public class IndirectBuffers {
public static final long DRAW_COMMAND_STRIDE = 40;
public static final long DRAW_COMMAND_OFFSET = 0;
- public static final int INSTANCE_INDEX = 0;
- public static final int TARGET_INDEX = 1;
- public static final int MODEL_INDEX_INDEX = 2;
- public static final int MODEL_INDEX = 3;
- public static final int DRAW_INDEX = 4;
-
-
// Offsets to the 3 segments
private static final long HANDLE_OFFSET = 0;
private static final long OFFSET_OFFSET = BUFFER_COUNT * INT_SIZE;
@@ -117,7 +110,7 @@ public void bindForDraw() {
private void multiBind() {
final long ptr = multiBindBlock.ptr();
- nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, 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);
}
/**
@@ -125,7 +118,7 @@ private void multiBind() {
*/
public void bindForCrumbling() {
final long ptr = multiBindBlock.ptr();
- nglBindBuffersRange(GL_SHADER_STORAGE_BUFFER, 0, 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() {
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java
index c4d5fb8ea..b79a52edd 100644
--- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectCullingGroup.java
@@ -280,6 +280,16 @@ public void delete() {
buffers.delete();
}
+ public boolean checkEmptyAndDelete() {
+ var out = indirectDraws.isEmpty();
+
+ if (out) {
+ delete();
+ }
+
+ return out;
+ }
+
private record MultiDraw(Material material, int start, int end) {
private void submit() {
if (GlCompat.DRIVER == Driver.INTEL) {
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java
index 8da0093f9..d9ef2254a 100644
--- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectDrawManager.java
@@ -20,6 +20,7 @@
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;
@@ -39,17 +40,17 @@ public class IndirectDrawManager extends DrawManager> {
private final GlVertexArray vertexArray;
private final Map, 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);
+ stagingBuffer = new StagingBuffer(this.programs);
meshPool = new MeshPool();
-
vertexArray = GlVertexArray.create();
-
meshPool.bind(vertexArray);
+ lightBuffers = new LightBuffers();
}
@Override
@@ -83,6 +84,7 @@ public void renderStage(RenderStage stage) {
TextureBinder.bindLightAndOverlay();
vertexArray.bindForDraw();
+ lightBuffers.bind();
Uniforms.bindAll();
for (var group : cullingGroups.values()) {
@@ -95,19 +97,25 @@ public void renderStage(RenderStage stage) {
}
@Override
- public void flush() {
- super.flush();
+ public void flush(LightStorage lightStorage) {
+ super.flush(lightStorage);
for (var group : cullingGroups.values()) {
group.flushInstancers();
}
- instancers.values().removeIf(instancer -> instancer.instanceCount() == 0);
+ cullingGroups.values()
+ .removeIf(IndirectCullingGroup::checkEmptyAndDelete);
+
+ instancers.values()
+ .removeIf(instancer -> instancer.instanceCount() == 0);
meshPool.flush();
stagingBuffer.reclaim();
+ lightBuffers.flush(stagingBuffer, lightStorage);
+
for (var group : cullingGroups.values()) {
group.upload(stagingBuffer);
}
@@ -159,7 +167,7 @@ public void renderCrumbling(List crumblingBlocks) {
var block = MemoryBlock.malloc(IndirectBuffers.DRAW_COMMAND_STRIDE);
GlBufferType.DRAW_INDIRECT_BUFFER.bind(crumblingDrawBuffer.handle());
- glBindBufferRange(GL_SHADER_STORAGE_BUFFER, IndirectBuffers.DRAW_INDEX, 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();
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectInstancer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectInstancer.java
index 38099c85d..b0ac9bc98 100644
--- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectInstancer.java
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/IndirectInstancer.java
@@ -125,8 +125,6 @@ private void uploadAllModelIndices(StagingBuffer stagingBuffer, long modelIndexB
@Override
public void delete() {
- super.delete();
-
for (IndirectDraw draw : draws()) {
draw.delete();
}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java
new file mode 100644
index 000000000..9db63afb0
--- /dev/null
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/indirect/LightBuffers.java
@@ -0,0 +1,43 @@
+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.LightStorage;
+
+public class LightBuffers {
+ private final ResizableStorageArray lut = new ResizableStorageArray(4);
+ private final ResizableStorageArray sections = new ResizableStorageArray(LightStorage.SECTION_SIZE_BYTES);
+
+ public void flush(StagingBuffer staging, LightStorage light) {
+ var capacity = light.capacity();
+
+ if (capacity == 0) {
+ return;
+ }
+
+ sections.ensureCapacity(capacity);
+ light.uploadChangedSections(staging, sections.handle());
+
+ if (light.checkNeedsLutRebuildAndClear()) {
+ var lut = light.createLut();
+
+ this.lut.ensureCapacity(lut.size());
+
+ staging.enqueueCopy((long) lut.size() * Integer.BYTES, this.lut.handle(), 0, ptr -> {
+ for (int i = 0; i < lut.size(); i++) {
+ MemoryUtil.memPutInt(ptr + (long) i * Integer.BYTES, lut.getInt(i));
+ }
+ });
+ }
+ }
+
+ public void bind() {
+ if (sections.capacity() == 0) {
+ return;
+ }
+
+ 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());
+ }
+}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java
index b532823ab..ae231de0b 100644
--- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedDrawManager.java
@@ -18,6 +18,7 @@
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;
@@ -42,6 +43,7 @@ public class InstancedDrawManager extends DrawManager> {
private final MeshPool meshPool;
private final GlVertexArray vao;
private final TextureBuffer instanceTexture;
+ private final InstancedLight light;
public InstancedDrawManager(InstancingPrograms programs) {
programs.acquire();
@@ -50,16 +52,17 @@ public InstancedDrawManager(InstancingPrograms programs) {
meshPool = new MeshPool();
vao = GlVertexArray.create();
instanceTexture = new TextureBuffer();
+ light = new InstancedLight();
meshPool.bind(vao);
}
@Override
- public void flush() {
- super.flush();
+ public void flush(LightStorage lightStorage) {
+ super.flush(lightStorage);
- var instancers = this.instancers.values();
- instancers.removeIf(instancer -> {
+ this.instancers.values()
+ .removeIf(instancer -> {
// Update the instancers and remove any that are empty.
instancer.update();
@@ -77,6 +80,8 @@ public void flush() {
}
meshPool.flush();
+
+ light.flush(lightStorage);
}
@Override
@@ -91,6 +96,7 @@ public void renderStage(RenderStage stage) {
Uniforms.bindAll();
vao.bindForDraw();
TextureBinder.bindLightAndOverlay();
+ light.bind();
drawSet.draw(instanceTexture, programs);
@@ -113,6 +119,8 @@ public void delete() {
programs.release();
vao.delete();
+ light.delete();
+
super.delete();
}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedInstancer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedInstancer.java
index 9ca3d5cc8..a5dff27d9 100644
--- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedInstancer.java
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedInstancer.java
@@ -116,8 +116,6 @@ public boolean needsToGrow(long capacity) {
}
public void delete() {
- super.delete();
-
if (vbo == null) {
return;
}
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedLight.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedLight.java
new file mode 100644
index 000000000..7831019d1
--- /dev/null
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedLight.java
@@ -0,0 +1,62 @@
+package dev.engine_room.flywheel.backend.engine.instancing;
+
+import org.lwjgl.opengl.GL32;
+import org.lwjgl.system.MemoryUtil;
+
+import dev.engine_room.flywheel.backend.Samplers;
+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;
+
+public class InstancedLight {
+ private final GlBuffer lut;
+ private final GlBuffer sections;
+ private final TextureBuffer lutTexture;
+ private final TextureBuffer sectionsTexture;
+
+ public InstancedLight() {
+ lut = new GlBuffer();
+ sections = new GlBuffer();
+ lutTexture = new TextureBuffer(GL32.GL_R32UI);
+ sectionsTexture = new TextureBuffer(GL32.GL_R32UI);
+ }
+
+ public void bind() {
+ Samplers.LIGHT_LUT.makeActive();
+ lutTexture.bind(lut.handle());
+ Samplers.LIGHT_SECTIONS.makeActive();
+ sectionsTexture.bind(sections.handle());
+ }
+
+ public void flush(LightStorage light) {
+ if (light.capacity() == 0) {
+ return;
+ }
+
+ light.upload(sections);
+
+ if (light.checkNeedsLutRebuildAndClear()) {
+ var lut = light.createLut();
+
+ var up = MemoryBlock.malloc((long) lut.size() * Integer.BYTES);
+
+ long ptr = up.ptr();
+
+ for (int i = 0; i < lut.size(); i++) {
+ MemoryUtil.memPutInt(ptr + (long) Integer.BYTES * i, lut.getInt(i));
+ }
+
+ this.lut.upload(up);
+
+ up.free();
+ }
+ }
+
+ public void delete() {
+ lut.delete();
+ sections.delete();
+ lutTexture.delete();
+ sectionsTexture.delete();
+ }
+}
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/java/dev/engine_room/flywheel/backend/gl/TextureBuffer.java b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/TextureBuffer.java
index 21eb9361f..d7bbc9cfc 100644
--- a/common/src/backend/java/dev/engine_room/flywheel/backend/gl/TextureBuffer.java
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/gl/TextureBuffer.java
@@ -5,14 +5,20 @@
public class TextureBuffer extends GlObject {
public static final int MAX_TEXELS = GL32.glGetInteger(GL32.GL_MAX_TEXTURE_BUFFER_SIZE);
public static final int MAX_BYTES = MAX_TEXELS * 16; // 4 channels * 4 bytes
+ private final int format;
public TextureBuffer() {
+ this(GL32.GL_RGBA32UI);
+ }
+
+ public TextureBuffer(int format) {
handle(GL32.glGenTextures());
+ this.format = format;
}
public void bind(int buffer) {
GL32.glBindTexture(GL32.GL_TEXTURE_BUFFER, handle());
- GL32.glTexBuffer(GL32.GL_TEXTURE_BUFFER, GL32.GL_RGBA32UI, buffer);
+ GL32.glTexBuffer(GL32.GL_TEXTURE_BUFFER, format, buffer);
}
@Override
diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/ClientChunkCacheMixin.java b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/ClientChunkCacheMixin.java
new file mode 100644
index 000000000..eb8c974ec
--- /dev/null
+++ b/common/src/backend/java/dev/engine_room/flywheel/backend/mixin/ClientChunkCacheMixin.java
@@ -0,0 +1,29 @@
+package dev.engine_room.flywheel.backend.mixin;
+
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+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.LightUpdateHolder;
+import net.minecraft.client.multiplayer.ClientChunkCache;
+import net.minecraft.client.multiplayer.ClientLevel;
+import net.minecraft.core.SectionPos;
+import net.minecraft.world.level.LightLayer;
+
+@Mixin(ClientChunkCache.class)
+abstract class ClientChunkCacheMixin {
+ @Shadow
+ @Final
+ ClientLevel level;
+
+ @Inject(method = "onLightUpdate", at = @At("HEAD"))
+ private void flywheel$backend$onLightUpdate(LightLayer layer, SectionPos pos, CallbackInfo ci) {
+ // This is duplicated from code in impl, but I'm not sure that it
+ // makes sense to be generically passed to backends.
+ LightUpdateHolder.get(level)
+ .add(pos.asLong());
+ }
+}
diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.frag
index dc8334916..af16b80e2 100644
--- a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.frag
+++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.frag
@@ -1,4 +1,5 @@
#include "flywheel:internal/material.glsl"
+#include "flywheel:internal/api_impl.glsl"
#include "flywheel:internal/uniforms/uniforms.glsl"
in vec4 flw_vertexPos;
diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.glsl
new file mode 100644
index 000000000..a839a81c2
--- /dev/null
+++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.glsl
@@ -0,0 +1,13 @@
+// TODO: Add config for light smoothness. Should work at a compile flag level
+
+/// Get the light at the given world position from the given normal.
+/// This may be interpolated for smooth lighting.
+bool flw_light(vec3 worldPos, vec3 normal, out vec2 light);
+
+/// Get the light at the given world position.
+/// This may be interpolated for smooth lighting.
+bool flw_light(vec3 worldPos, out vec2 light);
+
+/// Fetches the light value at the given block position.
+/// Returns false if the light for the given block is not available.
+bool flw_lightFetch(ivec3 blockPos, out vec2 light);
diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.vert
index d9934d42e..d1e751929 100644
--- a/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.vert
+++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/api_impl.vert
@@ -1,4 +1,5 @@
#include "flywheel:internal/material.glsl"
+#include "flywheel:internal/api_impl.glsl"
#include "flywheel:internal/uniforms/uniforms.glsl"
out vec4 flw_vertexPos;
diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag
index f73846dea..23df5b7e9 100644
--- a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag
+++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag
@@ -13,14 +13,6 @@ uniform sampler2D _flw_crumblingTex;
in vec2 _flw_crumblingTexCoord;
#endif
-#ifdef _FLW_EMBEDDED
-uniform sampler3D _flw_lightVolume;
-
-uniform bool _flw_useLightVolume;
-
-in vec3 _flw_lightVolumeCoord;
-#endif
-
flat in uint _flw_instanceID;
out vec4 _flw_outputColor;
@@ -43,12 +35,6 @@ void _flw_main() {
flw_fragOverlay = flw_vertexOverlay;
flw_fragLight = flw_vertexLight;
- #ifdef _FLW_EMBEDDED
- if (_flw_useLightVolume) {
- flw_fragLight = max(flw_fragLight, texture(_flw_lightVolume, _flw_lightVolumeCoord).rg);
- }
- #endif
-
flw_materialFragment();
#ifdef _FLW_CRUMBLING
@@ -98,11 +84,6 @@ void _flw_main() {
case 6u:
color = vec4(vec3(diffuseFactor), 1.);
break;
- #ifdef _FLW_EMBEDDED
- case 7u:
- color = vec4(_flw_lightVolumeCoord, 1.);
- break;
- #endif
}
_flw_outputColor = flw_fogFilter(color);
diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert
index ec009e414..075dae3f6 100644
--- a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert
+++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.vert
@@ -66,13 +66,9 @@ vec2 getCrumblingTexCoord() {
}
#endif
-#ifdef _FLW_EMBEDDED
-uniform vec3 _flw_oneOverLightBoxSize;
-uniform vec3 _flw_lightVolumeMin;
+#ifdef FLW_EMBEDDED
uniform mat4 _flw_modelMatrix;
uniform mat3 _flw_normalMatrix;
-
-out vec3 _flw_lightVolumeCoord;
#endif
flat out uint _flw_instanceID;
@@ -86,11 +82,9 @@ void _flw_main(in FlwInstance instance, in uint stableInstanceID) {
_flw_crumblingTexCoord = getCrumblingTexCoord();
#endif
- #ifdef _FLW_EMBEDDED
+ #ifdef FLW_EMBEDDED
flw_vertexPos = _flw_modelMatrix * flw_vertexPos;
flw_vertexNormal = _flw_normalMatrix * flw_vertexNormal;
-
- _flw_lightVolumeCoord = (flw_vertexPos.xyz - _flw_lightVolumeMin) * _flw_oneOverLightBoxSize;
#endif
flw_vertexNormal = normalize(flw_vertexNormal);
diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/buffer_bindings.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/buffer_bindings.glsl
index 4aa9edc56..c37db3502 100644
--- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/buffer_bindings.glsl
+++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/buffer_bindings.glsl
@@ -3,3 +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_BUFFER_BINDING 5
+#define _FLW_LIGHT_SECTIONS_BUFFER_BINDING 6
diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/light.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/light.glsl
new file mode 100644
index 000000000..48975050e
--- /dev/null
+++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/light.glsl
@@ -0,0 +1,17 @@
+#include "flywheel:internal/light_lut.glsl"
+
+layout(std430, binding = _FLW_LIGHT_LUT_BUFFER_BINDING) restrict readonly buffer LightLut {
+ uint _flw_lightLut[];
+};
+
+layout(std430, binding = _FLW_LIGHT_SECTIONS_BUFFER_BINDING) restrict readonly buffer LightSections {
+ uint _flw_lightSections[];
+};
+
+uint _flw_indexLut(uint index) {
+ return _flw_lightLut[index];
+}
+
+uint _flw_indexLight(uint index) {
+ return _flw_lightSections[index];
+}
diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag
index 334b7b461..b1dbb0822 100644
--- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag
+++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.frag
@@ -1,4 +1,6 @@
#include "flywheel:internal/common.frag"
+#include "flywheel:internal/indirect/buffer_bindings.glsl"
+#include "flywheel:internal/indirect/light.glsl"
flat in uvec3 _flw_packedMaterial;
diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert
index 55bd8fc0f..a95d31662 100644
--- a/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert
+++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/indirect/main.vert
@@ -2,6 +2,7 @@
#include "flywheel:internal/packed_material.glsl"
#include "flywheel:internal/indirect/buffer_bindings.glsl"
#include "flywheel:internal/indirect/draw_command.glsl"
+#include "flywheel:internal/indirect/light.glsl"
layout(std430, binding = _FLW_TARGET_BUFFER_BINDING) restrict readonly buffer TargetBuffer {
uint _flw_instanceIndices[];
diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/light.glsl b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/light.glsl
new file mode 100644
index 000000000..f023322a4
--- /dev/null
+++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/light.glsl
@@ -0,0 +1,12 @@
+#include "flywheel:internal/light_lut.glsl"
+
+uniform usamplerBuffer _flw_lightLut;
+uniform usamplerBuffer _flw_lightSections;
+
+uint _flw_indexLut(uint index) {
+ return texelFetch(_flw_lightLut, int(index)).r;
+}
+
+uint _flw_indexLight(uint index) {
+ return texelFetch(_flw_lightSections, int(index)).r;
+}
diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag
index 661c3a769..89b09b37f 100644
--- a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag
+++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.frag
@@ -1,4 +1,5 @@
#include "flywheel:internal/common.frag"
+#include "flywheel:internal/instancing/light.glsl"
uniform uvec4 _flw_packedMaterial;
diff --git a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert
index 8c4b9d6a3..4b6914eae 100644
--- a/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert
+++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/instancing/main.vert
@@ -1,5 +1,6 @@
#include "flywheel:internal/common.vert"
#include "flywheel:internal/packed_material.glsl"
+#include "flywheel:internal/instancing/light.glsl"
uniform uvec4 _flw_packedMaterial;
uniform int _flw_baseInstance = 0;
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
new file mode 100644
index 000000000..4d864928c
--- /dev/null
+++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl
@@ -0,0 +1,235 @@
+const uint _FLW_LIGHT_SECTION_SIZE_BYTES = 18 * 18 * 18;
+const uint _FLW_LIGHT_SECTION_SIZE_INTS = _FLW_LIGHT_SECTION_SIZE_BYTES / 4;
+
+uint _flw_indexLut(uint index);
+
+uint _flw_indexLight(uint index);
+
+/// Find the index for the next step in the LUT.
+/// @param base The base index in the LUT, should point to the start of a coordinate span.
+/// @param coord The coordinate to look for.
+/// @param next Output. The index of the next step in the LUT.
+/// @return true if the coordinate is not in the span.
+bool _flw_nextLut(uint base, int coord, out uint next) {
+ // The base coordinate.
+ int start = int(_flw_indexLut(base));
+ // The width of the coordinate span.
+ uint size = _flw_indexLut(base + 1);
+
+ // Index of the coordinate in the span.
+ int i = coord - start;
+
+ if (i < 0 || i >= size) {
+ // We missed.
+ return true;
+ }
+
+ next = _flw_indexLut(base + 2 + i);
+
+ return false;
+}
+
+bool _flw_chunkCoordToSectionIndex(ivec3 sectionPos, out uint index) {
+ uint y;
+ if (_flw_nextLut(0, sectionPos.x, y) || y == 0) {
+ return true;
+ }
+
+ uint z;
+ if (_flw_nextLut(y, sectionPos.y, z) || z == 0) {
+ return true;
+ }
+
+ uint sectionIndex;
+ if (_flw_nextLut(z, sectionPos.z, sectionIndex) || sectionIndex == 0) {
+ return true;
+ }
+
+ // The index is written as 1-based so we can properly detect missing sections.
+ index = sectionIndex - 1;
+
+ return false;
+}
+
+vec2 _flw_lightAt(uint sectionOffset, uvec3 blockInSectionPos) {
+ uint byteOffset = blockInSectionPos.x + blockInSectionPos.z * 18u + blockInSectionPos.y * 18u * 18u;
+
+ uint uintOffset = byteOffset >> 2u;
+ uint bitOffset = (byteOffset & 3u) << 3;
+
+ uint raw = _flw_indexLight(sectionOffset + uintOffset);
+ uint block = (raw >> bitOffset) & 0xFu;
+ uint sky = (raw >> (bitOffset + 4u)) & 0xFu;
+
+ return vec2(block, sky);
+}
+
+bool flw_lightFetch(ivec3 blockPos, out vec2 lightCoord) {
+ uint lightSectionIndex;
+ if (_flw_chunkCoordToSectionIndex(blockPos >> 4, lightSectionIndex)) {
+ return false;
+ }
+ // The offset of the section in the light buffer.
+ uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS;
+
+ uvec3 blockInSectionPos = (blockPos & 0xF) + 1;
+
+ lightCoord = _flw_lightAt(sectionOffset, blockInSectionPos) / 15.;
+ return true;
+}
+
+bool flw_light(vec3 worldPos, out vec2 lightCoord) {
+ // Always use the section of the block we are contained in to ensure accuracy.
+ // We don't want to interpolate between sections, but also we might not be able
+ // to rely on the existence neighboring sections, so don't do any extra rounding here.
+ ivec3 blockPos = ivec3(floor(worldPos));
+
+ uint lightSectionIndex;
+ if (_flw_chunkCoordToSectionIndex(blockPos >> 4, lightSectionIndex)) {
+ return false;
+ }
+ // The offset of the section in the light buffer.
+ uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS;
+
+ // The block's position in the section adjusted into 18x18x18 space
+ uvec3 blockInSectionPos = (blockPos & 0xF) + 1;
+
+ // The lowest corner of the 2x2x2 area we'll be trilinear interpolating.
+ // The ugly bit on the end evaluates to -1 or 0 depending on which side of 0.5 we are.
+ uvec3 lowestCorner = blockInSectionPos + ivec3(floor(fract(worldPos) - 0.5));
+
+ // The distance our fragment is from the center of the lowest corner.
+ vec3 interpolant = fract(worldPos - 0.5);
+
+ // Fetch everything for trilinear interpolation
+ // Hypothetically we could re-order these and do some calculations in-between fetches
+ // to help with latency hiding, but the compiler should be able to do that for us.
+ vec2 light000 = _flw_lightAt(sectionOffset, lowestCorner);
+ vec2 light001 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 0, 1));
+ vec2 light010 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 0));
+ vec2 light011 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 1));
+ vec2 light100 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 0));
+ vec2 light101 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 1));
+ vec2 light110 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 0));
+ vec2 light111 = _flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 1));
+
+ vec2 light00 = mix(light000, light001, interpolant.z);
+ vec2 light01 = mix(light010, light011, interpolant.z);
+ vec2 light10 = mix(light100, light101, interpolant.z);
+ vec2 light11 = mix(light110, light111, interpolant.z);
+
+ vec2 light0 = mix(light00, light01, interpolant.y);
+ vec2 light1 = mix(light10, light11, interpolant.y);
+
+ lightCoord = mix(light0, light1, interpolant.x) / 15.;
+ return true;
+}
+
+uint _flw_lightIndex(in uvec3 p) {
+ return p.x + p.z * 3u + p.y * 9u;
+}
+
+/// Premtively collect all light in a 3x3x3 area centered on our block.
+/// Depending on the normal, we won't use all the data, but fetching on demand will have many duplicated fetches.
+vec2[27] _flw_lightFetch3x3x3(uint sectionOffset, ivec3 blockInSectionPos) {
+ vec2[27] lights;
+
+ for (int y = -1; y <= 1; y++) {
+ for (int z = -1; z <= 1; z++) {
+ for (int x = -1; x <= 1; x++) {
+ lights[_flw_lightIndex(uvec3(x + 1, y + 1, z + 1))] = _flw_lightAt(sectionOffset, uvec3(blockInSectionPos + ivec3(x, y, z)));
+ }
+ }
+ }
+
+ return lights;
+}
+
+/// Calculate the light for a direction by averaging the light at the corners of the block.
+///
+/// To make this reusable across directions, c00..c11 choose what values relative to each corner to use.
+/// e.g. (0, 0, 0) (0, 0, 1) (0, 1, 0) (0, 1, 1) would give you the light coming from -x at each corner.
+/// In general, to get the light for a particular direction, you fix the x, y, or z coordinate of the c values, and permutate 0 and 1 for the other two.
+/// Fixing the x coordinate to 0 gives you the light from -x, 1 gives you the light from +x.
+///
+/// @param lights The light data for the 3x3x3 area.
+/// @param interpolant The position within the center block.
+/// @param c00..c11 4 offsets to determine which "direction" we are averaging.
+vec2 _flw_lightForDirection(in vec2[27] lights, in vec3 interpolant, in uvec3 c00, in uvec3 c01, in uvec3 c10, in uvec3 c11) {
+
+ vec2 light000 = lights[_flw_lightIndex(c00 + uvec3(0u, 0u, 0u))] + lights[_flw_lightIndex(c01 + uvec3(0u, 0u, 0u))] + lights[_flw_lightIndex(c10 + uvec3(0u, 0u, 0u))] + lights[_flw_lightIndex(c11 + uvec3(0u, 0u, 0u))];
+ vec2 light001 = lights[_flw_lightIndex(c00 + uvec3(0u, 0u, 1u))] + lights[_flw_lightIndex(c01 + uvec3(0u, 0u, 1u))] + lights[_flw_lightIndex(c10 + uvec3(0u, 0u, 1u))] + lights[_flw_lightIndex(c11 + uvec3(0u, 0u, 1u))];
+ vec2 light010 = lights[_flw_lightIndex(c00 + uvec3(0u, 1u, 0u))] + lights[_flw_lightIndex(c01 + uvec3(0u, 1u, 0u))] + lights[_flw_lightIndex(c10 + uvec3(0u, 1u, 0u))] + lights[_flw_lightIndex(c11 + uvec3(0u, 1u, 0u))];
+ vec2 light011 = lights[_flw_lightIndex(c00 + uvec3(0u, 1u, 1u))] + lights[_flw_lightIndex(c01 + uvec3(0u, 1u, 1u))] + lights[_flw_lightIndex(c10 + uvec3(0u, 1u, 1u))] + lights[_flw_lightIndex(c11 + uvec3(0u, 1u, 1u))];
+ vec2 light100 = lights[_flw_lightIndex(c00 + uvec3(1u, 0u, 0u))] + lights[_flw_lightIndex(c01 + uvec3(1u, 0u, 0u))] + lights[_flw_lightIndex(c10 + uvec3(1u, 0u, 0u))] + lights[_flw_lightIndex(c11 + uvec3(1u, 0u, 0u))];
+ vec2 light101 = lights[_flw_lightIndex(c00 + uvec3(1u, 0u, 1u))] + lights[_flw_lightIndex(c01 + uvec3(1u, 0u, 1u))] + lights[_flw_lightIndex(c10 + uvec3(1u, 0u, 1u))] + lights[_flw_lightIndex(c11 + uvec3(1u, 0u, 1u))];
+ vec2 light110 = lights[_flw_lightIndex(c00 + uvec3(1u, 1u, 0u))] + lights[_flw_lightIndex(c01 + uvec3(1u, 1u, 0u))] + lights[_flw_lightIndex(c10 + uvec3(1u, 1u, 0u))] + lights[_flw_lightIndex(c11 + uvec3(1u, 1u, 0u))];
+ vec2 light111 = lights[_flw_lightIndex(c00 + uvec3(1u, 1u, 1u))] + lights[_flw_lightIndex(c01 + uvec3(1u, 1u, 1u))] + lights[_flw_lightIndex(c10 + uvec3(1u, 1u, 1u))] + lights[_flw_lightIndex(c11 + uvec3(1u, 1u, 1u))];
+
+ vec2 light00 = mix(light000, light001, interpolant.z);
+ vec2 light01 = mix(light010, light011, interpolant.z);
+ vec2 light10 = mix(light100, light101, interpolant.z);
+ vec2 light11 = mix(light110, light111, interpolant.z);
+
+ vec2 light0 = mix(light00, light01, interpolant.y);
+ vec2 light1 = mix(light10, light11, interpolant.y);
+
+ // Divide by 60 (15 * 4) to normalize.
+ return mix(light0, light1, interpolant.x) / 63.;
+}
+
+bool flw_light(vec3 worldPos, vec3 normal, out vec2 lightCoord) {
+ // Always use the section of the block we are contained in to ensure accuracy.
+ // We don't want to interpolate between sections, but also we might not be able
+ // to rely on the existence neighboring sections, so don't do any extra rounding here.
+ ivec3 blockPos = ivec3(floor(worldPos));
+
+ uint lightSectionIndex;
+ if (_flw_chunkCoordToSectionIndex(blockPos >> 4, lightSectionIndex)) {
+ return false;
+ }
+ // The offset of the section in the light buffer.
+ uint sectionOffset = lightSectionIndex * _FLW_LIGHT_SECTION_SIZE_INTS;
+
+ // The block's position in the section adjusted into 18x18x18 space
+ ivec3 blockInSectionPos = (blockPos & 0xF) + 1;
+
+ // Fetch everything in a 3x3x3 area centered around the block.
+ vec2[27] lights = _flw_lightFetch3x3x3(sectionOffset, blockInSectionPos);
+
+ vec3 interpolant = fract(worldPos);
+
+ vec2 lightX;
+ if (normal.x > 0) {
+ lightX = _flw_lightForDirection(lights, interpolant, uvec3(1u, 0u, 0u), uvec3(1u, 0u, 1u), uvec3(1u, 1u, 0u), uvec3(1u, 1u, 1u));
+ } else if (normal.x < 0) {
+ lightX = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 0u), uvec3(0u, 0u, 1u), uvec3(0u, 1u, 0u), uvec3(0u, 1u, 1u));
+ } else {
+ lightX = vec2(0.);
+ }
+
+ vec2 lightZ;
+ if (normal.z > 0) {
+ lightZ = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 1u), uvec3(0u, 1u, 1u), uvec3(1u, 0u, 1u), uvec3(1u, 1u, 1u));
+ } else if (normal.z < 0) {
+ lightZ = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 0u), uvec3(0u, 1u, 0u), uvec3(1u, 0u, 0u), uvec3(1u, 1u, 0u));
+ } else {
+ lightZ = vec2(0.);
+ }
+
+ vec2 lightY;
+ // Average the light in relevant directions at each corner.
+ if (normal.y > 0.) {
+ lightY = _flw_lightForDirection(lights, interpolant, uvec3(0u, 1u, 0u), uvec3(0u, 1u, 1u), uvec3(1u, 1u, 0u), uvec3(1u, 1u, 1u));
+ } else if (normal.y < 0.) {
+ lightY = _flw_lightForDirection(lights, interpolant, uvec3(0u, 0u, 0u), uvec3(0u, 0u, 1u), uvec3(1u, 0u, 0u), uvec3(1u, 0u, 1u));
+ } else {
+ lightY = vec2(0.);
+ }
+
+ vec3 n2 = normal * normal;
+ lightCoord = lightX * n2.x + lightY * n2.y + lightZ * n2.z;
+
+ return true;
+}
+
diff --git a/common/src/backend/resources/flywheel.backend.mixins.json b/common/src/backend/resources/flywheel.backend.mixins.json
index 2d924bef0..e303f7b03 100644
--- a/common/src/backend/resources/flywheel.backend.mixins.json
+++ b/common/src/backend/resources/flywheel.backend.mixins.json
@@ -6,6 +6,7 @@
"refmap": "backend-flywheel.refmap.json",
"client": [
"AbstractClientPlayerAccessor",
+ "ClientChunkCacheMixin",
"GlStateManagerMixin",
"LevelRendererAccessor",
"OptionsMixin",
diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/box/Box.java b/common/src/lib/java/dev/engine_room/flywheel/lib/box/Box.java
deleted file mode 100644
index 3955cde1b..000000000
--- a/common/src/lib/java/dev/engine_room/flywheel/lib/box/Box.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package dev.engine_room.flywheel.lib.box;
-
-import net.minecraft.util.Mth;
-import net.minecraft.world.phys.AABB;
-
-public interface Box {
- int getMinX();
-
- int getMinY();
-
- int getMinZ();
-
- int getMaxX();
-
- int getMaxY();
-
- int getMaxZ();
-
- default int sizeX() {
- return getMaxX() - getMinX();
- }
-
- default int sizeY() {
- return getMaxY() - getMinY();
- }
-
- default int sizeZ() {
- return getMaxZ() - getMinZ();
- }
-
- default int volume() {
- return sizeX() * sizeY() * sizeZ();
- }
-
- default boolean isEmpty() {
- // if any dimension has side length 0 this box contains no volume
- return getMinX() == getMaxX() || getMinY() == getMaxY() || getMinZ() == getMaxZ();
- }
-
- default boolean sameAs(Box other) {
- return getMinX() == other.getMinX() && getMinY() == other.getMinY() && getMinZ() == other.getMinZ() && getMaxX() == other.getMaxX() && getMaxY() == other.getMaxY() && getMaxZ() == other.getMaxZ();
- }
-
- default boolean sameAs(Box other, int margin) {
- return getMinX() == other.getMinX() - margin &&
- getMinY() == other.getMinY() - margin &&
- getMinZ() == other.getMinZ() - margin &&
- getMaxX() == other.getMaxX() + margin &&
- getMaxY() == other.getMaxY() + margin &&
- getMaxZ() == other.getMaxZ() + margin;
- }
-
- default boolean sameAs(AABB other) {
- return getMinX() == Math.floor(other.minX)
- && getMinY() == Math.floor(other.minY)
- && getMinZ() == Math.floor(other.minZ)
- && getMaxX() == Math.ceil(other.maxX)
- && getMaxY() == Math.ceil(other.maxY)
- && getMaxZ() == Math.ceil(other.maxZ);
- }
-
- default boolean intersects(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
- return this.getMinX() < maxX && this.getMaxX() > minX && this.getMinY() < maxY && this.getMaxY() > minY && this.getMinZ() < maxZ && this.getMaxZ() > minZ;
- }
-
- default boolean intersects(Box other) {
- return this.intersects(other.getMinX(), other.getMinY(), other.getMinZ(), other.getMaxX(), other.getMaxY(), other.getMaxZ());
- }
-
- default boolean contains(int x, int y, int z) {
- return x >= getMinX()
- && x <= getMaxX()
- && y >= getMinY()
- && y <= getMaxY()
- && z >= getMinZ()
- && z <= getMaxZ();
- }
-
- default boolean contains(Box other) {
- return other.getMinX() >= this.getMinX()
- && other.getMaxX() <= this.getMaxX()
- && other.getMinY() >= this.getMinY()
- && other.getMaxY() <= this.getMaxY()
- && other.getMinZ() >= this.getMinZ()
- && other.getMaxZ() <= this.getMaxZ();
- }
-
- default void forEachContained(CoordinateConsumer func) {
- int minX = getMinX();
- int minY = getMinY();
- int minZ = getMinZ();
- int maxX = getMaxX();
- int maxY = getMaxY();
- int maxZ = getMaxZ();
-
- for (int x = minX; x < maxX; x++) {
- for (int y = minY; y < maxY; y++) {
- for (int z = minZ; z < maxZ; z++) {
- func.accept(x, y, z);
- }
- }
- }
- }
-
- default boolean hasPowerOf2Sides() {
- // this is only true if all individual side lengths are powers of 2
- return Mth.isPowerOfTwo(volume());
- }
-
- default MutableBox union(Box other) {
- int minX = Math.min(this.getMinX(), other.getMinX());
- int minY = Math.min(this.getMinY(), other.getMinY());
- int minZ = Math.min(this.getMinZ(), other.getMinZ());
- int maxX = Math.max(this.getMaxX(), other.getMaxX());
- int maxY = Math.max(this.getMaxY(), other.getMaxY());
- int maxZ = Math.max(this.getMaxZ(), other.getMaxZ());
- return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ);
- }
-
- default MutableBox intersect(Box other) {
- int minX = Math.max(this.getMinX(), other.getMinX());
- int minY = Math.max(this.getMinY(), other.getMinY());
- int minZ = Math.max(this.getMinZ(), other.getMinZ());
- int maxX = Math.min(this.getMaxX(), other.getMaxX());
- int maxY = Math.min(this.getMaxY(), other.getMaxY());
- int maxZ = Math.min(this.getMaxZ(), other.getMaxZ());
- return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ);
- }
-
- default AABB toAABB() {
- return new AABB(getMinX(), getMinY(), getMinZ(), getMaxX(), getMaxY(), getMaxZ());
- }
-
- default MutableBox copy() {
- return new MutableBox(getMinX(), getMinY(), getMinZ(), getMaxX(), getMaxY(), getMaxZ());
- }
-
- @FunctionalInterface
- interface CoordinateConsumer {
- void accept(int x, int y, int z);
- }
-}
diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/box/MutableBox.java b/common/src/lib/java/dev/engine_room/flywheel/lib/box/MutableBox.java
deleted file mode 100644
index db69a3883..000000000
--- a/common/src/lib/java/dev/engine_room/flywheel/lib/box/MutableBox.java
+++ /dev/null
@@ -1,329 +0,0 @@
-package dev.engine_room.flywheel.lib.box;
-
-import java.util.Collection;
-
-import net.minecraft.core.BlockPos;
-import net.minecraft.core.Direction;
-import net.minecraft.core.SectionPos;
-import net.minecraft.core.Vec3i;
-import net.minecraft.util.Mth;
-import net.minecraft.world.phys.AABB;
-
-public class MutableBox implements Box {
- protected int minX;
- protected int minY;
- protected int minZ;
- protected int maxX;
- protected int maxY;
- protected int maxZ;
-
- public MutableBox() {
- }
-
- public MutableBox(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
- this.minX = minX;
- this.minY = minY;
- this.minZ = minZ;
- this.maxX = maxX;
- this.maxY = maxY;
- this.maxZ = maxZ;
- }
-
- public static MutableBox from(AABB aabb) {
- int minX = (int) Math.floor(aabb.minX);
- int minY = (int) Math.floor(aabb.minY);
- int minZ = (int) Math.floor(aabb.minZ);
- int maxX = (int) Math.ceil(aabb.maxX);
- int maxY = (int) Math.ceil(aabb.maxY);
- int maxZ = (int) Math.ceil(aabb.maxZ);
- return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ);
- }
-
- public static MutableBox from(Vec3i pos) {
- return new MutableBox(pos.getX(), pos.getY(), pos.getZ(), pos.getX() + 1, pos.getY() + 1, pos.getZ() + 1);
- }
-
- public static MutableBox from(SectionPos pos) {
- return new MutableBox(pos.minBlockX(), pos.minBlockY(), pos.minBlockZ(), pos.maxBlockX() + 1, pos.maxBlockY() + 1, pos.maxBlockZ() + 1);
- }
-
- public static MutableBox from(Vec3i start, Vec3i end) {
- return new MutableBox(start.getX(), start.getY(), start.getZ(), end.getX() + 1, end.getY() + 1, end.getZ() + 1);
- }
-
- public static MutableBox ofRadius(int radius) {
- return new MutableBox(-radius, -radius, -radius, radius + 1, radius + 1, radius + 1);
- }
-
- public static Box containingAll(Collection positions) {
- if (positions.isEmpty()) {
- return new MutableBox();
- }
- int minX = Integer.MAX_VALUE;
- int minY = Integer.MAX_VALUE;
- int minZ = Integer.MAX_VALUE;
- int maxX = Integer.MIN_VALUE;
- int maxY = Integer.MIN_VALUE;
- int maxZ = Integer.MIN_VALUE;
- for (BlockPos pos : positions) {
- minX = Math.min(minX, pos.getX());
- minY = Math.min(minY, pos.getY());
- minZ = Math.min(minZ, pos.getZ());
- maxX = Math.max(maxX, pos.getX());
- maxY = Math.max(maxY, pos.getY());
- maxZ = Math.max(maxZ, pos.getZ());
- }
- return new MutableBox(minX, minY, minZ, maxX, maxY, maxZ);
- }
-
- @Override
- public int getMinX() {
- return minX;
- }
-
- @Override
- public int getMinY() {
- return minY;
- }
-
- @Override
- public int getMinZ() {
- return minZ;
- }
-
- @Override
- public int getMaxX() {
- return maxX;
- }
-
- @Override
- public int getMaxY() {
- return maxY;
- }
-
- @Override
- public int getMaxZ() {
- return maxZ;
- }
-
- public void setMinX(int minX) {
- this.minX = minX;
- }
-
- public void setMinY(int minY) {
- this.minY = minY;
- }
-
- public MutableBox setMinZ(int minZ) {
- this.minZ = minZ;
- return this;
- }
-
- public void setMaxX(int maxX) {
- this.maxX = maxX;
- }
-
- public void setMaxY(int maxY) {
- this.maxY = maxY;
- }
-
- public void setMaxZ(int maxZ) {
- this.maxZ = maxZ;
- }
-
- public void setMin(int x, int y, int z) {
- minX = x;
- minY = y;
- minZ = z;
- }
-
- public void setMax(int x, int y, int z) {
- maxX = x;
- maxY = y;
- maxZ = z;
- }
-
- public void setMin(Vec3i v) {
- setMin(v.getX(), v.getY(), v.getZ());
- }
-
- public void setMax(Vec3i v) {
- setMax(v.getX(), v.getY(), v.getZ());
- }
-
- public void assign(Box other) {
- minX = other.getMinX();
- minY = other.getMinY();
- minZ = other.getMinZ();
- maxX = other.getMaxX();
- maxY = other.getMaxY();
- maxZ = other.getMaxZ();
- }
-
- public void assign(AABB other) {
- minX = (int) Math.floor(other.minX);
- minY = (int) Math.floor(other.minY);
- minZ = (int) Math.floor(other.minZ);
- maxX = (int) Math.ceil(other.maxX);
- maxY = (int) Math.ceil(other.maxY);
- maxZ = (int) Math.ceil(other.maxZ);
- }
-
- public void assign(Vec3i start, Vec3i end) {
- minX = start.getX();
- minY = start.getY();
- minZ = start.getZ();
- maxX = end.getX() + 1;
- maxY = end.getY() + 1;
- maxZ = end.getZ() + 1;
- }
-
- public void unionAssign(Box other) {
- minX = Math.min(this.minX, other.getMinX());
- minY = Math.min(this.minY, other.getMinY());
- minZ = Math.min(this.minZ, other.getMinZ());
- maxX = Math.max(this.maxX, other.getMaxX());
- maxY = Math.max(this.maxY, other.getMaxY());
- maxZ = Math.max(this.maxZ, other.getMaxZ());
- }
-
- public void unionAssign(AABB other) {
- minX = Math.min(this.minX, (int) Math.floor(other.minX));
- minY = Math.min(this.minY, (int) Math.floor(other.minY));
- minZ = Math.min(this.minZ, (int) Math.floor(other.minZ));
- maxX = Math.max(this.maxX, (int) Math.ceil(other.maxX));
- maxY = Math.max(this.maxY, (int) Math.ceil(other.maxY));
- maxZ = Math.max(this.maxZ, (int) Math.ceil(other.maxZ));
- }
-
- public void intersectAssign(Box other) {
- minX = Math.max(this.minX, other.getMinX());
- minY = Math.max(this.minY, other.getMinY());
- minZ = Math.max(this.minZ, other.getMinZ());
- maxX = Math.min(this.maxX, other.getMaxX());
- maxY = Math.min(this.maxY, other.getMaxY());
- maxZ = Math.min(this.maxZ, other.getMaxZ());
- }
-
- public void fixMinMax() {
- int minX = Math.min(this.minX, this.maxX);
- int minY = Math.min(this.minY, this.maxY);
- int minZ = Math.min(this.minZ, this.maxZ);
- int maxX = Math.max(this.minX, this.maxX);
- int maxY = Math.max(this.minY, this.maxY);
- int maxZ = Math.max(this.minZ, this.maxZ);
-
- this.minX = minX;
- this.minY = minY;
- this.minZ = minZ;
- this.maxX = maxX;
- this.maxY = maxY;
- this.maxZ = maxZ;
- }
-
- public void translate(int x, int y, int z) {
- minX = minX + x;
- maxX = maxX + x;
- minY = minY + y;
- maxY = maxY + y;
- minZ = minZ + z;
- maxZ = maxZ + z;
- }
-
- public void translate(Vec3i by) {
- translate(by.getX(), by.getY(), by.getZ());
- }
-
- public void grow(int x, int y, int z) {
- minX = minX - x;
- minY = minY - y;
- minZ = minZ - z;
- maxX = maxX + x;
- maxY = maxY + y;
- maxZ = maxZ + z;
- }
-
- public void grow(int s) {
- this.grow(s, s, s);
- }
-
- /**
- * Grow this box to have power of 2 side lengths, scaling from the minimum coords.
- */
- public void nextPowerOf2() {
- int sizeX = Mth.smallestEncompassingPowerOfTwo(sizeX());
- int sizeY = Mth.smallestEncompassingPowerOfTwo(sizeY());
- int sizeZ = Mth.smallestEncompassingPowerOfTwo(sizeZ());
-
- maxX = minX + sizeX;
- maxY = minY + sizeY;
- maxZ = minZ + sizeZ;
- }
-
- /**
- * Grow this box to have power of 2 side length, scaling from the center.
- */
- public void nextPowerOf2Centered() {
- int sizeX = sizeX();
- int sizeY = sizeY();
- int sizeZ = sizeZ();
-
- int newSizeX = Mth.smallestEncompassingPowerOfTwo(sizeX);
- int newSizeY = Mth.smallestEncompassingPowerOfTwo(sizeY);
- int newSizeZ = Mth.smallestEncompassingPowerOfTwo(sizeZ);
-
- int diffX = newSizeX - sizeX;
- int diffY = newSizeY - sizeY;
- int diffZ = newSizeZ - sizeZ;
-
- minX = minX - diffX / 2; // floor division for the minimums
- minY = minY - diffY / 2;
- minZ = minZ - diffZ / 2;
- maxX = maxX + (diffX + 1) / 2; // ceiling divison for the maximums
- maxY = maxY + (diffY + 1) / 2;
- maxZ = maxZ + (diffZ + 1) / 2;
- }
-
- public void mirrorAbout(Direction.Axis axis) {
- Vec3i axisVec = Direction.get(Direction.AxisDirection.POSITIVE, axis)
- .getNormal();
- int flipX = axisVec.getX() - 1;
- int flipY = axisVec.getY() - 1;
- int flipZ = axisVec.getZ() - 1;
-
- int maxX = this.maxX * flipX;
- int maxY = this.maxY * flipY;
- int maxZ = this.maxZ * flipZ;
- this.maxX = this.minX * flipX;
- this.maxY = this.minY * flipY;
- this.maxZ = this.minZ * flipZ;
- this.minX = maxX;
- this.minY = maxY;
- this.minZ = maxZ;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null) return false;
- if (!(o instanceof Box that)) return false;
-
- return this.sameAs(that);
- }
-
- @Override
- public int hashCode() {
- int result = minX;
- result = 31 * result + minY;
- result = 31 * result + minZ;
- result = 31 * result + maxX;
- result = 31 * result + maxY;
- result = 31 * result + maxZ;
- return result;
- }
-
- @Override
- public String toString() {
- return "(" + minX + ", " + minY + ", " + minZ + ")->(" + maxX + ", " + maxY + ", " + maxZ + ')';
- }
-}
diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/light/LightPacking.java b/common/src/lib/java/dev/engine_room/flywheel/lib/light/LightPacking.java
deleted file mode 100644
index 7cd9ce37e..000000000
--- a/common/src/lib/java/dev/engine_room/flywheel/lib/light/LightPacking.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package dev.engine_room.flywheel.lib.light;
-
-/**
- * Utility class for bit-twiddling light.
- */
-public class LightPacking {
- public static int getBlock(short packed) {
- return (packed >> 4) & 0xF;
- }
-
- public static int getSky(short packed) {
- return (packed >> 12) & 0xF;
- }
-
- public static byte packLightNibbles(byte block, byte sky) {
- return (byte) (block | (sky << 4));
- }
-
- public static int getBlock(byte packed) {
- return packed & 0xF;
- }
-
- public static int getSky(byte packed) {
- return (packed >> 4) & 0xF;
- }
-}
diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/light/LightVolume.java b/common/src/lib/java/dev/engine_room/flywheel/lib/light/LightVolume.java
deleted file mode 100644
index ab71bcae3..000000000
--- a/common/src/lib/java/dev/engine_room/flywheel/lib/light/LightVolume.java
+++ /dev/null
@@ -1,217 +0,0 @@
-package dev.engine_room.flywheel.lib.light;
-
-import org.lwjgl.system.MemoryUtil;
-
-import dev.engine_room.flywheel.lib.box.Box;
-import dev.engine_room.flywheel.lib.box.MutableBox;
-import dev.engine_room.flywheel.lib.memory.MemoryBlock;
-import net.minecraft.core.BlockPos;
-import net.minecraft.core.SectionPos;
-import net.minecraft.world.level.BlockAndTintGetter;
-import net.minecraft.world.level.LightLayer;
-
-public class LightVolume implements Box {
- protected final BlockAndTintGetter level;
- protected final MutableBox box = new MutableBox();
- protected MemoryBlock lightData;
-
- public LightVolume(BlockAndTintGetter level, Box sampleVolume) {
- this.level = level;
- this.setBox(sampleVolume);
-
- this.lightData = MemoryBlock.malloc(this.box.volume() * 2);
- }
-
- public Box getVolume() {
- return box;
- }
-
- @Override
- public int getMinX() {
- return box.getMinX();
- }
-
- @Override
- public int getMinY() {
- return box.getMinY();
- }
-
- @Override
- public int getMinZ() {
- return box.getMinZ();
- }
-
- @Override
- public int getMaxX() {
- return box.getMaxX();
- }
-
- @Override
- public int getMaxY() {
- return box.getMaxY();
- }
-
- @Override
- public int getMaxZ() {
- return box.getMaxZ();
- }
-
- public boolean isInvalid() {
- return lightData == null;
- }
-
- protected void setBox(Box box) {
- this.box.assign(box);
- }
-
- public short getPackedLight(int x, int y, int z) {
- if (box.contains(x, y, z)) {
- return MemoryUtil.memGetShort(levelPosToPtr(x, y, z));
- } else {
- return 0;
- }
- }
-
- public void move(Box newSampleVolume) {
- if (lightData == null) return;
-
- setBox(newSampleVolume);
- int neededCapacity = box.volume() * 2;
- if (neededCapacity > lightData.size()) {
- lightData = lightData.realloc(neededCapacity);
- }
- initialize();
- }
-
- /**
- * Completely (re)populate this volume with block and sky lighting data.
- * This is expensive and should be avoided.
- */
- public void initialize() {
- if (lightData == null) return;
-
- copyLight(getVolume());
- markDirty();
- }
-
- protected void markDirty() {
- // noop
- }
-
- public void delete() {
- lightData.free();
- lightData = null;
- }
-
- /**
- * Copy all light from the level into this volume.
- *
- * @param levelVolume the region in the level to copy data from.
- */
- public void copyLight(Box levelVolume) {
- BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos();
-
- int xShift = box.getMinX();
- int yShift = box.getMinY();
- int zShift = box.getMinZ();
-
- levelVolume.forEachContained((x, y, z) -> {
- pos.set(x, y, z);
-
- int block = this.level.getBrightness(LightLayer.BLOCK, pos);
- int sky = this.level.getBrightness(LightLayer.SKY, pos);
-
- writeLight(x - xShift, y - yShift, z - zShift, block, sky);
- });
- }
-
- protected void writeLight(int x, int y, int z, int block, int sky) {
- byte b = (byte) ((block & 0xF) << 4);
- byte s = (byte) ((sky & 0xF) << 4);
-
- long ptr = boxPosToPtr(x, y, z);
- MemoryUtil.memPutByte(ptr, b);
- MemoryUtil.memPutByte(ptr + 1, s);
- }
-
- /**
- * Copy block light from the level into this volume.
- *
- * @param levelVolume the region in the level to copy data from.
- */
- public void copyBlock(Box levelVolume) {
- var pos = new BlockPos.MutableBlockPos();
-
- int xShift = box.getMinX();
- int yShift = box.getMinY();
- int zShift = box.getMinZ();
-
- levelVolume.forEachContained((x, y, z) -> {
- int light = this.level.getBrightness(LightLayer.BLOCK, pos.set(x, y, z));
-
- writeBlock(x - xShift, y - yShift, z - zShift, light);
- });
- }
-
- protected void writeBlock(int x, int y, int z, int block) {
- byte b = (byte) ((block & 0xF) << 4);
-
- MemoryUtil.memPutByte(boxPosToPtr(x, y, z), b);
- }
-
- /**
- * Copy sky light from the level into this volume.
- *
- * @param levelVolume the region in the level to copy data from.
- */
- public void copySky(Box levelVolume) {
- var pos = new BlockPos.MutableBlockPos();
-
- int xShift = box.getMinX();
- int yShift = box.getMinY();
- int zShift = box.getMinZ();
-
- levelVolume.forEachContained((x, y, z) -> {
- int light = this.level.getBrightness(LightLayer.SKY, pos.set(x, y, z));
-
- writeSky(x - xShift, y - yShift, z - zShift, light);
- });
- }
-
- protected void writeSky(int x, int y, int z, int sky) {
- byte s = (byte) ((sky & 0xF) << 4);
-
- MemoryUtil.memPutByte(boxPosToPtr(x, y, z) + 1, s);
- }
-
- protected long levelPosToPtr(int x, int y, int z) {
- return lightData.ptr() + levelPosToPtrOffset(x, y, z);
- }
-
- protected long boxPosToPtr(int x, int y, int z) {
- return lightData.ptr() + boxPosToPtrOffset(x, y, z);
- }
-
- protected int levelPosToPtrOffset(int x, int y, int z) {
- x -= box.getMinX();
- y -= box.getMinY();
- z -= box.getMinZ();
- return boxPosToPtrOffset(x, y, z);
- }
-
- protected int boxPosToPtrOffset(int x, int y, int z) {
- return (x + box.sizeX() * (y + z * box.sizeY())) * 2;
- }
-
- public void onLightUpdate(LightLayer type, SectionPos pos) {
- if (lightData == null) return;
-
- MutableBox vol = MutableBox.from(pos);
- if (!vol.intersects(getVolume())) return;
- vol.intersectAssign(getVolume()); // compute the region contained by us that has dirty lighting data.
-
- if (type == LightLayer.BLOCK) copyBlock(vol);
- else if (type == LightLayer.SKY) copySky(vol);
- markDirty();
- }
-}
diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java
index 59722198e..a3fafb59f 100644
--- a/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java
+++ b/common/src/lib/java/dev/engine_room/flywheel/lib/visual/AbstractBlockEntityVisual.java
@@ -1,18 +1,18 @@
package dev.engine_room.flywheel.lib.visual;
-import java.util.function.LongConsumer;
-
import org.jetbrains.annotations.Nullable;
import org.joml.FrustumIntersection;
import dev.engine_room.flywheel.api.visual.BlockEntityVisual;
import dev.engine_room.flywheel.api.visual.DynamicVisual;
-import dev.engine_room.flywheel.api.visual.LitVisual;
+import dev.engine_room.flywheel.api.visual.LightUpdatedVisual;
+import dev.engine_room.flywheel.api.visual.ShaderLightVisual;
import dev.engine_room.flywheel.api.visual.TickableVisual;
import dev.engine_room.flywheel.api.visualization.VisualManager;
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
import dev.engine_room.flywheel.lib.instance.FlatLit;
import dev.engine_room.flywheel.lib.math.MoreMath;
+import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.client.renderer.LevelRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
@@ -26,6 +26,8 @@
*
* - {@link DynamicVisual}
* - {@link TickableVisual}
+ * - {@link LightUpdatedVisual}
+ * - {@link ShaderLightVisual}
*
* See the interfaces' documentation for more information about each one.
*
@@ -34,13 +36,13 @@
*
* @param The type of {@link BlockEntity}.
*/
-public abstract class AbstractBlockEntityVisual extends AbstractVisual implements BlockEntityVisual, LitVisual {
+public abstract class AbstractBlockEntityVisual extends AbstractVisual implements BlockEntityVisual, LightUpdatedVisual {
protected final T blockEntity;
protected final BlockPos pos;
protected final BlockPos visualPos;
protected final BlockState blockState;
@Nullable
- protected LitVisual.Notifier notifier;
+ protected SectionCollector lightSections;
public AbstractBlockEntityVisual(VisualizationContext ctx, T blockEntity, float partialTick) {
super(ctx, blockEntity.getLevel(), partialTick);
@@ -51,13 +53,9 @@ public AbstractBlockEntityVisual(VisualizationContext ctx, T blockEntity, float
}
@Override
- public void setLightSectionNotifier(Notifier notifier) {
- this.notifier = notifier;
- }
-
- @Override
- public void collectLightSections(LongConsumer consumer) {
- consumer.accept(SectionPos.asLong(pos));
+ public void setSectionCollector(SectionCollector sectionCollector) {
+ this.lightSections = sectionCollector;
+ lightSections.sections(LongSet.of(SectionPos.asLong(pos)));
}
/**
diff --git a/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag b/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag
index 68dfe9a5b..f072c1849 100644
--- a/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag
+++ b/common/src/lib/resources/assets/flywheel/flywheel/material/default.frag
@@ -1,2 +1,8 @@
void flw_materialFragment() {
+ #ifdef FLW_EMBEDDED
+ vec2 embeddedLight;
+ if (flw_light(flw_vertexPos.xyz, flw_vertexNormal, embeddedLight)) {
+ flw_fragLight = max(flw_fragLight, embeddedLight);
+ }
+ #endif
}
diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ClientChunkCacheMixin.java b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ClientChunkCacheMixin.java
index d12415179..f55d6d2e9 100644
--- a/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ClientChunkCacheMixin.java
+++ b/common/src/main/java/dev/engine_room/flywheel/impl/mixin/ClientChunkCacheMixin.java
@@ -24,7 +24,7 @@ abstract class ClientChunkCacheMixin {
var manager = VisualizationManagerImpl.get(level);
if (manager != null) {
- manager.enqueueLightUpdateSection(pos.asLong());
+ manager.onLightUpdate(pos.asLong());
}
}
}
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 e0accaf2d..ccf69cc0e 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
@@ -45,6 +45,7 @@
import dev.engine_room.flywheel.lib.task.SimplePlan;
import dev.engine_room.flywheel.lib.util.LevelAttached;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import net.minecraft.client.Minecraft;
import net.minecraft.core.Vec3i;
import net.minecraft.server.level.BlockDestructionProgress;
@@ -104,6 +105,15 @@ private VisualizationManagerImpl(LevelAccessor level) {
.ifTrue(recreate)
.ifFalse(update)
.plan()
+ .then(SimplePlan.of(() -> {
+ if (blockEntities.areGpuLightSectionsDirty() || entities.areGpuLightSectionsDirty() || effects.areGpuLightSectionsDirty()) {
+ var out = new LongOpenHashSet();
+ out.addAll(blockEntities.gpuLightSections());
+ out.addAll(entities.gpuLightSections());
+ out.addAll(effects.gpuLightSections());
+ engine.lightSections(out);
+ }
+ }))
.then(RaisePlan.raise(frameVisualsFlag))
.then(engine.createFramePlan())
.then(RaisePlan.raise(frameFlag));
@@ -292,6 +302,12 @@ public void renderCrumbling(RenderContext context, Long2ObjectMap createRaw(BlockEntity obj, float partialTick) {
@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();
}
}
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 3abc3c7ac..f0d3420bb 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
@@ -10,6 +10,7 @@
import dev.engine_room.flywheel.impl.visualization.storage.Storage;
import dev.engine_room.flywheel.impl.visualization.storage.Transaction;
import dev.engine_room.flywheel.lib.task.SimplePlan;
+import it.unimi.dsi.fastutil.longs.LongSet;
public class VisualManagerImpl> implements VisualManager {
private final Queue> queue = new ConcurrentLinkedQueue<>();
@@ -53,10 +54,6 @@ public void queueUpdate(T obj) {
queue.add(Transaction.update(obj));
}
- public void invalidate() {
- getStorage().invalidate();
- }
-
public void processQueue(float partialTick) {
var storage = getStorage();
Transaction transaction;
@@ -74,4 +71,23 @@ public Plan tickPlan() {
return SimplePlan.of(context -> processQueue(1))
.then(storage.tickPlan());
}
+
+ public void onLightUpdate(long section) {
+ getStorage().lightUpdatedVisuals()
+ .onLightUpdate(section);
+ }
+
+ public boolean areGpuLightSectionsDirty() {
+ return getStorage().shaderLightVisuals()
+ .isDirty();
+ }
+
+ public LongSet gpuLightSections() {
+ return getStorage().shaderLightVisuals()
+ .sections();
+ }
+
+ public void invalidate() {
+ getStorage().invalidate();
+ }
}
diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LitVisualStorage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LightUpdatedVisualStorage.java
similarity index 63%
rename from common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LitVisualStorage.java
rename to common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LightUpdatedVisualStorage.java
index 3651fe278..3f730abc0 100644
--- a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LitVisualStorage.java
+++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/LightUpdatedVisualStorage.java
@@ -10,13 +10,12 @@
import dev.engine_room.flywheel.api.task.Plan;
import dev.engine_room.flywheel.api.task.TaskExecutor;
import dev.engine_room.flywheel.api.visual.DynamicVisual;
-import dev.engine_room.flywheel.api.visual.LitVisual;
+import dev.engine_room.flywheel.api.visual.LightUpdatedVisual;
import dev.engine_room.flywheel.lib.task.Distribute;
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 @@
/**
* Keeps track of what chunks/sections each listener is in, so we can update exactly what needs to be updated.
*/
-public class LitVisualStorage {
+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 visuals2Sections = new WeakHashMap<>();
- private final Long2ObjectMap> sections2Visuals = new Long2ObjectOpenHashMap<>();
+ private final Map visual2Sections = new WeakHashMap<>();
+ private final Long2ObjectMap> section2Updaters = new Long2ObjectOpenHashMap<>();
- private final Queue movedVisuals = new ConcurrentLinkedQueue<>();
private final LongSet sectionsUpdatedThisFrame = new LongOpenHashSet();
+ private final Queue movedVisuals = new ConcurrentLinkedQueue<>();
private long updateId = INITIAL_UPDATE_ID;
@@ -54,9 +53,9 @@ public Plan plan() {
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();
}
@@ -65,11 +64,11 @@ public Plan plan() {
}
private void processMoved() {
- LitVisual visual;
- while ((visual = movedVisuals.poll()) != null) {
+ MovedVisual moved;
+ 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(visual)) {
- add(visual);
+ if (remove(moved.visual)) {
+ addInner(moved.visual, moved.tracker);
}
}
}
@@ -86,72 +85,69 @@ private long getNextUpdateId() {
return out;
}
- public boolean isEmpty() {
- return visuals2Sections.isEmpty();
- }
+ public void add(LightUpdatedVisual visual, SectionTracker tracker) {
+ var moved = new MovedVisual(visual, tracker);
+ tracker.addListener(() -> movedVisuals.add(moved));
- public void setNotifierAndAdd(LitVisual visual) {
- visual.setLightSectionNotifier(new LitVisualNotifierImpl(visual));
- add(visual);
+ addInner(visual, tracker);
}
- private void add(LitVisual visual) {
- LongSet sections = new LongArraySet();
-
- visual.collectLightSections(sections::add);
+ 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
+ visual2Sections.put(visual, LongSet.of());
- // Add the visual to the map even if sections is empty, this way we can distinguish from deleted visuals
- visuals2Sections.put(visual, sections);
-
- // Don't bother creating an updater if the visual isn't in any sections.
- if (sections.isEmpty()) {
+ // Don't bother creating an updater if the visual isn't in any sections.
return;
}
+ 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.
*
* @param visual The visual to remove.
* @return {@code true} if the visual was removed, {@code false} otherwise.
*/
- public boolean remove(LitVisual visual) {
- var sections = visuals2Sections.remove(visual);
+ public boolean remove(LightUpdatedVisual visual) {
+ var sections = visual2Sections.remove(visual);
if (sections == null) {
return false;
}
for (long section : sections) {
- List listeners = sections2Visuals.get(section);
- if (listeners != null) {
- listeners.remove(indexOfUpdater(listeners, visual));
+ List updaters = section2Updaters.get(section);
+ if (updaters != null) {
+ updaters.remove(indexOfUpdater(updaters, visual));
}
}
return true;
}
+ public void onLightUpdate(long section) {
+ sectionsUpdatedThisFrame.add(section);
+ }
+
public void clear() {
- visuals2Sections.clear();
- sections2Visuals.clear();
+ visual2Sections.clear();
+ section2Updaters.clear();
sectionsUpdatedThisFrame.clear();
+ movedVisuals.clear();
}
- private static int indexOfUpdater(List listeners, LitVisual visual) {
- for (int i = 0; i < listeners.size(); i++) {
- if (listeners.get(i)
+ private static int indexOfUpdater(List updaters, LightUpdatedVisual visual) {
+ for (int i = 0; i < updaters.size(); i++) {
+ if (updaters.get(i)
.visual() == visual) {
return i;
}
@@ -159,7 +155,7 @@ private static int indexOfUpdater(List listeners, LitVisual visual) {
return -1;
}
- private static Updater createUpdater(LitVisual visual, int sectionCount) {
+ private static Updater createUpdater(LightUpdatedVisual visual, int sectionCount) {
if (sectionCount == 1) {
return new Updater.Simple(visual);
} else {
@@ -168,13 +164,13 @@ private static Updater createUpdater(LitVisual visual, int sectionCount) {
}
// 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);
- LitVisual visual();
+ LightUpdatedVisual visual();
// The visual is only in one section. In this case, we can just update the visual directly.
- record Simple(LitVisual visual) implements Updater {
+ record Simple(LightUpdatedVisual visual) implements Updater {
@Override
public void updateLight(Context ctx) {
visual.updateLight(ctx.partialTick);
@@ -183,7 +179,7 @@ public void updateLight(Context ctx) {
// The visual is in multiple sections. Here we need to make sure that the visual only gets updated once,
// even when multiple sections it was contained in are updated at the same time.
- record Synced(LitVisual visual, AtomicLong updateId) implements Updater {
+ record Synced(LightUpdatedVisual visual, AtomicLong updateId) implements Updater {
@Override
public void updateLight(Context ctx) {
// Different update ID means we won, so we can update the visual.
@@ -198,16 +194,6 @@ record Context(long updateId, float partialTick) {
}
}
- private final class LitVisualNotifierImpl implements LitVisual.Notifier {
- private final LitVisual litVisual;
-
- private LitVisualNotifierImpl(LitVisual litVisual) {
- this.litVisual = litVisual;
- }
-
- @Override
- public void notifySectionsChanged() {
- movedVisuals.add(litVisual);
- }
- }
+ private record MovedVisual(LightUpdatedVisual visual, SectionTracker tracker) {
+ }
}
diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionTracker.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionTracker.java
new file mode 100644
index 000000000..9cba32f69
--- /dev/null
+++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/SectionTracker.java
@@ -0,0 +1,33 @@
+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 SectionTracker implements SectionTrackedVisual.SectionCollector {
+ private final List listeners = new ArrayList<>(2);
+
+ @Unmodifiable
+ private LongSet sections = LongSet.of();
+
+ @Unmodifiable
+ public LongSet sections() {
+ return sections;
+ }
+
+ @Override
+ public void sections(LongSet sections) {
+ this.sections = LongSets.unmodifiable(new LongArraySet(sections));
+ listeners.forEach(Runnable::run);
+ }
+
+ public void addListener(Runnable listener) {
+ listeners.add(listener);
+ }
+}
diff --git a/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/ShaderLightVisualStorage.java b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/ShaderLightVisualStorage.java
new file mode 100644
index 000000000..052a9e06c
--- /dev/null
+++ b/common/src/main/java/dev/engine_room/flywheel/impl/visualization/storage/ShaderLightVisualStorage.java
@@ -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 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();
+ }
+}
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 b599dc26e..4e2d1ebaf 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
@@ -10,7 +10,9 @@
import dev.engine_room.flywheel.api.task.Plan;
import dev.engine_room.flywheel.api.visual.DynamicVisual;
-import dev.engine_room.flywheel.api.visual.LitVisual;
+import dev.engine_room.flywheel.api.visual.LightUpdatedVisual;
+import dev.engine_room.flywheel.api.visual.SectionTrackedVisual;
+import dev.engine_room.flywheel.api.visual.ShaderLightVisual;
import dev.engine_room.flywheel.api.visual.TickableVisual;
import dev.engine_room.flywheel.api.visual.Visual;
import dev.engine_room.flywheel.api.visualization.VisualizationContext;
@@ -23,13 +25,14 @@
public abstract class Storage {
protected final Supplier visualizationContextSupplier;
+
+ private final Map visuals = new Reference2ObjectOpenHashMap<>();
protected final PlanMap dynamicVisuals = new PlanMap<>();
protected final PlanMap tickableVisuals = new PlanMap<>();
protected final List simpleDynamicVisuals = new ArrayList<>();
protected final List simpleTickableVisuals = new ArrayList<>();
- protected final LitVisualStorage litVisuals = new LitVisualStorage();
-
- private final Map visuals = new Reference2ObjectOpenHashMap<>();
+ protected final LightUpdatedVisualStorage lightUpdatedVisuals = new LightUpdatedVisualStorage();
+ protected final ShaderLightVisualStorage shaderLightVisuals = new ShaderLightVisualStorage();
public Storage(Supplier visualizationContextSupplier) {
this.visualizationContextSupplier = visualizationContextSupplier;
@@ -39,6 +42,29 @@ public Collection getAllVisuals() {
return visuals.values();
}
+ public Plan framePlan() {
+ return NestedPlan.of(dynamicVisuals, lightUpdatedVisuals.plan(), ForEachPlan.of(() -> simpleDynamicVisuals, SimpleDynamicVisual::beginFrame));
+ }
+
+ public Plan 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);
@@ -54,6 +80,13 @@ public void remove(T obj) {
return;
}
+ if (visual instanceof DynamicVisual dynamic) {
+ if (visual instanceof SimpleDynamicVisual simpleDynamic) {
+ simpleDynamicVisuals.remove(simpleDynamic);
+ } else {
+ dynamicVisuals.remove(dynamic);
+ }
+ }
if (visual instanceof TickableVisual tickable) {
if (visual instanceof SimpleTickableVisual simpleTickable) {
simpleTickableVisuals.remove(simpleTickable);
@@ -61,16 +94,13 @@ public void remove(T obj) {
tickableVisuals.remove(tickable);
}
}
- if (visual instanceof DynamicVisual dynamic) {
- if (visual instanceof SimpleDynamicVisual simpleDynamic) {
- simpleDynamicVisuals.remove(simpleDynamic);
- } else {
- dynamicVisuals.remove(dynamic);
- }
+ if (visual instanceof LightUpdatedVisual lightUpdated) {
+ lightUpdatedVisuals.remove(lightUpdated);
}
- if (visual instanceof LitVisual lit) {
- litVisuals.remove(lit);
+ if (visual instanceof ShaderLightVisual shaderLight) {
+ shaderLightVisuals.remove(shaderLight);
}
+
visual.delete();
}
@@ -85,11 +115,13 @@ public void update(T obj, float partialTick) {
}
public void recreateAll(float partialTick) {
- tickableVisuals.clear();
dynamicVisuals.clear();
- simpleTickableVisuals.clear();
+ tickableVisuals.clear();
simpleDynamicVisuals.clear();
- litVisuals.clear();
+ simpleTickableVisuals.clear();
+ lightUpdatedVisuals.clear();
+ shaderLightVisuals.clear();
+
visuals.replaceAll((obj, visual) -> {
visual.delete();
@@ -103,15 +135,6 @@ public void recreateAll(float partialTick) {
});
}
- 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);
@@ -124,19 +147,15 @@ private void create(T obj, float partialTick) {
@Nullable
protected abstract Visual createRaw(T obj, float partialTick);
- public Plan framePlan() {
- return NestedPlan.of(dynamicVisuals, litVisuals.plan(), ForEachPlan.of(() -> simpleDynamicVisuals, SimpleDynamicVisual::beginFrame));
- }
-
- public Plan 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 DynamicVisual dynamic) {
+ if (visual instanceof SimpleDynamicVisual simpleDynamic) {
+ simpleDynamicVisuals.add(simpleDynamic);
+ } else {
+ dynamicVisuals.add(dynamic, dynamic.planFrame());
+ }
+ }
+
if (visual instanceof TickableVisual tickable) {
if (visual instanceof SimpleTickableVisual simpleTickable) {
simpleTickableVisuals.add(simpleTickable);
@@ -145,23 +164,31 @@ private void setup(Visual visual) {
}
}
- if (visual instanceof DynamicVisual dynamic) {
- if (visual instanceof SimpleDynamicVisual simpleDynamic) {
- simpleDynamicVisuals.add(simpleDynamic);
- } else {
- dynamicVisuals.add(dynamic, dynamic.planFrame());
+ 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) {
+ lightUpdatedVisuals.add(lightUpdated, tracker);
}
- }
- if (visual instanceof LitVisual lit) {
- litVisuals.setNotifierAndAdd(lit);
+ if (visual instanceof ShaderLightVisual 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 void invalidate() {
+ dynamicVisuals.clear();
+ tickableVisuals.clear();
+ simpleDynamicVisuals.clear();
+ simpleTickableVisuals.clear();
+ lightUpdatedVisuals.clear();
+ shaderLightVisuals.clear();
+ visuals.values()
+ .forEach(Visual::delete);
+ visuals.clear();
+ }
}
diff --git a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java
index b0a72f0a7..873ff2bce 100644
--- a/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java
+++ b/common/src/main/java/dev/engine_room/flywheel/vanilla/MinecartVisual.java
@@ -194,7 +194,7 @@ private void updateInstances(float partialTick) {
body.setTransform(stack)
.setChanged();
- // TODO: Use LitVisual if possible.
+ // TODO: Use LightUpdatedVisual/ShaderLightVisual if possible.
updateLight(partialTick);
}
diff --git a/docs/shader-api/common.glsl b/docs/shader-api/common.glsl
new file mode 100644
index 000000000..9362c693d
--- /dev/null
+++ b/docs/shader-api/common.glsl
@@ -0,0 +1,7 @@
+/// Get the light at the given world position.
+/// This may be interpolated for smooth lighting.
+bool flw_light(vec3 worldPos, out vec2 light);
+
+/// Fetches the light value at the given block position.
+/// Returns false if the light for the given block is not available.
+bool flw_lightFetch(ivec3 blockPos, out vec2 light);
diff --git a/docs/shader-api/fragment.glsl b/docs/shader-api/fragment.glsl
index a99837c25..e13ec6588 100644
--- a/docs/shader-api/fragment.glsl
+++ b/docs/shader-api/fragment.glsl
@@ -1,4 +1,5 @@
#include "flywheel:api/material.glsl"
+#include "flywheel:api/common.glsl"
/*const*/ vec4 flw_vertexPos;
/*const*/ vec4 flw_vertexColor;
@@ -24,10 +25,6 @@ vec4 flw_fogFilter(vec4 color);
// To be implemented by discard shaders.
bool flw_discardPredicate(vec4 finalColor);
-// To be implemented by the context shader.
-void flw_beginFragment();
-void flw_endFragment();
-
sampler2D flw_diffuseTex;
sampler2D flw_overlayTex;
sampler2D flw_lightTex;
diff --git a/docs/shader-api/vertex.glsl b/docs/shader-api/vertex.glsl
index 338923540..5dcab38e7 100644
--- a/docs/shader-api/vertex.glsl
+++ b/docs/shader-api/vertex.glsl
@@ -1,4 +1,5 @@
#include "flywheel:api/material.glsl"
+#include "flywheel:api/common.glsl"
vec4 flw_vertexPos;
vec4 flw_vertexColor;
@@ -17,7 +18,3 @@ void flw_transformBoundingSphere(in FlwInstance i, inout vec3 center, inout floa
// To be implemented by the material vertex shader.
void flw_materialVertex();
-
-// To be implemented by the context shader.
-void flw_beginVertex();
-void flw_endVertex();