From 601b70704a26ac4f7080dbfdfef64b73ab73a3ff Mon Sep 17 00:00:00 2001 From: Jozufozu Date: Sun, 28 Jul 2024 15:57:05 -0700 Subject: [PATCH] Solidly lit - Upload a bitset for each section indicating if blocks are solid - When interpolating light, count the number of transparent blocks and divide to avoid the incorrect "AO" effect near the ground --- .../flywheel/backend/engine/LightStorage.java | 57 +++++++- .../flywheel/flywheel/internal/light_lut.glsl | 129 +++++++++++++----- .../flywheel/lib/math/MoreMath.java | 4 + 3 files changed, 154 insertions(+), 36 deletions(-) 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 index ba8d2ce5b..214a56795 100644 --- 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 @@ -8,6 +8,7 @@ import org.lwjgl.system.MemoryUtil; import dev.engine_room.flywheel.api.task.Plan; import dev.engine_room.flywheel.backend.engine.indirect.StagingBuffer; import dev.engine_room.flywheel.backend.gl.buffer.GlBuffer; +import dev.engine_room.flywheel.lib.math.MoreMath; import dev.engine_room.flywheel.lib.task.SimplePlan; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.longs.Long2IntMap; @@ -36,7 +37,10 @@ import net.minecraft.world.level.lighting.LayerLightEventListener; *

Thus, each section occupies 5832 bytes. */ public class LightStorage { - public static final long SECTION_SIZE_BYTES = 9 * 9 * 9 * 8; + public static final int BLOCKS_PER_SECTION = 18 * 18 * 18; + public static final int LIGHT_SIZE_BYTES = BLOCKS_PER_SECTION; + public static final int SOLID_SIZE_BYTES = MoreMath.ceilingDiv(BLOCKS_PER_SECTION, Integer.SIZE) * Integer.BYTES; + public static final int SECTION_SIZE_BYTES = SOLID_SIZE_BYTES + LIGHT_SIZE_BYTES; private static final int DEFAULT_ARENA_CAPACITY_SECTIONS = 64; private static final int INVALID_SECTION = -1; @@ -158,6 +162,8 @@ public class LightStorage { // Zero it out first. This is basically free and makes it easier to handle missing sections later. MemoryUtil.memSet(ptr, 0, SECTION_SIZE_BYTES); + collectSolidData(ptr, section); + collectCenter(blockLight, skyLight, ptr, section); for (SectionEdge i : SectionEdge.values()) { @@ -175,6 +181,53 @@ public class LightStorage { collectCorners(blockLight, skyLight, ptr, section); } + private void collectSolidData(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)); + + var bitSet = new BitSet(BLOCKS_PER_SECTION); + int index = 0; + for (int y = -1; y < 17; y++) { + for (int z = -1; z < 17; z++) { + for (int x = -1; x < 17; x++) { + blockPos.set(xMin + x, yMin + y, zMin + z); + + boolean isFullBlock = level.getBlockState(blockPos) + .isCollisionShapeFullBlock(level, blockPos); + + if (isFullBlock) { + bitSet.set(index); + } + + index++; + } + } + } + + var longArray = bitSet.toLongArray(); + for (long l : longArray) { + MemoryUtil.memPutLong(ptr, l); + ptr += Long.BYTES; + } + } + + private void writeSolid(long ptr, int index, boolean blockValid) { + if (!blockValid) { + return; + } + int intIndex = index / Integer.SIZE; + int bitIndex = index % Integer.SIZE; + + long offset = intIndex * Integer.BYTES; + + int bitField = MemoryUtil.memGetInt(ptr + offset); + bitField |= 1 << bitIndex; + + MemoryUtil.memPutInt(ptr + offset, bitField); + } + 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); @@ -303,7 +356,7 @@ public class LightStorage { long packedByte = (block & 0xF) | ((sky & 0xF) << 4); - MemoryUtil.memPutByte(ptr + offset, (byte) packedByte); + MemoryUtil.memPutByte(ptr + SOLID_SIZE_BYTES + offset, (byte) packedByte); } /** 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 index 84aca1f3e..31c8e7594 100644 --- a/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl +++ b/common/src/backend/resources/assets/flywheel/flywheel/internal/light_lut.glsl @@ -1,6 +1,16 @@ -const uint _FLW_LIGHT_SECTION_SIZE_BYTES = 18 * 18 * 18; +const uint _FLW_BLOCKS_PER_SECTION = 18 * 18 * 18; +const uint _FLW_LIGHT_SIZE_BYTES = _FLW_BLOCKS_PER_SECTION; +const uint _FLW_SOLID_SIZE_BYTES = ((_FLW_BLOCKS_PER_SECTION + 31) / 32) * 4; +const uint _FLW_LIGHT_START_BYTES = _FLW_SOLID_SIZE_BYTES; +const uint _FLW_LIGHT_SECTION_SIZE_BYTES = _FLW_SOLID_SIZE_BYTES + _FLW_LIGHT_SIZE_BYTES; + +const uint _FLW_SOLID_START_INTS = 0; +const uint _FLW_LIGHT_START_INTS = _FLW_SOLID_SIZE_BYTES / 4; const uint _FLW_LIGHT_SECTION_SIZE_INTS = _FLW_LIGHT_SECTION_SIZE_BYTES / 4; +const uint _FLW_COMPLETELY_SOLID = 0x7FFFFFFu; +const float _FLW_EPSILON = 1e-5; + uint _flw_indexLut(uint index); uint _flw_indexLight(uint index); @@ -51,17 +61,28 @@ bool _flw_chunkCoordToSectionIndex(ivec3 sectionPos, out uint index) { return false; } -vec2 _flw_lightAt(uint sectionOffset, uvec3 blockInSectionPos) { +uvec2 _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 raw = _flw_indexLight(sectionOffset + _FLW_LIGHT_START_INTS + uintOffset); uint block = (raw >> bitOffset) & 0xFu; uint sky = (raw >> (bitOffset + 4u)) & 0xFu; - return vec2(block, sky); + return uvec2(block, sky); +} + +bool _flw_isSolid(uint sectionOffset, uvec3 blockInSectionPos) { + uint bitOffset = blockInSectionPos.x + blockInSectionPos.z * 18u + blockInSectionPos.y * 18u * 18u; + + uint uintOffset = bitOffset / 32u; + uint bitInWordOffset = bitOffset % 32u; + + uint word = _flw_indexLight(sectionOffset + _FLW_SOLID_START_INTS + uintOffset); + + return (word & (1u << bitInWordOffset)) != 0; } bool flw_lightFetch(ivec3 blockPos, out vec2 lightCoord) { @@ -74,7 +95,7 @@ bool flw_lightFetch(ivec3 blockPos, out vec2 lightCoord) { uvec3 blockInSectionPos = (blockPos & 0xF) + 1; - lightCoord = _flw_lightAt(sectionOffset, blockInSectionPos) / 15.; + lightCoord = vec2(_flw_lightAt(sectionOffset, blockInSectionPos)) / 15.; return true; } @@ -104,14 +125,14 @@ bool flw_light(vec3 worldPos, out vec2 lightCoord) { // 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 light000 = vec2(_flw_lightAt(sectionOffset, lowestCorner)); + vec2 light001 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 0, 1))); + vec2 light010 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 0))); + vec2 light011 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(0, 1, 1))); + vec2 light100 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 0))); + vec2 light101 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 0, 1))); + vec2 light110 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 0))); + vec2 light111 = vec2(_flw_lightAt(sectionOffset, lowestCorner + uvec3(1, 1, 1))); vec2 light00 = mix(light000, light001, interpolant.z); vec2 light01 = mix(light010, light011, interpolant.z); @@ -131,13 +152,19 @@ uint _flw_lightIndex(in uvec3 p) { /// 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; +uvec3[27] _flw_fetchLight3x3x3(uint sectionOffset, ivec3 blockInSectionPos, uint solid) { + uvec3[27] lights; + uint index = 0u; + uint mask = 1u; 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))); + // 0 if the block is solid, 1 if it's not. + uint flag = uint((solid & mask) == 0u); + lights[index] = uvec3(_flw_lightAt(sectionOffset, uvec3(blockInSectionPos + ivec3(x, y, z))), flag); + index++; + mask <<= 1; } } } @@ -145,6 +172,24 @@ vec2[27] _flw_lightFetch3x3x3(uint sectionOffset, ivec3 blockInSectionPos) { return lights; } +uint _flw_fetchSolid3x3x3(uint sectionOffset, ivec3 blockInSectionPos) { + uint ret = 0; + + uint index = 0; + for (int y = -1; y <= 1; y++) { + for (int z = -1; z <= 1; z++) { + for (int x = -1; x <= 1; x++) { + bool flag = _flw_isSolid(sectionOffset, uvec3(blockInSectionPos + ivec3(x, y, z))); + ret |= uint(flag) << index; + + index++; + } + } + } + + return ret; +} + /// 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. @@ -155,16 +200,26 @@ vec2[27] _flw_lightFetch3x3x3(uint sectionOffset, ivec3 blockInSectionPos) { /// @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 _flw_lightForDirection(in uvec3[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))]; + uvec3 i000 = 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))]; + uvec3 i001 = 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))]; + uvec3 i010 = 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))]; + uvec3 i011 = 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))]; + uvec3 i100 = 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))]; + uvec3 i101 = 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))]; + uvec3 i110 = 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))]; + uvec3 i111 = 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))]; + + // Divide by the number of light transmitting blocks to get the average. + vec2 light000 = i000.z == 0 ? vec2(0) : vec2(i000.xy) / float(i000.z); + vec2 light001 = i001.z == 0 ? vec2(0) : vec2(i001.xy) / float(i001.z); + vec2 light010 = i010.z == 0 ? vec2(0) : vec2(i010.xy) / float(i010.z); + vec2 light011 = i011.z == 0 ? vec2(0) : vec2(i011.xy) / float(i011.z); + vec2 light100 = i100.z == 0 ? vec2(0) : vec2(i100.xy) / float(i100.z); + vec2 light101 = i101.z == 0 ? vec2(0) : vec2(i101.xy) / float(i101.z); + vec2 light110 = i110.z == 0 ? vec2(0) : vec2(i110.xy) / float(i110.z); + vec2 light111 = i111.z == 0 ? vec2(0) : vec2(i111.xy) / float(i111.z); vec2 light00 = mix(light000, light001, interpolant.z); vec2 light01 = mix(light010, light011, interpolant.z); @@ -174,8 +229,7 @@ vec2 _flw_lightForDirection(in vec2[27] lights, in vec3 interpolant, in uvec3 c0 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.; + return mix(light0, light1, interpolant.x) / 15.; } bool flw_light(vec3 worldPos, vec3 normal, out vec2 lightCoord) { @@ -194,24 +248,31 @@ bool flw_light(vec3 worldPos, vec3 normal, out vec2 lightCoord) { // The block's position in the section adjusted into 18x18x18 space ivec3 blockInSectionPos = (blockPos & 0xF) + 1; + uint solid = _flw_fetchSolid3x3x3(sectionOffset, blockInSectionPos); + + if (solid == _FLW_COMPLETELY_SOLID) { + lightCoord = vec2(0.); + return true; + } + // Fetch everything in a 3x3x3 area centered around the block. - vec2[27] lights = _flw_lightFetch3x3x3(sectionOffset, blockInSectionPos); + uvec3[27] lights = _flw_fetchLight3x3x3(sectionOffset, blockInSectionPos, solid); vec3 interpolant = fract(worldPos); vec2 lightX; - if (normal.x > 0) { + if (normal.x > _FLW_EPSILON) { 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) { + } else if (normal.x < -_FLW_EPSILON) { 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) { + if (normal.z > _FLW_EPSILON) { 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) { + } else if (normal.z < -_FLW_EPSILON) { 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.); @@ -219,9 +280,9 @@ bool flw_light(vec3 worldPos, vec3 normal, out vec2 lightCoord) { vec2 lightY; // Average the light in relevant directions at each corner. - if (normal.y > 0.) { + if (normal.y > _FLW_EPSILON) { 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.) { + } else if (normal.y < -_FLW_EPSILON) { 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.); diff --git a/common/src/lib/java/dev/engine_room/flywheel/lib/math/MoreMath.java b/common/src/lib/java/dev/engine_room/flywheel/lib/math/MoreMath.java index 944f797e8..e995ab20e 100644 --- a/common/src/lib/java/dev/engine_room/flywheel/lib/math/MoreMath.java +++ b/common/src/lib/java/dev/engine_room/flywheel/lib/math/MoreMath.java @@ -9,6 +9,10 @@ public final class MoreMath { */ public static final float SQRT_3_OVER_2 = (float) (Math.sqrt(3.0) / 2.0); + public static int align32(int size) { + return (size + 31) & ~31; + } + public static int align16(int size) { return (size + 15) & ~15; }