From 495c0479682ab300e332a12eafc9481a54c9806b Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Fri, 5 Jul 2024 17:40:55 -0700 Subject: [PATCH] Lighting, for instance - Sideport light lut stuffs to instancing engine - Move actual lookup logic to light_lut.glsl, and have backend mains provide functions to index the backing storages for sanity's sake - Standardize naming of lut and sections - Pull in pepper's loom fix, so I can build :lwe: - Allow specifying the internal format of texture buffers so light can be a simple uint array --- .../flywheel/backend/Samplers.java | 2 + .../flywheel/backend/compile/Pipelines.java | 6 +- .../backend/engine/embed/LightStorage.java | 14 +- .../engine/indirect/BufferBindings.java | 4 +- .../backend/engine/indirect/LightBuffers.java | 12 +- .../instancing/InstancedDrawManager.java | 7 + .../engine/instancing/InstancedLight.java | 62 +++++++++ .../flywheel/backend/gl/TextureBuffer.java | 8 +- .../flywheel/flywheel/internal/common.frag | 2 - .../internal/indirect/buffer_bindings.glsl | 4 +- .../flywheel/internal/indirect/main.frag | 122 ++---------------- .../flywheel/internal/instancing/main.frag | 14 +- .../flywheel/flywheel/internal/light_lut.glsl | 115 +++++++++++++++++ 13 files changed, 236 insertions(+), 136 deletions(-) create mode 100644 common/src/backend/java/dev/engine_room/flywheel/backend/engine/instancing/InstancedLight.java create mode 100644 common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/Samplers.java b/common/src/backend/java/dev/engine_room/flywheel/backend/Samplers.java index cbdde0166..4441c6246 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/Samplers.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/Samplers.java @@ -8,4 +8,6 @@ public class Samplers { public static final GlTextureUnit LIGHT = GlTextureUnit.T2; public static final GlTextureUnit CRUMBLING = GlTextureUnit.T3; public static final GlTextureUnit INSTANCE_BUFFER = GlTextureUnit.T4; + public static final GlTextureUnit LIGHT_LUT = GlTextureUnit.T5; + public static final GlTextureUnit LIGHT_SECTIONS = GlTextureUnit.T6; } diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/Pipelines.java b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/Pipelines.java index 6b3af1ff9..65f122f4b 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/compile/Pipelines.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/compile/Pipelines.java @@ -11,7 +11,11 @@ public final class Pipelines { .vertexMain(Flywheel.rl("internal/instancing/main.vert")) .fragmentMain(Flywheel.rl("internal/instancing/main.frag")) .assembler(BufferTextureInstanceComponent::new) - .onLink(program -> program.setSamplerBinding("_flw_instances", Samplers.INSTANCE_BUFFER)) + .onLink(program -> { + program.setSamplerBinding("_flw_instances", Samplers.INSTANCE_BUFFER); + program.setSamplerBinding("_flw_lightLut", Samplers.LIGHT_LUT); + program.setSamplerBinding("_flw_lightSections", Samplers.LIGHT_SECTIONS); + }) .build(); public static final Pipeline INDIRECT = Pipeline.builder() diff --git a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java index 8521b1835..02cd179e8 100644 --- a/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java +++ b/common/src/backend/java/dev/engine_room/flywheel/backend/engine/embed/LightStorage.java @@ -8,6 +8,7 @@ import dev.engine_room.flywheel.api.event.RenderContext; import dev.engine_room.flywheel.api.task.Plan; import dev.engine_room.flywheel.backend.engine.EnvironmentStorage; 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; @@ -205,10 +206,6 @@ public class LightStorage { arena.delete(); } - public boolean hasChanges() { - return !changed.isEmpty(); - } - public boolean checkNeedsLutRebuildAndClear() { var out = needsLutRebuild; needsLutRebuild = false; @@ -222,6 +219,15 @@ public class LightStorage { 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); 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 index eba0afbfd..97970f3b5 100644 --- 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 @@ -6,8 +6,8 @@ public class BufferBindings { public static final int MODEL_INDEX_BUFFER_BINDING = 2; public static final int MODEL_BUFFER_BINDING = 3; public static final int DRAW_BUFFER_BINDING = 4; - public static final int EMBEDDING_LUT_BINDING = 5; - public static final int EMBEDDING_LIGHT_BINDING = 6; + public static final int LIGHT_LUT_BINDING = 5; + public static final int LIGHT_SECTION_BINDING = 6; private BufferBindings() { } 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 index 506e453f8..2423ae0fb 100644 --- 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 @@ -6,7 +6,7 @@ import org.lwjgl.system.MemoryUtil; import dev.engine_room.flywheel.backend.engine.embed.LightStorage; public class LightBuffers { - private final ResizableStorageArray lightArena = new ResizableStorageArray(LightStorage.SECTION_SIZE_BYTES); + private final ResizableStorageArray sections = new ResizableStorageArray(LightStorage.SECTION_SIZE_BYTES); private final ResizableStorageArray lut = new ResizableStorageArray(4); public LightBuffers() { @@ -19,8 +19,8 @@ public class LightBuffers { return; } - lightArena.ensureCapacity(capacity); - light.uploadChangedSections(staging, lightArena.handle()); + sections.ensureCapacity(capacity); + light.uploadChangedSections(staging, sections.handle()); if (light.checkNeedsLutRebuildAndClear()) { var lut = light.createLut(); @@ -36,11 +36,11 @@ public class LightBuffers { } public void bind() { - if (lightArena.capacity() == 0) { + if (sections.capacity() == 0) { return; } - GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.EMBEDDING_LUT_BINDING, lut.handle(), 0, lut.byteCapacity()); - GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.EMBEDDING_LIGHT_BINDING, lightArena.handle(), 0, lightArena.byteCapacity()); + GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.LIGHT_LUT_BINDING, lut.handle(), 0, lut.byteCapacity()); + GL46.glBindBufferRange(GL46.GL_SHADER_STORAGE_BUFFER, BufferBindings.LIGHT_SECTION_BINDING, sections.handle(), 0, sections.byteCapacity()); } } 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 bba7a716a..9130af672 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 @@ -43,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(); @@ -51,6 +52,7 @@ public class InstancedDrawManager extends DrawManager> { meshPool = new MeshPool(); vao = GlVertexArray.create(); instanceTexture = new TextureBuffer(); + light = new InstancedLight(); meshPool.bind(vao); } @@ -78,6 +80,8 @@ public class InstancedDrawManager extends DrawManager> { } meshPool.flush(); + + light.flush(lightStorage); } @Override @@ -92,6 +96,7 @@ public class InstancedDrawManager extends DrawManager> { Uniforms.bindAll(); vao.bindForDraw(); TextureBinder.bindLightAndOverlay(); + light.bind(); drawSet.draw(instanceTexture, programs); @@ -114,6 +119,8 @@ public class InstancedDrawManager extends DrawManager> { programs.release(); vao.delete(); + light.delete(); + super.delete(); } 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..3d966f0c8 --- /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.embed.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() { + sections = new GlBuffer(); + lut = 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() { + sections.delete(); + lut.delete(); + lutTexture.delete(); + sectionsTexture.delete(); + } +} 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 @@ import org.lwjgl.opengl.GL32; 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/resources/assets/flywheel/flywheel/internal/common.frag b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag index 491df32b3..2cfa92e96 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/common.frag @@ -13,9 +13,7 @@ uniform sampler2D _flw_crumblingTex; in vec2 _flw_crumblingTexCoord; #endif -#ifdef _FLW_EMBEDDED bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord); -#endif flat in uint _flw_instanceID; 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 439c78057..604da5269 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,5 +3,5 @@ #define _FLW_MODEL_INDEX_BUFFER_BINDING 2 #define _FLW_MODEL_BUFFER_BINDING 3 #define _FLW_DRAW_BUFFER_BINDING 4 -#define _FLW_EMBEDDING_LUT_BINDING 5 -#define _FLW_EMBEDDING_LIGHT_BINDING 6 +#define _FLW_LIGHT_LUT_BINDING 5 +#define _FLW_LIGHT_SECTIONS_BINDING 6 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 229b7babf..1711390a6 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,131 +1,25 @@ #include "flywheel:internal/common.frag" +#include "flywheel:internal/light_lut.glsl" #include "flywheel:internal/indirect/buffer_bindings.glsl" flat in uvec3 _flw_packedMaterial; -#ifdef _FLW_EMBEDDED - -layout(std430, binding = _FLW_EMBEDDING_LUT_BINDING) restrict readonly buffer EmbeddingLut { - uint _flw_embeddingLut[]; +layout(std430, binding = _FLW_LIGHT_LUT_BINDING) restrict readonly buffer LightLut { + uint _flw_lightLut[]; }; -const uint _FLW_LIGHT_SECTION_SIZE_BYTES = 18 * 18 * 18; -const uint _FLW_LIGHT_SECTION_SIZE_INTS = _FLW_LIGHT_SECTION_SIZE_BYTES / 4; - -layout(std430, binding = _FLW_EMBEDDING_LIGHT_BINDING) restrict readonly buffer LightSections { +layout(std430, binding = _FLW_LIGHT_SECTIONS_BINDING) restrict readonly buffer LightSections { uint _flw_lightSections[]; }; -/// 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_embeddingLut[base]); - // The width of the coordinate span. - uint size = _flw_embeddingLut[base + 1]; - - // Index of the coordinate in the span. - int i = coord - start; - - if (i < 0 || i >= size) { - // We missed. - return true; - } - - next = _flw_embeddingLut[base + 2 + i]; - - return false; +uint _flw_indexLut(uint index) { + return _flw_lightLut[index]; } -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; +uint _flw_indexLight(uint index) { + return _flw_lightSections[index]; } -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_lightSections[sectionOffset + uintOffset]; - uint block = (raw >> bitOffset) & 0xFu; - uint sky = (raw >> (bitOffset + 4u)) & 0xFu; - - return vec2(block, sky); -} - -bool _flw_embeddedLight(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)) { - // TODO: useful debug mode for this. - // flw_fragOverlay = ivec2(0, 3); - 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; -} - -#endif - void main() { _flw_uberMaterialFragmentIndex = _flw_packedMaterial.x; _flw_unpackUint2x16(_flw_packedMaterial.y, _flw_uberCutoutIndex, _flw_uberFogIndex); 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 ef56c1bce..80a4402ea 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,12 +1,18 @@ #include "flywheel:internal/common.frag" +#include "flywheel:internal/light_lut.glsl" uniform uvec4 _flw_packedMaterial; -#ifdef _FLW_EMBEDDED -bool _flw_embeddedLight(vec3 worldPos, out vec2 lightCoord) { - return true; +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; } -#endif void main() { _flw_uberMaterialFragmentIndex = _flw_packedMaterial.y; 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..e6abff4fa --- /dev/null +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl @@ -0,0 +1,115 @@ +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_embeddedLight(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)) { + // TODO: useful debug mode for this. + // flw_fragOverlay = ivec2(0, 3); + 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; +} +