From 59405b7b6cf4c87cc3385318d531c29035ad8721 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Fri, 2 Jun 2023 14:36:50 +0700 Subject: [PATCH 01/69] Working light propagation and data storage Light removal is still somewhat broken and sometimes cause lingering light data --- .../grondag/canvas/apiimpl/CanvasState.java | 2 + .../canvas/light/color/LightDebug.java | 54 +++ .../canvas/light/color/LightPropagation.java | 342 ++++++++++++++++++ .../canvas/light/color/LightSectionData.java | 194 ++++++++++ .../canvas/pipeline/ProgramTextureData.java | 8 +- .../canvas/terrain/region/RenderRegion.java | 18 +- 6 files changed, 612 insertions(+), 6 deletions(-) create mode 100644 src/main/java/grondag/canvas/light/color/LightDebug.java create mode 100644 src/main/java/grondag/canvas/light/color/LightPropagation.java create mode 100644 src/main/java/grondag/canvas/light/color/LightSectionData.java diff --git a/src/main/java/grondag/canvas/apiimpl/CanvasState.java b/src/main/java/grondag/canvas/apiimpl/CanvasState.java index 06c45c82e..02da93fcb 100644 --- a/src/main/java/grondag/canvas/apiimpl/CanvasState.java +++ b/src/main/java/grondag/canvas/apiimpl/CanvasState.java @@ -30,6 +30,7 @@ import grondag.canvas.apiimpl.rendercontext.CanvasEntityBlockRenderContext; import grondag.canvas.apiimpl.rendercontext.CanvasItemRenderContext; import grondag.canvas.config.Configurator; +import grondag.canvas.light.color.LightDebug; import grondag.canvas.material.property.TextureMaterialState; import grondag.canvas.perf.ChunkRebuildCounters; import grondag.canvas.perf.Timekeeper; @@ -52,6 +53,7 @@ public static void recompileIfNeeded(boolean forceRecompile) { if (forceRecompile) { CanvasMod.LOG.info(I18n.get("info.canvas.recompile")); + LightDebug.initialize(); PipelineLoader.reload(Minecraft.getInstance().getResourceManager()); PipelineManager.reload(); PreReleaseShaderCompat.reload(); diff --git a/src/main/java/grondag/canvas/light/color/LightDebug.java b/src/main/java/grondag/canvas/light/color/LightDebug.java new file mode 100644 index 000000000..1733ed530 --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/LightDebug.java @@ -0,0 +1,54 @@ +package grondag.canvas.light.color; + +import static grondag.canvas.light.color.LightSectionData.Const.WIDTH; + +import com.mojang.blaze3d.systems.RenderSystem; + +import grondag.canvas.CanvasMod; + +public class LightDebug { + public static LightSectionData debugData; + + public static void initialize() { + RenderSystem.assertOnRenderThread(); + + if (debugData != null) { + return; + } + + debugData = new LightSectionData(); + // clear(debugData); + // debugData.upload(); + + CanvasMod.LOG.info("Light debug render initialized."); + } + + public static int getTexture(String imageName) { + if (imageName.equals("_cv_debug_light_data") && debugData != null && !debugData.isClosed()) { + return debugData.getTexId(); + } + + return -1; + } + + static void clear(LightSectionData data) { + for (int x = 0; x < WIDTH; x ++) { + for (int y = 0; y < WIDTH; y ++) { + for (int z = 0; z < WIDTH; z ++) { + data.draw((short) 0); + } + } + } + } + + static void drawDummy(LightSectionData data) { + for (int x = 0; x < WIDTH; x ++) { + for (int y = 0; y < WIDTH; y ++) { + for (int z = 0; z < WIDTH; z ++) { + data.draw(LightSectionData.encodeRgba(x % 16, y % 16, z % 16, 0xF)); + // data.draw((x * WIDTH * WIDTH + y * WIDTH + z) * LightSectionData.Format.pixelBytes, LightSectionData.encodeRgba(x, y, z, 1)); + } + } + } + } +} diff --git a/src/main/java/grondag/canvas/light/color/LightPropagation.java b/src/main/java/grondag/canvas/light/color/LightPropagation.java new file mode 100644 index 000000000..44ec502de --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/LightPropagation.java @@ -0,0 +1,342 @@ +package grondag.canvas.light.color; + +import it.unimi.dsi.fastutil.ints.Int2ShortOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; + +import com.mojang.blaze3d.systems.RenderSystem; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.Block; + +import io.vram.frex.api.light.ItemLight; + +import grondag.canvas.CanvasMod; +import grondag.canvas.light.color.LightSectionData.Elem; + +public class LightPropagation { + final static BlockPos.MutableBlockPos searchPos = new BlockPos.MutableBlockPos(); + private static Int2ShortOpenHashMap lights = new Int2ShortOpenHashMap(); + private static LongArrayFIFOQueue incQueue = new LongArrayFIFOQueue(); + private static LongArrayFIFOQueue decQueue = new LongArrayFIFOQueue(); + private static boolean dirty = false; + private static Block debugBlock; + private static int debugCountThread = 0; + + static { + lights.defaultReturnValue((short) 0); + } + + public static synchronized void onStartBuildTerrain() { + debugCountThread++; + } + + public static synchronized void onBuildTerrain(BlockPos pos, final int lightEmission, Block block, boolean occluding) { + if (!LightDebug.debugData.withinExtents(pos)) { + return; + } + + debugBlock = block; + + short light = 0; + + if (lightEmission > 0) { + light = lights.get(block.hashCode()); + + if (light == 0) { + // PERF: modify ItemLight API or make new API that doesn't need ItemStack + // TODO: ItemLight API isn't suitable for this anyway since we rely on blockstates not blocks/items + final ItemStack stack = new ItemStack(block, 1); + final ItemLight itemLight = ItemLight.get(stack); + + if (itemLight != null) { + final int r = (int) (lightEmission * itemLight.red()); + final int g = (int) (lightEmission * itemLight.green()); + final int b = (int) (lightEmission * itemLight.blue()); + light = LightSectionData.encodeRgba(r, g, b, 0); + } else { + light = LightSectionData.encodeRgba(lightEmission, lightEmission, lightEmission, 0); + } + + lights.put(block.hashCode(), lightSource(light)); + } + } + + evaluateForQueue(pos, light, occluding); + } + + public static synchronized void onFinishedBuildTerrain() { + debugCountThread --; + + if (dirty && debugCountThread == 0) { + dirty = false; + // TODO: there are multiple worker threads. the queue can't handle that yet + // TODO: still not working + processQueues(); + CanvasMod.LOG.info("Uploading texture"); + RenderSystem.recordRenderCall(() -> LightDebug.debugData.upload()); + } + } + + private static void evaluateForQueue(BlockPos pos, short light, boolean occluding) { + final int index = LightDebug.debugData.indexify(pos); + final short getLight = LightDebug.debugData.get(index); + + lessThan(getLight, light); + // greaterThan(getLight, light); + + // short incLight = 0; + // short incLightMask = 0; + // + // short decLight = 0; + // short decLightMask = 0; + // + // final Elem[] elems = Elem.values(); + // + // for (int i = 0; i < 3; i++) { + // if (inc.get(i)) { + // incLight |= elems[i].of(light); + // incLightMask |= elems[i].mask; + // } else if (dec.get(i)) { + // decLight |= elems[i].of(light); + // decLightMask |= elems[i].mask; + // } + // } + // + // final short putLight = (short) (((getLight & ~incLightMask) & ~decLightMask) | incLight | decLight); + + if (less.any()) { + // int[] xyz = new int[3]; + // LightDebug.debugData.reverseIndexify(index, xyz); + // CanvasMod.LOG.info("test reverse index : " + pos + "/" + xyz[0] + "," + xyz[1] + "," + xyz[2]); + LightDebug.debugData.put(index, light); + enqueue(incQueue, index, light); + CanvasMod.LOG.info("Add light at " + pos + " light is (get,put) " + + Elem.text(getLight) + "," + Elem.text(light) + " block: " + debugBlock); + } else if (light == 0 && (isLightSource(getLight) || isOccluding(getLight) || occluding)) { + LightDebug.debugData.put(index, occluding((short) 0, occluding)); + enqueue(decQueue, index, getLight); + CanvasMod.LOG.info("Remove light at " + pos + " light is (get,put) " + + Elem.text(getLight) + "," + Elem.text(light) + " block: " + debugBlock); + } + } + + private static void enqueue(LongArrayFIFOQueue queue, long index, long light) { + dirty = true; + queue.enqueue((index << 16L) | light & 0xffffL); + } + + private static void processQueues() { + CanvasMod.LOG.info("Processing queues.. inc,dec " + incQueue.size() + "," + decQueue.size()); + + int[] pos = new int[3]; + int debugMaxDec = 0; + int debugMaxInc = 0; + + while(!decQueue.isEmpty()) { + debugMaxDec++; + + final long entry = decQueue.dequeueLong(); + final int index = (int) (entry >> 16L); + final short sourcePrevLight = (short) entry; + LightDebug.debugData.reverseIndexify(index, pos); + + for (var d: Direction.values()) { + final int nodeX = pos[0] + d.getStepX(); + final int nodeY = pos[1] + d.getStepY(); + final int nodeZ = pos[2] + d.getStepZ(); + + if (!LightDebug.debugData.withinExtents(nodeX, nodeY, nodeZ)) { + continue; + } + + final int nodeIndex = LightDebug.debugData.indexify(nodeX, nodeY, nodeZ); + short nodeLight = LightDebug.debugData.get(nodeIndex); + + if (pure(nodeLight) == 0) { + continue; + } + + // this is problematic, might result in residual light unless we know the light source color + // if (isLightSource(nodeLight)) { + // continue; + // } + + lessThan(nodeLight, sourcePrevLight); + + if (less.any()) { + int mask = 0; + + if (less.r) { + mask |= Elem.R.mask; + // LightDebug.debugData.put(nodeIndex, Elem.R, 0); + } + + if (less.g) { + mask |= Elem.G.mask; + // LightDebug.debugData.put(nodeIndex, Elem.G, 0); + } + + if (less.b) { + mask |= Elem.B.mask; + // LightDebug.debugData.put(nodeIndex, Elem.B, 0); + } + + final short nodeAfterLight = (short) (nodeLight & ~(mask)); + LightDebug.debugData.put(nodeIndex, nodeAfterLight); + + // final short nodeAfterLight = LightDebug.debugData.get(nodeIndex); + + enqueue(decQueue, nodeIndex, nodeLight); + + nodeLight = nodeAfterLight; + } + + if (!less.all()) { + // TODO: queue but only if it's a neighboring chunk?? nah that'd be wrong + enqueue(incQueue, nodeIndex, nodeLight); + } + } + } + + while (!incQueue.isEmpty()) { + debugMaxInc++; + + final long entry = incQueue.dequeueLong(); + final int index = (int) (entry >> 16L); + final short recordedLight = (short) entry; + final short sourceLight = LightDebug.debugData.get(index); + + if (sourceLight != recordedLight) { + continue; + } + + LightDebug.debugData.reverseIndexify(index, pos); + + for (var d: Direction.values()) { + final int nodeX = pos[0] + d.getStepX(); + final int nodeY = pos[1] + d.getStepY(); + final int nodeZ = pos[2] + d.getStepZ(); + + // CanvasMod.LOG.info("increase at " + nodeX + "," + nodeY + "," + nodeZ); + + if (!LightDebug.debugData.withinExtents(nodeX, nodeY, nodeZ)) { + continue; + } + + final int nodeIndex = LightDebug.debugData.indexify(nodeX, nodeY, nodeZ); + final short nodeLight = LightDebug.debugData.get(nodeIndex); + + if (isOccluding(nodeLight)) { + continue; + } + + // CanvasMod.LOG.info("current/neighbor index " + index + "/" + nodeIndex); + + lessThanMinusOne(nodeLight, sourceLight); + + if (less.any()) { + // TODO: optimize + if (less.r) { + LightDebug.debugData.put(nodeIndex, Elem.R, (Elem.R.of(sourceLight) - 1)); + } + + if (less.g) { + LightDebug.debugData.put(nodeIndex, Elem.G, (Elem.G.of(sourceLight) - 1)); + } + + if (less.b) { + LightDebug.debugData.put(nodeIndex, Elem.B, (Elem.B.of(sourceLight) - 1)); + } + + // TODO: optimize + final short nodeAfterLight = LightDebug.debugData.get(nodeIndex); + + // CanvasMod.LOG.info("updating neighbor to: " + nodeX + "," + nodeY + "," + nodeZ + "," + Elem.text(nodeAfterLight)); + + enqueue(incQueue, nodeIndex, nodeAfterLight); + } + } + } + + dirty = false; + + CanvasMod.LOG.info("Processed queues! Count: inc,dec " + debugMaxInc + "," + debugMaxDec); + } + + private static boolean isLightSource(short light) { + return (light & 0b1) != 0; + } + + private static boolean isOccluding(short light) { + return (light & 0b10) != 0; + } + + private static short lightSource(short light) { + return (short) (light | 0b1); + } + + private static short occluding(short light, boolean occluding) { + return occluding ? (short) (light | 0b10) : light; + } + + private static short pure(short light) { + return (short) (light & 0xfff0); + } + + private static class BVec { + boolean r, g, b; + + public BVec() { + this.r = false; + this.g = false; + this.b = false; + // this.a = false; + } + + public boolean get(int i) { + return switch (i) { + case 0 -> r; + case 1 -> g; + case 2 -> b; + default -> false; + }; + } + + public boolean any() { + return r || g || b; + } + + public boolean all() { + return r && g && b; + } + } + + private static BVec less = new BVec(); + private static BVec greater = new BVec(); + + private static void lessThan(short left, short right) { + less.r = Elem.R.of(left) < Elem.R.of(right); + less.g = Elem.G.of(left) < Elem.G.of(right); + less.b = Elem.B.of(left) < Elem.B.of(right); + } + + private static void lessThanMinusOne(short left, short right) { + less.r = Elem.R.of(left) < Elem.R.of(right) - 1; + less.g = Elem.G.of(left) < Elem.G.of(right) - 1; + less.b = Elem.B.of(left) < Elem.B.of(right) - 1; + } + + private static void equalsMinusOne(short left, short right) { + less.r = Elem.R.of(left) == Elem.R.of(right) - 1; + less.g = Elem.G.of(left) == Elem.G.of(right) - 1; + less.b = Elem.B.of(left) == Elem.B.of(right) - 1; + } + + private static void greaterThanOrEqual(short left, short right) { + greater.r = Elem.R.of(left) >= Elem.R.of(right); + greater.g = Elem.G.of(left) >= Elem.G.of(right); + greater.b = Elem.B.of(left) >= Elem.B.of(right); + } +} diff --git a/src/main/java/grondag/canvas/light/color/LightSectionData.java b/src/main/java/grondag/canvas/light/color/LightSectionData.java new file mode 100644 index 000000000..bfa8ff0b9 --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/LightSectionData.java @@ -0,0 +1,194 @@ +package grondag.canvas.light.color; + +import java.nio.ByteBuffer; + +import org.lwjgl.system.MemoryUtil; + +import com.mojang.blaze3d.platform.TextureUtil; + +import net.minecraft.core.BlockPos; + +import grondag.canvas.CanvasMod; +import grondag.canvas.render.CanvasTextureState; +import grondag.canvas.varia.GFX; + +public class LightSectionData { + public static class Format { + public static int target = GFX.GL_TEXTURE_3D; + public static int pixelBytes = 2; + public static int internalFormat = GFX.GL_RGBA4; + public static int pixelFormat = GFX.GL_RGBA; + public static int pixelDataType = GFX.GL_UNSIGNED_SHORT_4_4_4_4; + } + + public static class Const { + public static final int WIDTH = 16 * 2; + private static final int SIZE3D = WIDTH * WIDTH * WIDTH; + + public static final int WIDTH_SHIFT = (int) (Math.log(WIDTH) / Math.log(2)); + public static final int WIDTH_MASK = WIDTH - 1; + } + + public static enum Elem { + R(0xF000, 12, 0), + G(0x0F00, 8, 1), + B(0x00F0, 4, 2), + A(0x000F, 0, 3); + + public final int mask; + public final int shift; + public final int pos; + + Elem(int mask, int shift, int pos) { + this.mask = mask; + this.shift = shift; + this.pos = pos; + } + + public int of(short light) { + return (light & mask) >> shift; + } + + public static String text(short light) { + return "(" + R.of(light) + "," + G.of(light) + "," + B.of(light) + ")"; + } + } + + // placeholder + private int sectionBlockOffsetX = -16; + private int sectionBlockOffsetY = 100; + private int sectionBlockOffsetZ = -16; + + private ByteBuffer buffer; + // private long pointer; + private int glTexId; + private boolean closed = false; + + public static short encodeRgba(int r, int g, int b, int a) { + // PERF: alpha is unnecessary + return (short) ((r << 12) | (g << 8) | (b << 4) | a); + } + + public LightSectionData() { + glTexId = TextureUtil.generateTextureId(); + + CanvasTextureState.bindTexture(Format.target, glTexId); + GFX.objectLabel(GFX.GL_TEXTURE, glTexId, "IMG light_section_volume"); + + GFX.texParameter(Format.target, GFX.GL_TEXTURE_MIN_FILTER, GFX.GL_LINEAR); + GFX.texParameter(Format.target, GFX.GL_TEXTURE_MAG_FILTER, GFX.GL_LINEAR); + GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_S, GFX.GL_CLAMP_TO_EDGE); + GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_T, GFX.GL_CLAMP_TO_EDGE); + GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_R, GFX.GL_CLAMP_TO_EDGE); // ? + + buffer = MemoryUtil.memAlloc(Format.pixelBytes * Const.SIZE3D); + + // clear manually ? + while (buffer.position() < Format.pixelBytes * Const.SIZE3D) { + buffer.putShort((short) 0); + } + + // buffer = ByteBuffer.allocate(Format.pixelBytes * SIZE3D); + // pointer = MemoryUtil.nmemAlloc((long) Format.pixelBytes * SIZE3D); // ?? + + // allocate + GFX.texImage3D(Format.target, 0, Format.internalFormat, Const.WIDTH, Const.WIDTH, Const.WIDTH, 0, Format.pixelFormat, Format.pixelDataType, null); + } + + public void upload() { + CanvasTextureState.bindTexture(Format.target, glTexId); + + // Gotta clean up some states, otherwise will cause memory access violation + GFX.pixelStore(GFX.GL_UNPACK_SKIP_PIXELS, 0); + GFX.pixelStore(GFX.GL_UNPACK_SKIP_ROWS, 0); + GFX.pixelStore(GFX.GL_UNPACK_ROW_LENGTH, 0); + GFX.pixelStore(GFX.GL_UNPACK_ALIGNMENT, 2); + + // Importantly, reset the pointer without flip + buffer.position(0); + + // GFX.texImage3D(Format.target, 0, Format.internalFormat, Const.WIDTH, Const.WIDTH, Const.WIDTH, 0, Format.pixelFormat, Format.pixelDataType, buffer); + GFX.glTexSubImage3D(Format.target, 0, 0, 0, 0, Const.WIDTH, Const.WIDTH, Const.WIDTH, Format.pixelFormat, Format.pixelDataType, buffer); + } + + public void draw(long offset, short rgba) { + assert offset == buffer.position(); + buffer.putShort(rgba); + // MemoryUtil.memPutShort(pointer + offset, rgba); + } + + public void draw(short rgba) { + buffer.putShort(rgba); + } + + public void close() { + TextureUtil.releaseTextureId(glTexId); + MemoryUtil.memFree(buffer); + // MemoryUtil.nmemFree(pointer); + glTexId = -1; + closed = true; + } + + public short get(int index) { + return buffer.getShort(index); + } + + public void put(int index, short light) { + // int[] xyz = new int[3]; + // LightDebug.debugData.reverseIndexify(index, xyz); + // CanvasMod.LOG.info("putting light at : " + xyz[0] + "," + xyz[1] + "," + xyz[2] + " / " + Elem.text(light)); + buffer.putShort(index, light); + } + + public void put(int index, Elem elem, int elemLight) { + final short get = buffer.getShort(index); + final short put = (short) ((get & ~elem.mask) | (elemLight << elem.shift)); + buffer.putShort(index, put); + } + + public int indexify(BlockPos pos) { + return indexify(pos.getX(), pos.getY(), pos.getZ()); + } + + public int indexify(int x, int y, int z) { + final int localX = x - sectionBlockOffsetX; + final int localY = y - sectionBlockOffsetY; + final int localZ = z - sectionBlockOffsetZ; + + // x and z are swapped because opengl + return ((localZ << (Const.WIDTH_SHIFT * 2)) | (localY << Const.WIDTH_SHIFT) | localX) * Format.pixelBytes; + } + + public void reverseIndexify(int index, int[] result) { + assert result.length == 3; + + index = index / Format.pixelBytes; + + // x and z are swapped because opengl + result[0] = (index & Const.WIDTH_MASK) + sectionBlockOffsetX; + result[1] = ((index >> Const.WIDTH_SHIFT) & Const.WIDTH_MASK) + sectionBlockOffsetY; + result[2] = (index >> Const.WIDTH_SHIFT * 2) + sectionBlockOffsetZ; + } + + public boolean withinExtents(BlockPos pos) { + return withinExtents(pos.getX(), pos.getY(), pos.getZ()); + } + + public boolean withinExtents(int x, int y, int z) { + return (x >= sectionBlockOffsetX && x < sectionBlockOffsetX + Const.WIDTH) + && (y >= sectionBlockOffsetY && y < sectionBlockOffsetY + Const.WIDTH) + && (z >= sectionBlockOffsetZ && z < sectionBlockOffsetZ + Const.WIDTH); + } + + public int getTexId() { + if (closed) { + throw new IllegalStateException("Trying to access a deleted Light Section Data!"); + } + + return glTexId; + } + + public boolean isClosed() { + return closed; + } +} diff --git a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java index 7492bcf73..516517162 100644 --- a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java +++ b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java @@ -29,6 +29,8 @@ import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.resources.ResourceLocation; +import grondag.canvas.light.color.LightDebug; +import grondag.canvas.light.color.LightSectionData; import grondag.canvas.pipeline.config.ImageConfig; import grondag.canvas.pipeline.config.util.NamedDependency; @@ -45,8 +47,12 @@ public ProgramTextureData(NamedDependency[] samplerImages) { int imageBind = 0; int bindTarget = GL46.GL_TEXTURE_2D; + int lightDebugTex = LightDebug.getTexture(imageName); - if (imageName.contains(":")) { + if (lightDebugTex != -1) { + imageBind = lightDebugTex; + bindTarget = LightSectionData.Format.target; + } else if (imageName.contains(":")) { final AbstractTexture tex = tryLoadResourceTexture(new ResourceLocation(imageName)); if (tex != null) { diff --git a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java index 1a0aad732..7ece4796b 100644 --- a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java +++ b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java @@ -51,6 +51,7 @@ import grondag.canvas.apiimpl.rendercontext.CanvasTerrainRenderContext; import grondag.canvas.buffer.input.DrawableVertexCollector; import grondag.canvas.buffer.input.VertexCollectorList; +import grondag.canvas.light.color.LightPropagation; import grondag.canvas.material.state.TerrainRenderStates; import grondag.canvas.perf.ChunkRebuildCounters; import grondag.canvas.pipeline.Pipeline; @@ -420,14 +421,17 @@ private void buildTerrain(CanvasTerrainRenderContext context, RegionBuildState b final BlockRenderDispatcher blockRenderManager = Minecraft.getInstance().getBlockRenderer(); final RegionOcclusionCalculator occlusionRegion = region.occlusion; + LightPropagation.onStartBuildTerrain(); + for (int i = 0; i < RenderRegionStateIndexer.INTERIOR_STATE_COUNT; i++) { + final BlockState blockState = region.getLocalBlockState(i); + final int x = i & 0xF; + final int y = (i >> 4) & 0xF; + final int z = (i >> 8) & 0xF; + searchPos.set(xOrigin + x, yOrigin + y, zOrigin + z); + if (occlusionRegion.shouldRender(i)) { - final BlockState blockState = region.getLocalBlockState(i); final FluidState fluidState = blockState.getFluidState(); - final int x = i & 0xF; - final int y = (i >> 4) & 0xF; - final int z = (i >> 8) & 0xF; - searchPos.set(xOrigin + x, yOrigin + y, zOrigin + z); final boolean hasFluid = !fluidState.isEmpty(); // Vanilla only checks not invisible, but filters non-model shape down the line @@ -456,8 +460,12 @@ private void buildTerrain(CanvasTerrainRenderContext context, RegionBuildState b } } } + + LightPropagation.onBuildTerrain(searchPos, blockState.getLightEmission(), blockState.getBlock(), blockState.canOcclude()); } + LightPropagation.onFinishedBuildTerrain(); + buildState.prepareTranslucentIfNeeded(worldRenderState.sectorManager.cameraPos(), renderSector, collectors); if (ChunkRebuildCounters.ENABLED) { From e8901305f02038eec0f06c147b4c7c7d16e1cdc4 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Fri, 2 Jun 2023 16:33:39 +0700 Subject: [PATCH 02/69] Cleanup cruft --- .../canvas/light/color/LightDebug.java | 11 ----- .../canvas/light/color/LightPropagation.java | 44 ------------------- .../canvas/light/color/LightSectionData.java | 16 ------- 3 files changed, 71 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDebug.java b/src/main/java/grondag/canvas/light/color/LightDebug.java index 1733ed530..61f145d65 100644 --- a/src/main/java/grondag/canvas/light/color/LightDebug.java +++ b/src/main/java/grondag/canvas/light/color/LightDebug.java @@ -31,22 +31,11 @@ public static int getTexture(String imageName) { return -1; } - static void clear(LightSectionData data) { - for (int x = 0; x < WIDTH; x ++) { - for (int y = 0; y < WIDTH; y ++) { - for (int z = 0; z < WIDTH; z ++) { - data.draw((short) 0); - } - } - } - } - static void drawDummy(LightSectionData data) { for (int x = 0; x < WIDTH; x ++) { for (int y = 0; y < WIDTH; y ++) { for (int z = 0; z < WIDTH; z ++) { data.draw(LightSectionData.encodeRgba(x % 16, y % 16, z % 16, 0xF)); - // data.draw((x * WIDTH * WIDTH + y * WIDTH + z) * LightSectionData.Format.pixelBytes, LightSectionData.encodeRgba(x, y, z, 1)); } } } diff --git a/src/main/java/grondag/canvas/light/color/LightPropagation.java b/src/main/java/grondag/canvas/light/color/LightPropagation.java index 44ec502de..f1e6d2010 100644 --- a/src/main/java/grondag/canvas/light/color/LightPropagation.java +++ b/src/main/java/grondag/canvas/light/color/LightPropagation.java @@ -16,7 +16,6 @@ import grondag.canvas.light.color.LightSectionData.Elem; public class LightPropagation { - final static BlockPos.MutableBlockPos searchPos = new BlockPos.MutableBlockPos(); private static Int2ShortOpenHashMap lights = new Int2ShortOpenHashMap(); private static LongArrayFIFOQueue incQueue = new LongArrayFIFOQueue(); private static LongArrayFIFOQueue decQueue = new LongArrayFIFOQueue(); @@ -84,32 +83,8 @@ private static void evaluateForQueue(BlockPos pos, short light, boolean occludin final short getLight = LightDebug.debugData.get(index); lessThan(getLight, light); - // greaterThan(getLight, light); - - // short incLight = 0; - // short incLightMask = 0; - // - // short decLight = 0; - // short decLightMask = 0; - // - // final Elem[] elems = Elem.values(); - // - // for (int i = 0; i < 3; i++) { - // if (inc.get(i)) { - // incLight |= elems[i].of(light); - // incLightMask |= elems[i].mask; - // } else if (dec.get(i)) { - // decLight |= elems[i].of(light); - // decLightMask |= elems[i].mask; - // } - // } - // - // final short putLight = (short) (((getLight & ~incLightMask) & ~decLightMask) | incLight | decLight); if (less.any()) { - // int[] xyz = new int[3]; - // LightDebug.debugData.reverseIndexify(index, xyz); - // CanvasMod.LOG.info("test reverse index : " + pos + "/" + xyz[0] + "," + xyz[1] + "," + xyz[2]); LightDebug.debugData.put(index, light); enqueue(incQueue, index, light); CanvasMod.LOG.info("Add light at " + pos + " light is (get,put) " @@ -170,24 +145,19 @@ private static void processQueues() { if (less.r) { mask |= Elem.R.mask; - // LightDebug.debugData.put(nodeIndex, Elem.R, 0); } if (less.g) { mask |= Elem.G.mask; - // LightDebug.debugData.put(nodeIndex, Elem.G, 0); } if (less.b) { mask |= Elem.B.mask; - // LightDebug.debugData.put(nodeIndex, Elem.B, 0); } final short nodeAfterLight = (short) (nodeLight & ~(mask)); LightDebug.debugData.put(nodeIndex, nodeAfterLight); - // final short nodeAfterLight = LightDebug.debugData.get(nodeIndex); - enqueue(decQueue, nodeIndex, nodeLight); nodeLight = nodeAfterLight; @@ -292,7 +262,6 @@ public BVec() { this.r = false; this.g = false; this.b = false; - // this.a = false; } public boolean get(int i) { @@ -314,7 +283,6 @@ public boolean all() { } private static BVec less = new BVec(); - private static BVec greater = new BVec(); private static void lessThan(short left, short right) { less.r = Elem.R.of(left) < Elem.R.of(right); @@ -327,16 +295,4 @@ private static void lessThanMinusOne(short left, short right) { less.g = Elem.G.of(left) < Elem.G.of(right) - 1; less.b = Elem.B.of(left) < Elem.B.of(right) - 1; } - - private static void equalsMinusOne(short left, short right) { - less.r = Elem.R.of(left) == Elem.R.of(right) - 1; - less.g = Elem.G.of(left) == Elem.G.of(right) - 1; - less.b = Elem.B.of(left) == Elem.B.of(right) - 1; - } - - private static void greaterThanOrEqual(short left, short right) { - greater.r = Elem.R.of(left) >= Elem.R.of(right); - greater.g = Elem.G.of(left) >= Elem.G.of(right); - greater.b = Elem.B.of(left) >= Elem.B.of(right); - } } diff --git a/src/main/java/grondag/canvas/light/color/LightSectionData.java b/src/main/java/grondag/canvas/light/color/LightSectionData.java index bfa8ff0b9..407846844 100644 --- a/src/main/java/grondag/canvas/light/color/LightSectionData.java +++ b/src/main/java/grondag/canvas/light/color/LightSectionData.java @@ -8,7 +8,6 @@ import net.minecraft.core.BlockPos; -import grondag.canvas.CanvasMod; import grondag.canvas.render.CanvasTextureState; import grondag.canvas.varia.GFX; @@ -60,7 +59,6 @@ public static String text(short light) { private int sectionBlockOffsetZ = -16; private ByteBuffer buffer; - // private long pointer; private int glTexId; private boolean closed = false; @@ -88,9 +86,6 @@ public LightSectionData() { buffer.putShort((short) 0); } - // buffer = ByteBuffer.allocate(Format.pixelBytes * SIZE3D); - // pointer = MemoryUtil.nmemAlloc((long) Format.pixelBytes * SIZE3D); // ?? - // allocate GFX.texImage3D(Format.target, 0, Format.internalFormat, Const.WIDTH, Const.WIDTH, Const.WIDTH, 0, Format.pixelFormat, Format.pixelDataType, null); } @@ -107,16 +102,9 @@ public void upload() { // Importantly, reset the pointer without flip buffer.position(0); - // GFX.texImage3D(Format.target, 0, Format.internalFormat, Const.WIDTH, Const.WIDTH, Const.WIDTH, 0, Format.pixelFormat, Format.pixelDataType, buffer); GFX.glTexSubImage3D(Format.target, 0, 0, 0, 0, Const.WIDTH, Const.WIDTH, Const.WIDTH, Format.pixelFormat, Format.pixelDataType, buffer); } - public void draw(long offset, short rgba) { - assert offset == buffer.position(); - buffer.putShort(rgba); - // MemoryUtil.memPutShort(pointer + offset, rgba); - } - public void draw(short rgba) { buffer.putShort(rgba); } @@ -124,7 +112,6 @@ public void draw(short rgba) { public void close() { TextureUtil.releaseTextureId(glTexId); MemoryUtil.memFree(buffer); - // MemoryUtil.nmemFree(pointer); glTexId = -1; closed = true; } @@ -134,9 +121,6 @@ public short get(int index) { } public void put(int index, short light) { - // int[] xyz = new int[3]; - // LightDebug.debugData.reverseIndexify(index, xyz); - // CanvasMod.LOG.info("putting light at : " + xyz[0] + "," + xyz[1] + "," + xyz[2] + " / " + Elem.text(light)); buffer.putShort(index, light); } From 0fdac199d7444597f863c72dc466f8a33272a5e0 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Fri, 2 Jun 2023 16:35:56 +0700 Subject: [PATCH 03/69] Move encoding out into one place --- .../canvas/light/color/LightDebug.java | 4 +- .../canvas/light/color/LightPropagation.java | 37 +++++-------------- .../canvas/light/color/LightSectionData.java | 27 +++++++++++--- 3 files changed, 33 insertions(+), 35 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDebug.java b/src/main/java/grondag/canvas/light/color/LightDebug.java index 61f145d65..71f7768af 100644 --- a/src/main/java/grondag/canvas/light/color/LightDebug.java +++ b/src/main/java/grondag/canvas/light/color/LightDebug.java @@ -17,7 +17,7 @@ public static void initialize() { } debugData = new LightSectionData(); - // clear(debugData); + // drawDummy(debugData); // debugData.upload(); CanvasMod.LOG.info("Light debug render initialized."); @@ -35,7 +35,7 @@ static void drawDummy(LightSectionData data) { for (int x = 0; x < WIDTH; x ++) { for (int y = 0; y < WIDTH; y ++) { for (int z = 0; z < WIDTH; z ++) { - data.draw(LightSectionData.encodeRgba(x % 16, y % 16, z % 16, 0xF)); + data.draw(LightSectionData.Encoding.encodeLight(x % 16, y % 16, z % 16, false, false)); } } } diff --git a/src/main/java/grondag/canvas/light/color/LightPropagation.java b/src/main/java/grondag/canvas/light/color/LightPropagation.java index f1e6d2010..34bc7bbf3 100644 --- a/src/main/java/grondag/canvas/light/color/LightPropagation.java +++ b/src/main/java/grondag/canvas/light/color/LightPropagation.java @@ -14,11 +14,12 @@ import grondag.canvas.CanvasMod; import grondag.canvas.light.color.LightSectionData.Elem; +import grondag.canvas.light.color.LightSectionData.Encoding; public class LightPropagation { - private static Int2ShortOpenHashMap lights = new Int2ShortOpenHashMap(); - private static LongArrayFIFOQueue incQueue = new LongArrayFIFOQueue(); - private static LongArrayFIFOQueue decQueue = new LongArrayFIFOQueue(); + private static final Int2ShortOpenHashMap lights = new Int2ShortOpenHashMap(); + private static final LongArrayFIFOQueue incQueue = new LongArrayFIFOQueue(); + private static final LongArrayFIFOQueue decQueue = new LongArrayFIFOQueue(); private static boolean dirty = false; private static Block debugBlock; private static int debugCountThread = 0; @@ -53,12 +54,12 @@ public static synchronized void onBuildTerrain(BlockPos pos, final int lightEmis final int r = (int) (lightEmission * itemLight.red()); final int g = (int) (lightEmission * itemLight.green()); final int b = (int) (lightEmission * itemLight.blue()); - light = LightSectionData.encodeRgba(r, g, b, 0); + light = Encoding.encodeLight(r, g, b, true, occluding); } else { - light = LightSectionData.encodeRgba(lightEmission, lightEmission, lightEmission, 0); + light = Encoding.encodeLight(lightEmission, lightEmission, lightEmission, true, occluding); } - lights.put(block.hashCode(), lightSource(light)); + lights.put(block.hashCode(), light); } } @@ -129,7 +130,7 @@ private static void processQueues() { final int nodeIndex = LightDebug.debugData.indexify(nodeX, nodeY, nodeZ); short nodeLight = LightDebug.debugData.get(nodeIndex); - if (pure(nodeLight) == 0) { + if (Encoding.pure(nodeLight) == 0) { continue; } @@ -198,7 +199,7 @@ private static void processQueues() { final int nodeIndex = LightDebug.debugData.indexify(nodeX, nodeY, nodeZ); final short nodeLight = LightDebug.debugData.get(nodeIndex); - if (isOccluding(nodeLight)) { + if (Encoding.isOccluding(nodeLight)) { continue; } @@ -235,26 +236,6 @@ private static void processQueues() { CanvasMod.LOG.info("Processed queues! Count: inc,dec " + debugMaxInc + "," + debugMaxDec); } - private static boolean isLightSource(short light) { - return (light & 0b1) != 0; - } - - private static boolean isOccluding(short light) { - return (light & 0b10) != 0; - } - - private static short lightSource(short light) { - return (short) (light | 0b1); - } - - private static short occluding(short light, boolean occluding) { - return occluding ? (short) (light | 0b10) : light; - } - - private static short pure(short light) { - return (short) (light & 0xfff0); - } - private static class BVec { boolean r, g, b; diff --git a/src/main/java/grondag/canvas/light/color/LightSectionData.java b/src/main/java/grondag/canvas/light/color/LightSectionData.java index 407846844..cdd6fc980 100644 --- a/src/main/java/grondag/canvas/light/color/LightSectionData.java +++ b/src/main/java/grondag/canvas/light/color/LightSectionData.java @@ -53,6 +53,28 @@ public static String text(short light) { } } + public static class Encoding { + public static short encodeLight(int r, int g, int b, boolean isLightSource, boolean isOccluding) { + return Elem.encode(r, g, b, (isLightSource ? 0b1 : 0) | (isOccluding ? 0b10 : 0)); + } + + public static short encodeLight(int pureLight, boolean isLightSource, boolean isOccluding) { + return (short) (pureLight | (isLightSource ? 0b1 : 0) | (isOccluding ? 0b10 : 0)); + } + + public static boolean isLightSource(short light) { + return (light & 0b1) != 0; + } + + public static boolean isOccluding(short light) { + return (light & 0b10) != 0; + } + + public static short pure(short light) { + return (short) (light & 0xfff0); + } + } + // placeholder private int sectionBlockOffsetX = -16; private int sectionBlockOffsetY = 100; @@ -62,11 +84,6 @@ public static String text(short light) { private int glTexId; private boolean closed = false; - public static short encodeRgba(int r, int g, int b, int a) { - // PERF: alpha is unnecessary - return (short) ((r << 12) | (g << 8) | (b << 4) | a); - } - public LightSectionData() { glTexId = TextureUtil.generateTextureId(); From 47e53d5d3420c7938ef4b87dadbcf80965a637a3 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Fri, 2 Jun 2023 16:40:28 +0700 Subject: [PATCH 04/69] Perform light replacement in heap + Ensure widening cast safety --- .../canvas/light/color/LightPropagation.java | 26 +++++++++---------- .../canvas/light/color/LightSectionData.java | 18 +++++++------ 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightPropagation.java b/src/main/java/grondag/canvas/light/color/LightPropagation.java index 34bc7bbf3..48d89c182 100644 --- a/src/main/java/grondag/canvas/light/color/LightPropagation.java +++ b/src/main/java/grondag/canvas/light/color/LightPropagation.java @@ -90,8 +90,8 @@ private static void evaluateForQueue(BlockPos pos, short light, boolean occludin enqueue(incQueue, index, light); CanvasMod.LOG.info("Add light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + debugBlock); - } else if (light == 0 && (isLightSource(getLight) || isOccluding(getLight) || occluding)) { - LightDebug.debugData.put(index, occluding((short) 0, occluding)); + } else if (light == 0 && (Encoding.isLightSource(getLight) || Encoding.isOccluding(getLight) || occluding)) { + LightDebug.debugData.put(index, Encoding.encodeLight(0, false, occluding)); enqueue(decQueue, index, getLight); CanvasMod.LOG.info("Remove light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + debugBlock); @@ -156,12 +156,12 @@ private static void processQueues() { mask |= Elem.B.mask; } - final short nodeAfterLight = (short) (nodeLight & ~(mask)); - LightDebug.debugData.put(nodeIndex, nodeAfterLight); + final short resultLight = (short) (nodeLight & ~(mask)); + LightDebug.debugData.put(nodeIndex, resultLight); enqueue(decQueue, nodeIndex, nodeLight); - nodeLight = nodeAfterLight; + nodeLight = resultLight; } if (!less.all()) { @@ -208,25 +208,25 @@ private static void processQueues() { lessThanMinusOne(nodeLight, sourceLight); if (less.any()) { - // TODO: optimize + short resultLight = nodeLight; + if (less.r) { - LightDebug.debugData.put(nodeIndex, Elem.R, (Elem.R.of(sourceLight) - 1)); + resultLight = Elem.R.replace(resultLight, (short) (Elem.R.of(sourceLight) - 1)); } if (less.g) { - LightDebug.debugData.put(nodeIndex, Elem.G, (Elem.G.of(sourceLight) - 1)); + resultLight = Elem.G.replace(resultLight, (short) (Elem.G.of(sourceLight) - 1)); } if (less.b) { - LightDebug.debugData.put(nodeIndex, Elem.B, (Elem.B.of(sourceLight) - 1)); + resultLight = Elem.B.replace(resultLight, (short) (Elem.B.of(sourceLight) - 1)); } - // TODO: optimize - final short nodeAfterLight = LightDebug.debugData.get(nodeIndex); + LightDebug.debugData.put(nodeIndex, resultLight); - // CanvasMod.LOG.info("updating neighbor to: " + nodeX + "," + nodeY + "," + nodeZ + "," + Elem.text(nodeAfterLight)); + // CanvasMod.LOG.info("updating neighbor to: " + nodeX + "," + nodeY + "," + nodeZ + "," + Elem.text(resultLight)); - enqueue(incQueue, nodeIndex, nodeAfterLight); + enqueue(incQueue, nodeIndex, resultLight); } } } diff --git a/src/main/java/grondag/canvas/light/color/LightSectionData.java b/src/main/java/grondag/canvas/light/color/LightSectionData.java index cdd6fc980..376555148 100644 --- a/src/main/java/grondag/canvas/light/color/LightSectionData.java +++ b/src/main/java/grondag/canvas/light/color/LightSectionData.java @@ -45,7 +45,15 @@ public static enum Elem { } public int of(short light) { - return (light & mask) >> shift; + return (light >> shift) & 0xF; + } + + public short replace(short source, short elemLight) { + return (short) ((source & ~mask) | (elemLight << shift)); + } + + public static short encode(int r, int g, int b, int a) { + return (short) ((r << R.shift) | (g << G.shift) | (b << B.shift) | (a << A.shift)); } public static String text(short light) { @@ -141,12 +149,6 @@ public void put(int index, short light) { buffer.putShort(index, light); } - public void put(int index, Elem elem, int elemLight) { - final short get = buffer.getShort(index); - final short put = (short) ((get & ~elem.mask) | (elemLight << elem.shift)); - buffer.putShort(index, put); - } - public int indexify(BlockPos pos) { return indexify(pos.getX(), pos.getY(), pos.getZ()); } @@ -168,7 +170,7 @@ public void reverseIndexify(int index, int[] result) { // x and z are swapped because opengl result[0] = (index & Const.WIDTH_MASK) + sectionBlockOffsetX; result[1] = ((index >> Const.WIDTH_SHIFT) & Const.WIDTH_MASK) + sectionBlockOffsetY; - result[2] = (index >> Const.WIDTH_SHIFT * 2) + sectionBlockOffsetZ; + result[2] = ((index >> Const.WIDTH_SHIFT * 2) & Const.WIDTH_MASK) + sectionBlockOffsetZ; } public boolean withinExtents(BlockPos pos) { From 9582811ba50685fc31276b22e1fbc3871a5ffd71 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Fri, 2 Jun 2023 18:32:16 +0700 Subject: [PATCH 05/69] Make BVec cache thread-local because it just makes sense --- .../canvas/light/color/LightPropagation.java | 91 ++++++++++--------- 1 file changed, 47 insertions(+), 44 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightPropagation.java b/src/main/java/grondag/canvas/light/color/LightPropagation.java index 48d89c182..fd1c8e6f9 100644 --- a/src/main/java/grondag/canvas/light/color/LightPropagation.java +++ b/src/main/java/grondag/canvas/light/color/LightPropagation.java @@ -17,6 +17,47 @@ import grondag.canvas.light.color.LightSectionData.Encoding; public class LightPropagation { + private static class BVec { + boolean r, g, b; + + public BVec() { + this.r = false; + this.g = false; + this.b = false; + } + + public boolean get(int i) { + return switch (i) { + case 0 -> r; + case 1 -> g; + case 2 -> b; + default -> false; + }; + } + + public boolean any() { + return r || g || b; + } + + public boolean all() { + return r && g && b; + } + + private void lessThan(short left, short right) { + r = Elem.R.of(left) < Elem.R.of(right); + g = Elem.G.of(left) < Elem.G.of(right); + b = Elem.B.of(left) < Elem.B.of(right); + } + + private void lessThanMinusOne(short left, short right) { + r = Elem.R.of(left) < Elem.R.of(right) - 1; + g = Elem.G.of(left) < Elem.G.of(right) - 1; + b = Elem.B.of(left) < Elem.B.of(right) - 1; + } + } + + private static ThreadLocal lessPool = ThreadLocal.withInitial(BVec::new); + private static final Int2ShortOpenHashMap lights = new Int2ShortOpenHashMap(); private static final LongArrayFIFOQueue incQueue = new LongArrayFIFOQueue(); private static final LongArrayFIFOQueue decQueue = new LongArrayFIFOQueue(); @@ -82,8 +123,9 @@ public static synchronized void onFinishedBuildTerrain() { private static void evaluateForQueue(BlockPos pos, short light, boolean occluding) { final int index = LightDebug.debugData.indexify(pos); final short getLight = LightDebug.debugData.get(index); + final var less = lessPool.get(); - lessThan(getLight, light); + less.lessThan(getLight, light); if (less.any()) { LightDebug.debugData.put(index, light); @@ -106,6 +148,8 @@ private static void enqueue(LongArrayFIFOQueue queue, long index, long light) { private static void processQueues() { CanvasMod.LOG.info("Processing queues.. inc,dec " + incQueue.size() + "," + decQueue.size()); + final var less = lessPool.get(); + int[] pos = new int[3]; int debugMaxDec = 0; int debugMaxInc = 0; @@ -139,7 +183,7 @@ private static void processQueues() { // continue; // } - lessThan(nodeLight, sourcePrevLight); + less.lessThan(nodeLight, sourcePrevLight); if (less.any()) { int mask = 0; @@ -205,7 +249,7 @@ private static void processQueues() { // CanvasMod.LOG.info("current/neighbor index " + index + "/" + nodeIndex); - lessThanMinusOne(nodeLight, sourceLight); + less.lessThanMinusOne(nodeLight, sourceLight); if (less.any()) { short resultLight = nodeLight; @@ -235,45 +279,4 @@ private static void processQueues() { CanvasMod.LOG.info("Processed queues! Count: inc,dec " + debugMaxInc + "," + debugMaxDec); } - - private static class BVec { - boolean r, g, b; - - public BVec() { - this.r = false; - this.g = false; - this.b = false; - } - - public boolean get(int i) { - return switch (i) { - case 0 -> r; - case 1 -> g; - case 2 -> b; - default -> false; - }; - } - - public boolean any() { - return r || g || b; - } - - public boolean all() { - return r && g && b; - } - } - - private static BVec less = new BVec(); - - private static void lessThan(short left, short right) { - less.r = Elem.R.of(left) < Elem.R.of(right); - less.g = Elem.G.of(left) < Elem.G.of(right); - less.b = Elem.B.of(left) < Elem.B.of(right); - } - - private static void lessThanMinusOne(short left, short right) { - less.r = Elem.R.of(left) < Elem.R.of(right) - 1; - less.g = Elem.G.of(left) < Elem.G.of(right) - 1; - less.b = Elem.B.of(left) < Elem.B.of(right) - 1; - } } From fa9d2ea64bf6243061d2308433f2e388bf2df62f Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sat, 3 Jun 2023 01:52:06 +0700 Subject: [PATCH 06/69] Make propagation tasks child of RenderRegion task --- ...htPropagation.java => LightChunkTask.java} | 89 ++++--------------- .../canvas/light/color/LightRegistry.java | 43 +++++++++ .../canvas/light/color/LightSectionData.java | 8 +- .../canvas/terrain/region/RenderRegion.java | 20 +++-- 4 files changed, 77 insertions(+), 83 deletions(-) rename src/main/java/grondag/canvas/light/color/{LightPropagation.java => LightChunkTask.java} (68%) create mode 100644 src/main/java/grondag/canvas/light/color/LightRegistry.java diff --git a/src/main/java/grondag/canvas/light/color/LightPropagation.java b/src/main/java/grondag/canvas/light/color/LightChunkTask.java similarity index 68% rename from src/main/java/grondag/canvas/light/color/LightPropagation.java rename to src/main/java/grondag/canvas/light/color/LightChunkTask.java index fd1c8e6f9..f2c71ac71 100644 --- a/src/main/java/grondag/canvas/light/color/LightPropagation.java +++ b/src/main/java/grondag/canvas/light/color/LightChunkTask.java @@ -1,22 +1,18 @@ package grondag.canvas.light.color; -import it.unimi.dsi.fastutil.ints.Int2ShortOpenHashMap; import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.block.Block; - -import io.vram.frex.api.light.ItemLight; +import net.minecraft.world.level.block.state.BlockState; import grondag.canvas.CanvasMod; import grondag.canvas.light.color.LightSectionData.Elem; import grondag.canvas.light.color.LightSectionData.Encoding; -public class LightPropagation { +public class LightChunkTask { private static class BVec { boolean r, g, b; @@ -56,100 +52,45 @@ private void lessThanMinusOne(short left, short right) { } } - private static ThreadLocal lessPool = ThreadLocal.withInitial(BVec::new); - - private static final Int2ShortOpenHashMap lights = new Int2ShortOpenHashMap(); + private final BVec less = new BVec(); private static final LongArrayFIFOQueue incQueue = new LongArrayFIFOQueue(); private static final LongArrayFIFOQueue decQueue = new LongArrayFIFOQueue(); - private static boolean dirty = false; - private static Block debugBlock; - private static int debugCountThread = 0; - - static { - lights.defaultReturnValue((short) 0); - } - public static synchronized void onStartBuildTerrain() { - debugCountThread++; + private static void enqueue(LongArrayFIFOQueue queue, long index, long light) { + queue.enqueue((index << 16L) | light & 0xffffL); } - public static synchronized void onBuildTerrain(BlockPos pos, final int lightEmission, Block block, boolean occluding) { + public void checkBlock(BlockPos pos, BlockState blockState) { if (!LightDebug.debugData.withinExtents(pos)) { return; } - debugBlock = block; - short light = 0; - if (lightEmission > 0) { - light = lights.get(block.hashCode()); - - if (light == 0) { - // PERF: modify ItemLight API or make new API that doesn't need ItemStack - // TODO: ItemLight API isn't suitable for this anyway since we rely on blockstates not blocks/items - final ItemStack stack = new ItemStack(block, 1); - final ItemLight itemLight = ItemLight.get(stack); - - if (itemLight != null) { - final int r = (int) (lightEmission * itemLight.red()); - final int g = (int) (lightEmission * itemLight.green()); - final int b = (int) (lightEmission * itemLight.blue()); - light = Encoding.encodeLight(r, g, b, true, occluding); - } else { - light = Encoding.encodeLight(lightEmission, lightEmission, lightEmission, true, occluding); - } - - lights.put(block.hashCode(), light); - } - } - - evaluateForQueue(pos, light, occluding); - } - - public static synchronized void onFinishedBuildTerrain() { - debugCountThread --; - - if (dirty && debugCountThread == 0) { - dirty = false; - // TODO: there are multiple worker threads. the queue can't handle that yet - // TODO: still not working - processQueues(); - CanvasMod.LOG.info("Uploading texture"); - RenderSystem.recordRenderCall(() -> LightDebug.debugData.upload()); + if (blockState.getLightEmission() > 0) { + light = LightRegistry.get(blockState); } - } - private static void evaluateForQueue(BlockPos pos, short light, boolean occluding) { final int index = LightDebug.debugData.indexify(pos); final short getLight = LightDebug.debugData.get(index); - final var less = lessPool.get(); + final boolean occluding = blockState.canOcclude(); less.lessThan(getLight, light); if (less.any()) { LightDebug.debugData.put(index, light); enqueue(incQueue, index, light); - CanvasMod.LOG.info("Add light at " + pos + " light is (get,put) " - + Elem.text(getLight) + "," + Elem.text(light) + " block: " + debugBlock); - } else if (light == 0 && (Encoding.isLightSource(getLight) || Encoding.isOccluding(getLight) || occluding)) { + CanvasMod.LOG.info("Add light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + blockState); + } else if (light == 0 && (Encoding.isLightSource(getLight) || (Encoding.isOccluding(getLight) && !occluding) || occluding)) { LightDebug.debugData.put(index, Encoding.encodeLight(0, false, occluding)); enqueue(decQueue, index, getLight); - CanvasMod.LOG.info("Remove light at " + pos + " light is (get,put) " - + Elem.text(getLight) + "," + Elem.text(light) + " block: " + debugBlock); + CanvasMod.LOG.info("Remove light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + blockState); } } - private static void enqueue(LongArrayFIFOQueue queue, long index, long light) { - dirty = true; - queue.enqueue((index << 16L) | light & 0xffffL); - } - - private static void processQueues() { + public void propagateLight() { CanvasMod.LOG.info("Processing queues.. inc,dec " + incQueue.size() + "," + decQueue.size()); - final var less = lessPool.get(); - int[] pos = new int[3]; int debugMaxDec = 0; int debugMaxInc = 0; @@ -275,8 +216,8 @@ private static void processQueues() { } } - dirty = false; - CanvasMod.LOG.info("Processed queues! Count: inc,dec " + debugMaxInc + "," + debugMaxDec); + CanvasMod.LOG.info("Uploading texture"); + RenderSystem.recordRenderCall(() -> LightDebug.debugData.upload()); } } diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java new file mode 100644 index 000000000..3873fb625 --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -0,0 +1,43 @@ +package grondag.canvas.light.color; + +import it.unimi.dsi.fastutil.ints.Int2ShortOpenHashMap; + +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.state.BlockState; + +import io.vram.frex.api.light.ItemLight; + +public class LightRegistry { + private static final Int2ShortOpenHashMap lights = new Int2ShortOpenHashMap(); + + static { + lights.defaultReturnValue((short) 0); + } + + public static short get(BlockState blockState){ + final int stateKey = blockState.hashCode(); + short light = lights.get(stateKey); + + if (light == 0) { + // PERF: modify ItemLight API or make new API that doesn't need ItemStack + // TODO: ItemLight API isn't suitable for this anyway since we rely on blockstates not blocks/items + final ItemStack stack = new ItemStack(blockState.getBlock(), 1); + final ItemLight itemLight = ItemLight.get(stack); + final int lightEmission = blockState.getLightEmission(); + final boolean occluding = blockState.canOcclude(); + + if (itemLight != null) { + final int r = (int) (lightEmission * itemLight.red()); + final int g = (int) (lightEmission * itemLight.green()); + final int b = (int) (lightEmission * itemLight.blue()); + light = LightSectionData.Encoding.encodeLight(r, g, b, true, occluding); + } else { + light = LightSectionData.Encoding.encodeLight(lightEmission, lightEmission, lightEmission, true, occluding); + } + + lights.put(stateKey, light); + } + + return light; + } +} diff --git a/src/main/java/grondag/canvas/light/color/LightSectionData.java b/src/main/java/grondag/canvas/light/color/LightSectionData.java index 376555148..270c8cb6f 100644 --- a/src/main/java/grondag/canvas/light/color/LightSectionData.java +++ b/src/main/java/grondag/canvas/light/color/LightSectionData.java @@ -21,7 +21,7 @@ public static class Format { } public static class Const { - public static final int WIDTH = 16 * 2; + public static final int WIDTH = 16; private static final int SIZE3D = WIDTH * WIDTH * WIDTH; public static final int WIDTH_SHIFT = (int) (Math.log(WIDTH) / Math.log(2)); @@ -84,9 +84,9 @@ public static short pure(short light) { } // placeholder - private int sectionBlockOffsetX = -16; - private int sectionBlockOffsetY = 100; - private int sectionBlockOffsetZ = -16; + private int sectionBlockOffsetX = 0; + private int sectionBlockOffsetY = 80; + private int sectionBlockOffsetZ = 0; private ByteBuffer buffer; private int glTexId; diff --git a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java index 7ece4796b..a8ce76dd5 100644 --- a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java +++ b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java @@ -51,7 +51,8 @@ import grondag.canvas.apiimpl.rendercontext.CanvasTerrainRenderContext; import grondag.canvas.buffer.input.DrawableVertexCollector; import grondag.canvas.buffer.input.VertexCollectorList; -import grondag.canvas.light.color.LightPropagation; +import grondag.canvas.light.color.LightChunkTask; +import grondag.canvas.light.color.LightDebug; import grondag.canvas.material.state.TerrainRenderStates; import grondag.canvas.perf.ChunkRebuildCounters; import grondag.canvas.pipeline.Pipeline; @@ -80,6 +81,7 @@ public class RenderRegion implements TerrainExecutorTask { public final CameraRegionVisibility cameraVisibility; public final ShadowRegionVisibility shadowVisibility; public final NeighborRegions neighbors; + private final LightChunkTask lightChunkTask; private RegionRenderSector renderSector = null; @@ -127,6 +129,12 @@ public RenderRegion(RenderChunk chunk, long packedPos) { cameraVisibility = worldRenderState.terrainIterator.cameraVisibility.createRegionState(this); shadowVisibility = worldRenderState.terrainIterator.shadowVisibility.createRegionState(this); origin.update(); + + if (LightDebug.debugData.withinExtents(origin.getX(), origin.getY(), origin.getZ())) { + lightChunkTask = new LightChunkTask(); + } else { + lightChunkTask = null; + } } private static void addBlockEntity(List chunkEntities, Set globalEntities, E blockEntity) { @@ -421,8 +429,6 @@ private void buildTerrain(CanvasTerrainRenderContext context, RegionBuildState b final BlockRenderDispatcher blockRenderManager = Minecraft.getInstance().getBlockRenderer(); final RegionOcclusionCalculator occlusionRegion = region.occlusion; - LightPropagation.onStartBuildTerrain(); - for (int i = 0; i < RenderRegionStateIndexer.INTERIOR_STATE_COUNT; i++) { final BlockState blockState = region.getLocalBlockState(i); final int x = i & 0xF; @@ -461,10 +467,14 @@ private void buildTerrain(CanvasTerrainRenderContext context, RegionBuildState b } } - LightPropagation.onBuildTerrain(searchPos, blockState.getLightEmission(), blockState.getBlock(), blockState.canOcclude()); + if (lightChunkTask != null) { + lightChunkTask.checkBlock(searchPos, blockState); + } } - LightPropagation.onFinishedBuildTerrain(); + if (lightChunkTask != null) { + lightChunkTask.propagateLight(); + } buildState.prepareTranslucentIfNeeded(worldRenderState.sectorManager.cameraPos(), renderSector, collectors); From ee28df839ae9cf971b9bbfde17232e704983bb01 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sat, 3 Jun 2023 02:28:43 +0700 Subject: [PATCH 07/69] Small improvements - Enqueue only occlusion change - Compare based on pure light - Skip empty queue (don't upload) --- .../java/grondag/canvas/light/color/LightChunkTask.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightChunkTask.java b/src/main/java/grondag/canvas/light/color/LightChunkTask.java index f2c71ac71..ed711f98b 100644 --- a/src/main/java/grondag/canvas/light/color/LightChunkTask.java +++ b/src/main/java/grondag/canvas/light/color/LightChunkTask.java @@ -81,7 +81,7 @@ public void checkBlock(BlockPos pos, BlockState blockState) { LightDebug.debugData.put(index, light); enqueue(incQueue, index, light); CanvasMod.LOG.info("Add light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + blockState); - } else if (light == 0 && (Encoding.isLightSource(getLight) || (Encoding.isOccluding(getLight) && !occluding) || occluding)) { + } else if (light == 0 && (Encoding.isLightSource(getLight) || (Encoding.isOccluding(getLight) != occluding))) { LightDebug.debugData.put(index, Encoding.encodeLight(0, false, occluding)); enqueue(decQueue, index, getLight); CanvasMod.LOG.info("Remove light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + blockState); @@ -89,6 +89,11 @@ public void checkBlock(BlockPos pos, BlockState blockState) { } public void propagateLight() { + if (incQueue.isEmpty() && decQueue.isEmpty()) { + CanvasMod.LOG.info("Nothing to process!"); + return; + } + CanvasMod.LOG.info("Processing queues.. inc,dec " + incQueue.size() + "," + decQueue.size()); int[] pos = new int[3]; @@ -164,7 +169,7 @@ public void propagateLight() { final short recordedLight = (short) entry; final short sourceLight = LightDebug.debugData.get(index); - if (sourceLight != recordedLight) { + if (Encoding.pure(sourceLight) != Encoding.pure(recordedLight)) { continue; } From 0fac619832790a65aac2fd5536f95aee2f81dd40 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sat, 3 Jun 2023 15:06:05 +0700 Subject: [PATCH 08/69] Prevent propagating in the opposite direction Except for light sources which propagate in every direction. Bright nodes found while looping through light removal (decrease) queue are treated as light sources. --- .../canvas/light/color/LightChunkTask.java | 91 ++++++++++++++----- .../canvas/light/color/LightSectionData.java | 1 + 2 files changed, 71 insertions(+), 21 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightChunkTask.java b/src/main/java/grondag/canvas/light/color/LightChunkTask.java index ed711f98b..7169fd9d3 100644 --- a/src/main/java/grondag/canvas/light/color/LightChunkTask.java +++ b/src/main/java/grondag/canvas/light/color/LightChunkTask.java @@ -5,7 +5,6 @@ import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; import net.minecraft.world.level.block.state.BlockState; import grondag.canvas.CanvasMod; @@ -52,14 +51,52 @@ private void lessThanMinusOne(short left, short right) { } } + private static enum Direction { + DOWN(0, -1, 0, 1, 2), + UP(0, 1, 0, 2, 1), + NORTH(0, 0, -1, 3, 4), + SOUTH(0, 0, 1, 4, 3), + WEST(-1, 0, 0, 5, 6), + EAST(1, 0, 0, 6, 5); + + final int x, y, z, id, opposite; + final static int nullId = 0; + + Direction(int x, int y, int z, int id, int opposite) { + this.x = x; + this.y = y; + this.z = z; + this.id = id; + this.opposite = opposite; + } + } + + private static class Queues { + static void enqueue(LongArrayFIFOQueue queue, long index, long light) { + queue.enqueue(((long) Direction.nullId << 48L) | (index << 16L) | light & 0xffffL); + } + + static void enqueue(LongArrayFIFOQueue queue, long index, long light, Direction target) { + queue.enqueue(((long) target.opposite << 48L) | (index << 16L) | light & 0xffffL); + } + + static int from(long entry) { + return (int) (entry >> 48L); + } + + static int index(long entry) { + return (int) (entry >> 16L); + } + + static short light(long entry) { + return (short) (entry); + } + } + private final BVec less = new BVec(); private static final LongArrayFIFOQueue incQueue = new LongArrayFIFOQueue(); private static final LongArrayFIFOQueue decQueue = new LongArrayFIFOQueue(); - private static void enqueue(LongArrayFIFOQueue queue, long index, long light) { - queue.enqueue((index << 16L) | light & 0xffffL); - } - public void checkBlock(BlockPos pos, BlockState blockState) { if (!LightDebug.debugData.withinExtents(pos)) { return; @@ -79,11 +116,11 @@ public void checkBlock(BlockPos pos, BlockState blockState) { if (less.any()) { LightDebug.debugData.put(index, light); - enqueue(incQueue, index, light); + Queues.enqueue(incQueue, index, light); CanvasMod.LOG.info("Add light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + blockState); } else if (light == 0 && (Encoding.isLightSource(getLight) || (Encoding.isOccluding(getLight) != occluding))) { LightDebug.debugData.put(index, Encoding.encodeLight(0, false, occluding)); - enqueue(decQueue, index, getLight); + Queues.enqueue(decQueue, index, getLight); CanvasMod.LOG.info("Remove light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + blockState); } } @@ -104,14 +141,20 @@ public void propagateLight() { debugMaxDec++; final long entry = decQueue.dequeueLong(); - final int index = (int) (entry >> 16L); - final short sourcePrevLight = (short) entry; + final int index = Queues.index(entry); + final short sourcePrevLight = Queues.light(entry); + final int from = Queues.from(entry); + LightDebug.debugData.reverseIndexify(index, pos); for (var d: Direction.values()) { - final int nodeX = pos[0] + d.getStepX(); - final int nodeY = pos[1] + d.getStepY(); - final int nodeZ = pos[2] + d.getStepZ(); + if (d.id == from) { + continue; + } + + final int nodeX = pos[0] + d.x; + final int nodeY = pos[1] + d.y; + final int nodeZ = pos[2] + d.z; if (!LightDebug.debugData.withinExtents(nodeX, nodeY, nodeZ)) { continue; @@ -149,14 +192,14 @@ public void propagateLight() { final short resultLight = (short) (nodeLight & ~(mask)); LightDebug.debugData.put(nodeIndex, resultLight); - enqueue(decQueue, nodeIndex, nodeLight); + Queues.enqueue(decQueue, nodeIndex, nodeLight, d); nodeLight = resultLight; } if (!less.all()) { - // TODO: queue but only if it's a neighboring chunk?? nah that'd be wrong - enqueue(incQueue, nodeIndex, nodeLight); + // increases queued in decrease may propagate to all directions as if a light source + Queues.enqueue(incQueue, nodeIndex, nodeLight); } } } @@ -165,8 +208,10 @@ public void propagateLight() { debugMaxInc++; final long entry = incQueue.dequeueLong(); - final int index = (int) (entry >> 16L); - final short recordedLight = (short) entry; + final int index = Queues.index(entry); + final short recordedLight = Queues.light(entry); + final int from = Queues.from(entry); + final short sourceLight = LightDebug.debugData.get(index); if (Encoding.pure(sourceLight) != Encoding.pure(recordedLight)) { @@ -176,9 +221,13 @@ public void propagateLight() { LightDebug.debugData.reverseIndexify(index, pos); for (var d: Direction.values()) { - final int nodeX = pos[0] + d.getStepX(); - final int nodeY = pos[1] + d.getStepY(); - final int nodeZ = pos[2] + d.getStepZ(); + if (d.id == from) { + continue; + } + + final int nodeX = pos[0] + d.x; + final int nodeY = pos[1] + d.y; + final int nodeZ = pos[2] + d.z; // CanvasMod.LOG.info("increase at " + nodeX + "," + nodeY + "," + nodeZ); @@ -216,7 +265,7 @@ public void propagateLight() { // CanvasMod.LOG.info("updating neighbor to: " + nodeX + "," + nodeY + "," + nodeZ + "," + Elem.text(resultLight)); - enqueue(incQueue, nodeIndex, resultLight); + Queues.enqueue(incQueue, nodeIndex, resultLight, d); } } } diff --git a/src/main/java/grondag/canvas/light/color/LightSectionData.java b/src/main/java/grondag/canvas/light/color/LightSectionData.java index 270c8cb6f..02745c976 100644 --- a/src/main/java/grondag/canvas/light/color/LightSectionData.java +++ b/src/main/java/grondag/canvas/light/color/LightSectionData.java @@ -62,6 +62,7 @@ public static String text(short light) { } public static class Encoding { + // TODO: change flags to occlusion / light source states (block can occlude in 1 of 6 directions, e.g. slab) public static short encodeLight(int r, int g, int b, boolean isLightSource, boolean isOccluding) { return Elem.encode(r, g, b, (isLightSource ? 0b1 : 0) | (isOccluding ? 0b10 : 0)); } From 2499b0d2642ff8be93fe150f357e15d3dde7c54e Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sat, 3 Jun 2023 16:14:13 +0700 Subject: [PATCH 09/69] Upload light data in fixed location in the render loop --- .../canvas/light/color/LightChunkTask.java | 4 ++-- .../canvas/light/color/LightSectionData.java | 19 ++++++++++++++++++- .../render/world/CanvasWorldRenderer.java | 3 +++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightChunkTask.java b/src/main/java/grondag/canvas/light/color/LightChunkTask.java index 7169fd9d3..0d98462a6 100644 --- a/src/main/java/grondag/canvas/light/color/LightChunkTask.java +++ b/src/main/java/grondag/canvas/light/color/LightChunkTask.java @@ -271,7 +271,7 @@ public void propagateLight() { } CanvasMod.LOG.info("Processed queues! Count: inc,dec " + debugMaxInc + "," + debugMaxDec); - CanvasMod.LOG.info("Uploading texture"); - RenderSystem.recordRenderCall(() -> LightDebug.debugData.upload()); + CanvasMod.LOG.info("Marking texture as dirty"); + LightDebug.debugData.markAsDirty(); } } diff --git a/src/main/java/grondag/canvas/light/color/LightSectionData.java b/src/main/java/grondag/canvas/light/color/LightSectionData.java index 02745c976..85b16e0f4 100644 --- a/src/main/java/grondag/canvas/light/color/LightSectionData.java +++ b/src/main/java/grondag/canvas/light/color/LightSectionData.java @@ -5,9 +5,11 @@ import org.lwjgl.system.MemoryUtil; import com.mojang.blaze3d.platform.TextureUtil; +import com.mojang.blaze3d.systems.RenderSystem; import net.minecraft.core.BlockPos; +import grondag.canvas.CanvasMod; import grondag.canvas.render.CanvasTextureState; import grondag.canvas.varia.GFX; @@ -91,6 +93,7 @@ public static short pure(short light) { private ByteBuffer buffer; private int glTexId; + private boolean dirty = true; private boolean closed = false; public LightSectionData() { @@ -116,7 +119,21 @@ public LightSectionData() { GFX.texImage3D(Format.target, 0, Format.internalFormat, Const.WIDTH, Const.WIDTH, Const.WIDTH, 0, Format.pixelFormat, Format.pixelDataType, null); } - public void upload() { + public void markAsDirty() { + dirty = true; + } + + public void uploadIfDirty() { + RenderSystem.assertOnRenderThread(); + + if (!dirty) { + return; + } + + CanvasMod.LOG.info("Uploading texture..."); + + dirty = false; + CanvasTextureState.bindTexture(Format.target, glTexId); // Gotta clean up some states, otherwise will cause memory access violation diff --git a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java index fa9fc8dd6..470b99a0b 100644 --- a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java +++ b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java @@ -99,6 +99,7 @@ import grondag.canvas.compat.PlayerAnimatorHolder; import grondag.canvas.config.Configurator; import grondag.canvas.config.FlawlessFramesController; +import grondag.canvas.light.color.LightDebug; import grondag.canvas.material.property.TargetRenderState; import grondag.canvas.material.state.RenderContextState; import grondag.canvas.material.state.RenderState; @@ -376,6 +377,8 @@ public void renderWorld(PoseStack viewMatrixStack, float tickDelta, long frameSt Lighting.setupLevel(MatrixData.viewMatrix); } + LightDebug.debugData.uploadIfDirty(); + WorldRenderDraws.profileSwap(profiler, ProfilerGroup.StartWorld, "before_entities_event"); // Because we are passing identity stack to entity renders we need to From 6703cb72c8a792728c89f359b02656e2bd175337 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sun, 4 Jun 2023 13:53:57 +0700 Subject: [PATCH 10/69] Mask removals based on 0 bits --- .../canvas/light/color/LightChunkTask.java | 51 ++++++++++++------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightChunkTask.java b/src/main/java/grondag/canvas/light/color/LightChunkTask.java index 0d98462a6..de0e2c109 100644 --- a/src/main/java/grondag/canvas/light/color/LightChunkTask.java +++ b/src/main/java/grondag/canvas/light/color/LightChunkTask.java @@ -2,8 +2,6 @@ import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; -import com.mojang.blaze3d.systems.RenderSystem; - import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.state.BlockState; @@ -11,17 +9,20 @@ import grondag.canvas.light.color.LightSectionData.Elem; import grondag.canvas.light.color.LightSectionData.Encoding; +// TODO: edge chunks +// TODO: occlusion shapes +// TODO: re-propagate light sources upon removal public class LightChunkTask { private static class BVec { boolean r, g, b; - public BVec() { + BVec() { this.r = false; this.g = false; this.b = false; } - public boolean get(int i) { + boolean get(int i) { return switch (i) { case 0 -> r; case 1 -> g; @@ -30,25 +31,31 @@ public boolean get(int i) { }; } - public boolean any() { + boolean any() { return r || g || b; } - public boolean all() { + boolean all() { return r && g && b; } - private void lessThan(short left, short right) { + void lessThan(short left, short right) { r = Elem.R.of(left) < Elem.R.of(right); g = Elem.G.of(left) < Elem.G.of(right); b = Elem.B.of(left) < Elem.B.of(right); } - private void lessThanMinusOne(short left, short right) { + void lessThanMinusOne(short left, short right) { r = Elem.R.of(left) < Elem.R.of(right) - 1; g = Elem.G.of(left) < Elem.G.of(right) - 1; b = Elem.B.of(left) < Elem.B.of(right) - 1; } + + void and(BVec other, BVec another) { + r = other.r && another.r; + g = other.g && another.g; + b = other.b && another.b; + } } private static enum Direction { @@ -133,7 +140,10 @@ public void propagateLight() { CanvasMod.LOG.info("Processing queues.. inc,dec " + incQueue.size() + "," + decQueue.size()); - int[] pos = new int[3]; + final int[] pos = new int[3]; + final BVec removeFlag = new BVec(); + final BVec removeMask = new BVec(); + int debugMaxDec = 0; int debugMaxInc = 0; @@ -145,6 +155,10 @@ public void propagateLight() { final short sourcePrevLight = Queues.light(entry); final int from = Queues.from(entry); + // only remove elements that are less than 1 (zero) + final short sourceCurrentLight = LightDebug.debugData.get(index); + removeFlag.lessThan(sourceCurrentLight, (short) 0x1110); + LightDebug.debugData.reverseIndexify(index, pos); for (var d: Direction.values()) { @@ -167,25 +181,25 @@ public void propagateLight() { continue; } - // this is problematic, might result in residual light unless we know the light source color - // if (isLightSource(nodeLight)) { - // continue; - // } - less.lessThan(nodeLight, sourcePrevLight); - if (less.any()) { + // only propagate removal according to removeFlag + removeMask.and(less, removeFlag); + + // TODO: improve for different occlusion sides + // removal propagation can also be occluded + if (!Encoding.isOccluding(nodeLight) && removeMask.any()) { int mask = 0; - if (less.r) { + if (removeMask.r) { mask |= Elem.R.mask; } - if (less.g) { + if (removeMask.g) { mask |= Elem.G.mask; } - if (less.b) { + if (removeMask.b) { mask |= Elem.B.mask; } @@ -238,6 +252,7 @@ public void propagateLight() { final int nodeIndex = LightDebug.debugData.indexify(nodeX, nodeY, nodeZ); final short nodeLight = LightDebug.debugData.get(nodeIndex); + // TODO: improve for different occlusion sides if (Encoding.isOccluding(nodeLight)) { continue; } From 73096f712c5d55e666414f9e44c0714fd69aa949 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sun, 4 Jun 2023 17:14:51 +0700 Subject: [PATCH 11/69] Do per-face occlusion --- .../canvas/light/color/LightChunkTask.java | 119 ++++++++++++------ .../canvas/light/color/LightSectionData.java | 10 +- .../canvas/terrain/region/RenderRegion.java | 2 +- 3 files changed, 85 insertions(+), 46 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightChunkTask.java b/src/main/java/grondag/canvas/light/color/LightChunkTask.java index de0e2c109..8e395f4ae 100644 --- a/src/main/java/grondag/canvas/light/color/LightChunkTask.java +++ b/src/main/java/grondag/canvas/light/color/LightChunkTask.java @@ -3,7 +3,10 @@ import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.Shapes; import grondag.canvas.CanvasMod; import grondag.canvas.light.color.LightSectionData.Elem; @@ -58,33 +61,44 @@ void and(BVec other, BVec another) { } } - private static enum Direction { - DOWN(0, -1, 0, 1, 2), - UP(0, 1, 0, 2, 1), - NORTH(0, 0, -1, 3, 4), - SOUTH(0, 0, 1, 4, 3), - WEST(-1, 0, 0, 5, 6), - EAST(1, 0, 0, 6, 5); - - final int x, y, z, id, opposite; + private static enum Side { + DOWN(0, -1, 0, 1, Direction.DOWN), + UP(0, 1, 0, 2, Direction.UP), + NORTH(0, 0, -1, 3, Direction.NORTH), + SOUTH(0, 0, 1, 4, Direction.SOUTH), + WEST(-1, 0, 0, 5, Direction.WEST), + EAST(1, 0, 0, 6, Direction.EAST); + + final int x, y, z, id; + final Direction vanilla; + Side opposite; final static int nullId = 0; - Direction(int x, int y, int z, int id, int opposite) { + static { + DOWN.opposite = UP; + UP.opposite = DOWN; + NORTH.opposite = SOUTH; + SOUTH.opposite = NORTH; + WEST.opposite = EAST; + EAST.opposite = WEST; + } + + Side(int x, int y, int z, int id, Direction vanilla) { this.x = x; this.y = y; this.z = z; this.id = id; - this.opposite = opposite; + this.vanilla = vanilla; } } private static class Queues { static void enqueue(LongArrayFIFOQueue queue, long index, long light) { - queue.enqueue(((long) Direction.nullId << 48L) | (index << 16L) | light & 0xffffL); + queue.enqueue(((long) Side.nullId << 48L) | (index << 16L) | light & 0xffffL); } - static void enqueue(LongArrayFIFOQueue queue, long index, long light, Direction target) { - queue.enqueue(((long) target.opposite << 48L) | (index << 16L) | light & 0xffffL); + static void enqueue(LongArrayFIFOQueue queue, long index, long light, Side target) { + queue.enqueue(((long) target.opposite.id << 48L) | (index << 16L) | light & 0xffffL); } static int from(long entry) { @@ -101,6 +115,8 @@ static short light(long entry) { } private final BVec less = new BVec(); + private final BlockPos.MutableBlockPos sourcePos = new BlockPos.MutableBlockPos(); + private final BlockPos.MutableBlockPos nodePos = new BlockPos.MutableBlockPos(); private static final LongArrayFIFOQueue incQueue = new LongArrayFIFOQueue(); private static final LongArrayFIFOQueue decQueue = new LongArrayFIFOQueue(); @@ -132,7 +148,15 @@ public void checkBlock(BlockPos pos, BlockState blockState) { } } - public void propagateLight() { + private boolean occludeSide(BlockState state, Side dir, BlockAndTintGetter view, BlockPos pos) { + if (!state.canOcclude()) { + return false; + } + + return Shapes.faceShapeOccludes(Shapes.empty(), state.getFaceOcclusionShape(view, pos, dir.vanilla)); + } + + public void propagateLight(BlockAndTintGetter blockView) { if (incQueue.isEmpty() && decQueue.isEmpty()) { CanvasMod.LOG.info("Nothing to process!"); return; @@ -140,7 +164,6 @@ public void propagateLight() { CanvasMod.LOG.info("Processing queues.. inc,dec " + incQueue.size() + "," + decQueue.size()); - final int[] pos = new int[3]; final BVec removeFlag = new BVec(); final BVec removeMask = new BVec(); @@ -159,36 +182,47 @@ public void propagateLight() { final short sourceCurrentLight = LightDebug.debugData.get(index); removeFlag.lessThan(sourceCurrentLight, (short) 0x1110); - LightDebug.debugData.reverseIndexify(index, pos); + LightDebug.debugData.reverseIndexify(index, sourcePos); + + final BlockState sourceState = blockView.getBlockState(sourcePos); - for (var d: Direction.values()) { - if (d.id == from) { + for (var side:Side.values()) { + if (side.id == from) { continue; } - final int nodeX = pos[0] + d.x; - final int nodeY = pos[1] + d.y; - final int nodeZ = pos[2] + d.z; + // check self occlusion for decrease + if (!Encoding.isOccluding(sourceCurrentLight) && occludeSide(sourceState, side, blockView, sourcePos)) { + continue; + } - if (!LightDebug.debugData.withinExtents(nodeX, nodeY, nodeZ)) { + nodePos.setWithOffset(sourcePos, side.x, side.y, side.z); + + // TODO: remove + if (!LightDebug.debugData.withinExtents(nodePos)) { continue; } - final int nodeIndex = LightDebug.debugData.indexify(nodeX, nodeY, nodeZ); + final int nodeIndex = LightDebug.debugData.indexify(nodePos); short nodeLight = LightDebug.debugData.get(nodeIndex); if (Encoding.pure(nodeLight) == 0) { continue; } + final BlockState nodeState = blockView.getBlockState(nodePos); + + // check neighbor occlusion for decrease + if (occludeSide(nodeState, side.opposite, blockView, nodePos)) { + continue; + } + less.lessThan(nodeLight, sourcePrevLight); // only propagate removal according to removeFlag removeMask.and(less, removeFlag); - // TODO: improve for different occlusion sides - // removal propagation can also be occluded - if (!Encoding.isOccluding(nodeLight) && removeMask.any()) { + if (removeMask.any()) { int mask = 0; if (removeMask.r) { @@ -206,7 +240,7 @@ public void propagateLight() { final short resultLight = (short) (nodeLight & ~(mask)); LightDebug.debugData.put(nodeIndex, resultLight); - Queues.enqueue(decQueue, nodeIndex, nodeLight, d); + Queues.enqueue(decQueue, nodeIndex, nodeLight, side); nodeLight = resultLight; } @@ -232,28 +266,35 @@ public void propagateLight() { continue; } - LightDebug.debugData.reverseIndexify(index, pos); + LightDebug.debugData.reverseIndexify(index, sourcePos); + + final BlockState sourceState = blockView.getBlockState(sourcePos); + + for (var side:Side.values()) { + if (side.id == from) { + continue; + } - for (var d: Direction.values()) { - if (d.id == from) { + // check self occlusion for increase + if (!Encoding.isLightSource(sourceLight) && occludeSide(sourceState, side, blockView, sourcePos)) { continue; } - final int nodeX = pos[0] + d.x; - final int nodeY = pos[1] + d.y; - final int nodeZ = pos[2] + d.z; + nodePos.setWithOffset(sourcePos, side.x, side.y, side.z); // CanvasMod.LOG.info("increase at " + nodeX + "," + nodeY + "," + nodeZ); - if (!LightDebug.debugData.withinExtents(nodeX, nodeY, nodeZ)) { + // TODO: remove + if (!LightDebug.debugData.withinExtents(nodePos)) { continue; } - final int nodeIndex = LightDebug.debugData.indexify(nodeX, nodeY, nodeZ); + final int nodeIndex = LightDebug.debugData.indexify(nodePos); final short nodeLight = LightDebug.debugData.get(nodeIndex); + final BlockState nodeState = blockView.getBlockState(nodePos); - // TODO: improve for different occlusion sides - if (Encoding.isOccluding(nodeLight)) { + // check neighbor occlusion for increase + if (occludeSide(nodeState, side.opposite, blockView, nodePos)) { continue; } @@ -280,7 +321,7 @@ public void propagateLight() { // CanvasMod.LOG.info("updating neighbor to: " + nodeX + "," + nodeY + "," + nodeZ + "," + Elem.text(resultLight)); - Queues.enqueue(incQueue, nodeIndex, resultLight, d); + Queues.enqueue(incQueue, nodeIndex, resultLight, side); } } } diff --git a/src/main/java/grondag/canvas/light/color/LightSectionData.java b/src/main/java/grondag/canvas/light/color/LightSectionData.java index 85b16e0f4..470cfd6bf 100644 --- a/src/main/java/grondag/canvas/light/color/LightSectionData.java +++ b/src/main/java/grondag/canvas/light/color/LightSectionData.java @@ -180,15 +180,13 @@ public int indexify(int x, int y, int z) { return ((localZ << (Const.WIDTH_SHIFT * 2)) | (localY << Const.WIDTH_SHIFT) | localX) * Format.pixelBytes; } - public void reverseIndexify(int index, int[] result) { - assert result.length == 3; - + public void reverseIndexify(int index, BlockPos.MutableBlockPos result) { index = index / Format.pixelBytes; // x and z are swapped because opengl - result[0] = (index & Const.WIDTH_MASK) + sectionBlockOffsetX; - result[1] = ((index >> Const.WIDTH_SHIFT) & Const.WIDTH_MASK) + sectionBlockOffsetY; - result[2] = ((index >> Const.WIDTH_SHIFT * 2) & Const.WIDTH_MASK) + sectionBlockOffsetZ; + result.setX((index & Const.WIDTH_MASK) + sectionBlockOffsetX); + result.setY(((index >> Const.WIDTH_SHIFT) & Const.WIDTH_MASK) + sectionBlockOffsetY); + result.setZ(((index >> Const.WIDTH_SHIFT * 2) & Const.WIDTH_MASK) + sectionBlockOffsetZ); } public boolean withinExtents(BlockPos pos) { diff --git a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java index a8ce76dd5..ca751df95 100644 --- a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java +++ b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java @@ -473,7 +473,7 @@ private void buildTerrain(CanvasTerrainRenderContext context, RegionBuildState b } if (lightChunkTask != null) { - lightChunkTask.propagateLight(); + lightChunkTask.propagateLight(region); } buildState.prepareTranslucentIfNeeded(worldRenderState.sectorManager.cameraPos(), renderSector, collectors); From 3853ba64f794c3522a4696111746d7113843f393 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sun, 4 Jun 2023 17:47:17 +0700 Subject: [PATCH 12/69] Restore decimated light source --- .../canvas/light/color/LightChunkTask.java | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightChunkTask.java b/src/main/java/grondag/canvas/light/color/LightChunkTask.java index 8e395f4ae..9e9413d4b 100644 --- a/src/main/java/grondag/canvas/light/color/LightChunkTask.java +++ b/src/main/java/grondag/canvas/light/color/LightChunkTask.java @@ -149,6 +149,8 @@ public void checkBlock(BlockPos pos, BlockState blockState) { } private boolean occludeSide(BlockState state, Side dir, BlockAndTintGetter view, BlockPos pos) { + // vanilla checks state.useShapeForLightOcclusion() but here it's always false for some reason. this is fine... + if (!state.canOcclude()) { return false; } @@ -198,7 +200,7 @@ public void propagateLight(BlockAndTintGetter blockView) { nodePos.setWithOffset(sourcePos, side.x, side.y, side.z); - // TODO: remove + // TODO: change to chunk extents + edge checks if (!LightDebug.debugData.withinExtents(nodePos)) { continue; } @@ -222,6 +224,8 @@ public void propagateLight(BlockAndTintGetter blockView) { // only propagate removal according to removeFlag removeMask.and(less, removeFlag); + boolean restoreLightSource = removeMask.any() && Encoding.isLightSource(nodeLight); + if (removeMask.any()) { int mask = 0; @@ -243,9 +247,15 @@ public void propagateLight(BlockAndTintGetter blockView) { Queues.enqueue(decQueue, nodeIndex, nodeLight, side); nodeLight = resultLight; + + // restore obliterated light source + if (restoreLightSource) { + short registeredLight = LightRegistry.get(nodeState); + nodeLight |= (registeredLight & mask); + } } - if (!less.all()) { + if (!less.all() || restoreLightSource) { // increases queued in decrease may propagate to all directions as if a light source Queues.enqueue(incQueue, nodeIndex, nodeLight); } @@ -260,10 +270,15 @@ public void propagateLight(BlockAndTintGetter blockView) { final short recordedLight = Queues.light(entry); final int from = Queues.from(entry); - final short sourceLight = LightDebug.debugData.get(index); + short sourceLight = LightDebug.debugData.get(index); - if (Encoding.pure(sourceLight) != Encoding.pure(recordedLight)) { - continue; + if (sourceLight != recordedLight) { + if (Encoding.isLightSource(recordedLight)) { + sourceLight = recordedLight; + LightDebug.debugData.put(index, sourceLight); + } else { + continue; + } } LightDebug.debugData.reverseIndexify(index, sourcePos); @@ -284,7 +299,7 @@ public void propagateLight(BlockAndTintGetter blockView) { // CanvasMod.LOG.info("increase at " + nodeX + "," + nodeY + "," + nodeZ); - // TODO: remove + // TODO: change to chunk extents + edge checks if (!LightDebug.debugData.withinExtents(nodePos)) { continue; } From f2e35030d97cb155951c3391b5a956cae55accbb Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sun, 4 Jun 2023 23:25:22 +0700 Subject: [PATCH 13/69] Try expand static area with dummy allocator --- .../grondag/canvas/apiimpl/CanvasState.java | 4 +- .../canvas/light/color/LightDataManager.java | 144 ++++++++++++ .../canvas/light/color/LightDataTexture.java | 94 ++++++++ .../canvas/light/color/LightDebug.java | 43 ---- .../canvas/light/color/LightRegionData.java | 167 ++++++++++++++ ...ghtChunkTask.java => LightRegionTask.java} | 67 +++--- .../canvas/light/color/LightRegistry.java | 4 +- .../canvas/light/color/LightSectionData.java | 213 ------------------ .../canvas/pipeline/ProgramTextureData.java | 8 +- .../render/world/CanvasWorldRenderer.java | 4 +- .../canvas/terrain/region/RenderRegion.java | 20 +- 11 files changed, 461 insertions(+), 307 deletions(-) create mode 100644 src/main/java/grondag/canvas/light/color/LightDataManager.java create mode 100644 src/main/java/grondag/canvas/light/color/LightDataTexture.java delete mode 100644 src/main/java/grondag/canvas/light/color/LightDebug.java create mode 100644 src/main/java/grondag/canvas/light/color/LightRegionData.java rename src/main/java/grondag/canvas/light/color/{LightChunkTask.java => LightRegionTask.java} (80%) delete mode 100644 src/main/java/grondag/canvas/light/color/LightSectionData.java diff --git a/src/main/java/grondag/canvas/apiimpl/CanvasState.java b/src/main/java/grondag/canvas/apiimpl/CanvasState.java index 02da93fcb..06e29f0c9 100644 --- a/src/main/java/grondag/canvas/apiimpl/CanvasState.java +++ b/src/main/java/grondag/canvas/apiimpl/CanvasState.java @@ -30,7 +30,7 @@ import grondag.canvas.apiimpl.rendercontext.CanvasEntityBlockRenderContext; import grondag.canvas.apiimpl.rendercontext.CanvasItemRenderContext; import grondag.canvas.config.Configurator; -import grondag.canvas.light.color.LightDebug; +import grondag.canvas.light.color.LightDataManager; import grondag.canvas.material.property.TextureMaterialState; import grondag.canvas.perf.ChunkRebuildCounters; import grondag.canvas.perf.Timekeeper; @@ -53,7 +53,7 @@ public static void recompileIfNeeded(boolean forceRecompile) { if (forceRecompile) { CanvasMod.LOG.info(I18n.get("info.canvas.recompile")); - LightDebug.initialize(); + LightDataManager.initialize(); PipelineLoader.reload(Minecraft.getInstance().getResourceManager()); PipelineManager.reload(); PreReleaseShaderCompat.reload(); diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java new file mode 100644 index 000000000..7f729a840 --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -0,0 +1,144 @@ +package grondag.canvas.light.color; + +import it.unimi.dsi.fastutil.longs.Long2ObjectMap; +import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + +import net.minecraft.core.BlockPos; + +import grondag.canvas.CanvasMod; + +public class LightDataManager { + private static final int REGION_COUNT_LENGTH_WISE = 16; + // private static final int INITIAL_LIMIT = REGION_COUNT_LENGTH_WISE * REGION_COUNT_LENGTH_WISE * REGION_COUNT_LENGTH_WISE; + public static final LightDataManager INSTANCE = new LightDataManager(); + + private final Long2ObjectMap allocated = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); + // initial size based on 8 chunk render distance + + // placeholder + private int originBlockX = -8 * 16; + private int originBlockY = 64; + private int originBlockZ = -8 * 16; + + private int size = REGION_COUNT_LENGTH_WISE; + private LightDataTexture texture; + + { + allocated.defaultReturnValue(null); + } + + // TODO: stuff + public static void initialize() { + + } + + public void update() { + if (texture == null) { + initializeTexture(); + } + + // if (texture.size < supposedTextureSize()) { + // expandTexture(); + // } + + allocated.forEach((key, data) -> { + if (data.isDirty()) { + texture.upload( + data.regionOriginBlockX - originBlockX, + data.regionOriginBlockY - originBlockY, + data.regionOriginBlockZ - originBlockZ, + data.getBuffer()); + data.clearDirty(); + } + }); + } + + public LightRegionData getOrAllocate(BlockPos regionOrigin) { + LightRegionData data = allocated.get(regionOrigin.asLong()); + + if (data == null) { + return allocate(regionOrigin); + } + + return data; + } + + public boolean withinExtents(BlockPos pos) { + return withinExtents(pos.getX(), pos.getY(), pos.getZ()); + } + + public boolean withinExtents(int x, int y, int z) { + return (x >= originBlockX && x < originBlockX + sizeInBlocks()) + && (y >= originBlockY && y < originBlockY + sizeInBlocks()) + && (z >= originBlockZ && z < originBlockZ + sizeInBlocks()); + } + + // public boolean isAllocated(BlockPos regionOrigin) { + // return allocated.containsKey(regionOrigin.asLong()); + // } + + private LightRegionData allocate(BlockPos regionOrigin) { + // if (allocated.size() == size - 1) { + // requestExpand(); + // } + + CanvasMod.LOG.info("allocating for " + regionOrigin); + + final LightRegionData data = new LightRegionData(regionOrigin.getX(), regionOrigin.getY(), regionOrigin.getZ()); + allocated.put(regionOrigin.asLong(), data); + + return data; + } + + // private void requestExpand() { + // // I'm scared + // final int newLimit = size * 2; + // size = newLimit; + // + // CanvasMod.LOG.info(String.format("Reallocating light data texture from %d to %d! This is huge!", size, newLimit)); + // } + + private int sizeInBlocks() { + return size * LightRegionData.Const.WIDTH; + } + + private void initializeTexture() { + texture = new LightDataTexture(sizeInBlocks()); + } + + public void close() { + texture.close(); + + for (var data:allocated.values()) { + data.close(); + } + + allocated.clear(); + } + + public int getTexture(String imageName) { + if (texture == null) { + initializeTexture(); + } + + if (imageName.equals("_cv_debug_light_data")) { + return texture.getTexId(); + } + + return -1; + } + + // private void expandTexture() { + // if (texture != null && texture.size == supposedTextureSize()) { + // return; + // } + // + // if (texture != null) { + // texture.close(); + // texture = null; + // } + // + // initializeTexture(); + // } +} diff --git a/src/main/java/grondag/canvas/light/color/LightDataTexture.java b/src/main/java/grondag/canvas/light/color/LightDataTexture.java new file mode 100644 index 000000000..14265dcfd --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/LightDataTexture.java @@ -0,0 +1,94 @@ +package grondag.canvas.light.color; + +import java.nio.ByteBuffer; + +import org.lwjgl.system.MemoryUtil; + +import com.mojang.blaze3d.platform.TextureUtil; +import com.mojang.blaze3d.systems.RenderSystem; + +import grondag.canvas.CanvasMod; +import grondag.canvas.render.CanvasTextureState; +import grondag.canvas.varia.GFX; + +public class LightDataTexture { + + public static class Format { + public static int target = GFX.GL_TEXTURE_3D; + public static int pixelBytes = 2; + public static int internalFormat = GFX.GL_RGBA4; + public static int pixelFormat = GFX.GL_RGBA; + public static int pixelDataType = GFX.GL_UNSIGNED_SHORT_4_4_4_4; + } + + private int glTexId = -1; + final int size; + + LightDataTexture(int size) { + this.size = size; + + glTexId = TextureUtil.generateTextureId(); + + CanvasTextureState.bindTexture(Format.target, glTexId); + GFX.objectLabel(GFX.GL_TEXTURE, glTexId, "IMG light_section_volume"); + + GFX.texParameter(Format.target, GFX.GL_TEXTURE_MIN_FILTER, GFX.GL_LINEAR); + GFX.texParameter(Format.target, GFX.GL_TEXTURE_MAG_FILTER, GFX.GL_LINEAR); + GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_S, GFX.GL_CLAMP_TO_EDGE); + GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_T, GFX.GL_CLAMP_TO_EDGE); + GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_R, GFX.GL_CLAMP_TO_EDGE); + + // allocate + GFX.texImage3D(Format.target, 0, Format.internalFormat, size, size, size, 0, Format.pixelFormat, Format.pixelDataType, null); + + ByteBuffer clearer = MemoryUtil.memAlloc(size * size * size * Format.pixelBytes); + + while (clearer.position() < clearer.limit()) { + clearer.putShort((short) 0); + } + + // clear?? + upload(0, 0, 0, clearer); + + clearer.position(0); + MemoryUtil.memFree(clearer); + } + + public void close() { + TextureUtil.releaseTextureId(glTexId); + glTexId = -1; + } + + public void upload(int x, int y, int z, ByteBuffer buffer) { + upload(x, y, z, LightRegionData.Const.WIDTH, buffer); + } + + public void upload(int x, int y, int z, int regionSize, ByteBuffer buffer) { + if (glTexId == -1) { + throw new IllegalStateException("Uploading to a deleted texture!"); + } + + RenderSystem.assertOnRenderThread(); + + CanvasTextureState.bindTexture(LightDataTexture.Format.target, glTexId); + + // Gotta clean up some states, otherwise will cause memory access violation + GFX.pixelStore(GFX.GL_UNPACK_SKIP_PIXELS, 0); + GFX.pixelStore(GFX.GL_UNPACK_SKIP_ROWS, 0); + GFX.pixelStore(GFX.GL_UNPACK_ROW_LENGTH, 0); + GFX.pixelStore(GFX.GL_UNPACK_ALIGNMENT, 2); + + // Importantly, reset the pointer without flip + buffer.position(0); + + GFX.glTexSubImage3D(Format.target, 0, x, y, z, regionSize, regionSize, regionSize, Format.pixelFormat, Format.pixelDataType, buffer); + } + + public int getTexId() { + if (glTexId == -1) { + throw new IllegalStateException("Trying to access a deleted Light Data texture!"); + } + + return glTexId; + } +} diff --git a/src/main/java/grondag/canvas/light/color/LightDebug.java b/src/main/java/grondag/canvas/light/color/LightDebug.java deleted file mode 100644 index 71f7768af..000000000 --- a/src/main/java/grondag/canvas/light/color/LightDebug.java +++ /dev/null @@ -1,43 +0,0 @@ -package grondag.canvas.light.color; - -import static grondag.canvas.light.color.LightSectionData.Const.WIDTH; - -import com.mojang.blaze3d.systems.RenderSystem; - -import grondag.canvas.CanvasMod; - -public class LightDebug { - public static LightSectionData debugData; - - public static void initialize() { - RenderSystem.assertOnRenderThread(); - - if (debugData != null) { - return; - } - - debugData = new LightSectionData(); - // drawDummy(debugData); - // debugData.upload(); - - CanvasMod.LOG.info("Light debug render initialized."); - } - - public static int getTexture(String imageName) { - if (imageName.equals("_cv_debug_light_data") && debugData != null && !debugData.isClosed()) { - return debugData.getTexId(); - } - - return -1; - } - - static void drawDummy(LightSectionData data) { - for (int x = 0; x < WIDTH; x ++) { - for (int y = 0; y < WIDTH; y ++) { - for (int z = 0; z < WIDTH; z ++) { - data.draw(LightSectionData.Encoding.encodeLight(x % 16, y % 16, z % 16, false, false)); - } - } - } - } -} diff --git a/src/main/java/grondag/canvas/light/color/LightRegionData.java b/src/main/java/grondag/canvas/light/color/LightRegionData.java new file mode 100644 index 000000000..a96f7bc2f --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/LightRegionData.java @@ -0,0 +1,167 @@ +package grondag.canvas.light.color; + +import java.nio.ByteBuffer; + +import org.lwjgl.system.MemoryUtil; + +import net.minecraft.core.BlockPos; + +public class LightRegionData { + + public static class Const { + public static final int WIDTH = 16; + private static final int SIZE3D = WIDTH * WIDTH * WIDTH; + + public static final int WIDTH_SHIFT = (int) (Math.log(WIDTH) / Math.log(2)); + public static final int WIDTH_MASK = WIDTH - 1; + } + + public static enum Elem { + R(0xF000, 12, 0), + G(0x0F00, 8, 1), + B(0x00F0, 4, 2), + A(0x000F, 0, 3); + + public final int mask; + public final int shift; + public final int pos; + + Elem(int mask, int shift, int pos) { + this.mask = mask; + this.shift = shift; + this.pos = pos; + } + + public int of(short light) { + return (light >> shift) & 0xF; + } + + public short replace(short source, short elemLight) { + return (short) ((source & ~mask) | (elemLight << shift)); + } + + public static short encode(int r, int g, int b, int a) { + return (short) ((r << R.shift) | (g << G.shift) | (b << B.shift) | (a << A.shift)); + } + + public static String text(short light) { + return "(" + R.of(light) + "," + G.of(light) + "," + B.of(light) + ")"; + } + } + + public static class Encoding { + public static short encodeLight(int r, int g, int b, boolean isLightSource, boolean isOccluding) { + return Elem.encode(r, g, b, (isLightSource ? 0b1 : 0) | (isOccluding ? 0b10 : 0)); + } + + public static short encodeLight(int pureLight, boolean isLightSource, boolean isOccluding) { + return (short) (pureLight | (isLightSource ? 0b1 : 0) | (isOccluding ? 0b10 : 0)); + } + + public static boolean isLightSource(short light) { + return (light & 0b1) != 0; + } + + public static boolean isOccluding(short light) { + return (light & 0b10) != 0; + } + + public static short pure(short light) { + return (short) (light & 0xfff0); + } + } + + final int regionOriginBlockX; + final int regionOriginBlockY; + final int regionOriginBlockZ; + private final ByteBuffer buffer; + private boolean dirty = true; + private boolean closed = false; + + LightRegionData(int regionOriginBlockX, int regionOriginBlockY, int regionOriginBlockZ) { + this.regionOriginBlockX = regionOriginBlockX; + this.regionOriginBlockY = regionOriginBlockY; + this.regionOriginBlockZ = regionOriginBlockZ; + + buffer = MemoryUtil.memAlloc(LightDataTexture.Format.pixelBytes * Const.SIZE3D); + + // clear manually ? + while (buffer.position() < LightDataTexture.Format.pixelBytes * Const.SIZE3D) { + buffer.putShort((short) 0); + } + } + + public void markAsDirty() { + dirty = true; + } + + public void clearDirty() { + dirty = false; + } + + public void draw(short rgba) { + buffer.putShort(rgba); + } + + public void close() { + buffer.position(0); + MemoryUtil.memFree(buffer); + closed = true; + } + + public short get(int index) { + return buffer.getShort(index); + } + + public void put(int index, short light) { + buffer.putShort(index, light); + } + + public int indexify(BlockPos pos) { + return indexify(pos.getX(), pos.getY(), pos.getZ()); + } + + public int indexify(int x, int y, int z) { + final int localX = x - regionOriginBlockX; + final int localY = y - regionOriginBlockY; + final int localZ = z - regionOriginBlockZ; + + // x and z are swapped because opengl + return ((localZ << (Const.WIDTH_SHIFT * 2)) | (localY << Const.WIDTH_SHIFT) | localX) * LightDataTexture.Format.pixelBytes; + } + + public void reverseIndexify(int index, BlockPos.MutableBlockPos result) { + index = index / LightDataTexture.Format.pixelBytes; + + // x and z are swapped because opengl + result.setX((index & Const.WIDTH_MASK) + regionOriginBlockX); + result.setY(((index >> Const.WIDTH_SHIFT) & Const.WIDTH_MASK) + regionOriginBlockY); + result.setZ(((index >> Const.WIDTH_SHIFT * 2) & Const.WIDTH_MASK) + regionOriginBlockZ); + } + + public boolean withinExtents(BlockPos pos) { + return withinExtents(pos.getX(), pos.getY(), pos.getZ()); + } + + public boolean withinExtents(int x, int y, int z) { + return (x >= regionOriginBlockX && x < regionOriginBlockX + Const.WIDTH) + && (y >= regionOriginBlockY && y < regionOriginBlockY + Const.WIDTH) + && (z >= regionOriginBlockZ && z < regionOriginBlockZ + Const.WIDTH); + } + + ByteBuffer getBuffer() { + if (closed) { + throw new IllegalStateException("Attempting to access a closed light region buffer!"); + } + + return buffer; + } + + public boolean isDirty() { + return dirty; + } + + public boolean isClosed() { + return closed; + } +} diff --git a/src/main/java/grondag/canvas/light/color/LightChunkTask.java b/src/main/java/grondag/canvas/light/color/LightRegionTask.java similarity index 80% rename from src/main/java/grondag/canvas/light/color/LightChunkTask.java rename to src/main/java/grondag/canvas/light/color/LightRegionTask.java index 9e9413d4b..a7240e357 100644 --- a/src/main/java/grondag/canvas/light/color/LightChunkTask.java +++ b/src/main/java/grondag/canvas/light/color/LightRegionTask.java @@ -9,13 +9,13 @@ import net.minecraft.world.phys.shapes.Shapes; import grondag.canvas.CanvasMod; -import grondag.canvas.light.color.LightSectionData.Elem; -import grondag.canvas.light.color.LightSectionData.Encoding; +import grondag.canvas.light.color.LightRegionData.Elem; +import grondag.canvas.light.color.LightRegionData.Encoding; // TODO: edge chunks -// TODO: occlusion shapes -// TODO: re-propagate light sources upon removal -public class LightChunkTask { +// TODO: cluster slab allocation? +// TODO: a way to repopulate cluster if needed +public class LightRegionTask { private static class BVec { boolean r, g, b; @@ -114,14 +114,19 @@ static short light(long entry) { } } + private LightRegionData lightRegionData; private final BVec less = new BVec(); private final BlockPos.MutableBlockPos sourcePos = new BlockPos.MutableBlockPos(); private final BlockPos.MutableBlockPos nodePos = new BlockPos.MutableBlockPos(); - private static final LongArrayFIFOQueue incQueue = new LongArrayFIFOQueue(); - private static final LongArrayFIFOQueue decQueue = new LongArrayFIFOQueue(); + private final LongArrayFIFOQueue incQueue = new LongArrayFIFOQueue(); + private final LongArrayFIFOQueue decQueue = new LongArrayFIFOQueue(); + + public LightRegionTask(LightRegionData lightRegionData) { + this.lightRegionData = lightRegionData;; + } public void checkBlock(BlockPos pos, BlockState blockState) { - if (!LightDebug.debugData.withinExtents(pos)) { + if (!lightRegionData.withinExtents(pos)) { return; } @@ -131,20 +136,20 @@ public void checkBlock(BlockPos pos, BlockState blockState) { light = LightRegistry.get(blockState); } - final int index = LightDebug.debugData.indexify(pos); - final short getLight = LightDebug.debugData.get(index); + final int index = lightRegionData.indexify(pos); + final short getLight = lightRegionData.get(index); final boolean occluding = blockState.canOcclude(); less.lessThan(getLight, light); if (less.any()) { - LightDebug.debugData.put(index, light); + lightRegionData.put(index, light); Queues.enqueue(incQueue, index, light); - CanvasMod.LOG.info("Add light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + blockState); + // CanvasMod.LOG.info("Add light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + blockState); } else if (light == 0 && (Encoding.isLightSource(getLight) || (Encoding.isOccluding(getLight) != occluding))) { - LightDebug.debugData.put(index, Encoding.encodeLight(0, false, occluding)); + lightRegionData.put(index, Encoding.encodeLight(0, false, occluding)); Queues.enqueue(decQueue, index, getLight); - CanvasMod.LOG.info("Remove light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + blockState); + // CanvasMod.LOG.info("Remove light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + blockState); } } @@ -160,11 +165,11 @@ private boolean occludeSide(BlockState state, Side dir, BlockAndTintGetter view, public void propagateLight(BlockAndTintGetter blockView) { if (incQueue.isEmpty() && decQueue.isEmpty()) { - CanvasMod.LOG.info("Nothing to process!"); + // CanvasMod.LOG.info("Nothing to process!"); return; } - CanvasMod.LOG.info("Processing queues.. inc,dec " + incQueue.size() + "," + decQueue.size()); + // CanvasMod.LOG.info("Processing queues.. inc,dec " + incQueue.size() + "," + decQueue.size()); final BVec removeFlag = new BVec(); final BVec removeMask = new BVec(); @@ -181,10 +186,10 @@ public void propagateLight(BlockAndTintGetter blockView) { final int from = Queues.from(entry); // only remove elements that are less than 1 (zero) - final short sourceCurrentLight = LightDebug.debugData.get(index); + final short sourceCurrentLight = lightRegionData.get(index); removeFlag.lessThan(sourceCurrentLight, (short) 0x1110); - LightDebug.debugData.reverseIndexify(index, sourcePos); + lightRegionData.reverseIndexify(index, sourcePos); final BlockState sourceState = blockView.getBlockState(sourcePos); @@ -201,12 +206,12 @@ public void propagateLight(BlockAndTintGetter blockView) { nodePos.setWithOffset(sourcePos, side.x, side.y, side.z); // TODO: change to chunk extents + edge checks - if (!LightDebug.debugData.withinExtents(nodePos)) { + if (!lightRegionData.withinExtents(nodePos)) { continue; } - final int nodeIndex = LightDebug.debugData.indexify(nodePos); - short nodeLight = LightDebug.debugData.get(nodeIndex); + final int nodeIndex = lightRegionData.indexify(nodePos); + short nodeLight = lightRegionData.get(nodeIndex); if (Encoding.pure(nodeLight) == 0) { continue; @@ -242,7 +247,7 @@ public void propagateLight(BlockAndTintGetter blockView) { } final short resultLight = (short) (nodeLight & ~(mask)); - LightDebug.debugData.put(nodeIndex, resultLight); + lightRegionData.put(nodeIndex, resultLight); Queues.enqueue(decQueue, nodeIndex, nodeLight, side); @@ -270,18 +275,18 @@ public void propagateLight(BlockAndTintGetter blockView) { final short recordedLight = Queues.light(entry); final int from = Queues.from(entry); - short sourceLight = LightDebug.debugData.get(index); + short sourceLight = lightRegionData.get(index); if (sourceLight != recordedLight) { if (Encoding.isLightSource(recordedLight)) { sourceLight = recordedLight; - LightDebug.debugData.put(index, sourceLight); + lightRegionData.put(index, sourceLight); } else { continue; } } - LightDebug.debugData.reverseIndexify(index, sourcePos); + lightRegionData.reverseIndexify(index, sourcePos); final BlockState sourceState = blockView.getBlockState(sourcePos); @@ -300,12 +305,12 @@ public void propagateLight(BlockAndTintGetter blockView) { // CanvasMod.LOG.info("increase at " + nodeX + "," + nodeY + "," + nodeZ); // TODO: change to chunk extents + edge checks - if (!LightDebug.debugData.withinExtents(nodePos)) { + if (!lightRegionData.withinExtents(nodePos)) { continue; } - final int nodeIndex = LightDebug.debugData.indexify(nodePos); - final short nodeLight = LightDebug.debugData.get(nodeIndex); + final int nodeIndex = lightRegionData.indexify(nodePos); + final short nodeLight = lightRegionData.get(nodeIndex); final BlockState nodeState = blockView.getBlockState(nodePos); // check neighbor occlusion for increase @@ -332,7 +337,7 @@ public void propagateLight(BlockAndTintGetter blockView) { resultLight = Elem.B.replace(resultLight, (short) (Elem.B.of(sourceLight) - 1)); } - LightDebug.debugData.put(nodeIndex, resultLight); + lightRegionData.put(nodeIndex, resultLight); // CanvasMod.LOG.info("updating neighbor to: " + nodeX + "," + nodeY + "," + nodeZ + "," + Elem.text(resultLight)); @@ -342,7 +347,7 @@ public void propagateLight(BlockAndTintGetter blockView) { } CanvasMod.LOG.info("Processed queues! Count: inc,dec " + debugMaxInc + "," + debugMaxDec); - CanvasMod.LOG.info("Marking texture as dirty"); - LightDebug.debugData.markAsDirty(); + // CanvasMod.LOG.info("Marking texture as dirty"); + lightRegionData.markAsDirty(); } } diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java index 3873fb625..21f785bf0 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegistry.java +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -30,9 +30,9 @@ public static short get(BlockState blockState){ final int r = (int) (lightEmission * itemLight.red()); final int g = (int) (lightEmission * itemLight.green()); final int b = (int) (lightEmission * itemLight.blue()); - light = LightSectionData.Encoding.encodeLight(r, g, b, true, occluding); + light = LightRegionData.Encoding.encodeLight(r, g, b, true, occluding); } else { - light = LightSectionData.Encoding.encodeLight(lightEmission, lightEmission, lightEmission, true, occluding); + light = LightRegionData.Encoding.encodeLight(lightEmission, lightEmission, lightEmission, true, occluding); } lights.put(stateKey, light); diff --git a/src/main/java/grondag/canvas/light/color/LightSectionData.java b/src/main/java/grondag/canvas/light/color/LightSectionData.java deleted file mode 100644 index 470cfd6bf..000000000 --- a/src/main/java/grondag/canvas/light/color/LightSectionData.java +++ /dev/null @@ -1,213 +0,0 @@ -package grondag.canvas.light.color; - -import java.nio.ByteBuffer; - -import org.lwjgl.system.MemoryUtil; - -import com.mojang.blaze3d.platform.TextureUtil; -import com.mojang.blaze3d.systems.RenderSystem; - -import net.minecraft.core.BlockPos; - -import grondag.canvas.CanvasMod; -import grondag.canvas.render.CanvasTextureState; -import grondag.canvas.varia.GFX; - -public class LightSectionData { - public static class Format { - public static int target = GFX.GL_TEXTURE_3D; - public static int pixelBytes = 2; - public static int internalFormat = GFX.GL_RGBA4; - public static int pixelFormat = GFX.GL_RGBA; - public static int pixelDataType = GFX.GL_UNSIGNED_SHORT_4_4_4_4; - } - - public static class Const { - public static final int WIDTH = 16; - private static final int SIZE3D = WIDTH * WIDTH * WIDTH; - - public static final int WIDTH_SHIFT = (int) (Math.log(WIDTH) / Math.log(2)); - public static final int WIDTH_MASK = WIDTH - 1; - } - - public static enum Elem { - R(0xF000, 12, 0), - G(0x0F00, 8, 1), - B(0x00F0, 4, 2), - A(0x000F, 0, 3); - - public final int mask; - public final int shift; - public final int pos; - - Elem(int mask, int shift, int pos) { - this.mask = mask; - this.shift = shift; - this.pos = pos; - } - - public int of(short light) { - return (light >> shift) & 0xF; - } - - public short replace(short source, short elemLight) { - return (short) ((source & ~mask) | (elemLight << shift)); - } - - public static short encode(int r, int g, int b, int a) { - return (short) ((r << R.shift) | (g << G.shift) | (b << B.shift) | (a << A.shift)); - } - - public static String text(short light) { - return "(" + R.of(light) + "," + G.of(light) + "," + B.of(light) + ")"; - } - } - - public static class Encoding { - // TODO: change flags to occlusion / light source states (block can occlude in 1 of 6 directions, e.g. slab) - public static short encodeLight(int r, int g, int b, boolean isLightSource, boolean isOccluding) { - return Elem.encode(r, g, b, (isLightSource ? 0b1 : 0) | (isOccluding ? 0b10 : 0)); - } - - public static short encodeLight(int pureLight, boolean isLightSource, boolean isOccluding) { - return (short) (pureLight | (isLightSource ? 0b1 : 0) | (isOccluding ? 0b10 : 0)); - } - - public static boolean isLightSource(short light) { - return (light & 0b1) != 0; - } - - public static boolean isOccluding(short light) { - return (light & 0b10) != 0; - } - - public static short pure(short light) { - return (short) (light & 0xfff0); - } - } - - // placeholder - private int sectionBlockOffsetX = 0; - private int sectionBlockOffsetY = 80; - private int sectionBlockOffsetZ = 0; - - private ByteBuffer buffer; - private int glTexId; - private boolean dirty = true; - private boolean closed = false; - - public LightSectionData() { - glTexId = TextureUtil.generateTextureId(); - - CanvasTextureState.bindTexture(Format.target, glTexId); - GFX.objectLabel(GFX.GL_TEXTURE, glTexId, "IMG light_section_volume"); - - GFX.texParameter(Format.target, GFX.GL_TEXTURE_MIN_FILTER, GFX.GL_LINEAR); - GFX.texParameter(Format.target, GFX.GL_TEXTURE_MAG_FILTER, GFX.GL_LINEAR); - GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_S, GFX.GL_CLAMP_TO_EDGE); - GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_T, GFX.GL_CLAMP_TO_EDGE); - GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_R, GFX.GL_CLAMP_TO_EDGE); // ? - - buffer = MemoryUtil.memAlloc(Format.pixelBytes * Const.SIZE3D); - - // clear manually ? - while (buffer.position() < Format.pixelBytes * Const.SIZE3D) { - buffer.putShort((short) 0); - } - - // allocate - GFX.texImage3D(Format.target, 0, Format.internalFormat, Const.WIDTH, Const.WIDTH, Const.WIDTH, 0, Format.pixelFormat, Format.pixelDataType, null); - } - - public void markAsDirty() { - dirty = true; - } - - public void uploadIfDirty() { - RenderSystem.assertOnRenderThread(); - - if (!dirty) { - return; - } - - CanvasMod.LOG.info("Uploading texture..."); - - dirty = false; - - CanvasTextureState.bindTexture(Format.target, glTexId); - - // Gotta clean up some states, otherwise will cause memory access violation - GFX.pixelStore(GFX.GL_UNPACK_SKIP_PIXELS, 0); - GFX.pixelStore(GFX.GL_UNPACK_SKIP_ROWS, 0); - GFX.pixelStore(GFX.GL_UNPACK_ROW_LENGTH, 0); - GFX.pixelStore(GFX.GL_UNPACK_ALIGNMENT, 2); - - // Importantly, reset the pointer without flip - buffer.position(0); - - GFX.glTexSubImage3D(Format.target, 0, 0, 0, 0, Const.WIDTH, Const.WIDTH, Const.WIDTH, Format.pixelFormat, Format.pixelDataType, buffer); - } - - public void draw(short rgba) { - buffer.putShort(rgba); - } - - public void close() { - TextureUtil.releaseTextureId(glTexId); - MemoryUtil.memFree(buffer); - glTexId = -1; - closed = true; - } - - public short get(int index) { - return buffer.getShort(index); - } - - public void put(int index, short light) { - buffer.putShort(index, light); - } - - public int indexify(BlockPos pos) { - return indexify(pos.getX(), pos.getY(), pos.getZ()); - } - - public int indexify(int x, int y, int z) { - final int localX = x - sectionBlockOffsetX; - final int localY = y - sectionBlockOffsetY; - final int localZ = z - sectionBlockOffsetZ; - - // x and z are swapped because opengl - return ((localZ << (Const.WIDTH_SHIFT * 2)) | (localY << Const.WIDTH_SHIFT) | localX) * Format.pixelBytes; - } - - public void reverseIndexify(int index, BlockPos.MutableBlockPos result) { - index = index / Format.pixelBytes; - - // x and z are swapped because opengl - result.setX((index & Const.WIDTH_MASK) + sectionBlockOffsetX); - result.setY(((index >> Const.WIDTH_SHIFT) & Const.WIDTH_MASK) + sectionBlockOffsetY); - result.setZ(((index >> Const.WIDTH_SHIFT * 2) & Const.WIDTH_MASK) + sectionBlockOffsetZ); - } - - public boolean withinExtents(BlockPos pos) { - return withinExtents(pos.getX(), pos.getY(), pos.getZ()); - } - - public boolean withinExtents(int x, int y, int z) { - return (x >= sectionBlockOffsetX && x < sectionBlockOffsetX + Const.WIDTH) - && (y >= sectionBlockOffsetY && y < sectionBlockOffsetY + Const.WIDTH) - && (z >= sectionBlockOffsetZ && z < sectionBlockOffsetZ + Const.WIDTH); - } - - public int getTexId() { - if (closed) { - throw new IllegalStateException("Trying to access a deleted Light Section Data!"); - } - - return glTexId; - } - - public boolean isClosed() { - return closed; - } -} diff --git a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java index 516517162..c80d0fdc3 100644 --- a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java +++ b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java @@ -29,8 +29,8 @@ import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.resources.ResourceLocation; -import grondag.canvas.light.color.LightDebug; -import grondag.canvas.light.color.LightSectionData; +import grondag.canvas.light.color.LightDataManager; +import grondag.canvas.light.color.LightDataTexture; import grondag.canvas.pipeline.config.ImageConfig; import grondag.canvas.pipeline.config.util.NamedDependency; @@ -47,11 +47,11 @@ public ProgramTextureData(NamedDependency[] samplerImages) { int imageBind = 0; int bindTarget = GL46.GL_TEXTURE_2D; - int lightDebugTex = LightDebug.getTexture(imageName); + int lightDebugTex = LightDataManager.INSTANCE.getTexture(imageName); if (lightDebugTex != -1) { imageBind = lightDebugTex; - bindTarget = LightSectionData.Format.target; + bindTarget = LightDataTexture.Format.target; } else if (imageName.contains(":")) { final AbstractTexture tex = tryLoadResourceTexture(new ResourceLocation(imageName)); diff --git a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java index 470b99a0b..3342f8f85 100644 --- a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java +++ b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java @@ -99,7 +99,7 @@ import grondag.canvas.compat.PlayerAnimatorHolder; import grondag.canvas.config.Configurator; import grondag.canvas.config.FlawlessFramesController; -import grondag.canvas.light.color.LightDebug; +import grondag.canvas.light.color.LightDataManager; import grondag.canvas.material.property.TargetRenderState; import grondag.canvas.material.state.RenderContextState; import grondag.canvas.material.state.RenderState; @@ -377,7 +377,7 @@ public void renderWorld(PoseStack viewMatrixStack, float tickDelta, long frameSt Lighting.setupLevel(MatrixData.viewMatrix); } - LightDebug.debugData.uploadIfDirty(); + LightDataManager.INSTANCE.update(); WorldRenderDraws.profileSwap(profiler, ProfilerGroup.StartWorld, "before_entities_event"); diff --git a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java index ca751df95..ee2396480 100644 --- a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java +++ b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java @@ -51,8 +51,8 @@ import grondag.canvas.apiimpl.rendercontext.CanvasTerrainRenderContext; import grondag.canvas.buffer.input.DrawableVertexCollector; import grondag.canvas.buffer.input.VertexCollectorList; -import grondag.canvas.light.color.LightChunkTask; -import grondag.canvas.light.color.LightDebug; +import grondag.canvas.light.color.LightDataManager; +import grondag.canvas.light.color.LightRegionTask; import grondag.canvas.material.state.TerrainRenderStates; import grondag.canvas.perf.ChunkRebuildCounters; import grondag.canvas.pipeline.Pipeline; @@ -81,7 +81,7 @@ public class RenderRegion implements TerrainExecutorTask { public final CameraRegionVisibility cameraVisibility; public final ShadowRegionVisibility shadowVisibility; public final NeighborRegions neighbors; - private final LightChunkTask lightChunkTask; + private final LightRegionTask lightRegionTask; private RegionRenderSector renderSector = null; @@ -130,10 +130,10 @@ public RenderRegion(RenderChunk chunk, long packedPos) { shadowVisibility = worldRenderState.terrainIterator.shadowVisibility.createRegionState(this); origin.update(); - if (LightDebug.debugData.withinExtents(origin.getX(), origin.getY(), origin.getZ())) { - lightChunkTask = new LightChunkTask(); + if (LightDataManager.INSTANCE.withinExtents(origin)) { + lightRegionTask = new LightRegionTask(LightDataManager.INSTANCE.getOrAllocate(origin)); } else { - lightChunkTask = null; + lightRegionTask = null; } } @@ -467,13 +467,13 @@ private void buildTerrain(CanvasTerrainRenderContext context, RegionBuildState b } } - if (lightChunkTask != null) { - lightChunkTask.checkBlock(searchPos, blockState); + if (lightRegionTask != null) { + lightRegionTask.checkBlock(searchPos, blockState); } } - if (lightChunkTask != null) { - lightChunkTask.propagateLight(region); + if (lightRegionTask != null) { + lightRegionTask.propagateLight(region); } buildState.prepareTranslucentIfNeeded(worldRenderState.sectorManager.cameraPos(), renderSector, collectors); From 6a9dc23a7b7519a257e4f97251f809f0b4b261e6 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Mon, 5 Jun 2023 03:33:38 +0700 Subject: [PATCH 14/69] Propagate to neighboring chunks, do updates in render thread - Fix light source restoration; was blocked by occlusion check --- .../canvas/light/color/LightDataManager.java | 93 +++++++--- .../canvas/light/color/LightDataTexture.java | 1 - ...{LightRegionTask.java => LightRegion.java} | 173 +++++++++++++----- .../canvas/light/color/LightRegionData.java | 4 + .../render/world/CanvasWorldRenderer.java | 2 +- .../canvas/terrain/region/RenderRegion.java | 23 ++- 6 files changed, 212 insertions(+), 84 deletions(-) rename src/main/java/grondag/canvas/light/color/{LightRegionTask.java => LightRegion.java} (64%) diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 7f729a840..0ba514ed3 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -3,18 +3,21 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; +import it.unimi.dsi.fastutil.longs.LongPriorityQueue; +import it.unimi.dsi.fastutil.longs.LongPriorityQueues; import net.minecraft.core.BlockPos; - -import grondag.canvas.CanvasMod; +import net.minecraft.world.level.BlockAndTintGetter; public class LightDataManager { private static final int REGION_COUNT_LENGTH_WISE = 16; // private static final int INITIAL_LIMIT = REGION_COUNT_LENGTH_WISE * REGION_COUNT_LENGTH_WISE * REGION_COUNT_LENGTH_WISE; public static final LightDataManager INSTANCE = new LightDataManager(); - private final Long2ObjectMap allocated = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); + private final Long2ObjectMap allocated = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); // initial size based on 8 chunk render distance + private final LongPriorityQueue updateQueue = LongPriorityQueues.synchronize(new LongArrayFIFOQueue(512)); // placeholder private int originBlockX = -8 * 16; @@ -33,7 +36,7 @@ public static void initialize() { } - public void update() { + public void update(BlockAndTintGetter blockView) { if (texture == null) { initializeTexture(); } @@ -42,26 +45,54 @@ public void update() { // expandTexture(); // } - allocated.forEach((key, data) -> { - if (data.isDirty()) { - texture.upload( - data.regionOriginBlockX - originBlockX, - data.regionOriginBlockY - originBlockY, - data.regionOriginBlockZ - originBlockZ, - data.getBuffer()); - data.clearDirty(); + synchronized (updateQueue) { + while (!updateQueue.isEmpty()) { + final LightRegion lightRegion = allocated.get(updateQueue.dequeueLong()); + + if (lightRegion.isClosed()) { + continue; + } + + lightRegion.update(blockView); + + if (lightRegion.lightData.isDirty()) { + texture.upload( + lightRegion.lightData.regionOriginBlockX - originBlockX, + lightRegion.lightData.regionOriginBlockY - originBlockY, + lightRegion.lightData.regionOriginBlockZ - originBlockZ, + lightRegion.lightData.getBuffer()); + lightRegion.lightData.clearDirty(); + } } - }); + } + } + + public LightRegion getOrAllocate(BlockPos origin) { + LightRegion lightRegion = allocated.get(origin.asLong()); + + if (lightRegion == null) { + return allocate(origin); + } + + return lightRegion; + } + + public LightRegion getFromBlock(BlockPos blockPos) { + final long key = BlockPos.asLong( + blockPos.getX() & ~LightRegionData.Const.WIDTH_MASK, + blockPos.getY() & ~LightRegionData.Const.WIDTH_MASK, + blockPos.getZ() & ~LightRegionData.Const.WIDTH_MASK); + return allocated.get(key); } - public LightRegionData getOrAllocate(BlockPos regionOrigin) { - LightRegionData data = allocated.get(regionOrigin.asLong()); + public void deallocate(BlockPos regionOrigin) { + final LightRegion lightRegion = allocated.get(regionOrigin.asLong()); - if (data == null) { - return allocate(regionOrigin); + if (lightRegion != null && lightRegion.lightData != null) { + lightRegion.lightData.close(); } - return data; + allocated.remove(regionOrigin.asLong()); } public boolean withinExtents(BlockPos pos) { @@ -78,17 +109,15 @@ public boolean withinExtents(int x, int y, int z) { // return allocated.containsKey(regionOrigin.asLong()); // } - private LightRegionData allocate(BlockPos regionOrigin) { + private LightRegion allocate(BlockPos origin) { // if (allocated.size() == size - 1) { // requestExpand(); // } - CanvasMod.LOG.info("allocating for " + regionOrigin); + final LightRegion lightRegion = new LightRegion(origin); + allocated.put(origin.asLong(), lightRegion); - final LightRegionData data = new LightRegionData(regionOrigin.getX(), regionOrigin.getY(), regionOrigin.getZ()); - allocated.put(regionOrigin.asLong(), data); - - return data; + return lightRegion; } // private void requestExpand() { @@ -110,11 +139,15 @@ private void initializeTexture() { public void close() { texture.close(); - for (var data:allocated.values()) { - data.close(); - } + synchronized (allocated) { + for (var lightRegion : allocated.values()) { + if (lightRegion.lightData != null) { + lightRegion.lightData.close(); + } + } - allocated.clear(); + allocated.clear(); + } } public int getTexture(String imageName) { @@ -129,6 +162,10 @@ public int getTexture(String imageName) { return -1; } + public void queueUpdate(LightRegion lightRegion) { + updateQueue.enqueue(lightRegion.origin); + } + // private void expandTexture() { // if (texture != null && texture.size == supposedTextureSize()) { // return; diff --git a/src/main/java/grondag/canvas/light/color/LightDataTexture.java b/src/main/java/grondag/canvas/light/color/LightDataTexture.java index 14265dcfd..ead3174a2 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataTexture.java +++ b/src/main/java/grondag/canvas/light/color/LightDataTexture.java @@ -7,7 +7,6 @@ import com.mojang.blaze3d.platform.TextureUtil; import com.mojang.blaze3d.systems.RenderSystem; -import grondag.canvas.CanvasMod; import grondag.canvas.render.CanvasTextureState; import grondag.canvas.varia.GFX; diff --git a/src/main/java/grondag/canvas/light/color/LightRegionTask.java b/src/main/java/grondag/canvas/light/color/LightRegion.java similarity index 64% rename from src/main/java/grondag/canvas/light/color/LightRegionTask.java rename to src/main/java/grondag/canvas/light/color/LightRegion.java index a7240e357..be8c7195e 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegionTask.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -1,6 +1,8 @@ package grondag.canvas.light.color; import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; +import it.unimi.dsi.fastutil.longs.LongPriorityQueue; +import it.unimi.dsi.fastutil.longs.LongPriorityQueues; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -8,14 +10,12 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.shapes.Shapes; -import grondag.canvas.CanvasMod; import grondag.canvas.light.color.LightRegionData.Elem; import grondag.canvas.light.color.LightRegionData.Encoding; -// TODO: edge chunks // TODO: cluster slab allocation? // TODO: a way to repopulate cluster if needed -public class LightRegionTask { +public class LightRegion { private static class BVec { boolean r, g, b; @@ -93,11 +93,11 @@ private static enum Side { } private static class Queues { - static void enqueue(LongArrayFIFOQueue queue, long index, long light) { + static void enqueue(LongPriorityQueue queue, long index, long light) { queue.enqueue(((long) Side.nullId << 48L) | (index << 16L) | light & 0xffffL); } - static void enqueue(LongArrayFIFOQueue queue, long index, long light, Side target) { + static void enqueue(LongPriorityQueue queue, long index, long light, Side target) { queue.enqueue(((long) target.opposite.id << 48L) | (index << 16L) | light & 0xffffL); } @@ -114,19 +114,33 @@ static short light(long entry) { } } - private LightRegionData lightRegionData; + final LightRegionData lightData; + final long origin; private final BVec less = new BVec(); private final BlockPos.MutableBlockPos sourcePos = new BlockPos.MutableBlockPos(); private final BlockPos.MutableBlockPos nodePos = new BlockPos.MutableBlockPos(); private final LongArrayFIFOQueue incQueue = new LongArrayFIFOQueue(); private final LongArrayFIFOQueue decQueue = new LongArrayFIFOQueue(); - public LightRegionTask(LightRegionData lightRegionData) { - this.lightRegionData = lightRegionData;; + // This is bad and defeats the point of Canvas multithreading, maybe + private final LongPriorityQueue globalIncQueue = LongPriorityQueues.synchronize(new LongArrayFIFOQueue()); + private final LongPriorityQueue globalDecQueue = LongPriorityQueues.synchronize(new LongArrayFIFOQueue()); + + private boolean needsUpdate = false; + + LightRegion(BlockPos origin) { + this.origin = origin.asLong(); + + //debug + if (LightDataManager.INSTANCE.withinExtents(origin)) { + this.lightData = new LightRegionData(origin.getX(), origin.getY(), origin.getZ()); + } else { + this.lightData = null; + } } public void checkBlock(BlockPos pos, BlockState blockState) { - if (!lightRegionData.withinExtents(pos)) { + if (!lightData.withinExtents(pos)) { return; } @@ -136,19 +150,19 @@ public void checkBlock(BlockPos pos, BlockState blockState) { light = LightRegistry.get(blockState); } - final int index = lightRegionData.indexify(pos); - final short getLight = lightRegionData.get(index); + final int index = lightData.indexify(pos); + final short getLight = lightData.get(index); final boolean occluding = blockState.canOcclude(); less.lessThan(getLight, light); if (less.any()) { - lightRegionData.put(index, light); - Queues.enqueue(incQueue, index, light); + lightData.put(index, light); + Queues.enqueue(globalIncQueue, index, light); // CanvasMod.LOG.info("Add light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + blockState); } else if (light == 0 && (Encoding.isLightSource(getLight) || (Encoding.isOccluding(getLight) != occluding))) { - lightRegionData.put(index, Encoding.encodeLight(0, false, occluding)); - Queues.enqueue(decQueue, index, getLight); + lightData.put(index, Encoding.encodeLight(0, false, occluding)); + Queues.enqueue(globalDecQueue, index, getLight); // CanvasMod.LOG.info("Remove light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + blockState); } } @@ -163,7 +177,7 @@ private boolean occludeSide(BlockState state, Side dir, BlockAndTintGetter view, return Shapes.faceShapeOccludes(Shapes.empty(), state.getFaceOcclusionShape(view, pos, dir.vanilla)); } - public void propagateLight(BlockAndTintGetter blockView) { + private void propagateLight(BlockAndTintGetter blockView) { if (incQueue.isEmpty() && decQueue.isEmpty()) { // CanvasMod.LOG.info("Nothing to process!"); return; @@ -174,11 +188,11 @@ public void propagateLight(BlockAndTintGetter blockView) { final BVec removeFlag = new BVec(); final BVec removeMask = new BVec(); - int debugMaxDec = 0; - int debugMaxInc = 0; + int decCount = 0; + int incCount = 0; while(!decQueue.isEmpty()) { - debugMaxDec++; + decCount++; final long entry = decQueue.dequeueLong(); final int index = Queues.index(entry); @@ -186,10 +200,10 @@ public void propagateLight(BlockAndTintGetter blockView) { final int from = Queues.from(entry); // only remove elements that are less than 1 (zero) - final short sourceCurrentLight = lightRegionData.get(index); + final short sourceCurrentLight = lightData.get(index); removeFlag.lessThan(sourceCurrentLight, (short) 0x1110); - lightRegionData.reverseIndexify(index, sourcePos); + lightData.reverseIndexify(index, sourcePos); final BlockState sourceState = blockView.getBlockState(sourcePos); @@ -206,12 +220,30 @@ public void propagateLight(BlockAndTintGetter blockView) { nodePos.setWithOffset(sourcePos, side.x, side.y, side.z); // TODO: change to chunk extents + edge checks - if (!lightRegionData.withinExtents(nodePos)) { - continue; + final LightRegionData dataAccess; + final LongPriorityQueue increaseQueue; + final LongPriorityQueue decreaseQueue; + boolean isNeighbor = !lightData.withinExtents(nodePos); + LightRegion neighbor = null; + + if (isNeighbor) { + neighbor = LightDataManager.INSTANCE.getFromBlock(nodePos); + + if (neighbor == null || neighbor.isClosed()) { + continue; + } + + increaseQueue = neighbor.globalIncQueue; + decreaseQueue = neighbor.globalDecQueue; + dataAccess = neighbor.lightData; + } else { + increaseQueue = incQueue; + decreaseQueue = decQueue; + dataAccess = lightData; } - final int nodeIndex = lightRegionData.indexify(nodePos); - short nodeLight = lightRegionData.get(nodeIndex); + final int nodeIndex = dataAccess.indexify(nodePos); + short nodeLight = dataAccess.get(nodeIndex); if (Encoding.pure(nodeLight) == 0) { continue; @@ -220,7 +252,7 @@ public void propagateLight(BlockAndTintGetter blockView) { final BlockState nodeState = blockView.getBlockState(nodePos); // check neighbor occlusion for decrease - if (occludeSide(nodeState, side.opposite, blockView, nodePos)) { + if (!Encoding.isLightSource(nodeLight) && occludeSide(nodeState, side.opposite, blockView, nodePos)) { continue; } @@ -247,9 +279,9 @@ public void propagateLight(BlockAndTintGetter blockView) { } final short resultLight = (short) (nodeLight & ~(mask)); - lightRegionData.put(nodeIndex, resultLight); + dataAccess.put(nodeIndex, resultLight); - Queues.enqueue(decQueue, nodeIndex, nodeLight, side); + Queues.enqueue(decreaseQueue, nodeIndex, nodeLight, side); nodeLight = resultLight; @@ -258,35 +290,43 @@ public void propagateLight(BlockAndTintGetter blockView) { short registeredLight = LightRegistry.get(nodeState); nodeLight |= (registeredLight & mask); } + + if (neighbor != null) { + neighbor.markForUpdate(); + } } if (!less.all() || restoreLightSource) { // increases queued in decrease may propagate to all directions as if a light source - Queues.enqueue(incQueue, nodeIndex, nodeLight); + Queues.enqueue(increaseQueue, nodeIndex, nodeLight); + + if (neighbor != null) { + neighbor.markForUpdate(); + } } } } while (!incQueue.isEmpty()) { - debugMaxInc++; + incCount++; final long entry = incQueue.dequeueLong(); final int index = Queues.index(entry); final short recordedLight = Queues.light(entry); final int from = Queues.from(entry); - short sourceLight = lightRegionData.get(index); + short sourceLight = lightData.get(index); if (sourceLight != recordedLight) { if (Encoding.isLightSource(recordedLight)) { sourceLight = recordedLight; - lightRegionData.put(index, sourceLight); + lightData.put(index, sourceLight); } else { continue; } } - lightRegionData.reverseIndexify(index, sourcePos); + lightData.reverseIndexify(index, sourcePos); final BlockState sourceState = blockView.getBlockState(sourcePos); @@ -305,12 +345,27 @@ public void propagateLight(BlockAndTintGetter blockView) { // CanvasMod.LOG.info("increase at " + nodeX + "," + nodeY + "," + nodeZ); // TODO: change to chunk extents + edge checks - if (!lightRegionData.withinExtents(nodePos)) { - continue; + final LightRegionData dataAccess; + final LongPriorityQueue increaseQueue; + boolean isNeighbor = !lightData.withinExtents(nodePos); + LightRegion neighbor = null; + + if (isNeighbor) { + neighbor = LightDataManager.INSTANCE.getFromBlock(nodePos); + + if (neighbor == null || neighbor.isClosed()) { + continue; + } + + increaseQueue = neighbor.globalIncQueue; + dataAccess = neighbor.lightData; + } else { + increaseQueue = incQueue; + dataAccess = lightData; } - final int nodeIndex = lightRegionData.indexify(nodePos); - final short nodeLight = lightRegionData.get(nodeIndex); + final int nodeIndex = dataAccess.indexify(nodePos); + final short nodeLight = dataAccess.get(nodeIndex); final BlockState nodeState = blockView.getBlockState(nodePos); // check neighbor occlusion for increase @@ -337,17 +392,51 @@ public void propagateLight(BlockAndTintGetter blockView) { resultLight = Elem.B.replace(resultLight, (short) (Elem.B.of(sourceLight) - 1)); } - lightRegionData.put(nodeIndex, resultLight); + dataAccess.put(nodeIndex, resultLight); // CanvasMod.LOG.info("updating neighbor to: " + nodeX + "," + nodeY + "," + nodeZ + "," + Elem.text(resultLight)); - Queues.enqueue(incQueue, nodeIndex, resultLight, side); + Queues.enqueue(increaseQueue, nodeIndex, resultLight, side); + + if (neighbor != null) { + neighbor.markForUpdate(); + } } } } - CanvasMod.LOG.info("Processed queues! Count: inc,dec " + debugMaxInc + "," + debugMaxDec); - // CanvasMod.LOG.info("Marking texture as dirty"); - lightRegionData.markAsDirty(); + // CanvasMod.LOG.info("Processed queues! Count: inc,dec " + incCount + "," + decCount); + + if (decCount + incCount > 0) { + lightData.markAsDirty(); + } + } + + public void markForUpdate() { + if (!needsUpdate) { + needsUpdate = true; + LightDataManager.INSTANCE.queueUpdate(this); + } + } + + public void update(BlockAndTintGetter blockView) { + boolean updating = needsUpdate || !globalDecQueue.isEmpty() || !globalIncQueue.isEmpty(); + + while (!globalDecQueue.isEmpty()) { + decQueue.enqueue(globalDecQueue.dequeueLong()); + } + + while (!globalIncQueue.isEmpty()) { + incQueue.enqueue(globalIncQueue.dequeueLong()); + } + + if (updating) { + propagateLight(blockView); + needsUpdate = false; + } + } + + public boolean isClosed() { + return lightData == null || lightData.isClosed(); } } diff --git a/src/main/java/grondag/canvas/light/color/LightRegionData.java b/src/main/java/grondag/canvas/light/color/LightRegionData.java index a96f7bc2f..073f7131d 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegionData.java +++ b/src/main/java/grondag/canvas/light/color/LightRegionData.java @@ -104,6 +104,10 @@ public void draw(short rgba) { } public void close() { + if (closed) { + return; + } + buffer.position(0); MemoryUtil.memFree(buffer); closed = true; diff --git a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java index 3342f8f85..43348ca70 100644 --- a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java +++ b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java @@ -377,7 +377,7 @@ public void renderWorld(PoseStack viewMatrixStack, float tickDelta, long frameSt Lighting.setupLevel(MatrixData.viewMatrix); } - LightDataManager.INSTANCE.update(); + LightDataManager.INSTANCE.update(world); WorldRenderDraws.profileSwap(profiler, ProfilerGroup.StartWorld, "before_entities_event"); diff --git a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java index ee2396480..67c2607f5 100644 --- a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java +++ b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java @@ -52,7 +52,7 @@ import grondag.canvas.buffer.input.DrawableVertexCollector; import grondag.canvas.buffer.input.VertexCollectorList; import grondag.canvas.light.color.LightDataManager; -import grondag.canvas.light.color.LightRegionTask; +import grondag.canvas.light.color.LightRegion; import grondag.canvas.material.state.TerrainRenderStates; import grondag.canvas.perf.ChunkRebuildCounters; import grondag.canvas.pipeline.Pipeline; @@ -81,7 +81,7 @@ public class RenderRegion implements TerrainExecutorTask { public final CameraRegionVisibility cameraVisibility; public final ShadowRegionVisibility shadowVisibility; public final NeighborRegions neighbors; - private final LightRegionTask lightRegionTask; + private final LightRegion lightRegion; private RegionRenderSector renderSector = null; @@ -129,12 +129,7 @@ public RenderRegion(RenderChunk chunk, long packedPos) { cameraVisibility = worldRenderState.terrainIterator.cameraVisibility.createRegionState(this); shadowVisibility = worldRenderState.terrainIterator.shadowVisibility.createRegionState(this); origin.update(); - - if (LightDataManager.INSTANCE.withinExtents(origin)) { - lightRegionTask = new LightRegionTask(LightDataManager.INSTANCE.getOrAllocate(origin)); - } else { - lightRegionTask = null; - } + lightRegion = LightDataManager.INSTANCE.getOrAllocate(origin); } private static void addBlockEntity(List chunkEntities, Set globalEntities, E blockEntity) { @@ -175,6 +170,10 @@ void close() { if (renderSector != null) { renderSector = renderSector.release(origin); } + + if (!lightRegion.isClosed()) { + LightDataManager.INSTANCE.deallocate(origin); + } } } @@ -467,13 +466,13 @@ private void buildTerrain(CanvasTerrainRenderContext context, RegionBuildState b } } - if (lightRegionTask != null) { - lightRegionTask.checkBlock(searchPos, blockState); + if (!lightRegion.isClosed()) { + lightRegion.checkBlock(searchPos, blockState); } } - if (lightRegionTask != null) { - lightRegionTask.propagateLight(region); + if (!lightRegion.isClosed()) { + lightRegion.markForUpdate(); } buildState.prepareTranslucentIfNeeded(worldRenderState.sectorManager.cameraPos(), renderSector, collectors); From f38b99be66d083d169e27cf2f57ab4e2ec6cb297 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Mon, 5 Jun 2023 17:26:14 +0700 Subject: [PATCH 15/69] Loop through every regions within extent for update Don't use queue since we already have per region queue --- .../canvas/light/color/LightDataManager.java | 159 +++++++++--------- .../canvas/light/color/LightRegion.java | 97 +++++++---- .../canvas/light/color/LightRegionData.java | 2 +- .../render/world/CanvasWorldRenderer.java | 2 +- .../canvas/terrain/region/RenderRegion.java | 8 +- 5 files changed, 147 insertions(+), 121 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 0ba514ed3..d085ed611 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -3,28 +3,32 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; -import it.unimi.dsi.fastutil.longs.LongPriorityQueue; -import it.unimi.dsi.fastutil.longs.LongPriorityQueues; import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockAndTintGetter; +import grondag.canvas.CanvasMod; + public class LightDataManager { - private static final int REGION_COUNT_LENGTH_WISE = 16; + // NB: must be even + private static final int REGION_COUNT_LENGTH_WISE = 32; + private static final boolean debugRedrawEveryFrame = false; // private static final int INITIAL_LIMIT = REGION_COUNT_LENGTH_WISE * REGION_COUNT_LENGTH_WISE * REGION_COUNT_LENGTH_WISE; + public static final LightDataManager INSTANCE = new LightDataManager(); private final Long2ObjectMap allocated = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); // initial size based on 8 chunk render distance - private final LongPriorityQueue updateQueue = LongPriorityQueues.synchronize(new LongArrayFIFOQueue(512)); + // private final LongPriorityQueue updateQueue = LongPriorityQueues.synchronize(new LongArrayFIFOQueue(512)); + // private final LongPriorityQueue processQueue = new LongArrayFIFOQueue(512); - // placeholder - private int originBlockX = -8 * 16; - private int originBlockY = 64; - private int originBlockZ = -8 * 16; + private int extentStartBlockX = 0; + private int extentStartBlockY = 0; + private int extentStartBlockZ = 0; + private boolean cameraUninitialized = true; - private int size = REGION_COUNT_LENGTH_WISE; + // NB: must be even + private int extentSizeInRegions = REGION_COUNT_LENGTH_WISE; private LightDataTexture texture; { @@ -36,45 +40,60 @@ public static void initialize() { } - public void update(BlockAndTintGetter blockView) { + public void update(BlockAndTintGetter blockView, int cameraX, int cameraY, int cameraZ) { if (texture == null) { initializeTexture(); } - // if (texture.size < supposedTextureSize()) { - // expandTexture(); - // } + final int regionSnapMask = ~LightRegionData.Const.WIDTH_MASK; + final int halfRadius = extentSizeInBlocks(extentSizeInRegions / 2); - synchronized (updateQueue) { - while (!updateQueue.isEmpty()) { - final LightRegion lightRegion = allocated.get(updateQueue.dequeueLong()); + final int prevExtentX = extentStartBlockX; + final int prevExtentY = extentStartBlockY; + final int prevExtentZ = extentStartBlockZ; - if (lightRegion.isClosed()) { - continue; - } + // snap camera position to the nearest region (chunk) + extentStartBlockX = (cameraX & regionSnapMask) - halfRadius; + extentStartBlockY = (cameraY & regionSnapMask) - halfRadius; + extentStartBlockZ = (cameraZ & regionSnapMask) - halfRadius; - lightRegion.update(blockView); + if (!cameraUninitialized + && (extentStartBlockX != prevExtentX || extentStartBlockY != prevExtentY || extentStartBlockZ != prevExtentZ)) { + //TODO: IMPORTANT: re-draw newly entered regions + CanvasMod.LOG.info("Extent have changed"); + } + + cameraUninitialized = false; - if (lightRegion.lightData.isDirty()) { - texture.upload( - lightRegion.lightData.regionOriginBlockX - originBlockX, - lightRegion.lightData.regionOriginBlockY - originBlockY, - lightRegion.lightData.regionOriginBlockZ - originBlockZ, - lightRegion.lightData.getBuffer()); - lightRegion.lightData.clearDirty(); + // update all regions within extent + for (int i = extentStartBlockX; i < extentStartBlockX + extentSizeInBlocks(); i += extentSizeInBlocks(1)) { + for (int j = extentStartBlockY; j < extentStartBlockY + extentSizeInBlocks(); j += extentSizeInBlocks(1)) { + for (int k = extentStartBlockZ; k < extentStartBlockZ + extentSizeInBlocks(); k += extentSizeInBlocks(1)) { + long index = BlockPos.asLong(i, j, k); + updateRegion(index, blockView); } } } } - public LightRegion getOrAllocate(BlockPos origin) { - LightRegion lightRegion = allocated.get(origin.asLong()); + private void updateRegion(long index, BlockAndTintGetter blockView) { + final LightRegion lightRegion = allocated.get(index); - if (lightRegion == null) { - return allocate(origin); + if (lightRegion == null || lightRegion.isClosed()) { + return; } - return lightRegion; + lightRegion.update(blockView); + + if (lightRegion.lightData.isDirty() || debugRedrawEveryFrame) { + final int extentGridMask = extentSizeMask(); + final int x = lightRegion.lightData.regionOriginBlockX; + final int y = lightRegion.lightData.regionOriginBlockY; + final int z = lightRegion.lightData.regionOriginBlockZ; + // modulo into extent-grid + texture.upload(x & extentGridMask, y & extentGridMask, z & extentGridMask, lightRegion.lightData.getBuffer()); + lightRegion.lightData.clearDirty(); + } } public LightRegion getFromBlock(BlockPos blockPos) { @@ -88,52 +107,41 @@ public LightRegion getFromBlock(BlockPos blockPos) { public void deallocate(BlockPos regionOrigin) { final LightRegion lightRegion = allocated.get(regionOrigin.asLong()); - if (lightRegion != null && lightRegion.lightData != null) { - lightRegion.lightData.close(); + if (lightRegion != null && !lightRegion.isClosed()) { + lightRegion.close(); } allocated.remove(regionOrigin.asLong()); } - public boolean withinExtents(BlockPos pos) { - return withinExtents(pos.getX(), pos.getY(), pos.getZ()); - } - - public boolean withinExtents(int x, int y, int z) { - return (x >= originBlockX && x < originBlockX + sizeInBlocks()) - && (y >= originBlockY && y < originBlockY + sizeInBlocks()) - && (z >= originBlockZ && z < originBlockZ + sizeInBlocks()); - } - - // public boolean isAllocated(BlockPos regionOrigin) { - // return allocated.containsKey(regionOrigin.asLong()); - // } - - private LightRegion allocate(BlockPos origin) { - // if (allocated.size() == size - 1) { - // requestExpand(); - // } + public LightRegion allocate(BlockPos regionOrigin) { + if (allocated.containsKey(regionOrigin.asLong())) { + deallocate(regionOrigin); + // we can probably clear it and reuse it? + // final LightRegion lightRegion = allocated.get(regionOrigin.asLong()); + // lightRegion.reclaim(); + } - final LightRegion lightRegion = new LightRegion(origin); - allocated.put(origin.asLong(), lightRegion); + final LightRegion lightRegion = new LightRegion(regionOrigin); + allocated.put(regionOrigin.asLong(), lightRegion); return lightRegion; } - // private void requestExpand() { - // // I'm scared - // final int newLimit = size * 2; - // size = newLimit; - // - // CanvasMod.LOG.info(String.format("Reallocating light data texture from %d to %d! This is huge!", size, newLimit)); - // } + private int extentSizeInBlocks(int extentSize) { + return extentSize * LightRegionData.Const.WIDTH; + } + + private int extentSizeInBlocks() { + return extentSizeInBlocks(extentSizeInRegions); + } - private int sizeInBlocks() { - return size * LightRegionData.Const.WIDTH; + private int extentSizeMask() { + return extentSizeInBlocks() - 1; } private void initializeTexture() { - texture = new LightDataTexture(sizeInBlocks()); + texture = new LightDataTexture(extentSizeInBlocks()); } public void close() { @@ -141,8 +149,8 @@ public void close() { synchronized (allocated) { for (var lightRegion : allocated.values()) { - if (lightRegion.lightData != null) { - lightRegion.lightData.close(); + if (!lightRegion.isClosed()) { + lightRegion.close(); } } @@ -162,20 +170,7 @@ public int getTexture(String imageName) { return -1; } - public void queueUpdate(LightRegion lightRegion) { - updateQueue.enqueue(lightRegion.origin); - } - - // private void expandTexture() { - // if (texture != null && texture.size == supposedTextureSize()) { - // return; - // } - // - // if (texture != null) { - // texture.close(); - // texture = null; - // } - // - // initializeTexture(); + // public void queueUpdate(LightRegion lightRegion) { + // updateQueue.enqueue(lightRegion.origin); // } } diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index be8c7195e..41b843e52 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -126,17 +126,12 @@ static short light(long entry) { private final LongPriorityQueue globalIncQueue = LongPriorityQueues.synchronize(new LongArrayFIFOQueue()); private final LongPriorityQueue globalDecQueue = LongPriorityQueues.synchronize(new LongArrayFIFOQueue()); - private boolean needsUpdate = false; + // private boolean needsUpdate = false; + // private boolean needsClear = false; LightRegion(BlockPos origin) { this.origin = origin.asLong(); - - //debug - if (LightDataManager.INSTANCE.withinExtents(origin)) { - this.lightData = new LightRegionData(origin.getX(), origin.getY(), origin.getZ()); - } else { - this.lightData = null; - } + this.lightData = new LightRegionData(origin.getX(), origin.getY(), origin.getZ()); } public void checkBlock(BlockPos pos, BlockState blockState) { @@ -177,10 +172,10 @@ private boolean occludeSide(BlockState state, Side dir, BlockAndTintGetter view, return Shapes.faceShapeOccludes(Shapes.empty(), state.getFaceOcclusionShape(view, pos, dir.vanilla)); } - private void propagateLight(BlockAndTintGetter blockView) { + private boolean propagateLight(BlockAndTintGetter blockView) { if (incQueue.isEmpty() && decQueue.isEmpty()) { // CanvasMod.LOG.info("Nothing to process!"); - return; + return false; } // CanvasMod.LOG.info("Processing queues.. inc,dec " + incQueue.size() + "," + decQueue.size()); @@ -291,18 +286,18 @@ private void propagateLight(BlockAndTintGetter blockView) { nodeLight |= (registeredLight & mask); } - if (neighbor != null) { - neighbor.markForUpdate(); - } + // if (neighbor != null) { + // neighbor.markForUpdate(); + // } } if (!less.all() || restoreLightSource) { // increases queued in decrease may propagate to all directions as if a light source Queues.enqueue(increaseQueue, nodeIndex, nodeLight); - if (neighbor != null) { - neighbor.markForUpdate(); - } + // if (neighbor != null) { + // neighbor.markForUpdate(); + // } } } } @@ -398,29 +393,43 @@ private void propagateLight(BlockAndTintGetter blockView) { Queues.enqueue(increaseQueue, nodeIndex, resultLight, side); - if (neighbor != null) { - neighbor.markForUpdate(); - } + // if (neighbor != null) { + // neighbor.markForUpdate(); + // } } } } // CanvasMod.LOG.info("Processed queues! Count: inc,dec " + incCount + "," + decCount); - if (decCount + incCount > 0) { - lightData.markAsDirty(); - } + return decCount + incCount > 0; } - public void markForUpdate() { - if (!needsUpdate) { - needsUpdate = true; - LightDataManager.INSTANCE.queueUpdate(this); - } - } + // public void markForUpdate() { + // if (!needsUpdate) { + // needsUpdate = true; + // LightDataManager.INSTANCE.queueUpdate(this); + // } + // } public void update(BlockAndTintGetter blockView) { - boolean updating = needsUpdate || !globalDecQueue.isEmpty() || !globalIncQueue.isEmpty(); + // boolean neededUpdate = needsUpdate; + // needsUpdate = false; + // + // boolean wasCleared = false; + // + // if (needsClear) { + // for (int i = 0; i < LightRegionData.Const.SIZE3D; i++) { + // lightData.put(i * LightDataTexture.Format.pixelBytes, (short) 0); + // } + // + // needsClear = false; + // wasCleared = true; + // } + + // boolean propagating = wasCleared || neededUpdate || !globalDecQueue.isEmpty() || !globalIncQueue.isEmpty(); + + boolean propagating = !globalDecQueue.isEmpty() || !globalIncQueue.isEmpty(); while (!globalDecQueue.isEmpty()) { decQueue.enqueue(globalDecQueue.dequeueLong()); @@ -430,9 +439,33 @@ public void update(BlockAndTintGetter blockView) { incQueue.enqueue(globalIncQueue.dequeueLong()); } - if (updating) { - propagateLight(blockView); - needsUpdate = false; + boolean didPropagate = false; + + if (propagating) { + didPropagate = propagateLight(blockView); + } + + // if (didPropagate || wasCleared) { + if (didPropagate) { + lightData.markAsDirty(); + } + } + + // public void reclaim() { + // globalIncQueue.clear(); + // globalDecQueue.clear(); + // + // needsUpdate = false; + // + // // this is called from allocate(), so defer it to main thread + // needsClear = true; + // + // markForUpdate(); + // } + + public void close() { + if (!lightData.isClosed()) { + lightData.close(); } } diff --git a/src/main/java/grondag/canvas/light/color/LightRegionData.java b/src/main/java/grondag/canvas/light/color/LightRegionData.java index 073f7131d..6037f097b 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegionData.java +++ b/src/main/java/grondag/canvas/light/color/LightRegionData.java @@ -10,7 +10,7 @@ public class LightRegionData { public static class Const { public static final int WIDTH = 16; - private static final int SIZE3D = WIDTH * WIDTH * WIDTH; + public static final int SIZE3D = WIDTH * WIDTH * WIDTH; public static final int WIDTH_SHIFT = (int) (Math.log(WIDTH) / Math.log(2)); public static final int WIDTH_MASK = WIDTH - 1; diff --git a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java index 43348ca70..4b83406f1 100644 --- a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java +++ b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java @@ -377,7 +377,7 @@ public void renderWorld(PoseStack viewMatrixStack, float tickDelta, long frameSt Lighting.setupLevel(MatrixData.viewMatrix); } - LightDataManager.INSTANCE.update(world); + LightDataManager.INSTANCE.update(world, (int) frameCameraX, (int) frameCameraY, (int) frameCameraZ); WorldRenderDraws.profileSwap(profiler, ProfilerGroup.StartWorld, "before_entities_event"); diff --git a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java index 67c2607f5..5e66dea47 100644 --- a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java +++ b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java @@ -48,6 +48,7 @@ import io.vram.frex.api.math.MatrixStack; import io.vram.frex.api.model.fluid.FluidModel; +import grondag.canvas.CanvasMod; import grondag.canvas.apiimpl.rendercontext.CanvasTerrainRenderContext; import grondag.canvas.buffer.input.DrawableVertexCollector; import grondag.canvas.buffer.input.VertexCollectorList; @@ -129,7 +130,7 @@ public RenderRegion(RenderChunk chunk, long packedPos) { cameraVisibility = worldRenderState.terrainIterator.cameraVisibility.createRegionState(this); shadowVisibility = worldRenderState.terrainIterator.shadowVisibility.createRegionState(this); origin.update(); - lightRegion = LightDataManager.INSTANCE.getOrAllocate(origin); + lightRegion = LightDataManager.INSTANCE.allocate(origin); } private static void addBlockEntity(List chunkEntities, Set globalEntities, E blockEntity) { @@ -173,6 +174,7 @@ void close() { if (!lightRegion.isClosed()) { LightDataManager.INSTANCE.deallocate(origin); + CanvasMod.LOG.info("called deallocate() from Render Region"); } } } @@ -471,10 +473,6 @@ private void buildTerrain(CanvasTerrainRenderContext context, RegionBuildState b } } - if (!lightRegion.isClosed()) { - lightRegion.markForUpdate(); - } - buildState.prepareTranslucentIfNeeded(worldRenderState.sectorManager.cameraPos(), renderSector, collectors); if (ChunkRebuildCounters.ENABLED) { From b597751a46bf4bc85aadda31e7747fa43c1740e1 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Mon, 5 Jun 2023 21:25:33 +0700 Subject: [PATCH 16/69] Propagate chunk edge lights for new regions Add froglight colors Rename light data texture to "canvas:alpha/light_data" Cleanup cruft --- .../canvas/light/color/LightDataManager.java | 16 +- .../canvas/light/color/LightRegion.java | 196 +++++++++++++----- .../canvas/light/color/LightRegionData.java | 1 + .../canvas/pipeline/ProgramTextureData.java | 6 +- .../lights/item/ochre_froglight.json | 7 + .../lights/item/pearlescent_froglight.json | 7 + .../lights/item/verdant_froglight.json | 7 + 7 files changed, 176 insertions(+), 64 deletions(-) create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/item/ochre_froglight.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/item/pearlescent_froglight.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/item/verdant_froglight.json diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index d085ed611..59b5bafbf 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -18,9 +18,6 @@ public class LightDataManager { public static final LightDataManager INSTANCE = new LightDataManager(); private final Long2ObjectMap allocated = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); - // initial size based on 8 chunk render distance - // private final LongPriorityQueue updateQueue = LongPriorityQueues.synchronize(new LongArrayFIFOQueue(512)); - // private final LongPriorityQueue processQueue = new LongArrayFIFOQueue(512); private int extentStartBlockX = 0; private int extentStartBlockY = 0; @@ -60,6 +57,8 @@ public void update(BlockAndTintGetter blockView, int cameraX, int cameraY, int c if (!cameraUninitialized && (extentStartBlockX != prevExtentX || extentStartBlockY != prevExtentY || extentStartBlockZ != prevExtentZ)) { //TODO: IMPORTANT: re-draw newly entered regions + //TODO: if newly entered region is null, clear using dummy (empty) lightDataRegion + //TODO: cleanup dummy lightDataRegion in close() CanvasMod.LOG.info("Extent have changed"); } @@ -117,9 +116,6 @@ public void deallocate(BlockPos regionOrigin) { public LightRegion allocate(BlockPos regionOrigin) { if (allocated.containsKey(regionOrigin.asLong())) { deallocate(regionOrigin); - // we can probably clear it and reuse it? - // final LightRegion lightRegion = allocated.get(regionOrigin.asLong()); - // lightRegion.reclaim(); } final LightRegion lightRegion = new LightRegion(regionOrigin); @@ -159,11 +155,11 @@ public void close() { } public int getTexture(String imageName) { - if (texture == null) { - initializeTexture(); - } + if (imageName.equals("canvas:alpha/light_data")) { + if (texture == null) { + initializeTexture(); + } - if (imageName.equals("_cv_debug_light_data")) { return texture.getTexId(); } diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 41b843e52..3a0380512 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -13,7 +13,7 @@ import grondag.canvas.light.color.LightRegionData.Elem; import grondag.canvas.light.color.LightRegionData.Encoding; -// TODO: cluster slab allocation? +// TODO: cluster slab allocation? -> maybe unneeded now? // TODO: a way to repopulate cluster if needed public class LightRegion { private static class BVec { @@ -90,6 +90,21 @@ private static enum Side { this.id = id; this.vanilla = vanilla; } + + static Side infer(BlockPos from, BlockPos to) { + int x = to.getX() - from.getX(); + int y = to.getY() - from.getY(); + int z = to.getZ() - from.getZ(); + + for (Side side:Side.values()) { + if (side.x == x && side.y == y && side.z == z) { + return side; + } + } + + // detects programming error (happened once) + throw new IndexOutOfBoundsException("Can't infer side. From: " + from + ", To: " + to); + } } private static class Queues { @@ -126,8 +141,7 @@ static short light(long entry) { private final LongPriorityQueue globalIncQueue = LongPriorityQueues.synchronize(new LongArrayFIFOQueue()); private final LongPriorityQueue globalDecQueue = LongPriorityQueues.synchronize(new LongArrayFIFOQueue()); - // private boolean needsUpdate = false; - // private boolean needsClear = false; + private boolean needCheckEdges = true; LightRegion(BlockPos origin) { this.origin = origin.asLong(); @@ -214,7 +228,6 @@ private boolean propagateLight(BlockAndTintGetter blockView) { nodePos.setWithOffset(sourcePos, side.x, side.y, side.z); - // TODO: change to chunk extents + edge checks final LightRegionData dataAccess; final LongPriorityQueue increaseQueue; final LongPriorityQueue decreaseQueue; @@ -285,19 +298,11 @@ private boolean propagateLight(BlockAndTintGetter blockView) { short registeredLight = LightRegistry.get(nodeState); nodeLight |= (registeredLight & mask); } - - // if (neighbor != null) { - // neighbor.markForUpdate(); - // } } if (!less.all() || restoreLightSource) { // increases queued in decrease may propagate to all directions as if a light source Queues.enqueue(increaseQueue, nodeIndex, nodeLight); - - // if (neighbor != null) { - // neighbor.markForUpdate(); - // } } } } @@ -339,7 +344,6 @@ private boolean propagateLight(BlockAndTintGetter blockView) { // CanvasMod.LOG.info("increase at " + nodeX + "," + nodeY + "," + nodeZ); - // TODO: change to chunk extents + edge checks final LightRegionData dataAccess; final LongPriorityQueue increaseQueue; boolean isNeighbor = !lightData.withinExtents(nodePos); @@ -392,10 +396,6 @@ private boolean propagateLight(BlockAndTintGetter blockView) { // CanvasMod.LOG.info("updating neighbor to: " + nodeX + "," + nodeY + "," + nodeZ + "," + Elem.text(resultLight)); Queues.enqueue(increaseQueue, nodeIndex, resultLight, side); - - // if (neighbor != null) { - // neighbor.markForUpdate(); - // } } } } @@ -405,29 +405,136 @@ private boolean propagateLight(BlockAndTintGetter blockView) { return decCount + incCount > 0; } - // public void markForUpdate() { - // if (!needsUpdate) { - // needsUpdate = true; - // LightDataManager.INSTANCE.queueUpdate(this); - // } - // } + private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourcePos, BlockPos.MutableBlockPos targetPos, Side side, BlockAndTintGetter blockView) { + final int sourceIndex = neighbor.lightData.indexify(sourcePos); + final short sourceLight = neighbor.lightData.get(sourceIndex); + final BlockState sourceState = blockView.getBlockState(sourcePos); + + if (Encoding.pure(sourceLight) != 0) { + // TODO: generalize for all increase process, with check-neighbor flag + // check self occlusion for increase + if (!Encoding.isLightSource(sourceLight) && occludeSide(sourceState, side, blockView, sourcePos)) { + return; + } + + final int targetIndex = lightData.indexify(targetPos); + final short targetLight = lightData.get(targetIndex); + final BlockState nodeState = blockView.getBlockState(targetPos); + + // check neighbor occlusion for increase + if (occludeSide(nodeState, side.opposite, blockView, targetPos)) { + return; + } + + less.lessThanMinusOne(targetLight, sourceLight); + + if (less.any()) { + short resultLight = targetLight; + + if (less.r) { + resultLight = Elem.R.replace(resultLight, (short) (Elem.R.of(sourceLight) - 1)); + } + + if (less.g) { + resultLight = Elem.G.replace(resultLight, (short) (Elem.G.of(sourceLight) - 1)); + } + + if (less.b) { + resultLight = Elem.B.replace(resultLight, (short) (Elem.B.of(sourceLight) - 1)); + } + + lightData.put(targetIndex, resultLight); + + Queues.enqueue(incQueue, targetIndex, resultLight, side); + } + } + } + + private void checkEdges(BlockAndTintGetter blockView) { + final int size = LightRegionData.Const.WIDTH; + final BlockPos originPos = BlockPos.of(origin); + final BlockPos.MutableBlockPos searchPos = new BlockPos.MutableBlockPos(); + final BlockPos.MutableBlockPos targetPos = new BlockPos.MutableBlockPos(); + final int[] searchOffsets = new int[]{-1, size}; + final int[] targetOffsets = new int[]{0, size - 1}; + + for (int i = 0; i < searchOffsets.length; i ++) { + final int x = searchOffsets[i]; + final int xTarget = targetOffsets[i]; + + searchPos.setWithOffset(originPos, x, 0, 0); + targetPos.setWithOffset(originPos, xTarget, 0, 0); + final Side side = Side.infer(searchPos, targetPos); + final LightRegion neighbor = LightDataManager.INSTANCE.getFromBlock(searchPos); + + if (neighbor == null) { + continue; + } + + for (int y = 0; y < size; y++) { + for (int z = 0; z < size; z++) { + searchPos.setWithOffset(originPos, x, y, z); + targetPos.setWithOffset(originPos, xTarget, y, z); + checkEdgeBlock(neighbor, searchPos, targetPos, side, blockView); + } + } + } + + // TODO: generalize with Axis parameter + for (int i = 0; i < searchOffsets.length; i ++) { + final int y = searchOffsets[i]; + final int yTarget = targetOffsets[i]; + + searchPos.setWithOffset(originPos, 0, y, 0); + targetPos.setWithOffset(originPos, 0, yTarget, 0); + final Side side = Side.infer(searchPos, targetPos); + final LightRegion neighbor = LightDataManager.INSTANCE.getFromBlock(searchPos); + + if (neighbor == null) { + continue; + } + + for (int z = 0; z < size; z++) { + for (int x = 0; x < size; x++) { + searchPos.setWithOffset(originPos, x, y, z); + targetPos.setWithOffset(originPos, x, yTarget, z); + checkEdgeBlock(neighbor, searchPos, targetPos, side, blockView); + } + } + } + + for (int i = 0; i < searchOffsets.length; i ++) { + final int z = searchOffsets[i]; + final int zTarget = targetOffsets[i]; + + searchPos.set(origin); + searchPos.setWithOffset(searchPos, 0, 0, z); + targetPos.set(origin); + targetPos.setWithOffset(targetPos, 0, 0, zTarget); + final Side side = Side.infer(searchPos, targetPos); + final LightRegion neighbor = LightDataManager.INSTANCE.getFromBlock(searchPos); + + if (neighbor == null) { + continue; + } + + for (int x = 0; x < size; x++) { + for (int y = 0; y < size; y++) { + searchPos.set(origin); + searchPos.setWithOffset(searchPos, x, y, z); + targetPos.set(origin); + targetPos.setWithOffset(targetPos, x, y, zTarget); + checkEdgeBlock(neighbor, searchPos, targetPos, side, blockView); + } + } + } + } public void update(BlockAndTintGetter blockView) { - // boolean neededUpdate = needsUpdate; - // needsUpdate = false; - // - // boolean wasCleared = false; - // - // if (needsClear) { - // for (int i = 0; i < LightRegionData.Const.SIZE3D; i++) { - // lightData.put(i * LightDataTexture.Format.pixelBytes, (short) 0); - // } - // - // needsClear = false; - // wasCleared = true; - // } - - // boolean propagating = wasCleared || neededUpdate || !globalDecQueue.isEmpty() || !globalIncQueue.isEmpty(); + if (needCheckEdges) { + checkEdges(blockView); + needCheckEdges = false; + } boolean propagating = !globalDecQueue.isEmpty() || !globalIncQueue.isEmpty(); @@ -445,24 +552,11 @@ public void update(BlockAndTintGetter blockView) { didPropagate = propagateLight(blockView); } - // if (didPropagate || wasCleared) { if (didPropagate) { lightData.markAsDirty(); } } - // public void reclaim() { - // globalIncQueue.clear(); - // globalDecQueue.clear(); - // - // needsUpdate = false; - // - // // this is called from allocate(), so defer it to main thread - // needsClear = true; - // - // markForUpdate(); - // } - public void close() { if (!lightData.isClosed()) { lightData.close(); diff --git a/src/main/java/grondag/canvas/light/color/LightRegionData.java b/src/main/java/grondag/canvas/light/color/LightRegionData.java index 6037f097b..c58086e19 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegionData.java +++ b/src/main/java/grondag/canvas/light/color/LightRegionData.java @@ -109,6 +109,7 @@ public void close() { } buffer.position(0); + // very important MemoryUtil.memFree(buffer); closed = true; } diff --git a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java index c80d0fdc3..098a6bf9e 100644 --- a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java +++ b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java @@ -47,10 +47,10 @@ public ProgramTextureData(NamedDependency[] samplerImages) { int imageBind = 0; int bindTarget = GL46.GL_TEXTURE_2D; - int lightDebugTex = LightDataManager.INSTANCE.getTexture(imageName); + int lightDataTex = LightDataManager.INSTANCE.getTexture(imageName); - if (lightDebugTex != -1) { - imageBind = lightDebugTex; + if (lightDataTex != -1) { + imageBind = lightDataTex; bindTarget = LightDataTexture.Format.target; } else if (imageName.contains(":")) { final AbstractTexture tex = tryLoadResourceTexture(new ResourceLocation(imageName)); diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/item/ochre_froglight.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/item/ochre_froglight.json new file mode 100644 index 000000000..9b7bc8b94 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/item/ochre_froglight.json @@ -0,0 +1,7 @@ +{ + "intensity": 1.0, + "red": 1.0, + "green": 1.0, + "blue": 0.6, + "worksInFluid": true +} diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/item/pearlescent_froglight.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/item/pearlescent_froglight.json new file mode 100644 index 000000000..5835a7e48 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/item/pearlescent_froglight.json @@ -0,0 +1,7 @@ +{ + "intensity": 1.0, + "red": 1.0, + "green": 0.6, + "blue": 0.9, + "worksInFluid": true +} diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/item/verdant_froglight.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/item/verdant_froglight.json new file mode 100644 index 000000000..b484907dc --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/item/verdant_froglight.json @@ -0,0 +1,7 @@ +{ + "intensity": 1.0, + "red": 0.6, + "green": 1.0, + "blue": 0.7, + "worksInFluid": true +} From 78750718c29bfeed348b6eaa9b8cb22a16cf6c1f Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Wed, 7 Jun 2023 05:35:38 +0700 Subject: [PATCH 17/69] Use ResourceCache for light registry --- .../grondag/canvas/light/color/LightRegistry.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java index 21f785bf0..624d40f26 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegistry.java +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -6,17 +6,18 @@ import net.minecraft.world.level.block.state.BlockState; import io.vram.frex.api.light.ItemLight; +import io.vram.frex.base.renderer.util.ResourceCache; public class LightRegistry { - private static final Int2ShortOpenHashMap lights = new Int2ShortOpenHashMap(); - - static { - lights.defaultReturnValue((short) 0); - } + private static final ResourceCache lights = new ResourceCache<>(() -> { + final var newMap = new Int2ShortOpenHashMap(); + newMap.defaultReturnValue((short) 0); + return newMap; + }); public static short get(BlockState blockState){ final int stateKey = blockState.hashCode(); - short light = lights.get(stateKey); + short light = lights.getOrLoad().get(stateKey); if (light == 0) { // PERF: modify ItemLight API or make new API that doesn't need ItemStack @@ -35,7 +36,7 @@ public static short get(BlockState blockState){ light = LightRegionData.Encoding.encodeLight(lightEmission, lightEmission, lightEmission, true, occluding); } - lights.put(stateKey, light); + lights.getOrLoad().put(stateKey, light); } return light; From 4c16822eb92ba1d7611b5f8d183dee9cb82fc333 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Wed, 7 Jun 2023 21:12:40 +0700 Subject: [PATCH 18/69] Set alpha (flags) to registered during restoration --- src/main/java/grondag/canvas/light/color/LightRegion.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 3a0380512..71ea36bff 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -295,8 +295,9 @@ private boolean propagateLight(BlockAndTintGetter blockView) { // restore obliterated light source if (restoreLightSource) { - short registeredLight = LightRegistry.get(nodeState); - nodeLight |= (registeredLight & mask); + // we delay putting the data until increase step as to not mess with decrease step + // take RGB of maximum and Alpha of registered + nodeLight = (short) ((nodeLight & 0xFFF0) | LightRegistry.get(nodeState)); } } From b4a26cd6ba4ebb4230faad2c51d7a652b265aa66 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Wed, 7 Jun 2023 22:31:47 +0700 Subject: [PATCH 19/69] Process decrease before increase frame-wide, and also twice --- .../canvas/light/color/LightDataManager.java | 100 ++++++++++++++---- .../canvas/light/color/LightRegion.java | 70 +++++------- .../canvas/terrain/region/RenderRegion.java | 2 +- 3 files changed, 107 insertions(+), 65 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 59b5bafbf..8aa1319bb 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -3,6 +3,8 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongIterable; +import it.unimi.dsi.fastutil.longs.LongIterator; import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockAndTintGetter; @@ -27,11 +29,50 @@ public class LightDataManager { // NB: must be even private int extentSizeInRegions = REGION_COUNT_LENGTH_WISE; private LightDataTexture texture; + ExtentIterable extentIterable = new ExtentIterable(); { allocated.defaultReturnValue(null); } + private class ExtentIterable implements LongIterable, LongIterator { + @Override + public LongIterator iterator() { + x = y = z = 0; + return this; + } + + int x, y, z, startX, startY, startZ; + + void set(int startX, int startY, int startZ) { + this.startX = startX; + this.startY = startY; + this.startZ = startZ; + } + + @Override + public long nextLong() { + final int extent = extentSizeInBlocks(1); + final long value = BlockPos.asLong(startX + x * extent, startY + y * extent, startZ + z * extent); + if (++z >= extentSizeInRegions) { + z = 0; + y++; + } + + if (y >= extentSizeInRegions) { + y = 0; + x++; + } + + return value; + } + + @Override + public boolean hasNext() { + return x < extentSizeInRegions; + } + } + // TODO: stuff public static void initialize() { @@ -64,34 +105,53 @@ public void update(BlockAndTintGetter blockView, int cameraX, int cameraY, int c cameraUninitialized = false; + extentIterable.set(extentStartBlockX, extentStartBlockY, extentStartBlockZ); + // update all regions within extent - for (int i = extentStartBlockX; i < extentStartBlockX + extentSizeInBlocks(); i += extentSizeInBlocks(1)) { - for (int j = extentStartBlockY; j < extentStartBlockY + extentSizeInBlocks(); j += extentSizeInBlocks(1)) { - for (int k = extentStartBlockZ; k < extentStartBlockZ + extentSizeInBlocks(); k += extentSizeInBlocks(1)) { - long index = BlockPos.asLong(i, j, k); - updateRegion(index, blockView); - } + for (long index:extentIterable) { + final LightRegion lightRegion = allocated.get(index); + + if (lightRegion != null && !lightRegion.isClosed()) { + lightRegion.updateDecrease(blockView); } } - } - private void updateRegion(long index, BlockAndTintGetter blockView) { - final LightRegion lightRegion = allocated.get(index); + // this is called twice because outstanding decreases needs to be processed before increases and it's really stupid + for (long index:extentIterable) { + final LightRegion lightRegion = allocated.get(index); - if (lightRegion == null || lightRegion.isClosed()) { - return; + if (lightRegion != null && !lightRegion.isClosed()) { + lightRegion.updateDecrease(blockView); + } } - lightRegion.update(blockView); + // as for this one, it's called twice to process outstanding increases in the same frame thus prevents flickering when placing light + for (long index:extentIterable) { + final LightRegion lightRegion = allocated.get(index); - if (lightRegion.lightData.isDirty() || debugRedrawEveryFrame) { - final int extentGridMask = extentSizeMask(); - final int x = lightRegion.lightData.regionOriginBlockX; - final int y = lightRegion.lightData.regionOriginBlockY; - final int z = lightRegion.lightData.regionOriginBlockZ; - // modulo into extent-grid - texture.upload(x & extentGridMask, y & extentGridMask, z & extentGridMask, lightRegion.lightData.getBuffer()); - lightRegion.lightData.clearDirty(); + if (lightRegion != null && !lightRegion.isClosed()) { + lightRegion.updateIncrease(blockView); + } + } + + for (long index: extentIterable) { + final LightRegion lightRegion = allocated.get(index); + + if (lightRegion == null || lightRegion.isClosed()) { + continue; + } + + lightRegion.updateIncrease(blockView); + + if (lightRegion.lightData.isDirty() || debugRedrawEveryFrame) { + final int extentGridMask = extentSizeMask(); + final int x = lightRegion.lightData.regionOriginBlockX; + final int y = lightRegion.lightData.regionOriginBlockY; + final int z = lightRegion.lightData.regionOriginBlockZ; + // modulo into extent-grid + texture.upload(x & extentGridMask, y & extentGridMask, z & extentGridMask, lightRegion.lightData.getBuffer()); + lightRegion.lightData.clearDirty(); + } } } diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 71ea36bff..dcfcee342 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -186,19 +186,20 @@ private boolean occludeSide(BlockState state, Side dir, BlockAndTintGetter view, return Shapes.faceShapeOccludes(Shapes.empty(), state.getFaceOcclusionShape(view, pos, dir.vanilla)); } - private boolean propagateLight(BlockAndTintGetter blockView) { - if (incQueue.isEmpty() && decQueue.isEmpty()) { - // CanvasMod.LOG.info("Nothing to process!"); - return false; + public void updateDecrease(BlockAndTintGetter blockView) { + if (needCheckEdges) { + checkEdges(blockView); + needCheckEdges = false; } - // CanvasMod.LOG.info("Processing queues.. inc,dec " + incQueue.size() + "," + decQueue.size()); + while (!globalDecQueue.isEmpty()) { + decQueue.enqueue(globalDecQueue.dequeueLong()); + } final BVec removeFlag = new BVec(); final BVec removeMask = new BVec(); int decCount = 0; - int incCount = 0; while(!decQueue.isEmpty()) { decCount++; @@ -308,6 +309,18 @@ private boolean propagateLight(BlockAndTintGetter blockView) { } } + if (decCount > 0) { + lightData.markAsDirty(); + } + } + + public void updateIncrease(BlockAndTintGetter blockView) { + while (!globalIncQueue.isEmpty()) { + incQueue.enqueue(globalIncQueue.dequeueLong()); + } + + int incCount = 0; + while (!incQueue.isEmpty()) { incCount++; @@ -401,9 +414,9 @@ private boolean propagateLight(BlockAndTintGetter blockView) { } } - // CanvasMod.LOG.info("Processed queues! Count: inc,dec " + incCount + "," + decCount); - - return decCount + incCount > 0; + if (incCount > 0) { + lightData.markAsDirty(); + } } private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourcePos, BlockPos.MutableBlockPos targetPos, Side side, BlockAndTintGetter blockView) { @@ -508,10 +521,8 @@ private void checkEdges(BlockAndTintGetter blockView) { final int z = searchOffsets[i]; final int zTarget = targetOffsets[i]; - searchPos.set(origin); - searchPos.setWithOffset(searchPos, 0, 0, z); - targetPos.set(origin); - targetPos.setWithOffset(targetPos, 0, 0, zTarget); + searchPos.setWithOffset(originPos, 0, 0, z); + targetPos.setWithOffset(originPos, 0, 0, zTarget); final Side side = Side.infer(searchPos, targetPos); final LightRegion neighbor = LightDataManager.INSTANCE.getFromBlock(searchPos); @@ -521,43 +532,14 @@ private void checkEdges(BlockAndTintGetter blockView) { for (int x = 0; x < size; x++) { for (int y = 0; y < size; y++) { - searchPos.set(origin); - searchPos.setWithOffset(searchPos, x, y, z); - targetPos.set(origin); - targetPos.setWithOffset(targetPos, x, y, zTarget); + searchPos.setWithOffset(originPos, x, y, z); + targetPos.setWithOffset(originPos, x, y, zTarget); checkEdgeBlock(neighbor, searchPos, targetPos, side, blockView); } } } } - public void update(BlockAndTintGetter blockView) { - if (needCheckEdges) { - checkEdges(blockView); - needCheckEdges = false; - } - - boolean propagating = !globalDecQueue.isEmpty() || !globalIncQueue.isEmpty(); - - while (!globalDecQueue.isEmpty()) { - decQueue.enqueue(globalDecQueue.dequeueLong()); - } - - while (!globalIncQueue.isEmpty()) { - incQueue.enqueue(globalIncQueue.dequeueLong()); - } - - boolean didPropagate = false; - - if (propagating) { - didPropagate = propagateLight(blockView); - } - - if (didPropagate) { - lightData.markAsDirty(); - } - } - public void close() { if (!lightData.isClosed()) { lightData.close(); diff --git a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java index 5e66dea47..8d1bc8aeb 100644 --- a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java +++ b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java @@ -174,7 +174,7 @@ void close() { if (!lightRegion.isClosed()) { LightDataManager.INSTANCE.deallocate(origin); - CanvasMod.LOG.info("called deallocate() from Render Region"); + // CanvasMod.LOG.info("called deallocate() from Render Region"); } } } From 0e976c945a8ae4bfbffa53405dcbaf276a5181d4 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Wed, 7 Jun 2023 22:41:32 +0700 Subject: [PATCH 20/69] Actually call decrease three times to cover corner neighbors --- .../grondag/canvas/light/color/LightDataManager.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 8aa1319bb..62de0a1d9 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -125,6 +125,15 @@ public void update(BlockAndTintGetter blockView, int cameraX, int cameraY, int c } } + // frick it, third time's the charm (covers corner neighbors) + for (long index:extentIterable) { + final LightRegion lightRegion = allocated.get(index); + + if (lightRegion != null && !lightRegion.isClosed()) { + lightRegion.updateDecrease(blockView); + } + } + // as for this one, it's called twice to process outstanding increases in the same frame thus prevents flickering when placing light for (long index:extentIterable) { final LightRegion lightRegion = allocated.get(index); From b06e3159bb9fcf5e39c878705954ec0d49586074 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Thu, 8 Jun 2023 18:35:27 +0700 Subject: [PATCH 21/69] Fix light placing and make consistent --- .../canvas/light/color/LightRegion.java | 20 +++++-------------- .../canvas/light/color/LightRegionData.java | 5 +++++ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index dcfcee342..90a464d5b 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -163,10 +163,7 @@ public void checkBlock(BlockPos pos, BlockState blockState) { final short getLight = lightData.get(index); final boolean occluding = blockState.canOcclude(); - less.lessThan(getLight, light); - - if (less.any()) { - lightData.put(index, light); + if (Encoding.isLightSource(light)) { Queues.enqueue(globalIncQueue, index, light); // CanvasMod.LOG.info("Add light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + blockState); } else if (light == 0 && (Encoding.isLightSource(getLight) || (Encoding.isOccluding(getLight) != occluding))) { @@ -296,9 +293,10 @@ public void updateDecrease(BlockAndTintGetter blockView) { // restore obliterated light source if (restoreLightSource) { - // we delay putting the data until increase step as to not mess with decrease step + // defer putting light source as to not mess with decrease step // take RGB of maximum and Alpha of registered - nodeLight = (short) ((nodeLight & 0xFFF0) | LightRegistry.get(nodeState)); + final short registered = LightRegistry.get(nodeState); + nodeLight = Elem.maxRGB(nodeLight, registered, Elem.A.of(registered)); } } @@ -333,7 +331,7 @@ public void updateIncrease(BlockAndTintGetter blockView) { if (sourceLight != recordedLight) { if (Encoding.isLightSource(recordedLight)) { - sourceLight = recordedLight; + sourceLight = Elem.maxRGB(sourceLight, recordedLight, Elem.A.of(recordedLight)); lightData.put(index, sourceLight); } else { continue; @@ -356,8 +354,6 @@ public void updateIncrease(BlockAndTintGetter blockView) { nodePos.setWithOffset(sourcePos, side.x, side.y, side.z); - // CanvasMod.LOG.info("increase at " + nodeX + "," + nodeY + "," + nodeZ); - final LightRegionData dataAccess; final LongPriorityQueue increaseQueue; boolean isNeighbor = !lightData.withinExtents(nodePos); @@ -386,8 +382,6 @@ public void updateIncrease(BlockAndTintGetter blockView) { continue; } - // CanvasMod.LOG.info("current/neighbor index " + index + "/" + nodeIndex); - less.lessThanMinusOne(nodeLight, sourceLight); if (less.any()) { @@ -406,9 +400,6 @@ public void updateIncrease(BlockAndTintGetter blockView) { } dataAccess.put(nodeIndex, resultLight); - - // CanvasMod.LOG.info("updating neighbor to: " + nodeX + "," + nodeY + "," + nodeZ + "," + Elem.text(resultLight)); - Queues.enqueue(increaseQueue, nodeIndex, resultLight, side); } } @@ -458,7 +449,6 @@ private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourc } lightData.put(targetIndex, resultLight); - Queues.enqueue(incQueue, targetIndex, resultLight, side); } } diff --git a/src/main/java/grondag/canvas/light/color/LightRegionData.java b/src/main/java/grondag/canvas/light/color/LightRegionData.java index c58086e19..54359c60a 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegionData.java +++ b/src/main/java/grondag/canvas/light/color/LightRegionData.java @@ -44,6 +44,11 @@ public static short encode(int r, int g, int b, int a) { return (short) ((r << R.shift) | (g << G.shift) | (b << B.shift) | (a << A.shift)); } + public static short maxRGB(short left, short right, int a) { + return (short) (Math.max(left & R.mask, right & R.mask) | Math.max(left & G.mask, right & G.mask) + | Math.max(left & B.mask, right & B.mask) | a); + } + public static String text(short light) { return "(" + R.of(light) + "," + G.of(light) + "," + B.of(light) + ")"; } From f1370baa5ebba86efd5b0af1ef160dc7512eea25 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sat, 10 Jun 2023 01:18:55 +0700 Subject: [PATCH 22/69] Block light API draft, improve item light fallback --- .../java/io/vram/canvas/CanvasFabricMod.java | 2 + .../canvas/light/color/BlockLight.java | 48 ++++ .../light/color/BlockLightRegistry.java | 219 ++++++++++++++++++ .../canvas/light/color/LightRegion.java | 19 +- .../canvas/light/color/LightRegistry.java | 62 +++-- 5 files changed, 320 insertions(+), 30 deletions(-) create mode 100644 src/main/java/grondag/canvas/light/color/BlockLight.java create mode 100644 src/main/java/grondag/canvas/light/color/BlockLightRegistry.java diff --git a/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java b/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java index 5cbcc1467..c044e0dab 100644 --- a/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java +++ b/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java @@ -32,6 +32,7 @@ import net.fabricmc.loader.api.FabricLoader; import grondag.canvas.CanvasMod; +import grondag.canvas.light.color.BlockLightRegistry; import grondag.canvas.pipeline.config.PipelineLoader; import grondag.canvas.texture.MaterialIndexProvider; @@ -66,6 +67,7 @@ public ResourceLocation getFabricId() { public void onResourceManagerReload(ResourceManager manager) { PipelineLoader.reload(manager); MaterialIndexProvider.reload(); + BlockLightRegistry.reload(manager); } }); } diff --git a/src/main/java/grondag/canvas/light/color/BlockLight.java b/src/main/java/grondag/canvas/light/color/BlockLight.java new file mode 100644 index 000000000..ad1b0aca4 --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/BlockLight.java @@ -0,0 +1,48 @@ +package grondag.canvas.light.color; + +/** + * BlockLight API draft. + *

+ * Similar to Material, this needs to be constructed by a factory provided by implementation. + */ +public interface BlockLight { + /** + * The light level. Typically, this represents the light radius after multiplied with the + * highest color component, but also affects maximum brightness. + *

+ * Implementation may choose whether to prioritize the radius aspect or brightness aspect. + *

+ * Typical value is in range 0-15. Value outside of this range is implementation-specific. + *

+ * In JSON format, defaults to the vanilla registered light level when missing. + * Importantly, light level is attached to blocks, so for fluid states + * (not their block counterpart) the default is always 0. + * + * @return Raw light level value + */ + float lightLevel(); + + /** + * Red intensity. Behavior of values outside of range 0-1 is undefined. + * In JSON format, defaults to 0 when missing. + * + * @return Raw red intensity + */ + float red(); + + /** + * Green intensity. Behavior of values outside of range 0-1 is undefined. + * In JSON format, defaults to 0 when missing. + * + * @return Raw green intensity + */ + float green(); + + /** + * Blue intensity. Behavior of values outside of range 0-1 is undefined. + * In JSON format, defaults to 0 when missing. + * + * @return Raw blue intensity + */ + float blue(); +} diff --git a/src/main/java/grondag/canvas/light/color/BlockLightRegistry.java b/src/main/java/grondag/canvas/light/color/BlockLightRegistry.java new file mode 100644 index 000000000..9d048a74b --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/BlockLightRegistry.java @@ -0,0 +1,219 @@ +package grondag.canvas.light.color; + +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.IdentityHashMap; +import java.util.List; + +import com.google.gson.JsonObject; + +import net.minecraft.client.renderer.block.BlockModelShaper; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateHolder; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; + +import grondag.canvas.CanvasMod; + +public class BlockLightRegistry { + static BlockLightRegistry INSTANCE; + private static final CachedBlockLight DEFAULT_LIGHT = new CachedBlockLight(0f, 0f, 0f, 0f); + + final IdentityHashMap blockLights = new IdentityHashMap<>(); + final IdentityHashMap fluidLights = new IdentityHashMap<>(); + + public static void reload(ResourceManager manager) { + INSTANCE = new BlockLightRegistry(); + + for (Block block : BuiltInRegistries.BLOCK) { + INSTANCE.loadBlock(manager, block); + } + + for (Fluid fluid : BuiltInRegistries.FLUID) { + INSTANCE.loadFluid(manager, fluid); + } + } + + private void loadBlock(ResourceManager manager, Block block) { + final ResourceLocation blockId = BuiltInRegistries.BLOCK.getKey(block); + + final ResourceLocation id = new ResourceLocation(blockId.getNamespace(), "lights/block/" + blockId.getPath() + ".json"); + + try { + final var res = manager.getResource(id); + + if (res.isPresent()) { + deserialize(block.getStateDefinition().getPossibleStates(), id, new InputStreamReader(res.get().open(), StandardCharsets.UTF_8), blockLights); + } + } catch (final Exception e) { + CanvasMod.LOG.info("Unable to load block light map " + id.toString() + " due to exception " + e.toString()); + } + } + + private void loadFluid(ResourceManager manager, Fluid fluid) { + final ResourceLocation blockId = BuiltInRegistries.FLUID.getKey(fluid); + + final ResourceLocation id = new ResourceLocation(blockId.getNamespace(), "lights/fluid/" + blockId.getPath() + ".json"); + + try { + final var res = manager.getResource(id); + + if (res.isPresent()) { + deserialize(fluid.getStateDefinition().getPossibleStates(), id, new InputStreamReader(res.get().open(), StandardCharsets.UTF_8), fluidLights); + } + } catch (final Exception e) { + CanvasMod.LOG.info("Unable to load fluid light map " + id.toString() + " due to exception " + e.toString()); + } + } + + public static > void deserialize(List states, ResourceLocation idForLog, InputStreamReader reader, IdentityHashMap map) { + try { + final JsonObject json = GsonHelper.parse(reader); + final String idString = idForLog.toString(); + + final CachedBlockLight globalDefaultLight = DEFAULT_LIGHT; + final CachedBlockLight defaultLight; + final boolean radiusIsUnset; + + if (json.has("defaultLight")) { + defaultLight = loadLight(json.get("defaultLight").getAsJsonObject(), globalDefaultLight); + radiusIsUnset = !json.get("defaultLight").getAsJsonObject().has("radius"); + } else { + defaultLight = globalDefaultLight; + radiusIsUnset = true; + } + + JsonObject variants = null; + + if (json.has("variants")) { + variants = json.getAsJsonObject("variants"); + + if (variants.isJsonNull()) { + CanvasMod.LOG.warn("Unable to load variant lights for " + idString + " because the 'variants' block is empty. Using default map."); + variants = null; + } + } + + for (final T state : states) { + CachedBlockLight result = defaultLight; + + if (radiusIsUnset && state instanceof BlockState blockState) { + result = result.with(blockState.getLightEmission()); + } + + if (variants != null) { + final String stateId = BlockModelShaper.statePropertiesToString(state.getValues()); + result = loadLight(variants.getAsJsonObject(stateId), result); + } + + if (state instanceof BlockState blockState) { + result = result.with(blockState.canOcclude()); + } + + if (!result.equals(globalDefaultLight)) { + map.put(state, result); + } + } + } catch (final Exception e) { + CanvasMod.LOG.warn("Unable to load lights for " + idForLog.toString() + " due to unhandled exception:", e); + } + } + + public static CachedBlockLight loadLight(JsonObject obj, CachedBlockLight defaultValue) { + if (obj == null) { + return defaultValue; + } + + final var radiusObj = obj.get("radius"); + final var redObj = obj.get("red"); + final var greenObj = obj.get("green"); + final var blueObj = obj.get("blue"); + + final float radius = radiusObj == null ? defaultValue.lightLevel() : radiusObj.getAsFloat(); + final float red = redObj == null ? defaultValue.red() : redObj.getAsFloat(); + final float green = greenObj == null ? defaultValue.green() : greenObj.getAsFloat(); + final float blue = blueObj == null ? defaultValue.blue() : blueObj.getAsFloat(); + final var result = new CachedBlockLight(radius, red, green, blue); + + if (result.equals(defaultValue)) { + return defaultValue; + } else { + return result; + } + } + + private static int clampLight(float light) { + return org.joml.Math.clamp(0, 15, Math.round(light)); + } + + static record CachedBlockLight(float lightLevel, float red, float green, float blue, short value) implements BlockLight { + CachedBlockLight(float radius, float red, float green, float blue) { + this(radius, red, green, blue, computeValue(radius, red, green, blue)); + } + + CachedBlockLight with(float lightEmission) { + if (this.lightLevel == lightEmission) { + return this; + } else { + return new CachedBlockLight(lightEmission, red, green, blue); + } + } + + CachedBlockLight with(boolean isOccluding) { + if (isOccluding == LightRegionData.Encoding.isOccluding(value)) { + return this; + } + + final short rgb = LightRegionData.Encoding.pure(value); + final short newValue = computeValue(rgb, isOccluding); + + return new CachedBlockLight(lightLevel, red, green, blue, newValue); + } + + static short computeValue(float radius, float red, float green, float blue) { + final int blockRadius = radius == 0f ? 0 : org.joml.Math.clamp(1, 15, Math.round(radius)); + final short rgb = LightRegionData.Elem.encode(clampLight(blockRadius * red), clampLight(blockRadius * green), clampLight(blockRadius * blue), 0); + return computeValue(rgb, false); + } + + private static short computeValue(int rgb, boolean isOccluding) { + return LightRegionData.Encoding.encodeLight(rgb, rgb != 0, isOccluding); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + CachedBlockLight that = (CachedBlockLight) obj; + + if (that.lightLevel != lightLevel) { + return false; + } + + if (that.red != red) { + return false; + } + + if (that.green != green) { + return false; + } + + if (that.blue != blue) { + return false; + } + + return value == that.value; + } + } +} diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 90a464d5b..d63ea0073 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -153,23 +153,18 @@ public void checkBlock(BlockPos pos, BlockState blockState) { return; } - short light = 0; - - if (blockState.getLightEmission() > 0) { - light = LightRegistry.get(blockState); - } - + final short registeredLight = LightRegistry.get(blockState); final int index = lightData.indexify(pos); final short getLight = lightData.get(index); final boolean occluding = blockState.canOcclude(); - if (Encoding.isLightSource(light)) { - Queues.enqueue(globalIncQueue, index, light); - // CanvasMod.LOG.info("Add light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + blockState); - } else if (light == 0 && (Encoding.isLightSource(getLight) || (Encoding.isOccluding(getLight) != occluding))) { - lightData.put(index, Encoding.encodeLight(0, false, occluding)); + if (Encoding.isLightSource(registeredLight)) { + Queues.enqueue(globalIncQueue, index, registeredLight); + // CanvasMod.LOG.info("Add light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(registeredLight) + " block: " + blockState); + } else if (Encoding.isLightSource(getLight) || Encoding.isOccluding(getLight) != occluding) { + lightData.put(index, registeredLight); Queues.enqueue(globalDecQueue, index, getLight); - // CanvasMod.LOG.info("Remove light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(light) + " block: " + blockState); + // CanvasMod.LOG.info("Remove light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(registeredLight) + " block: " + blockState); } } diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java index 624d40f26..54c735137 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegistry.java +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -1,6 +1,6 @@ package grondag.canvas.light.color; -import it.unimi.dsi.fastutil.ints.Int2ShortOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ShortOpenHashMap; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.state.BlockState; @@ -8,37 +8,63 @@ import io.vram.frex.api.light.ItemLight; import io.vram.frex.base.renderer.util.ResourceCache; +import grondag.canvas.light.color.LightRegionData.Encoding; + public class LightRegistry { - private static final ResourceCache lights = new ResourceCache<>(() -> { - final var newMap = new Int2ShortOpenHashMap(); + private static final ResourceCache> cachedLights = new ResourceCache<>(() -> { + final var newMap = new Object2ShortOpenHashMap(); newMap.defaultReturnValue((short) 0); return newMap; }); public static short get(BlockState blockState){ - final int stateKey = blockState.hashCode(); - short light = lights.getOrLoad().get(stateKey); + if (BlockLightRegistry.INSTANCE == null) { + // TODO: WIP: remove this for production and fail silently.. + throw new IllegalStateException("BlockLightRegistry is unloaded"); + } + + BlockLightRegistry.CachedBlockLight getLight = BlockLightRegistry.INSTANCE.blockLights.get(blockState); + + if (getLight == null && !blockState.getFluidState().isEmpty()) { + getLight = BlockLightRegistry.INSTANCE.fluidLights.get(blockState.getFluidState()); + } + + if (getLight != null) { + return getLight.value(); + } else if (blockState.getLightEmission() <= 0) { + return Encoding.encodeLight(0, false, blockState.canOcclude()); + } - if (light == 0) { - // PERF: modify ItemLight API or make new API that doesn't need ItemStack - // TODO: ItemLight API isn't suitable for this anyway since we rely on blockstates not blocks/items + // CanvasMod.LOG.info("Can't find cached light for block state " + blockState); + + // Item Light color-only fallback (feature?) + + if (!cachedLights.getOrLoad().containsKey(blockState)) { final ItemStack stack = new ItemStack(blockState.getBlock(), 1); final ItemLight itemLight = ItemLight.get(stack); - final int lightEmission = blockState.getLightEmission(); + final int lightLevel = blockState.getLightEmission(); final boolean occluding = blockState.canOcclude(); + final short defaultLight = Encoding.encodeLight(lightLevel, lightLevel, lightLevel, true, occluding); + + if (itemLight == null) { + cachedLights.getOrLoad().put(blockState, defaultLight); + return defaultLight; + } + + float maxValue = Math.max(itemLight.red(), Math.max(itemLight.green(), itemLight.blue())); - if (itemLight != null) { - final int r = (int) (lightEmission * itemLight.red()); - final int g = (int) (lightEmission * itemLight.green()); - final int b = (int) (lightEmission * itemLight.blue()); - light = LightRegionData.Encoding.encodeLight(r, g, b, true, occluding); - } else { - light = LightRegionData.Encoding.encodeLight(lightEmission, lightEmission, lightEmission, true, occluding); + if (maxValue <= 0) { + cachedLights.getOrLoad().put(blockState, defaultLight); + return defaultLight; } - lights.getOrLoad().put(stateKey, light); + final int r = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.red() / maxValue)); + final int g = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.green() / maxValue)); + final int b = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.blue() / maxValue)); + + cachedLights.getOrLoad().put(blockState, Encoding.encodeLight(r, g, b, true, occluding)); } - return light; + return cachedLights.getOrLoad().getShort(blockState); } } From 8106230f16ccedaa877787a798f01d63dff9cf4a Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sat, 10 Jun 2023 02:03:19 +0700 Subject: [PATCH 23/69] Fix fluid occlusion value, refactor - API registry only holds pure RGB - Always cache all light - Restructure and separate "API" from internal code --- .../java/io/vram/canvas/CanvasFabricMod.java | 4 +- .../light/{color => api}/BlockLight.java | 2 +- .../impl/BlockLightLoader.java} | 50 +++++-------- .../java/grondag/canvas/light/color/Elem.java | 39 ++++++++++ .../canvas/light/color/LightDataTexture.java | 1 - .../canvas/light/color/LightRegion.java | 1 - .../canvas/light/color/LightRegionData.java | 43 +---------- .../canvas/light/color/LightRegistry.java | 72 ++++++++----------- 8 files changed, 91 insertions(+), 121 deletions(-) rename src/main/java/grondag/canvas/light/{color => api}/BlockLight.java (97%) rename src/main/java/grondag/canvas/light/{color/BlockLightRegistry.java => api/impl/BlockLightLoader.java} (78%) create mode 100644 src/main/java/grondag/canvas/light/color/Elem.java diff --git a/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java b/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java index c044e0dab..714205171 100644 --- a/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java +++ b/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java @@ -32,7 +32,7 @@ import net.fabricmc.loader.api.FabricLoader; import grondag.canvas.CanvasMod; -import grondag.canvas.light.color.BlockLightRegistry; +import grondag.canvas.light.api.impl.BlockLightLoader; import grondag.canvas.pipeline.config.PipelineLoader; import grondag.canvas.texture.MaterialIndexProvider; @@ -67,7 +67,7 @@ public ResourceLocation getFabricId() { public void onResourceManagerReload(ResourceManager manager) { PipelineLoader.reload(manager); MaterialIndexProvider.reload(); - BlockLightRegistry.reload(manager); + BlockLightLoader.reload(manager); } }); } diff --git a/src/main/java/grondag/canvas/light/color/BlockLight.java b/src/main/java/grondag/canvas/light/api/BlockLight.java similarity index 97% rename from src/main/java/grondag/canvas/light/color/BlockLight.java rename to src/main/java/grondag/canvas/light/api/BlockLight.java index ad1b0aca4..b08741138 100644 --- a/src/main/java/grondag/canvas/light/color/BlockLight.java +++ b/src/main/java/grondag/canvas/light/api/BlockLight.java @@ -1,4 +1,4 @@ -package grondag.canvas.light.color; +package grondag.canvas.light.api; /** * BlockLight API draft. diff --git a/src/main/java/grondag/canvas/light/color/BlockLightRegistry.java b/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java similarity index 78% rename from src/main/java/grondag/canvas/light/color/BlockLightRegistry.java rename to src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java index 9d048a74b..4e2e8ced0 100644 --- a/src/main/java/grondag/canvas/light/color/BlockLightRegistry.java +++ b/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java @@ -1,4 +1,4 @@ -package grondag.canvas.light.color; +package grondag.canvas.light.api.impl; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; @@ -19,16 +19,18 @@ import net.minecraft.world.level.material.FluidState; import grondag.canvas.CanvasMod; +import grondag.canvas.light.api.BlockLight; +import grondag.canvas.light.color.Elem; -public class BlockLightRegistry { - static BlockLightRegistry INSTANCE; +public class BlockLightLoader { + public static BlockLightLoader INSTANCE; private static final CachedBlockLight DEFAULT_LIGHT = new CachedBlockLight(0f, 0f, 0f, 0f); - final IdentityHashMap blockLights = new IdentityHashMap<>(); - final IdentityHashMap fluidLights = new IdentityHashMap<>(); + public final IdentityHashMap blockLights = new IdentityHashMap<>(); + public final IdentityHashMap fluidLights = new IdentityHashMap<>(); public static void reload(ResourceManager manager) { - INSTANCE = new BlockLightRegistry(); + INSTANCE = new BlockLightLoader(); for (Block block : BuiltInRegistries.BLOCK) { INSTANCE.loadBlock(manager, block); @@ -78,14 +80,14 @@ private void loadFluid(ResourceManager manager, Fluid fluid) { final CachedBlockLight globalDefaultLight = DEFAULT_LIGHT; final CachedBlockLight defaultLight; - final boolean radiusIsUnset; + final boolean levelIsUnset; if (json.has("defaultLight")) { defaultLight = loadLight(json.get("defaultLight").getAsJsonObject(), globalDefaultLight); - radiusIsUnset = !json.get("defaultLight").getAsJsonObject().has("radius"); + levelIsUnset = !json.get("defaultLight").getAsJsonObject().has("radius"); } else { defaultLight = globalDefaultLight; - radiusIsUnset = true; + levelIsUnset = true; } JsonObject variants = null; @@ -102,8 +104,8 @@ private void loadFluid(ResourceManager manager, Fluid fluid) { for (final T state : states) { CachedBlockLight result = defaultLight; - if (radiusIsUnset && state instanceof BlockState blockState) { - result = result.with(blockState.getLightEmission()); + if (levelIsUnset && state instanceof BlockState blockState) { + result = result.withLevel(blockState.getLightEmission()); } if (variants != null) { @@ -111,10 +113,6 @@ private void loadFluid(ResourceManager manager, Fluid fluid) { result = loadLight(variants.getAsJsonObject(stateId), result); } - if (state instanceof BlockState blockState) { - result = result.with(blockState.canOcclude()); - } - if (!result.equals(globalDefaultLight)) { map.put(state, result); } @@ -151,12 +149,12 @@ private static int clampLight(float light) { return org.joml.Math.clamp(0, 15, Math.round(light)); } - static record CachedBlockLight(float lightLevel, float red, float green, float blue, short value) implements BlockLight { + public static record CachedBlockLight(float lightLevel, float red, float green, float blue, short value) implements BlockLight { CachedBlockLight(float radius, float red, float green, float blue) { this(radius, red, green, blue, computeValue(radius, red, green, blue)); } - CachedBlockLight with(float lightEmission) { + CachedBlockLight withLevel(float lightEmission) { if (this.lightLevel == lightEmission) { return this; } else { @@ -164,25 +162,9 @@ CachedBlockLight with(float lightEmission) { } } - CachedBlockLight with(boolean isOccluding) { - if (isOccluding == LightRegionData.Encoding.isOccluding(value)) { - return this; - } - - final short rgb = LightRegionData.Encoding.pure(value); - final short newValue = computeValue(rgb, isOccluding); - - return new CachedBlockLight(lightLevel, red, green, blue, newValue); - } - static short computeValue(float radius, float red, float green, float blue) { final int blockRadius = radius == 0f ? 0 : org.joml.Math.clamp(1, 15, Math.round(radius)); - final short rgb = LightRegionData.Elem.encode(clampLight(blockRadius * red), clampLight(blockRadius * green), clampLight(blockRadius * blue), 0); - return computeValue(rgb, false); - } - - private static short computeValue(int rgb, boolean isOccluding) { - return LightRegionData.Encoding.encodeLight(rgb, rgb != 0, isOccluding); + return Elem.encode(clampLight(blockRadius * red), clampLight(blockRadius * green), clampLight(blockRadius * blue), 0); } @Override diff --git a/src/main/java/grondag/canvas/light/color/Elem.java b/src/main/java/grondag/canvas/light/color/Elem.java new file mode 100644 index 000000000..0b58a6eca --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/Elem.java @@ -0,0 +1,39 @@ +package grondag.canvas.light.color; + +public enum Elem { + R(0xF000, 12, 0), + G(0x0F00, 8, 1), + B(0x00F0, 4, 2), + A(0x000F, 0, 3); + + public final int mask; + public final int shift; + public final int pos; + + Elem(int mask, int shift, int pos) { + this.mask = mask; + this.shift = shift; + this.pos = pos; + } + + public int of(short light) { + return (light >> shift) & 0xF; + } + + public short replace(short source, short elemLight) { + return (short) ((source & ~mask) | (elemLight << shift)); + } + + public static short encode(int r, int g, int b, int a) { + return (short) ((r << R.shift) | (g << G.shift) | (b << B.shift) | (a << A.shift)); + } + + public static short maxRGB(short left, short right, int a) { + return (short) (Math.max(left & R.mask, right & R.mask) | Math.max(left & G.mask, right & G.mask) + | Math.max(left & B.mask, right & B.mask) | a); + } + + public static String text(short light) { + return "(" + R.of(light) + "," + G.of(light) + "," + B.of(light) + ")"; + } +} diff --git a/src/main/java/grondag/canvas/light/color/LightDataTexture.java b/src/main/java/grondag/canvas/light/color/LightDataTexture.java index ead3174a2..e0c38d7a0 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataTexture.java +++ b/src/main/java/grondag/canvas/light/color/LightDataTexture.java @@ -11,7 +11,6 @@ import grondag.canvas.varia.GFX; public class LightDataTexture { - public static class Format { public static int target = GFX.GL_TEXTURE_3D; public static int pixelBytes = 2; diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index d63ea0073..fc378d22d 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -10,7 +10,6 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.shapes.Shapes; -import grondag.canvas.light.color.LightRegionData.Elem; import grondag.canvas.light.color.LightRegionData.Encoding; // TODO: cluster slab allocation? -> maybe unneeded now? diff --git a/src/main/java/grondag/canvas/light/color/LightRegionData.java b/src/main/java/grondag/canvas/light/color/LightRegionData.java index 54359c60a..3ffd9b7c1 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegionData.java +++ b/src/main/java/grondag/canvas/light/color/LightRegionData.java @@ -6,8 +6,7 @@ import net.minecraft.core.BlockPos; -public class LightRegionData { - +class LightRegionData { public static class Const { public static final int WIDTH = 16; public static final int SIZE3D = WIDTH * WIDTH * WIDTH; @@ -16,45 +15,7 @@ public static class Const { public static final int WIDTH_MASK = WIDTH - 1; } - public static enum Elem { - R(0xF000, 12, 0), - G(0x0F00, 8, 1), - B(0x00F0, 4, 2), - A(0x000F, 0, 3); - - public final int mask; - public final int shift; - public final int pos; - - Elem(int mask, int shift, int pos) { - this.mask = mask; - this.shift = shift; - this.pos = pos; - } - - public int of(short light) { - return (light >> shift) & 0xF; - } - - public short replace(short source, short elemLight) { - return (short) ((source & ~mask) | (elemLight << shift)); - } - - public static short encode(int r, int g, int b, int a) { - return (short) ((r << R.shift) | (g << G.shift) | (b << B.shift) | (a << A.shift)); - } - - public static short maxRGB(short left, short right, int a) { - return (short) (Math.max(left & R.mask, right & R.mask) | Math.max(left & G.mask, right & G.mask) - | Math.max(left & B.mask, right & B.mask) | a); - } - - public static String text(short light) { - return "(" + R.of(light) + "," + G.of(light) + "," + B.of(light) + ")"; - } - } - - public static class Encoding { + static class Encoding { public static short encodeLight(int r, int g, int b, boolean isLightSource, boolean isOccluding) { return Elem.encode(r, g, b, (isLightSource ? 0b1 : 0) | (isOccluding ? 0b10 : 0)); } diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java index 54c735137..2dd372506 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegistry.java +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -8,63 +8,53 @@ import io.vram.frex.api.light.ItemLight; import io.vram.frex.base.renderer.util.ResourceCache; +import grondag.canvas.CanvasMod; import grondag.canvas.light.color.LightRegionData.Encoding; +import grondag.canvas.light.api.impl.BlockLightLoader; -public class LightRegistry { - private static final ResourceCache> cachedLights = new ResourceCache<>(() -> { - final var newMap = new Object2ShortOpenHashMap(); - newMap.defaultReturnValue((short) 0); - return newMap; - }); +class LightRegistry { + private static final ResourceCache> cachedLights = new ResourceCache<>(Object2ShortOpenHashMap::new); public static short get(BlockState blockState){ - if (BlockLightRegistry.INSTANCE == null) { - // TODO: WIP: remove this for production and fail silently.. - throw new IllegalStateException("BlockLightRegistry is unloaded"); - } + return cachedLights.getOrLoad().computeIfAbsent(blockState, LightRegistry::generate); + } + + private static short generate(BlockState blockState) { + final int lightLevel = blockState.getLightEmission(); + final short defaultLight = Encoding.encodeLight(lightLevel, lightLevel, lightLevel, lightLevel > 0, blockState.canOcclude()); - BlockLightRegistry.CachedBlockLight getLight = BlockLightRegistry.INSTANCE.blockLights.get(blockState); + BlockLightLoader.CachedBlockLight apiLight = BlockLightLoader.INSTANCE.blockLights.get(blockState); - if (getLight == null && !blockState.getFluidState().isEmpty()) { - getLight = BlockLightRegistry.INSTANCE.fluidLights.get(blockState.getFluidState()); + if (apiLight == null && !blockState.getFluidState().isEmpty()) { + apiLight = BlockLightLoader.INSTANCE.fluidLights.get(blockState.getFluidState()); } - if (getLight != null) { - return getLight.value(); - } else if (blockState.getLightEmission() <= 0) { - return Encoding.encodeLight(0, false, blockState.canOcclude()); + if (apiLight != null) { + return Encoding.encodeLight(apiLight.value(), apiLight.value() != 0, blockState.canOcclude()); } - // CanvasMod.LOG.info("Can't find cached light for block state " + blockState); + if (lightLevel < 1) { + return defaultLight; + } // Item Light color-only fallback (feature?) + final ItemStack stack = new ItemStack(blockState.getBlock(), 1); + final ItemLight itemLight = ItemLight.get(stack); - if (!cachedLights.getOrLoad().containsKey(blockState)) { - final ItemStack stack = new ItemStack(blockState.getBlock(), 1); - final ItemLight itemLight = ItemLight.get(stack); - final int lightLevel = blockState.getLightEmission(); - final boolean occluding = blockState.canOcclude(); - final short defaultLight = Encoding.encodeLight(lightLevel, lightLevel, lightLevel, true, occluding); - - if (itemLight == null) { - cachedLights.getOrLoad().put(blockState, defaultLight); - return defaultLight; - } - - float maxValue = Math.max(itemLight.red(), Math.max(itemLight.green(), itemLight.blue())); - - if (maxValue <= 0) { - cachedLights.getOrLoad().put(blockState, defaultLight); - return defaultLight; - } + if (itemLight == null) { + return defaultLight; + } - final int r = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.red() / maxValue)); - final int g = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.green() / maxValue)); - final int b = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.blue() / maxValue)); + float maxValue = Math.max(itemLight.red(), Math.max(itemLight.green(), itemLight.blue())); - cachedLights.getOrLoad().put(blockState, Encoding.encodeLight(r, g, b, true, occluding)); + if (maxValue <= 0) { + return defaultLight; } - return cachedLights.getOrLoad().getShort(blockState); + final int r = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.red() / maxValue)); + final int g = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.green() / maxValue)); + final int b = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.blue() / maxValue)); + + return Encoding.encodeLight(r, g, b, true, blockState.canOcclude()); } } From b130eaa1434994612eb6f4a0670b7c7bdb535aa0 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sat, 10 Jun 2023 02:08:36 +0700 Subject: [PATCH 24/69] Make light registry thread safe --- .../java/io/vram/canvas/CanvasFabricMod.java | 3 ++- .../canvas/light/color/LightRegistry.java | 16 ++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java b/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java index 714205171..9f466f936 100644 --- a/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java +++ b/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java @@ -33,6 +33,7 @@ import grondag.canvas.CanvasMod; import grondag.canvas.light.api.impl.BlockLightLoader; +import grondag.canvas.light.color.LightRegistry; import grondag.canvas.pipeline.config.PipelineLoader; import grondag.canvas.texture.MaterialIndexProvider; @@ -67,7 +68,7 @@ public ResourceLocation getFabricId() { public void onResourceManagerReload(ResourceManager manager) { PipelineLoader.reload(manager); MaterialIndexProvider.reload(); - BlockLightLoader.reload(manager); + LightRegistry.reload(manager); } }); } diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java index 2dd372506..e52524e2b 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegistry.java +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -1,22 +1,26 @@ package grondag.canvas.light.color; -import it.unimi.dsi.fastutil.objects.Object2ShortOpenHashMap; +import java.util.concurrent.ConcurrentHashMap; +import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.state.BlockState; import io.vram.frex.api.light.ItemLight; -import io.vram.frex.base.renderer.util.ResourceCache; -import grondag.canvas.CanvasMod; import grondag.canvas.light.color.LightRegionData.Encoding; import grondag.canvas.light.api.impl.BlockLightLoader; -class LightRegistry { - private static final ResourceCache> cachedLights = new ResourceCache<>(Object2ShortOpenHashMap::new); +public class LightRegistry { + private static final ConcurrentHashMap cachedLights = new ConcurrentHashMap<>(); + + public static void reload(ResourceManager manager) { + cachedLights.clear(); + BlockLightLoader.reload(manager); + } public static short get(BlockState blockState){ - return cachedLights.getOrLoad().computeIfAbsent(blockState, LightRegistry::generate); + return cachedLights.computeIfAbsent(blockState, LightRegistry::generate); } private static short generate(BlockState blockState) { From cc30e9d3d2afe3051aefb76157e598971482dd29 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sat, 10 Jun 2023 02:26:32 +0700 Subject: [PATCH 25/69] Whoops --- .../grondag/canvas/light/api/impl/BlockLightLoader.java | 6 +++--- src/main/java/grondag/canvas/light/color/LightRegistry.java | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java b/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java index 4e2e8ced0..c1d43ec08 100644 --- a/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java +++ b/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java @@ -127,16 +127,16 @@ public static CachedBlockLight loadLight(JsonObject obj, CachedBlockLight defaul return defaultValue; } - final var radiusObj = obj.get("radius"); + final var lightLevelObj = obj.get("lightLevel"); final var redObj = obj.get("red"); final var greenObj = obj.get("green"); final var blueObj = obj.get("blue"); - final float radius = radiusObj == null ? defaultValue.lightLevel() : radiusObj.getAsFloat(); + final float lightLevel = lightLevelObj == null ? defaultValue.lightLevel() : lightLevelObj.getAsFloat(); final float red = redObj == null ? defaultValue.red() : redObj.getAsFloat(); final float green = greenObj == null ? defaultValue.green() : greenObj.getAsFloat(); final float blue = blueObj == null ? defaultValue.blue() : blueObj.getAsFloat(); - final var result = new CachedBlockLight(radius, red, green, blue); + final var result = new CachedBlockLight(lightLevel, red, green, blue); if (result.equals(defaultValue)) { return defaultValue; diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java index e52524e2b..68ef8f4d7 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegistry.java +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -20,6 +20,7 @@ public static void reload(ResourceManager manager) { } public static short get(BlockState blockState){ + // maybe just populate it during reload? don't want to slow down resource reload though return cachedLights.computeIfAbsent(blockState, LightRegistry::generate); } From 996468bad7bbdb0a00735e8726d2ec065694865c Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sat, 10 Jun 2023 03:33:03 +0700 Subject: [PATCH 26/69] Account for state change --- .../canvas/light/color/LightRegion.java | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index fc378d22d..95757285c 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -7,6 +7,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.shapes.Shapes; @@ -158,12 +159,20 @@ public void checkBlock(BlockPos pos, BlockState blockState) { final boolean occluding = blockState.canOcclude(); if (Encoding.isLightSource(registeredLight)) { - Queues.enqueue(globalIncQueue, index, registeredLight); - // CanvasMod.LOG.info("Add light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(registeredLight) + " block: " + blockState); + if (getLight != registeredLight) { + // replace light + if (Encoding.isLightSource(getLight)) { + lightData.put(index, (short) 0); + Queues.enqueue(globalDecQueue, index, getLight); + } + + // place light + Queues.enqueue(globalIncQueue, index, registeredLight); + } } else if (Encoding.isLightSource(getLight) || Encoding.isOccluding(getLight) != occluding) { + // remove light or replace occluder lightData.put(index, registeredLight); Queues.enqueue(globalDecQueue, index, getLight); - // CanvasMod.LOG.info("Remove light at " + pos + " light is (get,put) " + Elem.text(getLight) + "," + Elem.text(registeredLight) + " block: " + blockState); } } @@ -214,9 +223,9 @@ public void updateDecrease(BlockAndTintGetter blockView) { } // check self occlusion for decrease - if (!Encoding.isOccluding(sourceCurrentLight) && occludeSide(sourceState, side, blockView, sourcePos)) { - continue; - } + // if (!Encoding.isOccluding(sourceCurrentLight) && occludeSide(sourceState, side, blockView, sourcePos)) { + // continue; + // } nodePos.setWithOffset(sourcePos, side.x, side.y, side.z); From 2ae11a1dca4aa80b9a32486fc0219a1d1464ac2e Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sat, 10 Jun 2023 19:36:46 +0700 Subject: [PATCH 27/69] Keep doing decrease until it's done - Remove decrease self occlusion check completely, Occluding blocks should be able to propagate decrease outwards after all. - Add more filtering for increase queuing - Some simplifications --- .../canvas/light/color/LightDataManager.java | 40 +++++------------- .../canvas/light/color/LightRegion.java | 41 +++++++++++-------- 2 files changed, 35 insertions(+), 46 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 62de0a1d9..5bb993b68 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -107,45 +107,25 @@ public void update(BlockAndTintGetter blockView, int cameraX, int cameraY, int c extentIterable.set(extentStartBlockX, extentStartBlockY, extentStartBlockZ); - // update all regions within extent - for (long index:extentIterable) { - final LightRegion lightRegion = allocated.get(index); - - if (lightRegion != null && !lightRegion.isClosed()) { - lightRegion.updateDecrease(blockView); - } - } + boolean needUpdate = true; - // this is called twice because outstanding decreases needs to be processed before increases and it's really stupid - for (long index:extentIterable) { - final LightRegion lightRegion = allocated.get(index); - - if (lightRegion != null && !lightRegion.isClosed()) { - lightRegion.updateDecrease(blockView); - } - } + // process all active regions' decrease queue until none is left + while (needUpdate) { + needUpdate = false; - // frick it, third time's the charm (covers corner neighbors) - for (long index:extentIterable) { - final LightRegion lightRegion = allocated.get(index); + for (long index : extentIterable) { + final LightRegion lightRegion = allocated.get(index); - if (lightRegion != null && !lightRegion.isClosed()) { - lightRegion.updateDecrease(blockView); + if (lightRegion != null && !lightRegion.isClosed()) { + needUpdate = needUpdate || lightRegion.updateDecrease(blockView); + } } } - // as for this one, it's called twice to process outstanding increases in the same frame thus prevents flickering when placing light + // update all regions within extent for (long index:extentIterable) { final LightRegion lightRegion = allocated.get(index); - if (lightRegion != null && !lightRegion.isClosed()) { - lightRegion.updateIncrease(blockView); - } - } - - for (long index: extentIterable) { - final LightRegion lightRegion = allocated.get(index); - if (lightRegion == null || lightRegion.isClosed()) { continue; } diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 95757285c..ce9268871 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -7,7 +7,6 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.world.level.BlockAndTintGetter; -import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.shapes.Shapes; @@ -42,22 +41,31 @@ boolean all() { return r && g && b; } + BVec not() { + r = !r; + g = !g; + b = !b; + return this; + } + void lessThan(short left, short right) { r = Elem.R.of(left) < Elem.R.of(right); g = Elem.G.of(left) < Elem.G.of(right); b = Elem.B.of(left) < Elem.B.of(right); } - void lessThanMinusOne(short left, short right) { + BVec lessThanMinusOne(short left, short right) { r = Elem.R.of(left) < Elem.R.of(right) - 1; g = Elem.G.of(left) < Elem.G.of(right) - 1; b = Elem.B.of(left) < Elem.B.of(right) - 1; + return this; } - void and(BVec other, BVec another) { + BVec and(BVec other, BVec another) { r = other.r && another.r; g = other.g && another.g; b = other.b && another.b; + return this; } } @@ -186,12 +194,17 @@ private boolean occludeSide(BlockState state, Side dir, BlockAndTintGetter view, return Shapes.faceShapeOccludes(Shapes.empty(), state.getFaceOcclusionShape(view, pos, dir.vanilla)); } - public void updateDecrease(BlockAndTintGetter blockView) { + public boolean updateDecrease(BlockAndTintGetter blockView) { if (needCheckEdges) { checkEdges(blockView); needCheckEdges = false; } + // faster exit when not necessary + if (globalDecQueue.isEmpty()) { + return false; + } + while (!globalDecQueue.isEmpty()) { decQueue.enqueue(globalDecQueue.dequeueLong()); } @@ -200,6 +213,7 @@ public void updateDecrease(BlockAndTintGetter blockView) { final BVec removeMask = new BVec(); int decCount = 0; + boolean accessedNeighborDecrease = false; while(!decQueue.isEmpty()) { decCount++; @@ -222,11 +236,6 @@ public void updateDecrease(BlockAndTintGetter blockView) { continue; } - // check self occlusion for decrease - // if (!Encoding.isOccluding(sourceCurrentLight) && occludeSide(sourceState, side, blockView, sourcePos)) { - // continue; - // } - nodePos.setWithOffset(sourcePos, side.x, side.y, side.z); final LightRegionData dataAccess; @@ -301,9 +310,11 @@ public void updateDecrease(BlockAndTintGetter blockView) { final short registered = LightRegistry.get(nodeState); nodeLight = Elem.maxRGB(nodeLight, registered, Elem.A.of(registered)); } + + accessedNeighborDecrease = accessedNeighborDecrease || isNeighbor; } - if (!less.all() || restoreLightSource) { + if (removeMask.and(less.not(), removeFlag).any() || restoreLightSource) { // increases queued in decrease may propagate to all directions as if a light source Queues.enqueue(increaseQueue, nodeIndex, nodeLight); } @@ -313,6 +324,8 @@ public void updateDecrease(BlockAndTintGetter blockView) { if (decCount > 0) { lightData.markAsDirty(); } + + return accessedNeighborDecrease; } public void updateIncrease(BlockAndTintGetter blockView) { @@ -385,9 +398,7 @@ public void updateIncrease(BlockAndTintGetter blockView) { continue; } - less.lessThanMinusOne(nodeLight, sourceLight); - - if (less.any()) { + if (less.lessThanMinusOne(nodeLight, sourceLight).any()) { short resultLight = nodeLight; if (less.r) { @@ -434,9 +445,7 @@ private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourc return; } - less.lessThanMinusOne(targetLight, sourceLight); - - if (less.any()) { + if (less.lessThanMinusOne(targetLight, sourceLight).any()) { short resultLight = targetLight; if (less.r) { From 3051e47f5e1bb9d61824b2c4bc0c4cf3912d0855 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sat, 10 Jun 2023 20:19:06 +0700 Subject: [PATCH 28/69] Set light level fallback dynamically --- .../light/api/impl/BlockLightLoader.java | 26 +++++++++---------- .../canvas/light/color/LightDataManager.java | 1 + .../canvas/light/color/LightRegistry.java | 4 +++ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java b/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java index c1d43ec08..0dffc0d5e 100644 --- a/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java +++ b/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java @@ -24,7 +24,7 @@ public class BlockLightLoader { public static BlockLightLoader INSTANCE; - private static final CachedBlockLight DEFAULT_LIGHT = new CachedBlockLight(0f, 0f, 0f, 0f); + private static final CachedBlockLight DEFAULT_LIGHT = new CachedBlockLight(0f, 0f, 0f, 0f, false); public final IdentityHashMap blockLights = new IdentityHashMap<>(); public final IdentityHashMap fluidLights = new IdentityHashMap<>(); @@ -80,14 +80,11 @@ private void loadFluid(ResourceManager manager, Fluid fluid) { final CachedBlockLight globalDefaultLight = DEFAULT_LIGHT; final CachedBlockLight defaultLight; - final boolean levelIsUnset; if (json.has("defaultLight")) { defaultLight = loadLight(json.get("defaultLight").getAsJsonObject(), globalDefaultLight); - levelIsUnset = !json.get("defaultLight").getAsJsonObject().has("radius"); } else { defaultLight = globalDefaultLight; - levelIsUnset = true; } JsonObject variants = null; @@ -104,7 +101,7 @@ private void loadFluid(ResourceManager manager, Fluid fluid) { for (final T state : states) { CachedBlockLight result = defaultLight; - if (levelIsUnset && state instanceof BlockState blockState) { + if (!result.levelIsSet && state instanceof BlockState blockState) { result = result.withLevel(blockState.getLightEmission()); } @@ -136,7 +133,8 @@ public static CachedBlockLight loadLight(JsonObject obj, CachedBlockLight defaul final float red = redObj == null ? defaultValue.red() : redObj.getAsFloat(); final float green = greenObj == null ? defaultValue.green() : greenObj.getAsFloat(); final float blue = blueObj == null ? defaultValue.blue() : blueObj.getAsFloat(); - final var result = new CachedBlockLight(lightLevel, red, green, blue); + final boolean levelIsSet = lightLevelObj == null ? defaultValue.levelIsSet() : true; + final var result = new CachedBlockLight(lightLevel, red, green, blue, levelIsSet); if (result.equals(defaultValue)) { return defaultValue; @@ -149,21 +147,21 @@ private static int clampLight(float light) { return org.joml.Math.clamp(0, 15, Math.round(light)); } - public static record CachedBlockLight(float lightLevel, float red, float green, float blue, short value) implements BlockLight { - CachedBlockLight(float radius, float red, float green, float blue) { - this(radius, red, green, blue, computeValue(radius, red, green, blue)); + public static record CachedBlockLight(float lightLevel, float red, float green, float blue, short value, boolean levelIsSet) implements BlockLight { + CachedBlockLight(float lightLevel, float red, float green, float blue, boolean levelIsSet) { + this(lightLevel, red, green, blue, computeValue(lightLevel, red, green, blue), levelIsSet); } - CachedBlockLight withLevel(float lightEmission) { - if (this.lightLevel == lightEmission) { + public CachedBlockLight withLevel(float lightEmission) { + if (this.lightLevel == lightEmission && this.levelIsSet) { return this; } else { - return new CachedBlockLight(lightEmission, red, green, blue); + return new CachedBlockLight(lightEmission, red, green, blue, true); } } - static short computeValue(float radius, float red, float green, float blue) { - final int blockRadius = radius == 0f ? 0 : org.joml.Math.clamp(1, 15, Math.round(radius)); + static short computeValue(float lightLevel, float red, float green, float blue) { + final int blockRadius = lightLevel == 0f ? 0 : org.joml.Math.clamp(1, 15, Math.round(lightLevel)); return Elem.encode(clampLight(blockRadius * red), clampLight(blockRadius * green), clampLight(blockRadius * blue), 0); } diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 5bb993b68..89c70c77f 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -109,6 +109,7 @@ public void update(BlockAndTintGetter blockView, int cameraX, int cameraY, int c boolean needUpdate = true; + // TODO: account for extent-edge chunks? // process all active regions' decrease queue until none is left while (needUpdate) { needUpdate = false; diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java index 68ef8f4d7..d5a28cd66 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegistry.java +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -35,6 +35,10 @@ private static short generate(BlockState blockState) { } if (apiLight != null) { + if (!apiLight.levelIsSet()) { + apiLight = apiLight.withLevel(blockState.getLightEmission()); + } + return Encoding.encodeLight(apiLight.value(), apiLight.value() != 0, blockState.canOcclude()); } From 1cc7e371536a26203ef7596146aff75dccd6ac70 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Thu, 29 Jun 2023 03:21:36 +0700 Subject: [PATCH 29/69] Add license --- .../grondag/canvas/light/api/BlockLight.java | 20 +++++++++++++++++++ .../light/api/impl/BlockLightLoader.java | 20 +++++++++++++++++++ .../java/grondag/canvas/light/color/Elem.java | 20 +++++++++++++++++++ .../canvas/light/color/LightDataManager.java | 20 +++++++++++++++++++ .../canvas/light/color/LightDataTexture.java | 20 +++++++++++++++++++ .../canvas/light/color/LightRegion.java | 20 +++++++++++++++++++ .../canvas/light/color/LightRegionData.java | 20 +++++++++++++++++++ .../canvas/light/color/LightRegistry.java | 20 +++++++++++++++++++ 8 files changed, 160 insertions(+) diff --git a/src/main/java/grondag/canvas/light/api/BlockLight.java b/src/main/java/grondag/canvas/light/api/BlockLight.java index b08741138..3052e3f59 100644 --- a/src/main/java/grondag/canvas/light/api/BlockLight.java +++ b/src/main/java/grondag/canvas/light/api/BlockLight.java @@ -1,3 +1,23 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + package grondag.canvas.light.api; /** diff --git a/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java b/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java index 0dffc0d5e..83181ef8a 100644 --- a/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java +++ b/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java @@ -1,3 +1,23 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + package grondag.canvas.light.api.impl; import java.io.InputStreamReader; diff --git a/src/main/java/grondag/canvas/light/color/Elem.java b/src/main/java/grondag/canvas/light/color/Elem.java index 0b58a6eca..87c0339c5 100644 --- a/src/main/java/grondag/canvas/light/color/Elem.java +++ b/src/main/java/grondag/canvas/light/color/Elem.java @@ -1,3 +1,23 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + package grondag.canvas.light.color; public enum Elem { diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 89c70c77f..1d832c1ad 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -1,3 +1,23 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + package grondag.canvas.light.color; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; diff --git a/src/main/java/grondag/canvas/light/color/LightDataTexture.java b/src/main/java/grondag/canvas/light/color/LightDataTexture.java index e0c38d7a0..cad64437e 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataTexture.java +++ b/src/main/java/grondag/canvas/light/color/LightDataTexture.java @@ -1,3 +1,23 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + package grondag.canvas.light.color; import java.nio.ByteBuffer; diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index ce9268871..71f6ae72a 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -1,3 +1,23 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + package grondag.canvas.light.color; import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; diff --git a/src/main/java/grondag/canvas/light/color/LightRegionData.java b/src/main/java/grondag/canvas/light/color/LightRegionData.java index 3ffd9b7c1..34f342968 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegionData.java +++ b/src/main/java/grondag/canvas/light/color/LightRegionData.java @@ -1,3 +1,23 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + package grondag.canvas.light.color; import java.nio.ByteBuffer; diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java index d5a28cd66..8aa54d46c 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegistry.java +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -1,3 +1,23 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + package grondag.canvas.light.color; import java.util.concurrent.ConcurrentHashMap; From 9166f3157accc4306fe449cb85d6a4e37f192d64 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Thu, 29 Jun 2023 03:23:48 +0700 Subject: [PATCH 30/69] Try letting pipeline define light data image --- .../grondag/canvas/apiimpl/CanvasState.java | 3 +- .../canvas/light/color/LightDataManager.java | 236 +++++++++++------- .../canvas/light/color/LightDataTexture.java | 60 ++--- .../canvas/light/color/LightRegion.java | 6 +- .../canvas/light/color/LightRegionAccess.java | 21 ++ .../canvas/pipeline/ProgramTextureData.java | 6 +- .../pipeline/config/LightVolumeConfig.java | 62 +++++ .../pipeline/config/PipelineConfig.java | 3 + .../config/PipelineConfigBuilder.java | 10 + .../render/world/CanvasWorldRenderer.java | 2 +- .../canvas/terrain/region/RenderRegion.java | 10 +- 11 files changed, 264 insertions(+), 155 deletions(-) create mode 100644 src/main/java/grondag/canvas/light/color/LightRegionAccess.java create mode 100644 src/main/java/grondag/canvas/pipeline/config/LightVolumeConfig.java diff --git a/src/main/java/grondag/canvas/apiimpl/CanvasState.java b/src/main/java/grondag/canvas/apiimpl/CanvasState.java index 06e29f0c9..4f859514a 100644 --- a/src/main/java/grondag/canvas/apiimpl/CanvasState.java +++ b/src/main/java/grondag/canvas/apiimpl/CanvasState.java @@ -34,6 +34,7 @@ import grondag.canvas.material.property.TextureMaterialState; import grondag.canvas.perf.ChunkRebuildCounters; import grondag.canvas.perf.Timekeeper; +import grondag.canvas.pipeline.Pipeline; import grondag.canvas.pipeline.PipelineManager; import grondag.canvas.pipeline.config.PipelineLoader; import grondag.canvas.shader.GlMaterialProgramManager; @@ -53,9 +54,9 @@ public static void recompileIfNeeded(boolean forceRecompile) { if (forceRecompile) { CanvasMod.LOG.info(I18n.get("info.canvas.recompile")); - LightDataManager.initialize(); PipelineLoader.reload(Minecraft.getInstance().getResourceManager()); PipelineManager.reload(); + LightDataManager.reload(); PreReleaseShaderCompat.reload(); MaterialProgram.reload(); GlShaderManager.INSTANCE.reload(); diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 1d832c1ad..5963fc896 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -30,100 +30,118 @@ import net.minecraft.world.level.BlockAndTintGetter; import grondag.canvas.CanvasMod; +import grondag.canvas.pipeline.Image; +import grondag.canvas.pipeline.Pipeline; public class LightDataManager { - // NB: must be even - private static final int REGION_COUNT_LENGTH_WISE = 32; private static final boolean debugRedrawEveryFrame = false; - // private static final int INITIAL_LIMIT = REGION_COUNT_LENGTH_WISE * REGION_COUNT_LENGTH_WISE * REGION_COUNT_LENGTH_WISE; - public static final LightDataManager INSTANCE = new LightDataManager(); + static LightDataManager INSTANCE; + + public static LightRegionAccess allocate(BlockPos regionOrigin) { + if (INSTANCE == null) { + return LightRegionAccess.EMPTY; + } + + return INSTANCE.allocateInner(regionOrigin); + } + + public static void reload() { + if (Pipeline.config().lightVolume != null) { + final var image = Pipeline.getImage(Pipeline.config().lightVolume.lightImage.name); + + if (INSTANCE == null) { + INSTANCE = new LightDataManager(image); + } else { + INSTANCE.resize(image); + } + + CanvasMod.LOG.info("Light volume is enabled"); + } else { + if (INSTANCE != null) { + INSTANCE.close(); + INSTANCE = null; + } + + CanvasMod.LOG.info("Light volume is disabled"); + } + } + + public static void free(BlockPos regionOrigin) { + if (INSTANCE != null) { + INSTANCE.freeInner(regionOrigin); + } + } + + public static void update(BlockAndTintGetter blockView, int cameraX, int cameraY, int cameraZ) { + if (INSTANCE != null) { + INSTANCE.updateInner(blockView, cameraX, cameraY, cameraZ); + } + } private final Long2ObjectMap allocated = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); private int extentStartBlockX = 0; private int extentStartBlockY = 0; private int extentStartBlockZ = 0; + + private int extentGridMaskX; + private int extentGridMaskY; + private int extentGridMaskZ; + + private int extentSizeX; + private int extentSizeY; + private int extentSizeZ; + private boolean cameraUninitialized = true; + private boolean extentWasResized = false; - // NB: must be even - private int extentSizeInRegions = REGION_COUNT_LENGTH_WISE; + private int extentSizeXInRegions = 0; + private int extentSizeYInRegions = 0; + private int extentSizeZInRegions = 0; private LightDataTexture texture; ExtentIterable extentIterable = new ExtentIterable(); - { + public LightDataManager(Image image) { allocated.defaultReturnValue(null); + init(image); } - private class ExtentIterable implements LongIterable, LongIterator { - @Override - public LongIterator iterator() { - x = y = z = 0; - return this; - } - - int x, y, z, startX, startY, startZ; + private void resize(Image image) { + init(image); + extentWasResized = true; + } - void set(int startX, int startY, int startZ) { - this.startX = startX; - this.startY = startY; - this.startZ = startZ; - } + private void init(Image image) { + // TODO: for some reason it's not working properly when width =/= depth (height is fine) + extentSizeXInRegions = image.config.width / 16; + extentSizeYInRegions = image.config.height / 16; + extentSizeZInRegions = image.config.depth / 16; - @Override - public long nextLong() { - final int extent = extentSizeInBlocks(1); - final long value = BlockPos.asLong(startX + x * extent, startY + y * extent, startZ + z * extent); - if (++z >= extentSizeInRegions) { - z = 0; - y++; - } - - if (y >= extentSizeInRegions) { - y = 0; - x++; - } + extentGridMaskX = extentSizeMask(extentSizeXInRegions); + extentGridMaskY = extentSizeMask(extentSizeYInRegions); + extentGridMaskZ = extentSizeMask(extentSizeZInRegions); - return value; - } - - @Override - public boolean hasNext() { - return x < extentSizeInRegions; - } - } - - // TODO: stuff - public static void initialize() { + extentSizeX = extentSizeInBlocks(extentSizeXInRegions); + extentSizeY = extentSizeInBlocks(extentSizeYInRegions); + extentSizeZ = extentSizeInBlocks(extentSizeZInRegions); + texture = new LightDataTexture(image); } - public void update(BlockAndTintGetter blockView, int cameraX, int cameraY, int cameraZ) { - if (texture == null) { - initializeTexture(); - } - + private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, int cameraZ) { final int regionSnapMask = ~LightRegionData.Const.WIDTH_MASK; - final int halfRadius = extentSizeInBlocks(extentSizeInRegions / 2); - final int prevExtentX = extentStartBlockX; - final int prevExtentY = extentStartBlockY; - final int prevExtentZ = extentStartBlockZ; + final int prevExtentBlockX = extentStartBlockX; + final int prevExtentBlockY = extentStartBlockY; + final int prevExtentBlockZ = extentStartBlockZ; // snap camera position to the nearest region (chunk) - extentStartBlockX = (cameraX & regionSnapMask) - halfRadius; - extentStartBlockY = (cameraY & regionSnapMask) - halfRadius; - extentStartBlockZ = (cameraZ & regionSnapMask) - halfRadius; - - if (!cameraUninitialized - && (extentStartBlockX != prevExtentX || extentStartBlockY != prevExtentY || extentStartBlockZ != prevExtentZ)) { - //TODO: IMPORTANT: re-draw newly entered regions - //TODO: if newly entered region is null, clear using dummy (empty) lightDataRegion - //TODO: cleanup dummy lightDataRegion in close() - CanvasMod.LOG.info("Extent have changed"); - } + extentStartBlockX = (cameraX & regionSnapMask) - extentSizeInBlocks(extentSizeXInRegions / 2); + extentStartBlockY = (cameraY & regionSnapMask) - extentSizeInBlocks(extentSizeYInRegions / 2); + extentStartBlockZ = (cameraZ & regionSnapMask) - extentSizeInBlocks(extentSizeZInRegions / 2); - cameraUninitialized = false; + boolean extentMoved = extentStartBlockX != prevExtentBlockX || extentStartBlockY != prevExtentBlockY || extentStartBlockZ != prevExtentBlockZ; extentIterable.set(extentStartBlockX, extentStartBlockY, extentStartBlockZ); @@ -143,6 +161,8 @@ public void update(BlockAndTintGetter blockView, int cameraX, int cameraY, int c } } + boolean shouldRedraw = !cameraUninitialized && extentMoved; + // update all regions within extent for (long index:extentIterable) { final LightRegion lightRegion = allocated.get(index); @@ -153,19 +173,31 @@ public void update(BlockAndTintGetter blockView, int cameraX, int cameraY, int c lightRegion.updateIncrease(blockView); - if (lightRegion.lightData.isDirty() || debugRedrawEveryFrame) { - final int extentGridMask = extentSizeMask(); - final int x = lightRegion.lightData.regionOriginBlockX; - final int y = lightRegion.lightData.regionOriginBlockY; - final int z = lightRegion.lightData.regionOriginBlockZ; + boolean outsidePrev = false; + + final int x = lightRegion.lightData.regionOriginBlockX; + final int y = lightRegion.lightData.regionOriginBlockY; + final int z = lightRegion.lightData.regionOriginBlockZ; + + if (shouldRedraw) { + // Redraw regions that just entered the current-frame extent + outsidePrev |= x < prevExtentBlockX || x >= (prevExtentBlockX + extentSizeX); + outsidePrev |= y < prevExtentBlockY || y >= (prevExtentBlockY + extentSizeY); + outsidePrev |= z < prevExtentBlockZ || z >= (prevExtentBlockZ + extentSizeZ); + } + + if (lightRegion.lightData.isDirty() || outsidePrev || extentWasResized || debugRedrawEveryFrame) { // modulo into extent-grid - texture.upload(x & extentGridMask, y & extentGridMask, z & extentGridMask, lightRegion.lightData.getBuffer()); + texture.upload(x & extentGridMaskX, y & extentGridMaskY, z & extentGridMaskZ, lightRegion.lightData.getBuffer()); lightRegion.lightData.clearDirty(); } } + + extentWasResized = false; + cameraUninitialized = false; } - public LightRegion getFromBlock(BlockPos blockPos) { + LightRegion getFromBlock(BlockPos blockPos) { final long key = BlockPos.asLong( blockPos.getX() & ~LightRegionData.Const.WIDTH_MASK, blockPos.getY() & ~LightRegionData.Const.WIDTH_MASK, @@ -173,7 +205,7 @@ public LightRegion getFromBlock(BlockPos blockPos) { return allocated.get(key); } - public void deallocate(BlockPos regionOrigin) { + private void freeInner(BlockPos regionOrigin) { final LightRegion lightRegion = allocated.get(regionOrigin.asLong()); if (lightRegion != null && !lightRegion.isClosed()) { @@ -183,9 +215,9 @@ public void deallocate(BlockPos regionOrigin) { allocated.remove(regionOrigin.asLong()); } - public LightRegion allocate(BlockPos regionOrigin) { + private LightRegion allocateInner(BlockPos regionOrigin) { if (allocated.containsKey(regionOrigin.asLong())) { - deallocate(regionOrigin); + freeInner(regionOrigin); } final LightRegion lightRegion = new LightRegion(regionOrigin); @@ -198,16 +230,8 @@ private int extentSizeInBlocks(int extentSize) { return extentSize * LightRegionData.Const.WIDTH; } - private int extentSizeInBlocks() { - return extentSizeInBlocks(extentSizeInRegions); - } - - private int extentSizeMask() { - return extentSizeInBlocks() - 1; - } - - private void initializeTexture() { - texture = new LightDataTexture(extentSizeInBlocks()); + private int extentSizeMask(int extentSize) { + return extentSizeInBlocks(extentSize) - 1; } public void close() { @@ -224,19 +248,41 @@ public void close() { } } - public int getTexture(String imageName) { - if (imageName.equals("canvas:alpha/light_data")) { - if (texture == null) { - initializeTexture(); + private class ExtentIterable implements LongIterable, LongIterator { + @Override + public LongIterator iterator() { + x = y = z = 0; + return this; + } + + int x, y, z, startX, startY, startZ; + + void set(int startX, int startY, int startZ) { + this.startX = startX; + this.startY = startY; + this.startZ = startZ; + } + + @Override + public long nextLong() { + final int extent = extentSizeInBlocks(1); + final long value = BlockPos.asLong(startX + x * extent, startY + y * extent, startZ + z * extent); + if (++z >= extentSizeZInRegions) { + z = 0; + y++; + } + + if (y >= extentSizeYInRegions) { + y = 0; + x++; } - return texture.getTexId(); + return value; } - return -1; + @Override + public boolean hasNext() { + return x < extentSizeZInRegions; + } } - - // public void queueUpdate(LightRegion lightRegion) { - // updateQueue.enqueue(lightRegion.origin); - // } } diff --git a/src/main/java/grondag/canvas/light/color/LightDataTexture.java b/src/main/java/grondag/canvas/light/color/LightDataTexture.java index cad64437e..52dd49177 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataTexture.java +++ b/src/main/java/grondag/canvas/light/color/LightDataTexture.java @@ -22,11 +22,9 @@ import java.nio.ByteBuffer; -import org.lwjgl.system.MemoryUtil; - -import com.mojang.blaze3d.platform.TextureUtil; import com.mojang.blaze3d.systems.RenderSystem; +import grondag.canvas.pipeline.Image; import grondag.canvas.render.CanvasTextureState; import grondag.canvas.varia.GFX; @@ -39,42 +37,26 @@ public static class Format { public static int pixelDataType = GFX.GL_UNSIGNED_SHORT_4_4_4_4; } - private int glTexId = -1; - final int size; - - LightDataTexture(int size) { - this.size = size; - - glTexId = TextureUtil.generateTextureId(); - - CanvasTextureState.bindTexture(Format.target, glTexId); - GFX.objectLabel(GFX.GL_TEXTURE, glTexId, "IMG light_section_volume"); - - GFX.texParameter(Format.target, GFX.GL_TEXTURE_MIN_FILTER, GFX.GL_LINEAR); - GFX.texParameter(Format.target, GFX.GL_TEXTURE_MAG_FILTER, GFX.GL_LINEAR); - GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_S, GFX.GL_CLAMP_TO_EDGE); - GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_T, GFX.GL_CLAMP_TO_EDGE); - GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_R, GFX.GL_CLAMP_TO_EDGE); + private final Image image; - // allocate - GFX.texImage3D(Format.target, 0, Format.internalFormat, size, size, size, 0, Format.pixelFormat, Format.pixelDataType, null); + LightDataTexture(Image image) { + this.image = image; - ByteBuffer clearer = MemoryUtil.memAlloc(size * size * size * Format.pixelBytes); + // ByteBuffer clearer = MemoryUtil.memAlloc(image.config.width * image.config.height * image.config.depth * Format.pixelBytes); + // + // while (clearer.position() < clearer.limit()) { + // clearer.putShort((short) 0); + // } - while (clearer.position() < clearer.limit()) { - clearer.putShort((short) 0); - } + // // clear?? NOTE: this is wrong + // upload(0, 0, 0, clearer); - // clear?? - upload(0, 0, 0, clearer); - - clearer.position(0); - MemoryUtil.memFree(clearer); + // clearer.position(0); + // MemoryUtil.memFree(clearer); } public void close() { - TextureUtil.releaseTextureId(glTexId); - glTexId = -1; + // Image closing is already handled by pipeline manager } public void upload(int x, int y, int z, ByteBuffer buffer) { @@ -82,13 +64,9 @@ public void upload(int x, int y, int z, ByteBuffer buffer) { } public void upload(int x, int y, int z, int regionSize, ByteBuffer buffer) { - if (glTexId == -1) { - throw new IllegalStateException("Uploading to a deleted texture!"); - } - RenderSystem.assertOnRenderThread(); - CanvasTextureState.bindTexture(LightDataTexture.Format.target, glTexId); + CanvasTextureState.bindTexture(LightDataTexture.Format.target, image.glId()); // Gotta clean up some states, otherwise will cause memory access violation GFX.pixelStore(GFX.GL_UNPACK_SKIP_PIXELS, 0); @@ -101,12 +79,4 @@ public void upload(int x, int y, int z, int regionSize, ByteBuffer buffer) { GFX.glTexSubImage3D(Format.target, 0, x, y, z, regionSize, regionSize, regionSize, Format.pixelFormat, Format.pixelDataType, buffer); } - - public int getTexId() { - if (glTexId == -1) { - throw new IllegalStateException("Trying to access a deleted Light Data texture!"); - } - - return glTexId; - } } diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 71f6ae72a..1f4921611 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -34,7 +34,7 @@ // TODO: cluster slab allocation? -> maybe unneeded now? // TODO: a way to repopulate cluster if needed -public class LightRegion { +class LightRegion implements LightRegionAccess{ private static class BVec { boolean r, g, b; @@ -176,6 +176,7 @@ static short light(long entry) { this.lightData = new LightRegionData(origin.getX(), origin.getY(), origin.getZ()); } + @Override public void checkBlock(BlockPos pos, BlockState blockState) { if (!lightData.withinExtents(pos)) { return; @@ -214,7 +215,7 @@ private boolean occludeSide(BlockState state, Side dir, BlockAndTintGetter view, return Shapes.faceShapeOccludes(Shapes.empty(), state.getFaceOcclusionShape(view, pos, dir.vanilla)); } - public boolean updateDecrease(BlockAndTintGetter blockView) { + boolean updateDecrease(BlockAndTintGetter blockView) { if (needCheckEdges) { checkEdges(blockView); needCheckEdges = false; @@ -568,6 +569,7 @@ public void close() { } } + @Override public boolean isClosed() { return lightData == null || lightData.isClosed(); } diff --git a/src/main/java/grondag/canvas/light/color/LightRegionAccess.java b/src/main/java/grondag/canvas/light/color/LightRegionAccess.java new file mode 100644 index 000000000..d894e9839 --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/LightRegionAccess.java @@ -0,0 +1,21 @@ +package grondag.canvas.light.color; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.state.BlockState; + +public interface LightRegionAccess { + LightRegionAccess EMPTY = new Empty(); + + void checkBlock(BlockPos pos, BlockState blockState); + boolean isClosed(); + + class Empty implements LightRegionAccess { + @Override + public void checkBlock(BlockPos pos, BlockState blockState) { } + + @Override + public boolean isClosed() { + return true; + } + } +} diff --git a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java index 098a6bf9e..1fd74b316 100644 --- a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java +++ b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java @@ -47,12 +47,8 @@ public ProgramTextureData(NamedDependency[] samplerImages) { int imageBind = 0; int bindTarget = GL46.GL_TEXTURE_2D; - int lightDataTex = LightDataManager.INSTANCE.getTexture(imageName); - if (lightDataTex != -1) { - imageBind = lightDataTex; - bindTarget = LightDataTexture.Format.target; - } else if (imageName.contains(":")) { + if (imageName.contains(":")) { final AbstractTexture tex = tryLoadResourceTexture(new ResourceLocation(imageName)); if (tex != null) { diff --git a/src/main/java/grondag/canvas/pipeline/config/LightVolumeConfig.java b/src/main/java/grondag/canvas/pipeline/config/LightVolumeConfig.java new file mode 100644 index 000000000..c45072750 --- /dev/null +++ b/src/main/java/grondag/canvas/pipeline/config/LightVolumeConfig.java @@ -0,0 +1,62 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.pipeline.config; + +import blue.endless.jankson.JsonObject; + +import grondag.canvas.light.color.LightDataTexture; +import grondag.canvas.pipeline.GlSymbolLookup; +import grondag.canvas.pipeline.config.util.AbstractConfig; +import grondag.canvas.pipeline.config.util.ConfigContext; +import grondag.canvas.pipeline.config.util.NamedDependency; + +public class LightVolumeConfig extends AbstractConfig { + public final NamedDependency lightImage; + + protected LightVolumeConfig(ConfigContext ctx, JsonObject config) { + super(ctx); + lightImage = ctx.images.dependOn(ctx.dynamic.getString(config, "lightImage")); + } + + @Override + public boolean validate() { + final var image = lightImage.value(); + + if (image != null) { + boolean valid = image.validate(); + + valid &= assertAndWarn(image.target == LightDataTexture.Format.target, "Invalid pipeline config for image %s. Light data image needs to target %s", image.name, GlSymbolLookup.reverseLookup(LightDataTexture.Format.target)); + valid &= assertAndWarn(image.internalFormat == LightDataTexture.Format.internalFormat, "Invalid pipeline config for image %s. Light data image needs to have internal format of %s", image.name, GlSymbolLookup.reverseLookup(LightDataTexture.Format.internalFormat)); + valid &= assertAndWarn(image.pixelFormat == LightDataTexture.Format.pixelFormat, "Invalid pipeline config for image %s. Light data image needs to have pixel format of %s", image.name, GlSymbolLookup.reverseLookup(LightDataTexture.Format.pixelFormat)); + valid &= assertAndWarn(image.pixelDataType == LightDataTexture.Format.pixelDataType, "Invalid pipeline config for image %s. Light data image needs to have pixel data type of %s", image.name, GlSymbolLookup.reverseLookup(LightDataTexture.Format.pixelDataType)); + valid &= assertAndWarn(image.width > 0 && image.height > 0 && image.depth > 0, "Invalid pipeline config for image %s. Light data image needs to have non-zero width, height, and depth", image.name); + valid &= assertAndWarn(image.width % 16 == 0 && image.height % 16 == 0 && image.depth % 16 == 0, "Invalid pipeline config for image %s. Light data image needs to have width, height, and depth that are multiples of 16", image.name); + + return valid; + } + + if (lightImage.name == null) { + return assertAndWarn(false, "Invalid pipeline light volume config. Light image is unspecified."); + } else { + return assertAndWarn(false, "Invalid pipeline light volume config. Image %s doesn't exist", lightImage.name); + } + } +} diff --git a/src/main/java/grondag/canvas/pipeline/config/PipelineConfig.java b/src/main/java/grondag/canvas/pipeline/config/PipelineConfig.java index 215ec85fa..c346d7320 100644 --- a/src/main/java/grondag/canvas/pipeline/config/PipelineConfig.java +++ b/src/main/java/grondag/canvas/pipeline/config/PipelineConfig.java @@ -54,6 +54,7 @@ public class PipelineConfig { @Nullable public final DrawTargetsConfig drawTargets; @Nullable public final SkyShadowConfig skyShadow; @Nullable public final SkyConfig sky; + @Nullable public final LightVolumeConfig lightVolume; public final NamedDependency defaultFramebuffer; @@ -83,6 +84,7 @@ private PipelineConfig() { fabulosity = null; skyShadow = null; sky = null; + lightVolume = null; drawTargets = DrawTargetsConfig.makeDefault(context); defaultFramebuffer = context.frameBuffers.dependOn("default"); materialProgram = new MaterialProgramConfig(context); @@ -105,6 +107,7 @@ private PipelineConfig() { drawTargets = builder.drawTargets; skyShadow = builder.skyShadow; sky = builder.sky; + lightVolume = builder.lightVolume; for (final OptionConfig opt : builder.options) { optionMap.put(opt.includeToken, opt); diff --git a/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java b/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java index cae59bd40..47f23dd52 100644 --- a/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java +++ b/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java @@ -63,6 +63,7 @@ public class PipelineConfigBuilder { @Nullable public DrawTargetsConfig drawTargets; @Nullable public SkyShadowConfig skyShadow; @Nullable public SkyConfig sky; + @Nullable public LightVolumeConfig lightVolume; public boolean smoothBrightnessBidirectionaly = false; public int brightnessSmoothingFrames = 20; @@ -140,6 +141,14 @@ public void load(JsonObject configJson) { } } + if (configJson.containsKey("lightVolume")) { + if (lightVolume == null) { + lightVolume = LoadHelper.loadObject(context, configJson, "lightVolume", LightVolumeConfig::new); + } else { + CanvasMod.LOG.warn("Invalid pipeline config - duplicate 'lightVolume' ignored."); + } + } + if (configJson.containsKey("sky")) { if (sky == null) { sky = LoadHelper.loadObject(context, configJson, "sky", SkyConfig::new); @@ -184,6 +193,7 @@ public boolean validate() { valid &= (fabulosity == null || fabulosity.validate()); valid &= (skyShadow == null || skyShadow.validate()); + valid &= (lightVolume == null || lightVolume.validate()); valid &= defaultFramebuffer != null && defaultFramebuffer.validate("Invalid pipeline config - missing or invalid defaultFramebuffer."); diff --git a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java index 4b83406f1..3a1f282c0 100644 --- a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java +++ b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java @@ -377,7 +377,7 @@ public void renderWorld(PoseStack viewMatrixStack, float tickDelta, long frameSt Lighting.setupLevel(MatrixData.viewMatrix); } - LightDataManager.INSTANCE.update(world, (int) frameCameraX, (int) frameCameraY, (int) frameCameraZ); + LightDataManager.update(world, (int) frameCameraX, (int) frameCameraY, (int) frameCameraZ); WorldRenderDraws.profileSwap(profiler, ProfilerGroup.StartWorld, "before_entities_event"); diff --git a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java index 8d1bc8aeb..4ba94d97d 100644 --- a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java +++ b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java @@ -48,12 +48,11 @@ import io.vram.frex.api.math.MatrixStack; import io.vram.frex.api.model.fluid.FluidModel; -import grondag.canvas.CanvasMod; import grondag.canvas.apiimpl.rendercontext.CanvasTerrainRenderContext; import grondag.canvas.buffer.input.DrawableVertexCollector; import grondag.canvas.buffer.input.VertexCollectorList; import grondag.canvas.light.color.LightDataManager; -import grondag.canvas.light.color.LightRegion; +import grondag.canvas.light.color.LightRegionAccess; import grondag.canvas.material.state.TerrainRenderStates; import grondag.canvas.perf.ChunkRebuildCounters; import grondag.canvas.pipeline.Pipeline; @@ -82,7 +81,7 @@ public class RenderRegion implements TerrainExecutorTask { public final CameraRegionVisibility cameraVisibility; public final ShadowRegionVisibility shadowVisibility; public final NeighborRegions neighbors; - private final LightRegion lightRegion; + private final LightRegionAccess lightRegion; private RegionRenderSector renderSector = null; @@ -130,7 +129,7 @@ public RenderRegion(RenderChunk chunk, long packedPos) { cameraVisibility = worldRenderState.terrainIterator.cameraVisibility.createRegionState(this); shadowVisibility = worldRenderState.terrainIterator.shadowVisibility.createRegionState(this); origin.update(); - lightRegion = LightDataManager.INSTANCE.allocate(origin); + lightRegion = LightDataManager.allocate(origin); } private static void addBlockEntity(List chunkEntities, Set globalEntities, E blockEntity) { @@ -173,8 +172,7 @@ void close() { } if (!lightRegion.isClosed()) { - LightDataManager.INSTANCE.deallocate(origin); - // CanvasMod.LOG.info("called deallocate() from Render Region"); + LightDataManager.free(origin); } } } From b346b270e4c1d27fe3c397f42b47816a357790d7 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Thu, 29 Jun 2023 23:11:22 +0700 Subject: [PATCH 31/69] Properly reload regions on pipeline selection This is done by fully parsing pipeline description, which impacts resource reload time. --- .../canvas/config/PipelineOptionScreen.java | 10 +++- .../canvas/light/color/LightDataManager.java | 7 +-- .../grondag/canvas/pipeline/Pipeline.java | 7 +++ .../config/PipelineConfigBuilder.java | 21 ++++---- .../pipeline/config/PipelineDescription.java | 50 ++++++++++++++++--- .../pipeline/config/PipelineLoader.java | 12 ++--- 6 files changed, 77 insertions(+), 30 deletions(-) diff --git a/src/main/java/grondag/canvas/config/PipelineOptionScreen.java b/src/main/java/grondag/canvas/config/PipelineOptionScreen.java index f17203081..f38145b82 100644 --- a/src/main/java/grondag/canvas/config/PipelineOptionScreen.java +++ b/src/main/java/grondag/canvas/config/PipelineOptionScreen.java @@ -40,6 +40,7 @@ import grondag.canvas.config.gui.BaseButton; import grondag.canvas.config.gui.BaseScreen; import grondag.canvas.config.gui.ListWidget; +import grondag.canvas.pipeline.Pipeline; import grondag.canvas.pipeline.config.PipelineConfig; import grondag.canvas.pipeline.config.PipelineConfigBuilder; import grondag.canvas.pipeline.config.PipelineLoader; @@ -117,9 +118,14 @@ protected void init() { } private void savePipelineSelection(ResourceLocation newPipelineId) { + final var newPipeline = PipelineLoader.get(newPipelineId.toString()); + + boolean shadowsChanged = Pipeline.shadowsEnabled() != newPipeline.shadowsEnabled; + boolean lightVolumeChanged = Pipeline.lightVolumeEnabled() != newPipeline.lightVolumeEnabled; + boolean needRegionsReloaded = (shadowsChanged && !Configurator.advancedTerrainCulling) || lightVolumeChanged; + Configurator.pipelineId = newPipelineId.toString(); - // When advanced terrain culling is *soft* disabled, better clear the region storage - ConfigManager.saveUserInput(Configurator.advancedTerrainCulling ? RELOAD_PIPELINE : RELOAD_EVERYTHING); + ConfigManager.saveUserInput(needRegionsReloaded ? RELOAD_EVERYTHING : RELOAD_PIPELINE); } private void save() { diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 5963fc896..8e55064f1 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -47,7 +47,8 @@ public static LightRegionAccess allocate(BlockPos regionOrigin) { } public static void reload() { - if (Pipeline.config().lightVolume != null) { + if (Pipeline.lightVolumeEnabled()) { + assert Pipeline.config().lightVolume != null; final var image = Pipeline.getImage(Pipeline.config().lightVolume.lightImage.name); if (INSTANCE == null) { @@ -55,15 +56,11 @@ public static void reload() { } else { INSTANCE.resize(image); } - - CanvasMod.LOG.info("Light volume is enabled"); } else { if (INSTANCE != null) { INSTANCE.close(); INSTANCE = null; } - - CanvasMod.LOG.info("Light volume is disabled"); } } diff --git a/src/main/java/grondag/canvas/pipeline/Pipeline.java b/src/main/java/grondag/canvas/pipeline/Pipeline.java index bb483a748..245494bc0 100644 --- a/src/main/java/grondag/canvas/pipeline/Pipeline.java +++ b/src/main/java/grondag/canvas/pipeline/Pipeline.java @@ -97,11 +97,16 @@ public class Pipeline { private static PipelineConfig config; private static boolean advancedTerrainCulling; + private static boolean lightVolumeEnabled; public static boolean shadowsEnabled() { return skyShadowFbo != null; } + public static boolean lightVolumeEnabled() { + return lightVolumeEnabled; + } + public static boolean advancedTerrainCulling() { return advancedTerrainCulling; } @@ -207,6 +212,8 @@ static void activate(PrimaryFrameBuffer primary, int width, int height) { defaultZenithAngle = 0f; } + lightVolumeEnabled = config.lightVolume != null; + if (isFabulous) { final FabulousConfig fc = config.fabulosity; diff --git a/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java b/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java index 47f23dd52..c8f5707d5 100644 --- a/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java +++ b/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java @@ -79,8 +79,7 @@ public class PipelineConfigBuilder { /** * Priority-pass loading. Loads options before anything else. This is necessary for the - * current design of {@link grondag.canvas.pipeline.config.util.DynamicLoader}, at the cost - * of reading the disk twice. + * current design of {@link grondag.canvas.pipeline.config.util.DynamicLoader}. * * @param configJson the json file being read */ @@ -229,13 +228,7 @@ public boolean validate() { final ObjectArrayFIFOQueue primaryLoadQueue = new ObjectArrayFIFOQueue<>(); final ObjectArrayFIFOQueue secondLoadQueue = new ObjectArrayFIFOQueue<>(); - readQueue.enqueue(id); - included.add(id); - - while (!readQueue.isEmpty()) { - final ResourceLocation target = readQueue.dequeue(); - readResource(target, readQueue, primaryLoadQueue, included, rm); - } + loadResources(id, readQueue, primaryLoadQueue, included, rm); while (!primaryLoadQueue.isEmpty()) { final JsonObject target = primaryLoadQueue.dequeue(); @@ -258,6 +251,16 @@ public boolean validate() { } } + static void loadResources(ResourceLocation id, ObjectArrayFIFOQueue queue, ObjectArrayFIFOQueue loadQueue, ObjectOpenHashSet included, ResourceManager rm) { + queue.enqueue(id); + included.add(id); + + while (!queue.isEmpty()) { + final ResourceLocation target = queue.dequeue(); + readResource(target, queue, loadQueue, included, rm); + } + } + private static void readResource(ResourceLocation target, ObjectArrayFIFOQueue queue, ObjectArrayFIFOQueue loadQueue, ObjectOpenHashSet included, ResourceManager rm) { // Allow flexibility on JSON vs JSON5 extensions if (rm.getResource(target).isEmpty()) { diff --git a/src/main/java/grondag/canvas/pipeline/config/PipelineDescription.java b/src/main/java/grondag/canvas/pipeline/config/PipelineDescription.java index 31fae8f5d..ebb4ac111 100644 --- a/src/main/java/grondag/canvas/pipeline/config/PipelineDescription.java +++ b/src/main/java/grondag/canvas/pipeline/config/PipelineDescription.java @@ -21,9 +21,12 @@ package grondag.canvas.pipeline.config; import blue.endless.jankson.JsonObject; +import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.minecraft.client.resources.language.I18n; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; import grondag.canvas.pipeline.config.util.JanksonHelper; @@ -32,16 +35,49 @@ public class PipelineDescription { public final String nameKey; public final String descriptionKey; public final boolean isFabulous; + public final boolean shadowsEnabled; + public final boolean lightVolumeEnabled; - public PipelineDescription (ResourceLocation id, JsonObject config) { - this.id = id; + public static PipelineDescription create(ResourceLocation id, ResourceManager rm) { + final var included = new ObjectOpenHashSet(); + final var reading = new ObjectArrayFIFOQueue(); + final var objects = new ObjectArrayFIFOQueue(); + + PipelineConfigBuilder.loadResources(id, reading, objects, included, rm); + + if (objects.isEmpty()) { + return null; + } + + var config = objects.dequeue(); + + final String getNameKey = JanksonHelper.asString(config.get("nameKey")); + final String nameKey = getNameKey == null ? id.toString() : getNameKey; - final String nameKey = JanksonHelper.asString(config.get("nameKey")); - this.nameKey = nameKey == null ? id.toString() : nameKey; - isFabulous = config.containsKey("fabulousTargets"); + final String getDescriptionKey = JanksonHelper.asString(config.get("descriptionKey")); + final String descriptionKey = getDescriptionKey == null ? "pipeline.no_desc" : getDescriptionKey; - final String descriptionKey = JanksonHelper.asString(config.get("descriptionKey")); - this.descriptionKey = descriptionKey == null ? "pipeline.no_desc" : descriptionKey; + boolean fabulous = config.containsKey("fabulousTargets"); + boolean shadows = config.containsKey("skyShadows"); + boolean lightVolume = config.containsKey("lightVolume"); + + while (!objects.isEmpty()) { + var obj = objects.dequeue(); + fabulous |= obj.containsKey("fabulousTargets"); + shadows |= obj.containsKey("skyShadows"); + lightVolume |= obj.containsKey("lightVolume"); + } + + return new PipelineDescription(id, nameKey, descriptionKey, fabulous, shadows, lightVolume); + } + + public PipelineDescription(ResourceLocation id, String nameKey, String descriptionKey, boolean isFabulous, boolean shadowsEnabled, boolean lightVolumeEnabled) { + this.id = id; + this.nameKey = nameKey; + this.descriptionKey = descriptionKey; + this.isFabulous = isFabulous; + this.shadowsEnabled = shadowsEnabled; + this.lightVolumeEnabled = lightVolumeEnabled; } public String name() { diff --git a/src/main/java/grondag/canvas/pipeline/config/PipelineLoader.java b/src/main/java/grondag/canvas/pipeline/config/PipelineLoader.java index b3190ebfc..9c8f9a5d9 100644 --- a/src/main/java/grondag/canvas/pipeline/config/PipelineLoader.java +++ b/src/main/java/grondag/canvas/pipeline/config/PipelineLoader.java @@ -49,12 +49,12 @@ public static void reload(ResourceManager manager) { final String stringx = location.toString(); return stringx.endsWith(".json") || stringx.endsWith(".json5"); }).forEach((id, resource) -> { - try (InputStream inputStream = resource.open()) { - final JsonObject configJson = ConfigManager.JANKSON.load(inputStream); - final PipelineDescription p = new PipelineDescription(id, configJson); + final PipelineDescription p = PipelineDescription.create(id, manager); + + if (p == null) { + CanvasMod.LOG.warn(String.format("Unable to load pipeline configuration %s", id)); + } else { MAP.put(id.toString(), p); - } catch (final Exception e) { - CanvasMod.LOG.warn(String.format("Unable to load pipeline configuration %s due to unhandled exception.", id), e); } }); } @@ -72,6 +72,4 @@ public static PipelineDescription get(String idString) { public static PipelineDescription[] array() { return MAP.values().toArray(new PipelineDescription[MAP.size()]); } - - public static final Function NAME_TEXT_FUNCTION = s -> Component.translatable(get(s).nameKey); } From d66c739e36f3439859711aab6d6698f2bb7b3db2 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Thu, 29 Jun 2023 23:21:43 +0700 Subject: [PATCH 32/69] Comment, style, cleanup --- .../java/io/vram/canvas/CanvasFabricMod.java | 1 - .../java/grondag/canvas/apiimpl/CanvasState.java | 1 - .../grondag/canvas/light/api/BlockLight.java | 16 ++++++++-------- .../canvas/light/color/LightDataManager.java | 4 ++-- .../grondag/canvas/light/color/LightRegion.java | 14 +++++++------- .../canvas/light/color/LightRegistry.java | 2 +- .../canvas/pipeline/ProgramTextureData.java | 2 -- .../pipeline/config/PipelineConfigBuilder.java | 14 +++++++++----- .../canvas/pipeline/config/PipelineLoader.java | 6 ------ 9 files changed, 27 insertions(+), 33 deletions(-) diff --git a/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java b/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java index 9f466f936..4d66aa7e3 100644 --- a/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java +++ b/fabric/src/main/java/io/vram/canvas/CanvasFabricMod.java @@ -32,7 +32,6 @@ import net.fabricmc.loader.api.FabricLoader; import grondag.canvas.CanvasMod; -import grondag.canvas.light.api.impl.BlockLightLoader; import grondag.canvas.light.color.LightRegistry; import grondag.canvas.pipeline.config.PipelineLoader; import grondag.canvas.texture.MaterialIndexProvider; diff --git a/src/main/java/grondag/canvas/apiimpl/CanvasState.java b/src/main/java/grondag/canvas/apiimpl/CanvasState.java index 4f859514a..9888d85fd 100644 --- a/src/main/java/grondag/canvas/apiimpl/CanvasState.java +++ b/src/main/java/grondag/canvas/apiimpl/CanvasState.java @@ -34,7 +34,6 @@ import grondag.canvas.material.property.TextureMaterialState; import grondag.canvas.perf.ChunkRebuildCounters; import grondag.canvas.perf.Timekeeper; -import grondag.canvas.pipeline.Pipeline; import grondag.canvas.pipeline.PipelineManager; import grondag.canvas.pipeline.config.PipelineLoader; import grondag.canvas.shader.GlMaterialProgramManager; diff --git a/src/main/java/grondag/canvas/light/api/BlockLight.java b/src/main/java/grondag/canvas/light/api/BlockLight.java index 3052e3f59..a834c2ae7 100644 --- a/src/main/java/grondag/canvas/light/api/BlockLight.java +++ b/src/main/java/grondag/canvas/light/api/BlockLight.java @@ -22,19 +22,19 @@ /** * BlockLight API draft. - *

- * Similar to Material, this needs to be constructed by a factory provided by implementation. + * + *

Similar to Material, this needs to be constructed by a factory provided by implementation. */ public interface BlockLight { /** * The light level. Typically, this represents the light radius after multiplied with the * highest color component, but also affects maximum brightness. - *

- * Implementation may choose whether to prioritize the radius aspect or brightness aspect. - *

- * Typical value is in range 0-15. Value outside of this range is implementation-specific. - *

- * In JSON format, defaults to the vanilla registered light level when missing. + * + *

Implementation may choose whether to prioritize the radius aspect or brightness aspect. + * + *

Typical value is in range 0-15. Value outside of this range is implementation-specific. + * + *

In JSON format, defaults to the vanilla registered light level when missing. * Importantly, light level is attached to blocks, so for fluid states * (not their block counterpart) the default is always 0. * diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 8e55064f1..8b04afd1b 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -29,7 +29,6 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockAndTintGetter; -import grondag.canvas.CanvasMod; import grondag.canvas.pipeline.Image; import grondag.canvas.pipeline.Pipeline; @@ -245,7 +244,7 @@ public void close() { } } - private class ExtentIterable implements LongIterable, LongIterator { + private class ExtentIterable implements LongIterable, LongIterator { @Override public LongIterator iterator() { x = y = z = 0; @@ -264,6 +263,7 @@ void set(int startX, int startY, int startZ) { public long nextLong() { final int extent = extentSizeInBlocks(1); final long value = BlockPos.asLong(startX + x * extent, startY + y * extent, startZ + z * extent); + if (++z >= extentSizeZInRegions) { z = 0; y++; diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 1f4921611..2a126d8f6 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -34,7 +34,7 @@ // TODO: cluster slab allocation? -> maybe unneeded now? // TODO: a way to repopulate cluster if needed -class LightRegion implements LightRegionAccess{ +class LightRegion implements LightRegionAccess { private static class BVec { boolean r, g, b; @@ -89,7 +89,7 @@ BVec and(BVec other, BVec another) { } } - private static enum Side { + private enum Side { DOWN(0, -1, 0, 1, Direction.DOWN), UP(0, 1, 0, 2, Direction.UP), NORTH(0, 0, -1, 3, Direction.NORTH), @@ -100,7 +100,7 @@ private static enum Side { final int x, y, z, id; final Direction vanilla; Side opposite; - final static int nullId = 0; + static final int nullId = 0; static { DOWN.opposite = UP; @@ -236,7 +236,7 @@ boolean updateDecrease(BlockAndTintGetter blockView) { int decCount = 0; boolean accessedNeighborDecrease = false; - while(!decQueue.isEmpty()) { + while (!decQueue.isEmpty()) { decCount++; final long entry = decQueue.dequeueLong(); @@ -495,7 +495,7 @@ private void checkEdges(BlockAndTintGetter blockView) { final int[] searchOffsets = new int[]{-1, size}; final int[] targetOffsets = new int[]{0, size - 1}; - for (int i = 0; i < searchOffsets.length; i ++) { + for (int i = 0; i < searchOffsets.length; i++) { final int x = searchOffsets[i]; final int xTarget = targetOffsets[i]; @@ -518,7 +518,7 @@ private void checkEdges(BlockAndTintGetter blockView) { } // TODO: generalize with Axis parameter - for (int i = 0; i < searchOffsets.length; i ++) { + for (int i = 0; i < searchOffsets.length; i++) { final int y = searchOffsets[i]; final int yTarget = targetOffsets[i]; @@ -540,7 +540,7 @@ private void checkEdges(BlockAndTintGetter blockView) { } } - for (int i = 0; i < searchOffsets.length; i ++) { + for (int i = 0; i < searchOffsets.length; i++) { final int z = searchOffsets[i]; final int zTarget = targetOffsets[i]; diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java index 8aa54d46c..f7fdd3c32 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegistry.java +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -39,7 +39,7 @@ public static void reload(ResourceManager manager) { BlockLightLoader.reload(manager); } - public static short get(BlockState blockState){ + public static short get(BlockState blockState) { // maybe just populate it during reload? don't want to slow down resource reload though return cachedLights.computeIfAbsent(blockState, LightRegistry::generate); } diff --git a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java index 1fd74b316..7492bcf73 100644 --- a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java +++ b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java @@ -29,8 +29,6 @@ import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.resources.ResourceLocation; -import grondag.canvas.light.color.LightDataManager; -import grondag.canvas.light.color.LightDataTexture; import grondag.canvas.pipeline.config.ImageConfig; import grondag.canvas.pipeline.config.util.NamedDependency; diff --git a/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java b/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java index c8f5707d5..98a1c572d 100644 --- a/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java +++ b/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java @@ -222,11 +222,11 @@ public boolean validate() { return null; } - final PipelineConfigBuilder result = new PipelineConfigBuilder(); - final ObjectOpenHashSet included = new ObjectOpenHashSet<>(); - final ObjectArrayFIFOQueue readQueue = new ObjectArrayFIFOQueue<>(); - final ObjectArrayFIFOQueue primaryLoadQueue = new ObjectArrayFIFOQueue<>(); - final ObjectArrayFIFOQueue secondLoadQueue = new ObjectArrayFIFOQueue<>(); + final var result = new PipelineConfigBuilder(); + final var included = new ObjectOpenHashSet(); + final var readQueue = new ObjectArrayFIFOQueue(); + final var primaryLoadQueue = new ObjectArrayFIFOQueue(); + final var secondLoadQueue = new ObjectArrayFIFOQueue(); loadResources(id, readQueue, primaryLoadQueue, included, rm); @@ -251,6 +251,10 @@ public boolean validate() { } } + /** + * This function is also used in {@link PipelineDescription} to parse the entire pipeline. + * Notably, this and subsequently called functions shouldn't contain any building code. + */ static void loadResources(ResourceLocation id, ObjectArrayFIFOQueue queue, ObjectArrayFIFOQueue loadQueue, ObjectOpenHashSet included, ResourceManager rm) { queue.enqueue(id); included.add(id); diff --git a/src/main/java/grondag/canvas/pipeline/config/PipelineLoader.java b/src/main/java/grondag/canvas/pipeline/config/PipelineLoader.java index 9c8f9a5d9..86f8dcb7f 100644 --- a/src/main/java/grondag/canvas/pipeline/config/PipelineLoader.java +++ b/src/main/java/grondag/canvas/pipeline/config/PipelineLoader.java @@ -20,17 +20,11 @@ package grondag.canvas.pipeline.config; -import java.io.InputStream; -import java.util.function.Function; - -import blue.endless.jankson.JsonObject; import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.minecraft.network.chat.Component; import net.minecraft.server.packs.resources.ResourceManager; import grondag.canvas.CanvasMod; -import grondag.canvas.config.ConfigManager; public class PipelineLoader { private static boolean hasLoadedOnce = false; From 6ea5ea0edfc68b405fb367275a759884ea8f8bc7 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Thu, 29 Jun 2023 23:36:08 +0700 Subject: [PATCH 33/69] Rename "light volume" to "colored lights" This feature is more than just providing light volume, since we also do propagation and manage the custom light color registry. And ultimately the goal is to add colored lights. Voxel tracing, LPV, and other light volume related aspects would count as separate or spin-off projects. --- src/main/java/grondag/canvas/CanvasMod.java | 1 - .../grondag/canvas/config/PipelineOptionScreen.java | 4 ++-- .../grondag/canvas/light/color/LightDataManager.java | 6 +++--- src/main/java/grondag/canvas/pipeline/Pipeline.java | 8 ++++---- ...ghtVolumeConfig.java => ColoredLightsConfig.java} | 4 ++-- .../canvas/pipeline/config/PipelineConfig.java | 6 +++--- .../pipeline/config/PipelineConfigBuilder.java | 12 ++++++------ .../canvas/pipeline/config/PipelineDescription.java | 12 ++++++------ 8 files changed, 26 insertions(+), 27 deletions(-) rename src/main/java/grondag/canvas/pipeline/config/{LightVolumeConfig.java => ColoredLightsConfig.java} (96%) diff --git a/src/main/java/grondag/canvas/CanvasMod.java b/src/main/java/grondag/canvas/CanvasMod.java index 19e0f3836..0beeb3984 100644 --- a/src/main/java/grondag/canvas/CanvasMod.java +++ b/src/main/java/grondag/canvas/CanvasMod.java @@ -54,7 +54,6 @@ //FEAT: pbr textures //PERF: disable animated textures when not in view //PERF: improve light smoothing performance -//FEAT: colored lights //FEAT: weather uniforms //FEAT: biome texture in shader diff --git a/src/main/java/grondag/canvas/config/PipelineOptionScreen.java b/src/main/java/grondag/canvas/config/PipelineOptionScreen.java index f38145b82..b5c108c3c 100644 --- a/src/main/java/grondag/canvas/config/PipelineOptionScreen.java +++ b/src/main/java/grondag/canvas/config/PipelineOptionScreen.java @@ -121,8 +121,8 @@ private void savePipelineSelection(ResourceLocation newPipelineId) { final var newPipeline = PipelineLoader.get(newPipelineId.toString()); boolean shadowsChanged = Pipeline.shadowsEnabled() != newPipeline.shadowsEnabled; - boolean lightVolumeChanged = Pipeline.lightVolumeEnabled() != newPipeline.lightVolumeEnabled; - boolean needRegionsReloaded = (shadowsChanged && !Configurator.advancedTerrainCulling) || lightVolumeChanged; + boolean coloredLightsChanged = Pipeline.coloredLightsEnabled() != newPipeline.coloredLightsEnabled; + boolean needRegionsReloaded = (shadowsChanged && !Configurator.advancedTerrainCulling) || coloredLightsChanged; Configurator.pipelineId = newPipelineId.toString(); ConfigManager.saveUserInput(needRegionsReloaded ? RELOAD_EVERYTHING : RELOAD_PIPELINE); diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 8b04afd1b..de67d2cc0 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -46,9 +46,9 @@ public static LightRegionAccess allocate(BlockPos regionOrigin) { } public static void reload() { - if (Pipeline.lightVolumeEnabled()) { - assert Pipeline.config().lightVolume != null; - final var image = Pipeline.getImage(Pipeline.config().lightVolume.lightImage.name); + if (Pipeline.coloredLightsEnabled()) { + assert Pipeline.config().coloredLights != null; + final var image = Pipeline.getImage(Pipeline.config().coloredLights.lightImage.name); if (INSTANCE == null) { INSTANCE = new LightDataManager(image); diff --git a/src/main/java/grondag/canvas/pipeline/Pipeline.java b/src/main/java/grondag/canvas/pipeline/Pipeline.java index 245494bc0..6799912e6 100644 --- a/src/main/java/grondag/canvas/pipeline/Pipeline.java +++ b/src/main/java/grondag/canvas/pipeline/Pipeline.java @@ -97,14 +97,14 @@ public class Pipeline { private static PipelineConfig config; private static boolean advancedTerrainCulling; - private static boolean lightVolumeEnabled; + private static boolean coloredLightsEnabled; public static boolean shadowsEnabled() { return skyShadowFbo != null; } - public static boolean lightVolumeEnabled() { - return lightVolumeEnabled; + public static boolean coloredLightsEnabled() { + return coloredLightsEnabled; } public static boolean advancedTerrainCulling() { @@ -212,7 +212,7 @@ static void activate(PrimaryFrameBuffer primary, int width, int height) { defaultZenithAngle = 0f; } - lightVolumeEnabled = config.lightVolume != null; + coloredLightsEnabled = config.coloredLights != null; if (isFabulous) { final FabulousConfig fc = config.fabulosity; diff --git a/src/main/java/grondag/canvas/pipeline/config/LightVolumeConfig.java b/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java similarity index 96% rename from src/main/java/grondag/canvas/pipeline/config/LightVolumeConfig.java rename to src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java index c45072750..7490da941 100644 --- a/src/main/java/grondag/canvas/pipeline/config/LightVolumeConfig.java +++ b/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java @@ -28,10 +28,10 @@ import grondag.canvas.pipeline.config.util.ConfigContext; import grondag.canvas.pipeline.config.util.NamedDependency; -public class LightVolumeConfig extends AbstractConfig { +public class ColoredLightsConfig extends AbstractConfig { public final NamedDependency lightImage; - protected LightVolumeConfig(ConfigContext ctx, JsonObject config) { + protected ColoredLightsConfig(ConfigContext ctx, JsonObject config) { super(ctx); lightImage = ctx.images.dependOn(ctx.dynamic.getString(config, "lightImage")); } diff --git a/src/main/java/grondag/canvas/pipeline/config/PipelineConfig.java b/src/main/java/grondag/canvas/pipeline/config/PipelineConfig.java index c346d7320..ccfab2e1c 100644 --- a/src/main/java/grondag/canvas/pipeline/config/PipelineConfig.java +++ b/src/main/java/grondag/canvas/pipeline/config/PipelineConfig.java @@ -54,7 +54,7 @@ public class PipelineConfig { @Nullable public final DrawTargetsConfig drawTargets; @Nullable public final SkyShadowConfig skyShadow; @Nullable public final SkyConfig sky; - @Nullable public final LightVolumeConfig lightVolume; + @Nullable public final ColoredLightsConfig coloredLights; public final NamedDependency defaultFramebuffer; @@ -84,7 +84,7 @@ private PipelineConfig() { fabulosity = null; skyShadow = null; sky = null; - lightVolume = null; + coloredLights = null; drawTargets = DrawTargetsConfig.makeDefault(context); defaultFramebuffer = context.frameBuffers.dependOn("default"); materialProgram = new MaterialProgramConfig(context); @@ -107,7 +107,7 @@ private PipelineConfig() { drawTargets = builder.drawTargets; skyShadow = builder.skyShadow; sky = builder.sky; - lightVolume = builder.lightVolume; + coloredLights = builder.coloredLights; for (final OptionConfig opt : builder.options) { optionMap.put(opt.includeToken, opt); diff --git a/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java b/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java index 98a1c572d..d364594bd 100644 --- a/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java +++ b/src/main/java/grondag/canvas/pipeline/config/PipelineConfigBuilder.java @@ -63,7 +63,7 @@ public class PipelineConfigBuilder { @Nullable public DrawTargetsConfig drawTargets; @Nullable public SkyShadowConfig skyShadow; @Nullable public SkyConfig sky; - @Nullable public LightVolumeConfig lightVolume; + @Nullable public ColoredLightsConfig coloredLights; public boolean smoothBrightnessBidirectionaly = false; public int brightnessSmoothingFrames = 20; @@ -140,11 +140,11 @@ public void load(JsonObject configJson) { } } - if (configJson.containsKey("lightVolume")) { - if (lightVolume == null) { - lightVolume = LoadHelper.loadObject(context, configJson, "lightVolume", LightVolumeConfig::new); + if (configJson.containsKey("coloredLights")) { + if (coloredLights == null) { + coloredLights = LoadHelper.loadObject(context, configJson, "coloredLights", ColoredLightsConfig::new); } else { - CanvasMod.LOG.warn("Invalid pipeline config - duplicate 'lightVolume' ignored."); + CanvasMod.LOG.warn("Invalid pipeline config - duplicate 'coloredLights' ignored."); } } @@ -192,7 +192,7 @@ public boolean validate() { valid &= (fabulosity == null || fabulosity.validate()); valid &= (skyShadow == null || skyShadow.validate()); - valid &= (lightVolume == null || lightVolume.validate()); + valid &= (coloredLights == null || coloredLights.validate()); valid &= defaultFramebuffer != null && defaultFramebuffer.validate("Invalid pipeline config - missing or invalid defaultFramebuffer."); diff --git a/src/main/java/grondag/canvas/pipeline/config/PipelineDescription.java b/src/main/java/grondag/canvas/pipeline/config/PipelineDescription.java index ebb4ac111..fd643d9c1 100644 --- a/src/main/java/grondag/canvas/pipeline/config/PipelineDescription.java +++ b/src/main/java/grondag/canvas/pipeline/config/PipelineDescription.java @@ -36,7 +36,7 @@ public class PipelineDescription { public final String descriptionKey; public final boolean isFabulous; public final boolean shadowsEnabled; - public final boolean lightVolumeEnabled; + public final boolean coloredLightsEnabled; public static PipelineDescription create(ResourceLocation id, ResourceManager rm) { final var included = new ObjectOpenHashSet(); @@ -59,25 +59,25 @@ public static PipelineDescription create(ResourceLocation id, ResourceManager rm boolean fabulous = config.containsKey("fabulousTargets"); boolean shadows = config.containsKey("skyShadows"); - boolean lightVolume = config.containsKey("lightVolume"); + boolean coloredLights = config.containsKey("coloredLights"); while (!objects.isEmpty()) { var obj = objects.dequeue(); fabulous |= obj.containsKey("fabulousTargets"); shadows |= obj.containsKey("skyShadows"); - lightVolume |= obj.containsKey("lightVolume"); + coloredLights |= obj.containsKey("coloredLights"); } - return new PipelineDescription(id, nameKey, descriptionKey, fabulous, shadows, lightVolume); + return new PipelineDescription(id, nameKey, descriptionKey, fabulous, shadows, coloredLights); } - public PipelineDescription(ResourceLocation id, String nameKey, String descriptionKey, boolean isFabulous, boolean shadowsEnabled, boolean lightVolumeEnabled) { + public PipelineDescription(ResourceLocation id, String nameKey, String descriptionKey, boolean isFabulous, boolean shadowsEnabled, boolean coloredLightsEnabled) { this.id = id; this.nameKey = nameKey; this.descriptionKey = descriptionKey; this.isFabulous = isFabulous; this.shadowsEnabled = shadowsEnabled; - this.lightVolumeEnabled = lightVolumeEnabled; + this.coloredLightsEnabled = coloredLightsEnabled; } public String name() { From d7dd044eb0e33376e08a79632762530244e8aa1f Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Fri, 30 Jun 2023 01:57:31 +0700 Subject: [PATCH 34/69] Add shader API for light volume constants --- .../canvas/light/color/LightDataManager.java | 26 ++++++++++--------- .../grondag/canvas/pipeline/Pipeline.java | 15 ++++++++++- .../java/grondag/canvas/shader/GlShader.java | 8 ++++++ .../grondag/canvas/shader/data/FloatData.java | 3 +++ .../canvas/shader/data/ShaderDataManager.java | 9 +++++++ .../assets/canvas/shaders/internal/world.glsl | 3 +++ .../assets/frex/shaders/api/header.glsl | 2 ++ .../assets/frex/shaders/api/view.glsl | 1 + 8 files changed, 54 insertions(+), 13 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index de67d2cc0..d520c9aa7 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -25,12 +25,14 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongIterable; import it.unimi.dsi.fastutil.longs.LongIterator; +import org.joml.Vector3i; import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockAndTintGetter; import grondag.canvas.pipeline.Image; import grondag.canvas.pipeline.Pipeline; +import grondag.canvas.shader.data.ShaderDataManager; public class LightDataManager { private static final boolean debugRedrawEveryFrame = false; @@ -77,9 +79,7 @@ public static void update(BlockAndTintGetter blockView, int cameraX, int cameraY private final Long2ObjectMap allocated = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); - private int extentStartBlockX = 0; - private int extentStartBlockY = 0; - private int extentStartBlockZ = 0; + private final Vector3i extentOrigin = new Vector3i(); private int extentGridMaskX; private int extentGridMaskY; @@ -128,18 +128,20 @@ private void init(Image image) { private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, int cameraZ) { final int regionSnapMask = ~LightRegionData.Const.WIDTH_MASK; - final int prevExtentBlockX = extentStartBlockX; - final int prevExtentBlockY = extentStartBlockY; - final int prevExtentBlockZ = extentStartBlockZ; + final int prevExtentBlockX = extentOrigin.x; + final int prevExtentBlockY = extentOrigin.y; + final int prevExtentBlockZ = extentOrigin.z; // snap camera position to the nearest region (chunk) - extentStartBlockX = (cameraX & regionSnapMask) - extentSizeInBlocks(extentSizeXInRegions / 2); - extentStartBlockY = (cameraY & regionSnapMask) - extentSizeInBlocks(extentSizeYInRegions / 2); - extentStartBlockZ = (cameraZ & regionSnapMask) - extentSizeInBlocks(extentSizeZInRegions / 2); + extentOrigin.x = (cameraX & regionSnapMask) - extentSizeInBlocks(extentSizeXInRegions / 2); + extentOrigin.y = (cameraY & regionSnapMask) - extentSizeInBlocks(extentSizeYInRegions / 2); + extentOrigin.z = (cameraZ & regionSnapMask) - extentSizeInBlocks(extentSizeZInRegions / 2); - boolean extentMoved = extentStartBlockX != prevExtentBlockX || extentStartBlockY != prevExtentBlockY || extentStartBlockZ != prevExtentBlockZ; + ShaderDataManager.updateLightVolumeOrigin(extentOrigin); - extentIterable.set(extentStartBlockX, extentStartBlockY, extentStartBlockZ); + boolean extentMoved = !extentOrigin.equals(prevExtentBlockX, prevExtentBlockY, prevExtentBlockZ); + + extentIterable.set(extentOrigin.x, extentOrigin.y, extentOrigin.z); boolean needUpdate = true; @@ -152,7 +154,7 @@ private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, final LightRegion lightRegion = allocated.get(index); if (lightRegion != null && !lightRegion.isClosed()) { - needUpdate = needUpdate || lightRegion.updateDecrease(blockView); + needUpdate |= lightRegion.updateDecrease(blockView); } } } diff --git a/src/main/java/grondag/canvas/pipeline/Pipeline.java b/src/main/java/grondag/canvas/pipeline/Pipeline.java index 6799912e6..9ef93a461 100644 --- a/src/main/java/grondag/canvas/pipeline/Pipeline.java +++ b/src/main/java/grondag/canvas/pipeline/Pipeline.java @@ -24,6 +24,7 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import org.joml.Vector3i; import net.minecraft.client.GraphicsStatus; import net.minecraft.client.Minecraft; @@ -98,6 +99,7 @@ public class Pipeline { private static boolean advancedTerrainCulling; private static boolean coloredLightsEnabled; + private static final Vector3i lightVolumeSize = new Vector3i(); public static boolean shadowsEnabled() { return skyShadowFbo != null; @@ -107,6 +109,10 @@ public static boolean coloredLightsEnabled() { return coloredLightsEnabled; } + public static Vector3i lightVolumeSize() { + return lightVolumeSize; + } + public static boolean advancedTerrainCulling() { return advancedTerrainCulling; } @@ -212,7 +218,14 @@ static void activate(PrimaryFrameBuffer primary, int width, int height) { defaultZenithAngle = 0f; } - coloredLightsEnabled = config.coloredLights != null; + if (config.coloredLights != null) { + coloredLightsEnabled = true; + var image = config.coloredLights.lightImage.value(); + lightVolumeSize.set(image.width, image.height, image.depth); + } else { + coloredLightsEnabled = false; + lightVolumeSize.set(0); + } if (isFabulous) { final FabulousConfig fc = config.fabulosity; diff --git a/src/main/java/grondag/canvas/shader/GlShader.java b/src/main/java/grondag/canvas/shader/GlShader.java index fb6a6d575..210694211 100644 --- a/src/main/java/grondag/canvas/shader/GlShader.java +++ b/src/main/java/grondag/canvas/shader/GlShader.java @@ -269,6 +269,14 @@ private String getSource() { result = StringUtils.replace(result, "#define SHADOW_MAP_SIZE 1024", "//#define SHADOW_MAP_SIZE 1024"); } + if (Pipeline.coloredLightsEnabled()) { + var lightSize = Pipeline.lightVolumeSize(); + result = StringUtils.replace(result, "#define LIGHT_VOLUME_SIZE vec3(256.0)", String.format("#define LIGHT_VOLUME_SIZE vec3(%d, %d, %d)", lightSize.x, lightSize.y, lightSize.z)); + } else { + result = StringUtils.replace(result, "#define COLORED_LIGHTS_ENABLED", "//#define COLORED_LIGHTS_ENABLED"); + result = StringUtils.replace(result, "#define LIGHT_VOLUME_SIZE vec3(256.0)", "//#define LIGHT_VOLUME_SIZE vec3(256.0)"); + } + result = StringUtils.replace(result, "#define _CV_MAX_SHADER_COUNT 0", "#define _CV_MAX_SHADER_COUNT " + MaterialConstants.MAX_SHADERS); // prepend GLSL version diff --git a/src/main/java/grondag/canvas/shader/data/FloatData.java b/src/main/java/grondag/canvas/shader/data/FloatData.java index 4b4f6117c..1f60798a8 100644 --- a/src/main/java/grondag/canvas/shader/data/FloatData.java +++ b/src/main/java/grondag/canvas/shader/data/FloatData.java @@ -112,4 +112,7 @@ private FloatData() { } static final int THUNDER_STRENGTH = VEC_WEATHER + 1; static final int SMOOTHED_RAIN_STRENGTH = VEC_WEATHER + 2; static final int SMOOTHED_THUNDER_STRENGTH = VEC_WEATHER + 3; + + static final int VEC_LIGHT_VOLUME_ORIGIN = 4 * 21; + static final int LIGHT_VOLUME_ORIGIN = VEC_LIGHT_VOLUME_ORIGIN; } diff --git a/src/main/java/grondag/canvas/shader/data/ShaderDataManager.java b/src/main/java/grondag/canvas/shader/data/ShaderDataManager.java index bb17a941e..1cd0039e9 100644 --- a/src/main/java/grondag/canvas/shader/data/ShaderDataManager.java +++ b/src/main/java/grondag/canvas/shader/data/ShaderDataManager.java @@ -39,6 +39,7 @@ import static grondag.canvas.shader.data.FloatData.HELD_LIGHT_INTENSITY; import static grondag.canvas.shader.data.FloatData.HELD_LIGHT_OUTER_ANGLE; import static grondag.canvas.shader.data.FloatData.HELD_LIGHT_RED; +import static grondag.canvas.shader.data.FloatData.LIGHT_VOLUME_ORIGIN; import static grondag.canvas.shader.data.FloatData.MOON_SIZE; import static grondag.canvas.shader.data.FloatData.NIGHT_VISION_STRENGTH; import static grondag.canvas.shader.data.FloatData.PLAYER_MOOD; @@ -136,6 +137,7 @@ import org.joml.Matrix4f; import org.joml.Vector3f; +import org.joml.Vector3i; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack.Pose; @@ -165,6 +167,7 @@ import grondag.canvas.CanvasMod; import grondag.canvas.config.Configurator; +import grondag.canvas.light.color.LightDataManager; import grondag.canvas.mixinterface.DimensionTypeExt; import grondag.canvas.pipeline.Pipeline; import grondag.canvas.pipeline.PipelineManager; @@ -647,6 +650,12 @@ public static void updateEmissiveColor(int color) { FLOAT_VECTOR_DATA.put(EMISSIVE_COLOR_BLUE, (color & 0xFF) / 255f); } + public static void updateLightVolumeOrigin(Vector3i lightOrigin) { + FLOAT_VECTOR_DATA.put(LIGHT_VOLUME_ORIGIN, lightOrigin.x); + FLOAT_VECTOR_DATA.put(LIGHT_VOLUME_ORIGIN + 1, lightOrigin.y); + FLOAT_VECTOR_DATA.put(LIGHT_VOLUME_ORIGIN + 2, lightOrigin.z); + } + private static void putViewVector(int index, float yaw, float pitch, Vector3f storeTo) { final Vec3 vec = Vec3.directionFromRotation(pitch, yaw); final float x = (float) vec.x; diff --git a/src/main/resources/assets/canvas/shaders/internal/world.glsl b/src/main/resources/assets/canvas/shaders/internal/world.glsl index 8f91bfe50..18ee50224 100644 --- a/src/main/resources/assets/canvas/shaders/internal/world.glsl +++ b/src/main/resources/assets/canvas/shaders/internal/world.glsl @@ -72,6 +72,9 @@ // w = smoothed thunder strength #define _CV_WEATHER 20 +// w is unused +#define _CV_LIGHT_VOLUME_ORIGIN 21 + // UINT ARRAY #define _CV_RENDER_FRAMES 0 diff --git a/src/main/resources/assets/frex/shaders/api/header.glsl b/src/main/resources/assets/frex/shaders/api/header.glsl index 1b2b9b731..635e26e8f 100644 --- a/src/main/resources/assets/frex/shaders/api/header.glsl +++ b/src/main/resources/assets/frex/shaders/api/header.glsl @@ -4,6 +4,8 @@ #define ANIMATED_FOLIAGE #define SHADOW_MAP_PRESENT #define SHADOW_MAP_SIZE 1024 +#define COLORED_LIGHTS_ENABLED +#define LIGHT_VOLUME_SIZE vec3(256.0) #define MATERIAL_TARGET_UNKNOWN //#define DEPTH_PASS //#define PBR_ENABLED diff --git a/src/main/resources/assets/frex/shaders/api/view.glsl b/src/main/resources/assets/frex/shaders/api/view.glsl index 20997bf0f..a8bcb1d7d 100644 --- a/src/main/resources/assets/frex/shaders/api/view.glsl +++ b/src/main/resources/assets/frex/shaders/api/view.glsl @@ -38,6 +38,7 @@ #define frx_shadowProjectionMatrix(index) (_cvu_matrix[_CV_MAT_SHADOW_PROJ_0 + index]) #define frx_shadowViewProjectionMatrix(index) (_cvu_matrix[_CV_MAT_SHADOW_VIEW_PROJ_0 + index]) #define frx_shadowCenter(index) (_cvu_world[_CV_SHADOW_CENTER + index]) +#define frx_lightVolumeOrigin _cvu_world[_CV_LIGHT_VOLUME_ORIGIN].xyz #define frx_viewWidth _cvu_world[_CV_VIEW_PARAMS].x #define frx_viewHeight _cvu_world[_CV_VIEW_PARAMS].y #define frx_viewAspectRatio _cvu_world[_CV_VIEW_PARAMS].z From 4aea58eda035984a14245252eb4aded84ac849fd Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Fri, 30 Jun 2023 02:15:26 +0700 Subject: [PATCH 35/69] Fixed big stupid --- .../canvas/light/color/LightDataManager.java | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index d520c9aa7..61c118b53 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -141,8 +141,6 @@ private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, boolean extentMoved = !extentOrigin.equals(prevExtentBlockX, prevExtentBlockY, prevExtentBlockZ); - extentIterable.set(extentOrigin.x, extentOrigin.y, extentOrigin.z); - boolean needUpdate = true; // TODO: account for extent-edge chunks? @@ -247,24 +245,19 @@ public void close() { } private class ExtentIterable implements LongIterable, LongIterator { + final int extent = extentSizeInBlocks(1); + @Override public LongIterator iterator() { x = y = z = 0; return this; } - int x, y, z, startX, startY, startZ; - - void set(int startX, int startY, int startZ) { - this.startX = startX; - this.startY = startY; - this.startZ = startZ; - } + int x, y, z; @Override public long nextLong() { - final int extent = extentSizeInBlocks(1); - final long value = BlockPos.asLong(startX + x * extent, startY + y * extent, startZ + z * extent); + final long value = BlockPos.asLong(extentOrigin.x + x * extent, extentOrigin.y + y * extent, extentOrigin.z + z * extent); if (++z >= extentSizeZInRegions) { z = 0; @@ -281,7 +274,7 @@ public long nextLong() { @Override public boolean hasNext() { - return x < extentSizeZInRegions; + return x < extentSizeXInRegions; } } } From 51afa2481237b0791477c3f660859bbd3e19ba33 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sun, 2 Jul 2023 03:14:51 +0700 Subject: [PATCH 36/69] Add shader API, consolidate LightOp - Built-in selective neighbor filtering - Move decrease and increase operations to LightOp - Do multiple loop for increase too --- .../light/api/impl/BlockLightLoader.java | 4 +- .../java/grondag/canvas/light/color/Elem.java | 59 ------ .../canvas/light/color/LightDataManager.java | 19 +- .../grondag/canvas/light/color/LightOp.java | 169 ++++++++++++++++ .../canvas/light/color/LightRegion.java | 172 ++++------------ .../canvas/light/color/LightRegionAccess.java | 20 ++ .../canvas/light/color/LightRegionData.java | 22 --- .../canvas/light/color/LightRegistry.java | 7 +- .../java/grondag/canvas/shader/GlShader.java | 4 +- .../grondag/canvas/shader/data/FloatData.java | 2 +- .../canvas/shader/data/ShaderDataManager.java | 9 +- .../assets/canvas/shaders/internal/world.glsl | 2 +- .../assets/frex/shaders/api/header.glsl | 5 +- .../assets/frex/shaders/api/light.glsl | 187 ++++++++++++++++++ .../assets/frex/shaders/api/sampler.glsl | 12 ++ .../assets/frex/shaders/api/view.glsl | 4 - 16 files changed, 463 insertions(+), 234 deletions(-) delete mode 100644 src/main/java/grondag/canvas/light/color/Elem.java create mode 100644 src/main/java/grondag/canvas/light/color/LightOp.java create mode 100644 src/main/resources/assets/frex/shaders/api/light.glsl diff --git a/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java b/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java index 83181ef8a..d3085efa3 100644 --- a/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java +++ b/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java @@ -40,7 +40,7 @@ import grondag.canvas.CanvasMod; import grondag.canvas.light.api.BlockLight; -import grondag.canvas.light.color.Elem; +import grondag.canvas.light.color.LightOp; public class BlockLightLoader { public static BlockLightLoader INSTANCE; @@ -182,7 +182,7 @@ public CachedBlockLight withLevel(float lightEmission) { static short computeValue(float lightLevel, float red, float green, float blue) { final int blockRadius = lightLevel == 0f ? 0 : org.joml.Math.clamp(1, 15, Math.round(lightLevel)); - return Elem.encode(clampLight(blockRadius * red), clampLight(blockRadius * green), clampLight(blockRadius * blue), 0); + return LightOp.encode(clampLight(blockRadius * red), clampLight(blockRadius * green), clampLight(blockRadius * blue), 0); } @Override diff --git a/src/main/java/grondag/canvas/light/color/Elem.java b/src/main/java/grondag/canvas/light/color/Elem.java deleted file mode 100644 index 87c0339c5..000000000 --- a/src/main/java/grondag/canvas/light/color/Elem.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * This file is part of Canvas Renderer and is licensed to the project under - * terms that are compatible with the GNU Lesser General Public License. - * See the NOTICE file distributed with this work for additional information - * regarding copyright ownership and licensing. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package grondag.canvas.light.color; - -public enum Elem { - R(0xF000, 12, 0), - G(0x0F00, 8, 1), - B(0x00F0, 4, 2), - A(0x000F, 0, 3); - - public final int mask; - public final int shift; - public final int pos; - - Elem(int mask, int shift, int pos) { - this.mask = mask; - this.shift = shift; - this.pos = pos; - } - - public int of(short light) { - return (light >> shift) & 0xF; - } - - public short replace(short source, short elemLight) { - return (short) ((source & ~mask) | (elemLight << shift)); - } - - public static short encode(int r, int g, int b, int a) { - return (short) ((r << R.shift) | (g << G.shift) | (b << B.shift) | (a << A.shift)); - } - - public static short maxRGB(short left, short right, int a) { - return (short) (Math.max(left & R.mask, right & R.mask) | Math.max(left & G.mask, right & G.mask) - | Math.max(left & B.mask, right & B.mask) | a); - } - - public static String text(short light) { - return "(" + R.of(light) + "," + G.of(light) + "," + B.of(light) + ")"; - } -} diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 61c118b53..38ad09125 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -159,7 +159,22 @@ private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, boolean shouldRedraw = !cameraUninitialized && extentMoved; - // update all regions within extent + needUpdate = true; + + // TODO: optimize active region traversal with queue + while (needUpdate) { + needUpdate = false; + + for (long index : extentIterable) { + final LightRegion lightRegion = allocated.get(index); + + if (lightRegion != null && !lightRegion.isClosed()) { + needUpdate |= lightRegion.updateIncrease(blockView); + } + } + } + + // TODO: swap texture in case of sparse, perhaps for (long index:extentIterable) { final LightRegion lightRegion = allocated.get(index); @@ -167,8 +182,6 @@ private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, continue; } - lightRegion.updateIncrease(blockView); - boolean outsidePrev = false; final int x = lightRegion.lightData.regionOriginBlockX; diff --git a/src/main/java/grondag/canvas/light/color/LightOp.java b/src/main/java/grondag/canvas/light/color/LightOp.java new file mode 100644 index 000000000..26c334983 --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/LightOp.java @@ -0,0 +1,169 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.light.color; + +public enum LightOp { + R(0xF000, 12, 0), + G(0x0F00, 8, 1), + B(0x00F0, 4, 2), + A(0x000F, 0, 3); + + public final int mask; + public final int shift; + public final int pos; + + LightOp(int mask, int shift, int pos) { + this.mask = mask; + this.shift = shift; + this.pos = pos; + } + + private static final int EMITTER_FLAG = 0b1; + private static final int OCCLUDER_FLAG = 0b10; + private static final int FULL_FLAG = 0b100; + private static final int USEFUL_FLAG = 0b1000; + + public static short EMPTY = 0; + + public static short encode(int r, int g, int b, int a) { + return (short) ((r << R.shift) | (g << G.shift) | (b << B.shift) | (a << A.shift)); + } + + public static short encodeLight(int r, int g, int b, boolean isEmitter, boolean isOccluding) { + return ensureUsefulness(encode(r, g, b, encodeAlpha(isEmitter, isOccluding))); + } + + public static short encodeLight(int pureLight, boolean isEmitter, boolean isOccluding) { + return ensureUsefulness((short) (pureLight | encodeAlpha(isEmitter, isOccluding))); + } + + private static int encodeAlpha(boolean isEmitter, boolean isOccluding) { + return (isEmitter ? EMITTER_FLAG : 0) | (isOccluding ? OCCLUDER_FLAG : 0); + } + + private static short ensureUsefulness(short light) { + boolean useful = lit(light) || !occluder(light); + return useful ? (short) (light | USEFUL_FLAG) : (short) (light & ~USEFUL_FLAG); + } + + public static boolean emitter(short light) { + return (light & EMITTER_FLAG) != 0; + } + + public static boolean occluder(short light) { + return (light & OCCLUDER_FLAG) != 0; + } + + public static boolean full(short light) { + return (light & FULL_FLAG) != 0; + } + + public static boolean lit(short light) { + return (light & 0xfff0) != 0; + } + + public static short max(short master, short sub) { + final short max = (short) (Math.max(master & R.mask, sub & R.mask) + | Math.max(master & G.mask, sub & G.mask) + | Math.max(master & B.mask, sub & B.mask) + | master & 0xf); + return ensureUsefulness(max); + } + + public static short replaceMinusOne(short target, short source, BVec opFlag) { + if (opFlag.r) { + target = R.replace(target, (short) (R.of(source) - 1)); + } + + if (opFlag.g) { + target = G.replace(target, (short) (G.of(source) - 1)); + } + + if (opFlag.b) { + target = B.replace(target, (short) (B.of(source) - 1)); + } + + return LightOp.ensureUsefulness(target); + } + + public static short remove(short target, BVec opFlag) { + int mask = (opFlag.r ? LightOp.R.mask : 0) | (opFlag.g ? LightOp.G.mask : 0) | (opFlag.b ? LightOp.B.mask : 0); + return LightOp.ensureUsefulness((short) (target & ~mask)); + } + + public int of(short light) { + return (light >> shift) & 0xF; + } + + private short replace(short source, short elemLight) { + return (short) ((source & ~mask) | (elemLight << shift)); + } + + public static String text(short light) { + return "(" + R.of(light) + "," + G.of(light) + "," + B.of(light) + ")"; + } + + static class BVec { + boolean r, g, b; + + BVec() { + this.r = false; + this.g = false; + this.b = false; + } + + boolean any() { + return r || g || b; + } + + boolean all() { + return r && g && b; + } + + BVec not() { + r = !r; + g = !g; + b = !b; + return this; + } + + BVec lessThan(short left, short right) { + r = R.of(left) < R.of(right); + g = G.of(left) < G.of(right); + b = B.of(left) < B.of(right); + return this; + } + + BVec lessThanMinusOne(short left, short right) { + r = R.of(left) < R.of(right) - 1; + g = G.of(left) < G.of(right) - 1; + b = B.of(left) < B.of(right) - 1; + return this; + } + + BVec and(BVec other, BVec another) { + r = other.r && another.r; + g = other.g && another.g; + b = other.b && another.b; + return this; + } + } +} diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 2a126d8f6..40e146395 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -30,65 +30,9 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.shapes.Shapes; -import grondag.canvas.light.color.LightRegionData.Encoding; - // TODO: cluster slab allocation? -> maybe unneeded now? // TODO: a way to repopulate cluster if needed class LightRegion implements LightRegionAccess { - private static class BVec { - boolean r, g, b; - - BVec() { - this.r = false; - this.g = false; - this.b = false; - } - - boolean get(int i) { - return switch (i) { - case 0 -> r; - case 1 -> g; - case 2 -> b; - default -> false; - }; - } - - boolean any() { - return r || g || b; - } - - boolean all() { - return r && g && b; - } - - BVec not() { - r = !r; - g = !g; - b = !b; - return this; - } - - void lessThan(short left, short right) { - r = Elem.R.of(left) < Elem.R.of(right); - g = Elem.G.of(left) < Elem.G.of(right); - b = Elem.B.of(left) < Elem.B.of(right); - } - - BVec lessThanMinusOne(short left, short right) { - r = Elem.R.of(left) < Elem.R.of(right) - 1; - g = Elem.G.of(left) < Elem.G.of(right) - 1; - b = Elem.B.of(left) < Elem.B.of(right) - 1; - return this; - } - - BVec and(BVec other, BVec another) { - r = other.r && another.r; - g = other.g && another.g; - b = other.b && another.b; - return this; - } - } - private enum Side { DOWN(0, -1, 0, 1, Direction.DOWN), UP(0, 1, 0, 2, Direction.UP), @@ -159,7 +103,7 @@ static short light(long entry) { final LightRegionData lightData; final long origin; - private final BVec less = new BVec(); + private final LightOp.BVec less = new LightOp.BVec(); private final BlockPos.MutableBlockPos sourcePos = new BlockPos.MutableBlockPos(); private final BlockPos.MutableBlockPos nodePos = new BlockPos.MutableBlockPos(); private final LongArrayFIFOQueue incQueue = new LongArrayFIFOQueue(); @@ -187,19 +131,19 @@ public void checkBlock(BlockPos pos, BlockState blockState) { final short getLight = lightData.get(index); final boolean occluding = blockState.canOcclude(); - if (Encoding.isLightSource(registeredLight)) { + if (LightOp.emitter(registeredLight)) { if (getLight != registeredLight) { - // replace light - if (Encoding.isLightSource(getLight)) { - lightData.put(index, (short) 0); + // remove old emitter + if (LightOp.emitter(getLight)) { + lightData.put(index, LightOp.EMPTY); Queues.enqueue(globalDecQueue, index, getLight); } - // place light + // place new emitter Queues.enqueue(globalIncQueue, index, registeredLight); } - } else if (Encoding.isLightSource(getLight) || Encoding.isOccluding(getLight) != occluding) { - // remove light or replace occluder + } else if (LightOp.emitter(getLight) || LightOp.occluder(getLight) != occluding || (LightOp.lit(getLight) && occluding)) { + // remove emitter or replace occluder lightData.put(index, registeredLight); Queues.enqueue(globalDecQueue, index, getLight); } @@ -230,8 +174,8 @@ boolean updateDecrease(BlockAndTintGetter blockView) { decQueue.enqueue(globalDecQueue.dequeueLong()); } - final BVec removeFlag = new BVec(); - final BVec removeMask = new BVec(); + final LightOp.BVec removeFlag = new LightOp.BVec(); + final LightOp.BVec removeMask = new LightOp.BVec(); int decCount = 0; boolean accessedNeighborDecrease = false; @@ -282,62 +226,48 @@ boolean updateDecrease(BlockAndTintGetter blockView) { } final int nodeIndex = dataAccess.indexify(nodePos); - short nodeLight = dataAccess.get(nodeIndex); + final short nodeLight = dataAccess.get(nodeIndex); - if (Encoding.pure(nodeLight) == 0) { + if (!LightOp.lit(nodeLight)) { continue; } final BlockState nodeState = blockView.getBlockState(nodePos); // check neighbor occlusion for decrease - if (!Encoding.isLightSource(nodeLight) && occludeSide(nodeState, side.opposite, blockView, nodePos)) { + if (!LightOp.emitter(nodeLight) && occludeSide(nodeState, side.opposite, blockView, nodePos)) { continue; } - less.lessThan(nodeLight, sourcePrevLight); - // only propagate removal according to removeFlag - removeMask.and(less, removeFlag); + removeMask.and(less.lessThan(nodeLight, sourcePrevLight), removeFlag); - boolean restoreLightSource = removeMask.any() && Encoding.isLightSource(nodeLight); + final boolean restoreLightSource = removeMask.any() && LightOp.emitter(nodeLight); + final short repropLight; if (removeMask.any()) { - int mask = 0; - - if (removeMask.r) { - mask |= Elem.R.mask; - } - - if (removeMask.g) { - mask |= Elem.G.mask; - } - - if (removeMask.b) { - mask |= Elem.B.mask; - } - - final short resultLight = (short) (nodeLight & ~(mask)); + final short resultLight = LightOp.remove(nodeLight, removeMask); dataAccess.put(nodeIndex, resultLight); - Queues.enqueue(decreaseQueue, nodeIndex, nodeLight, side); - nodeLight = resultLight; - // restore obliterated light source if (restoreLightSource) { // defer putting light source as to not mess with decrease step // take RGB of maximum and Alpha of registered final short registered = LightRegistry.get(nodeState); - nodeLight = Elem.maxRGB(nodeLight, registered, Elem.A.of(registered)); + repropLight = LightOp.max(registered, resultLight); + } else { + repropLight = resultLight; } - accessedNeighborDecrease = accessedNeighborDecrease || isNeighbor; + accessedNeighborDecrease |= isNeighbor; + } else { + repropLight = nodeLight; } if (removeMask.and(less.not(), removeFlag).any() || restoreLightSource) { // increases queued in decrease may propagate to all directions as if a light source - Queues.enqueue(increaseQueue, nodeIndex, nodeLight); + Queues.enqueue(increaseQueue, nodeIndex, repropLight); } } } @@ -349,12 +279,13 @@ boolean updateDecrease(BlockAndTintGetter blockView) { return accessedNeighborDecrease; } - public void updateIncrease(BlockAndTintGetter blockView) { + public boolean updateIncrease(BlockAndTintGetter blockView) { while (!globalIncQueue.isEmpty()) { incQueue.enqueue(globalIncQueue.dequeueLong()); } int incCount = 0; + boolean accessedNeighbor = false; while (!incQueue.isEmpty()) { incCount++; @@ -364,15 +295,18 @@ public void updateIncrease(BlockAndTintGetter blockView) { final short recordedLight = Queues.light(entry); final int from = Queues.from(entry); - short sourceLight = lightData.get(index); + final short getLight = lightData.get(index); + final short sourceLight; - if (sourceLight != recordedLight) { - if (Encoding.isLightSource(recordedLight)) { - sourceLight = Elem.maxRGB(sourceLight, recordedLight, Elem.A.of(recordedLight)); + if (getLight != recordedLight) { + if (LightOp.emitter(recordedLight)) { + sourceLight = LightOp.max(recordedLight, getLight); lightData.put(index, sourceLight); } else { continue; } + } else { + sourceLight = getLight; } lightData.reverseIndexify(index, sourcePos); @@ -385,7 +319,7 @@ public void updateIncrease(BlockAndTintGetter blockView) { } // check self occlusion for increase - if (!Encoding.isLightSource(sourceLight) && occludeSide(sourceState, side, blockView, sourcePos)) { + if (!LightOp.emitter(sourceLight) && occludeSide(sourceState, side, blockView, sourcePos)) { continue; } @@ -420,22 +354,11 @@ public void updateIncrease(BlockAndTintGetter blockView) { } if (less.lessThanMinusOne(nodeLight, sourceLight).any()) { - short resultLight = nodeLight; - - if (less.r) { - resultLight = Elem.R.replace(resultLight, (short) (Elem.R.of(sourceLight) - 1)); - } - - if (less.g) { - resultLight = Elem.G.replace(resultLight, (short) (Elem.G.of(sourceLight) - 1)); - } - - if (less.b) { - resultLight = Elem.B.replace(resultLight, (short) (Elem.B.of(sourceLight) - 1)); - } - + final short resultLight = LightOp.replaceMinusOne(nodeLight, sourceLight, less); dataAccess.put(nodeIndex, resultLight); Queues.enqueue(increaseQueue, nodeIndex, resultLight, side); + + accessedNeighbor |= isNeighbor; } } } @@ -443,6 +366,8 @@ public void updateIncrease(BlockAndTintGetter blockView) { if (incCount > 0) { lightData.markAsDirty(); } + + return accessedNeighbor; } private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourcePos, BlockPos.MutableBlockPos targetPos, Side side, BlockAndTintGetter blockView) { @@ -450,10 +375,10 @@ private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourc final short sourceLight = neighbor.lightData.get(sourceIndex); final BlockState sourceState = blockView.getBlockState(sourcePos); - if (Encoding.pure(sourceLight) != 0) { + if (LightOp.lit(sourceLight)) { // TODO: generalize for all increase process, with check-neighbor flag // check self occlusion for increase - if (!Encoding.isLightSource(sourceLight) && occludeSide(sourceState, side, blockView, sourcePos)) { + if (!LightOp.emitter(sourceLight) && occludeSide(sourceState, side, blockView, sourcePos)) { return; } @@ -467,20 +392,7 @@ private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourc } if (less.lessThanMinusOne(targetLight, sourceLight).any()) { - short resultLight = targetLight; - - if (less.r) { - resultLight = Elem.R.replace(resultLight, (short) (Elem.R.of(sourceLight) - 1)); - } - - if (less.g) { - resultLight = Elem.G.replace(resultLight, (short) (Elem.G.of(sourceLight) - 1)); - } - - if (less.b) { - resultLight = Elem.B.replace(resultLight, (short) (Elem.B.of(sourceLight) - 1)); - } - + final short resultLight = LightOp.replaceMinusOne(targetLight, sourceLight, less); lightData.put(targetIndex, resultLight); Queues.enqueue(incQueue, targetIndex, resultLight, side); } diff --git a/src/main/java/grondag/canvas/light/color/LightRegionAccess.java b/src/main/java/grondag/canvas/light/color/LightRegionAccess.java index d894e9839..72b2d8d4c 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegionAccess.java +++ b/src/main/java/grondag/canvas/light/color/LightRegionAccess.java @@ -1,3 +1,23 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + package grondag.canvas.light.color; import net.minecraft.core.BlockPos; diff --git a/src/main/java/grondag/canvas/light/color/LightRegionData.java b/src/main/java/grondag/canvas/light/color/LightRegionData.java index 34f342968..9af87f998 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegionData.java +++ b/src/main/java/grondag/canvas/light/color/LightRegionData.java @@ -35,28 +35,6 @@ public static class Const { public static final int WIDTH_MASK = WIDTH - 1; } - static class Encoding { - public static short encodeLight(int r, int g, int b, boolean isLightSource, boolean isOccluding) { - return Elem.encode(r, g, b, (isLightSource ? 0b1 : 0) | (isOccluding ? 0b10 : 0)); - } - - public static short encodeLight(int pureLight, boolean isLightSource, boolean isOccluding) { - return (short) (pureLight | (isLightSource ? 0b1 : 0) | (isOccluding ? 0b10 : 0)); - } - - public static boolean isLightSource(short light) { - return (light & 0b1) != 0; - } - - public static boolean isOccluding(short light) { - return (light & 0b10) != 0; - } - - public static short pure(short light) { - return (short) (light & 0xfff0); - } - } - final int regionOriginBlockX; final int regionOriginBlockY; final int regionOriginBlockZ; diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java index f7fdd3c32..1342f372d 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegistry.java +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -28,7 +28,6 @@ import io.vram.frex.api.light.ItemLight; -import grondag.canvas.light.color.LightRegionData.Encoding; import grondag.canvas.light.api.impl.BlockLightLoader; public class LightRegistry { @@ -46,7 +45,7 @@ public static short get(BlockState blockState) { private static short generate(BlockState blockState) { final int lightLevel = blockState.getLightEmission(); - final short defaultLight = Encoding.encodeLight(lightLevel, lightLevel, lightLevel, lightLevel > 0, blockState.canOcclude()); + final short defaultLight = LightOp.encodeLight(lightLevel, lightLevel, lightLevel, lightLevel > 0, blockState.canOcclude()); BlockLightLoader.CachedBlockLight apiLight = BlockLightLoader.INSTANCE.blockLights.get(blockState); @@ -59,7 +58,7 @@ private static short generate(BlockState blockState) { apiLight = apiLight.withLevel(blockState.getLightEmission()); } - return Encoding.encodeLight(apiLight.value(), apiLight.value() != 0, blockState.canOcclude()); + return LightOp.encodeLight(apiLight.value(), apiLight.value() != 0, blockState.canOcclude()); } if (lightLevel < 1) { @@ -84,6 +83,6 @@ private static short generate(BlockState blockState) { final int g = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.green() / maxValue)); final int b = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.blue() / maxValue)); - return Encoding.encodeLight(r, g, b, true, blockState.canOcclude()); + return LightOp.encodeLight(r, g, b, true, blockState.canOcclude()); } } diff --git a/src/main/java/grondag/canvas/shader/GlShader.java b/src/main/java/grondag/canvas/shader/GlShader.java index 210694211..8f235ddd9 100644 --- a/src/main/java/grondag/canvas/shader/GlShader.java +++ b/src/main/java/grondag/canvas/shader/GlShader.java @@ -271,10 +271,10 @@ private String getSource() { if (Pipeline.coloredLightsEnabled()) { var lightSize = Pipeline.lightVolumeSize(); - result = StringUtils.replace(result, "#define LIGHT_VOLUME_SIZE vec3(256.0)", String.format("#define LIGHT_VOLUME_SIZE vec3(%d, %d, %d)", lightSize.x, lightSize.y, lightSize.z)); + result = StringUtils.replace(result, "#define _CV_LIGHT_DATA_SIZE vec3(256.0)", String.format("#define _CV_LIGHT_DATA_SIZE vec3(%d, %d, %d)", lightSize.x, lightSize.y, lightSize.z)); } else { result = StringUtils.replace(result, "#define COLORED_LIGHTS_ENABLED", "//#define COLORED_LIGHTS_ENABLED"); - result = StringUtils.replace(result, "#define LIGHT_VOLUME_SIZE vec3(256.0)", "//#define LIGHT_VOLUME_SIZE vec3(256.0)"); + result = StringUtils.replace(result, "#define _CV_LIGHT_DATA_SIZE vec3(256.0)", "//#define _CV_LIGHT_DATA_SIZE vec3(256.0)"); } result = StringUtils.replace(result, "#define _CV_MAX_SHADER_COUNT 0", "#define _CV_MAX_SHADER_COUNT " + MaterialConstants.MAX_SHADERS); diff --git a/src/main/java/grondag/canvas/shader/data/FloatData.java b/src/main/java/grondag/canvas/shader/data/FloatData.java index 1f60798a8..079f9deaf 100644 --- a/src/main/java/grondag/canvas/shader/data/FloatData.java +++ b/src/main/java/grondag/canvas/shader/data/FloatData.java @@ -114,5 +114,5 @@ private FloatData() { } static final int SMOOTHED_THUNDER_STRENGTH = VEC_WEATHER + 3; static final int VEC_LIGHT_VOLUME_ORIGIN = 4 * 21; - static final int LIGHT_VOLUME_ORIGIN = VEC_LIGHT_VOLUME_ORIGIN; + static final int LIGHT_DATA_ORIGIN = VEC_LIGHT_VOLUME_ORIGIN; } diff --git a/src/main/java/grondag/canvas/shader/data/ShaderDataManager.java b/src/main/java/grondag/canvas/shader/data/ShaderDataManager.java index 1cd0039e9..5d2265d0a 100644 --- a/src/main/java/grondag/canvas/shader/data/ShaderDataManager.java +++ b/src/main/java/grondag/canvas/shader/data/ShaderDataManager.java @@ -39,7 +39,7 @@ import static grondag.canvas.shader.data.FloatData.HELD_LIGHT_INTENSITY; import static grondag.canvas.shader.data.FloatData.HELD_LIGHT_OUTER_ANGLE; import static grondag.canvas.shader.data.FloatData.HELD_LIGHT_RED; -import static grondag.canvas.shader.data.FloatData.LIGHT_VOLUME_ORIGIN; +import static grondag.canvas.shader.data.FloatData.LIGHT_DATA_ORIGIN; import static grondag.canvas.shader.data.FloatData.MOON_SIZE; import static grondag.canvas.shader.data.FloatData.NIGHT_VISION_STRENGTH; import static grondag.canvas.shader.data.FloatData.PLAYER_MOOD; @@ -167,7 +167,6 @@ import grondag.canvas.CanvasMod; import grondag.canvas.config.Configurator; -import grondag.canvas.light.color.LightDataManager; import grondag.canvas.mixinterface.DimensionTypeExt; import grondag.canvas.pipeline.Pipeline; import grondag.canvas.pipeline.PipelineManager; @@ -651,9 +650,9 @@ public static void updateEmissiveColor(int color) { } public static void updateLightVolumeOrigin(Vector3i lightOrigin) { - FLOAT_VECTOR_DATA.put(LIGHT_VOLUME_ORIGIN, lightOrigin.x); - FLOAT_VECTOR_DATA.put(LIGHT_VOLUME_ORIGIN + 1, lightOrigin.y); - FLOAT_VECTOR_DATA.put(LIGHT_VOLUME_ORIGIN + 2, lightOrigin.z); + FLOAT_VECTOR_DATA.put(LIGHT_DATA_ORIGIN, lightOrigin.x); + FLOAT_VECTOR_DATA.put(LIGHT_DATA_ORIGIN + 1, lightOrigin.y); + FLOAT_VECTOR_DATA.put(LIGHT_DATA_ORIGIN + 2, lightOrigin.z); } private static void putViewVector(int index, float yaw, float pitch, Vector3f storeTo) { diff --git a/src/main/resources/assets/canvas/shaders/internal/world.glsl b/src/main/resources/assets/canvas/shaders/internal/world.glsl index 18ee50224..2f8d37d69 100644 --- a/src/main/resources/assets/canvas/shaders/internal/world.glsl +++ b/src/main/resources/assets/canvas/shaders/internal/world.glsl @@ -73,7 +73,7 @@ #define _CV_WEATHER 20 // w is unused -#define _CV_LIGHT_VOLUME_ORIGIN 21 +#define _CV_LIGHT_DATA_ORIGIN 21 // UINT ARRAY #define _CV_RENDER_FRAMES 0 diff --git a/src/main/resources/assets/frex/shaders/api/header.glsl b/src/main/resources/assets/frex/shaders/api/header.glsl index 635e26e8f..e2deac3fc 100644 --- a/src/main/resources/assets/frex/shaders/api/header.glsl +++ b/src/main/resources/assets/frex/shaders/api/header.glsl @@ -5,7 +5,10 @@ #define SHADOW_MAP_PRESENT #define SHADOW_MAP_SIZE 1024 #define COLORED_LIGHTS_ENABLED -#define LIGHT_VOLUME_SIZE vec3(256.0) +//#define SPARSE_LIGHT_DATA +//#define LIGHT_DATA_HAS_OCCLUSION +#define _CV_LIGHT_DATA_COMPLEX_FILTER +#define _CV_LIGHT_DATA_SIZE vec3(256.0) #define MATERIAL_TARGET_UNKNOWN //#define DEPTH_PASS //#define PBR_ENABLED diff --git a/src/main/resources/assets/frex/shaders/api/light.glsl b/src/main/resources/assets/frex/shaders/api/light.glsl new file mode 100644 index 000000000..1a6412ee3 --- /dev/null +++ b/src/main/resources/assets/frex/shaders/api/light.glsl @@ -0,0 +1,187 @@ +/**************************************************************** + * frex:shaders/api/light.glsl - Canvas Implementation + ***************************************************************/ + +#ifdef COLORED_LIGHTS_ENABLED + +#ifdef SPARSE_LIGHT_DATA +#define LightSampler sampler2D + +bool _cv_hasLightData(vec3 worldPos) { + return false; +} + +vec2 _cv_lightCoords(vec3 worldPos) { + return vec2(0.0); +} + +ivec2 _cv_lightTexelCoords(vec3 worldPos) { + return ivec2(0); +} +#else +#define LightSampler sampler3D + +bool _cv_hasLightData(vec3 worldPos) { + return clamp(worldPos, _cvu_world[_CV_LIGHT_DATA_ORIGIN].xyz, _cvu_world[_CV_LIGHT_DATA_ORIGIN].xyz + _CV_LIGHT_DATA_SIZE) == worldPos; +} + +vec3 _cv_lightCoords(vec3 worldPos) { + return mod(worldPos, _CV_LIGHT_DATA_SIZE) / _CV_LIGHT_DATA_SIZE; +} + +ivec3 _cv_lightTexelCoords(vec3 worldPos) { + return ivec3(mod(clamp(worldPos, _cvu_world[_CV_LIGHT_DATA_ORIGIN].xyz, _cvu_world[_CV_LIGHT_DATA_ORIGIN].xyz + _CV_LIGHT_DATA_SIZE), _CV_LIGHT_DATA_SIZE)); +} +#endif + +bool _cv_isUseful(float a) { + return (int(a * 15.0) & 8) > 0; +} + +bool _cv_isOccluder(float a) { + return (int(a * 15.0) & 2) > 0; +} + +#if !defined(SPARSE_LIGHT_DATA) || defined(_CV_LIGHT_DATA_COMPLEX_FILTER) +vec3 _cv_getLightFiltered(LightSampler lightSampler, vec3 worldPos, vec3 fallback) { + if (!_cv_hasLightData(worldPos)) { + return fallback; + } + + vec3 pos = worldPos - vec3(0.5); + + #ifdef _CV_LIGHT_DATA_COMPLEX_FILTER + pos = floor(pos) + vec3(0.5); + #endif + + vec4 tex000 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(0.0, 0.0, 0.0)), 0); + vec4 tex001 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(0.0, 0.0, 1.0)), 0); + vec4 tex010 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(0.0, 1.0, 0.0)), 0); + vec4 tex011 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(0.0, 1.0, 1.0)), 0); + vec4 tex101 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(1.0, 0.0, 1.0)), 0); + vec4 tex110 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(1.0, 1.0, 0.0)), 0); + vec4 tex100 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(1.0, 0.0, 0.0)), 0); + vec4 tex111 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(1.0, 1.0, 1.0)), 0); + + #ifdef _CV_LIGHT_DATA_COMPLEX_FILTER + vec3 center = worldPos - pos; + vec3 pos000 = vec3(0.0, 0.0, 0.0) - center; + vec3 pos001 = vec3(0.0, 0.0, 1.0) - center; + vec3 pos010 = vec3(0.0, 1.0, 0.0) - center; + vec3 pos011 = vec3(0.0, 1.0, 1.0) - center; + vec3 pos101 = vec3(1.0, 0.0, 1.0) - center; + vec3 pos110 = vec3(1.0, 1.0, 0.0) - center; + vec3 pos100 = vec3(1.0, 0.0, 0.0) - center; + vec3 pos111 = vec3(1.0, 1.0, 1.0) - center; + + float w000 = float(_cv_isUseful(tex000.a)) * abs(pos111.x * pos111.y * pos111.z); + float w001 = float(_cv_isUseful(tex001.a)) * abs(pos110.x * pos110.y * pos110.z); + float w010 = float(_cv_isUseful(tex010.a)) * abs(pos101.x * pos101.y * pos101.z); + float w011 = float(_cv_isUseful(tex011.a)) * abs(pos100.x * pos100.y * pos100.z); + float w101 = float(_cv_isUseful(tex101.a)) * abs(pos010.x * pos010.y * pos010.z); + float w110 = float(_cv_isUseful(tex110.a)) * abs(pos001.x * pos001.y * pos001.z); + float w100 = float(_cv_isUseful(tex100.a)) * abs(pos011.x * pos011.y * pos011.z); + float w111 = float(_cv_isUseful(tex111.a)) * abs(pos000.x * pos000.y * pos000.z); + + float weight = w000 + w001 + w010 + w011 + w101 + w110 + w100 + w111; + vec3 finalMix = weight == 0.0 ? vec3(0.0) : (tex000.rgb * w000 + tex001.rgb * w001 + tex010.rgb * w010 + tex011.rgb * w011 + tex101.rgb * w101 + tex110.rgb * w110 + tex100.rgb * w100 + tex111.rgb * w111) / weight; + #else + vec3 fac = fract(pos); + + vec3 mix001 = mix(tex000.rgb, tex001.rgb, fac.z); + vec3 mix011 = mix(tex010.rgb, tex011.rgb, fac.z); + vec3 mix010 = mix(mix001, mix011, fac.y); + + vec3 mix101 = mix(tex100.rgb, tex101.rgb, fac.z); + vec3 mix111 = mix(tex110.rgb, tex111.rgb, fac.z); + vec3 mix110 = mix(mix101, mix111, fac.y); + + vec3 finalMix = mix(mix010, mix110, fac.x); + #endif + + return finalMix; + +// HALL OF SHAME +// +// #ifdef _CV_LIGHT_DATA_COMPLEX_FILTER +// vec3 cTex = texelFetch(lightSampler, _cv_lightTexelCoords(worldPos), 0).rgb; +// tex000 = tex000 == vec3(0.0) ? cTex : tex000; +// tex001 = tex001 == vec3(0.0) ? cTex : tex001; +// tex010 = tex010 == vec3(0.0) ? cTex : tex010; +// tex011 = tex011 == vec3(0.0) ? cTex : tex011; +// tex100 = tex100 == vec3(0.0) ? cTex : tex100; +// tex101 = tex101 == vec3(0.0) ? cTex : tex101; +// tex110 = tex110 == vec3(0.0) ? cTex : tex110; +// tex111 = tex111 == vec3(0.0) ? cTex : tex111; +// #endif +// +// float w000 = tex000 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(0.0, 0.0, 0.0) - center)), vec3(1.0)) / 3.0); +// float w001 = tex001 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(0.0, 0.0, 1.0) - center)), vec3(1.0)) / 3.0); +// float w010 = tex010 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(0.0, 1.0, 0.0) - center)), vec3(1.0)) / 3.0); +// float w011 = tex011 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(0.0, 1.0, 1.0) - center)), vec3(1.0)) / 3.0); +// +// float w101 = tex101 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(1.0, 0.0, 1.0) - center)), vec3(1.0)) / 3.0); +// float w110 = tex110 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(1.0, 1.0, 0.0) - center)), vec3(1.0)) / 3.0); +// float w100 = tex100 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(1.0, 0.0, 0.0) - center)), vec3(1.0)) / 3.0); +// float w111 = tex111 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(1.0, 1.0, 1.0) - center)), vec3(1.0)) / 3.0); + +// vec3 mix001 = tex000 == vec3(0.0) ? tex001 : (tex001 == vec3(0.0) ? tex000 : mix(tex000, tex001, fac.z)); +// vec3 mix011 = tex010 == vec3(0.0) ? tex011 : (tex011 == vec3(0.0) ? tex010 : mix(tex010, tex011, fac.z)); +// vec3 mix010 = mix001 == vec3(0.0) ? mix011 : (mix011 == vec3(0.0) ? mix001 : mix(mix001, mix011, fac.y)); +// +// vec3 mix101 = tex100 == vec3(0.0) ? tex101 : (tex101 == vec3(0.0) ? tex100 : mix(tex100, tex101, fac.z)); +// vec3 mix111 = tex110 == vec3(0.0) ? tex111 : (tex111 == vec3(0.0) ? tex110 : mix(tex110, tex111, fac.z)); +// vec3 mix110 = mix101 == vec3(0.0) ? mix111 : (mix111 == vec3(0.0) ? mix101 : mix(mix101, mix111, fac.y)); +// +// vec3 finalMix = mix010 == vec3(0.0) ? mix110 : (mix110 == vec3(0.0) ? mix010 : mix(mix010, mix110, fac.x)); + +// float w0 = 1.0 - (fac.x + fac.y + fac.z) * 0.33333333333; +// +// float w1 = fac.x; +// float w2 = fac.y; +// float w3 = fac.z; +// float w4 = (fac.x + fac.y) * 0.5; +// float w5 = (fac.y + fac.z) * 0.5; +// float w6 = (fac.x + fac.z) * 0.5; +// float w7 = (fac.x + fac.y + fac.z) * 0.33333333333; +// +// #ifdef _CV_LIGHT_DATA_COMPLEX_FILTER +// w1 *= (tex1 == vec3(0.0)) ? 0.0 : 1.0; +// w2 *= (tex2 == vec3(0.0)) ? 0.0 : 1.0; +// w3 *= (tex3 == vec3(0.0)) ? 0.0 : 1.0; +// w4 *= (tex4 == vec3(0.0)) ? 0.0 : 1.0; +// w5 *= (tex5 == vec3(0.0)) ? 0.0 : 1.0; +// w6 *= (tex6 == vec3(0.0)) ? 0.0 : 1.0; +// w7 *= (tex7 == vec3(0.0)) ? 0.0 : 1.0; +// #endif + +// float weight = w0 + w1 + w2 + w3 + w4 + w5 + w6 + w7; +// +// return (tex0 * w0 + tex1 * w1 + tex2 * w2 + tex3 * w3 + tex4 * w4 + tex5 * w5 + tex6 * w6 + tex7 * w7) / weight; +} +#endif + +vec4 frx_getLightRaw(LightSampler lightSampler, vec3 worldPos) { + if (!_cv_hasLightData(worldPos)) { + return vec4(0.0); + } + + vec4 tex = texture(lightSampler, _cv_lightCoords(worldPos)); + return vec4(tex.rgb, float(_cv_isUseful(tex.a))); +} + +bool frx_lightDataExists(vec3 worldPos) { + return _cv_hasLightData(worldPos); +} + +#ifdef _CV_LIGHT_DATA_COMPLEX_FILTER + #define frx_getLightFiltered _cv_getLightFiltered +#else + #ifdef SPARSE_LIGHT_DATA + #define frx_getLightFiltered _cv_getLightFiltered + #else + #define frx_getLightFiltered frx_getLightRaw + #endif +#endif + +#endif diff --git a/src/main/resources/assets/frex/shaders/api/sampler.glsl b/src/main/resources/assets/frex/shaders/api/sampler.glsl index ed4f5e365..ac831e459 100644 --- a/src/main/resources/assets/frex/shaders/api/sampler.glsl +++ b/src/main/resources/assets/frex/shaders/api/sampler.glsl @@ -1,4 +1,5 @@ #include canvas:shaders/internal/program.glsl +#include frex:shaders/api/light.glsl /**************************************************************** * frex:shaders/api/sampler.glsl - Canvas Implementation @@ -24,3 +25,14 @@ uniform sampler2DArrayShadow frxs_shadowMap; uniform sampler2DArray frxs_shadowMapTexture; #endif #endif + +#ifdef COLORED_LIGHTS_ENABLED +#ifdef SPARSE_LIGHT_DATA +uniform sampler2D frxs_lightData; +#else +uniform sampler3D frxs_lightData; +#endif + +#define frx_getLightFiltered(worldPos, fallback) frx_getLightFiltered(frxs_lightData, worldPos, fallback) +#define frx_getLightRaw(worldPos) frx_getLightRaw(frxs_lightData, worldPos) +#endif diff --git a/src/main/resources/assets/frex/shaders/api/view.glsl b/src/main/resources/assets/frex/shaders/api/view.glsl index a8bcb1d7d..46c12e38f 100644 --- a/src/main/resources/assets/frex/shaders/api/view.glsl +++ b/src/main/resources/assets/frex/shaders/api/view.glsl @@ -38,7 +38,6 @@ #define frx_shadowProjectionMatrix(index) (_cvu_matrix[_CV_MAT_SHADOW_PROJ_0 + index]) #define frx_shadowViewProjectionMatrix(index) (_cvu_matrix[_CV_MAT_SHADOW_VIEW_PROJ_0 + index]) #define frx_shadowCenter(index) (_cvu_world[_CV_SHADOW_CENTER + index]) -#define frx_lightVolumeOrigin _cvu_world[_CV_LIGHT_VOLUME_ORIGIN].xyz #define frx_viewWidth _cvu_world[_CV_VIEW_PARAMS].x #define frx_viewHeight _cvu_world[_CV_VIEW_PARAMS].y #define frx_viewAspectRatio _cvu_world[_CV_VIEW_PARAMS].z @@ -54,6 +53,3 @@ #define frx_cameraInSnow int((_cvu_flags[_CV_WORLD_FLAGS_INDEX] >> 25) & 1u) #define frx_viewFlag(flag) (((_cvu_flags[_CV_WORLD_FLAGS_INDEX] >> flag) & 1u) == 1u) // DEPRECATED - DO NOT USE - - - From 0b02b8c6961d33af909aa9528f3b3f1f984e8ad8 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Mon, 3 Jul 2023 19:54:59 +0700 Subject: [PATCH 37/69] Light occlusion data complete retrieval, cleanup --- .../grondag/canvas/light/color/LightOp.java | 12 +- .../canvas/light/color/LightRegistry.java | 53 ++++++++- .../java/grondag/canvas/shader/GlShader.java | 4 +- .../assets/frex/shaders/api/light.glsl | 109 +++++------------- .../assets/frex/shaders/api/sampler.glsl | 3 +- 5 files changed, 92 insertions(+), 89 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightOp.java b/src/main/java/grondag/canvas/light/color/LightOp.java index 26c334983..04be9931c 100644 --- a/src/main/java/grondag/canvas/light/color/LightOp.java +++ b/src/main/java/grondag/canvas/light/color/LightOp.java @@ -47,16 +47,16 @@ public static short encode(int r, int g, int b, int a) { return (short) ((r << R.shift) | (g << G.shift) | (b << B.shift) | (a << A.shift)); } - public static short encodeLight(int r, int g, int b, boolean isEmitter, boolean isOccluding) { - return ensureUsefulness(encode(r, g, b, encodeAlpha(isEmitter, isOccluding))); + public static short encodeLight(int r, int g, int b, boolean isFull, boolean isEmitter, boolean isOccluding) { + return ensureUsefulness(encode(r, g, b, encodeAlpha(isFull, isEmitter, isOccluding))); } - public static short encodeLight(int pureLight, boolean isEmitter, boolean isOccluding) { - return ensureUsefulness((short) (pureLight | encodeAlpha(isEmitter, isOccluding))); + public static short encodeLight(int pureLight, boolean isFull, boolean isEmitter, boolean isOccluding) { + return ensureUsefulness((short) (pureLight | encodeAlpha(isFull, isEmitter, isOccluding))); } - private static int encodeAlpha(boolean isEmitter, boolean isOccluding) { - return (isEmitter ? EMITTER_FLAG : 0) | (isOccluding ? OCCLUDER_FLAG : 0); + private static int encodeAlpha(boolean isFull, boolean isEmitter, boolean isOccluding) { + return (isFull ? FULL_FLAG : 0) | (isEmitter ? EMITTER_FLAG : 0) | (isOccluding ? OCCLUDER_FLAG : 0); } private static short ensureUsefulness(short light) { diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java index 1342f372d..4e8a13356 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegistry.java +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -22,9 +22,17 @@ import java.util.concurrent.ConcurrentHashMap; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.core.BlockPos; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; import io.vram.frex.api.light.ItemLight; @@ -32,6 +40,8 @@ public class LightRegistry { private static final ConcurrentHashMap cachedLights = new ConcurrentHashMap<>(); + // Only for full block checks. Real MC world is only needed for blocks with positional offset like flowers, etc. + private static final DummyWorld DUMMY_WORLD = new DummyWorld(); public static void reload(ResourceManager manager) { cachedLights.clear(); @@ -44,8 +54,9 @@ public static short get(BlockState blockState) { } private static short generate(BlockState blockState) { + final boolean isFullCube = blockState.isCollisionShapeFullBlock(DUMMY_WORLD.set(blockState), DummyWorld.origin); final int lightLevel = blockState.getLightEmission(); - final short defaultLight = LightOp.encodeLight(lightLevel, lightLevel, lightLevel, lightLevel > 0, blockState.canOcclude()); + final short defaultLight = LightOp.encodeLight(lightLevel, lightLevel, lightLevel, isFullCube, lightLevel > 0, blockState.canOcclude()); BlockLightLoader.CachedBlockLight apiLight = BlockLightLoader.INSTANCE.blockLights.get(blockState); @@ -58,7 +69,7 @@ private static short generate(BlockState blockState) { apiLight = apiLight.withLevel(blockState.getLightEmission()); } - return LightOp.encodeLight(apiLight.value(), apiLight.value() != 0, blockState.canOcclude()); + return LightOp.encodeLight(apiLight.value(), isFullCube, apiLight.value() != 0, blockState.canOcclude()); } if (lightLevel < 1) { @@ -83,6 +94,42 @@ private static short generate(BlockState blockState) { final int g = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.green() / maxValue)); final int b = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.blue() / maxValue)); - return LightOp.encodeLight(r, g, b, true, blockState.canOcclude()); + return LightOp.encodeLight(r, g, b, isFullCube, true, blockState.canOcclude()); + } + + private static class DummyWorld implements BlockGetter { + private static final BlockPos origin = new BlockPos(0, 0, 0); + private static BlockState state; + + private DummyWorld set(BlockState state) { + this.state = state; + return this; + } + + @Nullable + @Override + public BlockEntity getBlockEntity(BlockPos blockPos) { + return null; + } + + @Override + public BlockState getBlockState(BlockPos blockPos) { + return blockPos.equals(origin) ? state : Blocks.AIR.defaultBlockState(); + } + + @Override + public FluidState getFluidState(BlockPos blockPos) { + return blockPos.equals(origin) ? state.getFluidState() : Fluids.EMPTY.defaultFluidState(); + } + + @Override + public int getHeight() { + return 1; + } + + @Override + public int getMinBuildHeight() { + return 0; + } } } diff --git a/src/main/java/grondag/canvas/shader/GlShader.java b/src/main/java/grondag/canvas/shader/GlShader.java index 8f235ddd9..dd81e0094 100644 --- a/src/main/java/grondag/canvas/shader/GlShader.java +++ b/src/main/java/grondag/canvas/shader/GlShader.java @@ -199,14 +199,14 @@ private void outputDebugSource(String source, String error) { File shaderDir = path.toFile(); if (shaderDir.mkdir()) { - CanvasMod.LOG.info("Created shader debug output folder" + shaderDir.toString()); + CanvasMod.LOG.info("Created shader debug output folder " + shaderDir.toString()); } if (error != null) { shaderDir = path.resolve("failed").toFile(); if (shaderDir.mkdir()) { - CanvasMod.LOG.info("Created shader debug output failure folder" + shaderDir.toString()); + CanvasMod.LOG.info("Created shader debug output failure folder " + shaderDir.toString()); } source += "\n\n///////// ERROR ////////\n" + error + "\n////////////////////////\n"; diff --git a/src/main/resources/assets/frex/shaders/api/light.glsl b/src/main/resources/assets/frex/shaders/api/light.glsl index 1a6412ee3..f79b3a432 100644 --- a/src/main/resources/assets/frex/shaders/api/light.glsl +++ b/src/main/resources/assets/frex/shaders/api/light.glsl @@ -38,14 +38,9 @@ bool _cv_isUseful(float a) { return (int(a * 15.0) & 8) > 0; } -bool _cv_isOccluder(float a) { - return (int(a * 15.0) & 2) > 0; -} - -#if !defined(SPARSE_LIGHT_DATA) || defined(_CV_LIGHT_DATA_COMPLEX_FILTER) -vec3 _cv_getLightFiltered(LightSampler lightSampler, vec3 worldPos, vec3 fallback) { +vec4 frx_getLightFiltered(LightSampler lightSampler, vec3 worldPos) { if (!_cv_hasLightData(worldPos)) { - return fallback; + return vec4(0.0); } vec3 pos = worldPos - vec3(0.5); @@ -84,7 +79,7 @@ vec3 _cv_getLightFiltered(LightSampler lightSampler, vec3 worldPos, vec3 fallbac float w111 = float(_cv_isUseful(tex111.a)) * abs(pos000.x * pos000.y * pos000.z); float weight = w000 + w001 + w010 + w011 + w101 + w110 + w100 + w111; - vec3 finalMix = weight == 0.0 ? vec3(0.0) : (tex000.rgb * w000 + tex001.rgb * w001 + tex010.rgb * w010 + tex011.rgb * w011 + tex101.rgb * w101 + tex110.rgb * w110 + tex100.rgb * w100 + tex111.rgb * w111) / weight; + vec4 finalMix = weight == 0.0 ? vec4(0.0) : vec4((tex000.rgb * w000 + tex001.rgb * w001 + tex010.rgb * w010 + tex011.rgb * w011 + tex101.rgb * w101 + tex110.rgb * w110 + tex100.rgb * w100 + tex111.rgb * w111) / weight, 1.0); #else vec3 fac = fract(pos); @@ -96,92 +91,52 @@ vec3 _cv_getLightFiltered(LightSampler lightSampler, vec3 worldPos, vec3 fallbac vec3 mix111 = mix(tex110.rgb, tex111.rgb, fac.z); vec3 mix110 = mix(mix101, mix111, fac.y); - vec3 finalMix = mix(mix010, mix110, fac.x); + vec4 finalMix = vec4(mix(mix010, mix110, fac.x), 1.0); #endif return finalMix; - -// HALL OF SHAME -// -// #ifdef _CV_LIGHT_DATA_COMPLEX_FILTER -// vec3 cTex = texelFetch(lightSampler, _cv_lightTexelCoords(worldPos), 0).rgb; -// tex000 = tex000 == vec3(0.0) ? cTex : tex000; -// tex001 = tex001 == vec3(0.0) ? cTex : tex001; -// tex010 = tex010 == vec3(0.0) ? cTex : tex010; -// tex011 = tex011 == vec3(0.0) ? cTex : tex011; -// tex100 = tex100 == vec3(0.0) ? cTex : tex100; -// tex101 = tex101 == vec3(0.0) ? cTex : tex101; -// tex110 = tex110 == vec3(0.0) ? cTex : tex110; -// tex111 = tex111 == vec3(0.0) ? cTex : tex111; -// #endif -// -// float w000 = tex000 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(0.0, 0.0, 0.0) - center)), vec3(1.0)) / 3.0); -// float w001 = tex001 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(0.0, 0.0, 1.0) - center)), vec3(1.0)) / 3.0); -// float w010 = tex010 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(0.0, 1.0, 0.0) - center)), vec3(1.0)) / 3.0); -// float w011 = tex011 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(0.0, 1.0, 1.0) - center)), vec3(1.0)) / 3.0); -// -// float w101 = tex101 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(1.0, 0.0, 1.0) - center)), vec3(1.0)) / 3.0); -// float w110 = tex110 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(1.0, 1.0, 0.0) - center)), vec3(1.0)) / 3.0); -// float w100 = tex100 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(1.0, 0.0, 0.0) - center)), vec3(1.0)) / 3.0); -// float w111 = tex111 == vec3(0.0) ? 0.0 : 1.0 - (dot(abs((vec3(1.0, 1.0, 1.0) - center)), vec3(1.0)) / 3.0); - -// vec3 mix001 = tex000 == vec3(0.0) ? tex001 : (tex001 == vec3(0.0) ? tex000 : mix(tex000, tex001, fac.z)); -// vec3 mix011 = tex010 == vec3(0.0) ? tex011 : (tex011 == vec3(0.0) ? tex010 : mix(tex010, tex011, fac.z)); -// vec3 mix010 = mix001 == vec3(0.0) ? mix011 : (mix011 == vec3(0.0) ? mix001 : mix(mix001, mix011, fac.y)); -// -// vec3 mix101 = tex100 == vec3(0.0) ? tex101 : (tex101 == vec3(0.0) ? tex100 : mix(tex100, tex101, fac.z)); -// vec3 mix111 = tex110 == vec3(0.0) ? tex111 : (tex111 == vec3(0.0) ? tex110 : mix(tex110, tex111, fac.z)); -// vec3 mix110 = mix101 == vec3(0.0) ? mix111 : (mix111 == vec3(0.0) ? mix101 : mix(mix101, mix111, fac.y)); -// -// vec3 finalMix = mix010 == vec3(0.0) ? mix110 : (mix110 == vec3(0.0) ? mix010 : mix(mix010, mix110, fac.x)); - -// float w0 = 1.0 - (fac.x + fac.y + fac.z) * 0.33333333333; -// -// float w1 = fac.x; -// float w2 = fac.y; -// float w3 = fac.z; -// float w4 = (fac.x + fac.y) * 0.5; -// float w5 = (fac.y + fac.z) * 0.5; -// float w6 = (fac.x + fac.z) * 0.5; -// float w7 = (fac.x + fac.y + fac.z) * 0.33333333333; -// -// #ifdef _CV_LIGHT_DATA_COMPLEX_FILTER -// w1 *= (tex1 == vec3(0.0)) ? 0.0 : 1.0; -// w2 *= (tex2 == vec3(0.0)) ? 0.0 : 1.0; -// w3 *= (tex3 == vec3(0.0)) ? 0.0 : 1.0; -// w4 *= (tex4 == vec3(0.0)) ? 0.0 : 1.0; -// w5 *= (tex5 == vec3(0.0)) ? 0.0 : 1.0; -// w6 *= (tex6 == vec3(0.0)) ? 0.0 : 1.0; -// w7 *= (tex7 == vec3(0.0)) ? 0.0 : 1.0; -// #endif - -// float weight = w0 + w1 + w2 + w3 + w4 + w5 + w6 + w7; -// -// return (tex0 * w0 + tex1 * w1 + tex2 * w2 + tex3 * w3 + tex4 * w4 + tex5 * w5 + tex6 * w6 + tex7 * w7) / weight; } -#endif vec4 frx_getLightRaw(LightSampler lightSampler, vec3 worldPos) { if (!_cv_hasLightData(worldPos)) { return vec4(0.0); } + // TODO: use texture() for debug purpose. should be texelFetc() eventually vec4 tex = texture(lightSampler, _cv_lightCoords(worldPos)); return vec4(tex.rgb, float(_cv_isUseful(tex.a))); } +vec3 frx_getLight(LightSampler lightSampler, vec3 worldPos, vec3 fallback) { + vec4 light = frx_getLightFiltered(lightSampler, worldPos); + return mix(fallback, light.rgb, light.a); +} + bool frx_lightDataExists(vec3 worldPos) { return _cv_hasLightData(worldPos); } -#ifdef _CV_LIGHT_DATA_COMPLEX_FILTER - #define frx_getLightFiltered _cv_getLightFiltered -#else - #ifdef SPARSE_LIGHT_DATA - #define frx_getLightFiltered _cv_getLightFiltered - #else - #define frx_getLightFiltered frx_getLightRaw - #endif -#endif +#ifdef LIGHT_DATA_HAS_OCCLUSION +struct frx_LightData { + vec4 light; + bool isLightSource; + bool isOccluder; + bool isFullCube; +}; + +frx_LightData frx_getLightOcclusionData(LightSampler lightSampler, vec3 worldPos) { + if (!_cv_hasLightData(worldPos)) { + return frx_LightData(vec4(0.0), false, false, false); + } + vec4 tex = texelFetch(lightSampler, _cv_lightTexelCoords(worldPos)); + int flags = int(a * 15.0); + + bool isFullCube = (flags & 4) > 0; + bool isOccluder = (flags & 2) > 0; + bool isLightSource = (flags & 1) > 0; + + return frx_LightData(vec4(tex.rgb, 1.0), isLightSource, isOccluder, isFullCube); +} +#endif #endif diff --git a/src/main/resources/assets/frex/shaders/api/sampler.glsl b/src/main/resources/assets/frex/shaders/api/sampler.glsl index ac831e459..56653396c 100644 --- a/src/main/resources/assets/frex/shaders/api/sampler.glsl +++ b/src/main/resources/assets/frex/shaders/api/sampler.glsl @@ -33,6 +33,7 @@ uniform sampler2D frxs_lightData; uniform sampler3D frxs_lightData; #endif -#define frx_getLightFiltered(worldPos, fallback) frx_getLightFiltered(frxs_lightData, worldPos, fallback) +#define frx_getLightFiltered(worldPos) frx_getLightFiltered(frxs_lightData, worldPos) #define frx_getLightRaw(worldPos) frx_getLightRaw(frxs_lightData, worldPos) +#define frx_getLight(worldPos, fallback) frx_getLight(frxs_lightData, worldPos, fallback) #endif From a033f2d9eaa9f088f038af4e977d27bdf36915b2 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Wed, 12 Jul 2023 06:51:48 +0700 Subject: [PATCH 38/69] Improve / fix complex light sampling around the edges It's a tad more complex This also includes "fix" for a bug that was discovered after sampling was improved. The bug remains, this only reduces the frequency (pretty much "edge case" for now). --- .../canvas/light/color/LightRegion.java | 20 ++---- .../assets/frex/shaders/api/light.glsl | 64 +++++++++++-------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 40e146395..2e2a1f4db 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -130,22 +130,16 @@ public void checkBlock(BlockPos pos, BlockState blockState) { final int index = lightData.indexify(pos); final short getLight = lightData.get(index); final boolean occluding = blockState.canOcclude(); + final boolean emitting = LightOp.emitter(registeredLight); - if (LightOp.emitter(registeredLight)) { - if (getLight != registeredLight) { - // remove old emitter - if (LightOp.emitter(getLight)) { - lightData.put(index, LightOp.EMPTY); - Queues.enqueue(globalDecQueue, index, getLight); - } + if ((emitting && getLight != registeredLight) || LightOp.emitter(getLight) || LightOp.occluder(getLight) != occluding || (LightOp.lit(getLight) && occluding)) { + final short combined = emitting ? LightOp.max(registeredLight, getLight) : registeredLight; + lightData.put(index, combined); + Queues.enqueue(globalDecQueue, index, getLight); - // place new emitter - Queues.enqueue(globalIncQueue, index, registeredLight); + if (emitting) { + Queues.enqueue(globalIncQueue, index, combined); } - } else if (LightOp.emitter(getLight) || LightOp.occluder(getLight) != occluding || (LightOp.lit(getLight) && occluding)) { - // remove emitter or replace occluder - lightData.put(index, registeredLight); - Queues.enqueue(globalDecQueue, index, getLight); } } diff --git a/src/main/resources/assets/frex/shaders/api/light.glsl b/src/main/resources/assets/frex/shaders/api/light.glsl index f79b3a432..2911e4286 100644 --- a/src/main/resources/assets/frex/shaders/api/light.glsl +++ b/src/main/resources/assets/frex/shaders/api/light.glsl @@ -43,40 +43,52 @@ vec4 frx_getLightFiltered(LightSampler lightSampler, vec3 worldPos) { return vec4(0.0); } - vec3 pos = worldPos - vec3(0.5); - #ifdef _CV_LIGHT_DATA_COMPLEX_FILTER - pos = floor(pos) + vec3(0.5); + vec3 pos = floor(worldPos) + vec3(0.5); + vec3 H = sign(fract(worldPos) - vec3(0.5)); + #else + vec3 pos = worldPos - vec3(0.5); + const vec3 H = vec3(1.0); #endif vec4 tex000 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(0.0, 0.0, 0.0)), 0); - vec4 tex001 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(0.0, 0.0, 1.0)), 0); - vec4 tex010 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(0.0, 1.0, 0.0)), 0); - vec4 tex011 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(0.0, 1.0, 1.0)), 0); - vec4 tex101 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(1.0, 0.0, 1.0)), 0); - vec4 tex110 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(1.0, 1.0, 0.0)), 0); - vec4 tex100 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(1.0, 0.0, 0.0)), 0); - vec4 tex111 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(1.0, 1.0, 1.0)), 0); + vec4 tex001 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(0.0, 0.0, H.z)), 0); + vec4 tex010 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(0.0, H.y, 0.0)), 0); + vec4 tex011 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(0.0, H.y, H.z)), 0); + vec4 tex101 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(H.x, 0.0, H.z)), 0); + vec4 tex110 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(H.x, H.y, 0.0)), 0); + vec4 tex100 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(H.x, 0.0, 0.0)), 0); + vec4 tex111 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(H.x, H.y, H.z)), 0); #ifdef _CV_LIGHT_DATA_COMPLEX_FILTER vec3 center = worldPos - pos; vec3 pos000 = vec3(0.0, 0.0, 0.0) - center; - vec3 pos001 = vec3(0.0, 0.0, 1.0) - center; - vec3 pos010 = vec3(0.0, 1.0, 0.0) - center; - vec3 pos011 = vec3(0.0, 1.0, 1.0) - center; - vec3 pos101 = vec3(1.0, 0.0, 1.0) - center; - vec3 pos110 = vec3(1.0, 1.0, 0.0) - center; - vec3 pos100 = vec3(1.0, 0.0, 0.0) - center; - vec3 pos111 = vec3(1.0, 1.0, 1.0) - center; - - float w000 = float(_cv_isUseful(tex000.a)) * abs(pos111.x * pos111.y * pos111.z); - float w001 = float(_cv_isUseful(tex001.a)) * abs(pos110.x * pos110.y * pos110.z); - float w010 = float(_cv_isUseful(tex010.a)) * abs(pos101.x * pos101.y * pos101.z); - float w011 = float(_cv_isUseful(tex011.a)) * abs(pos100.x * pos100.y * pos100.z); - float w101 = float(_cv_isUseful(tex101.a)) * abs(pos010.x * pos010.y * pos010.z); - float w110 = float(_cv_isUseful(tex110.a)) * abs(pos001.x * pos001.y * pos001.z); - float w100 = float(_cv_isUseful(tex100.a)) * abs(pos011.x * pos011.y * pos011.z); - float w111 = float(_cv_isUseful(tex111.a)) * abs(pos000.x * pos000.y * pos000.z); + vec3 pos001 = vec3(0.0, 0.0, H.z) - center; + vec3 pos010 = vec3(0.0, H.y, 0.0) - center; + vec3 pos011 = vec3(0.0, H.y, H.z) - center; + vec3 pos101 = vec3(H.x, 0.0, H.z) - center; + vec3 pos110 = vec3(H.x, H.y, 0.0) - center; + vec3 pos100 = vec3(H.x, 0.0, 0.0) - center; + vec3 pos111 = vec3(H.x, H.y, H.z) - center; + + // origin filter + float a000 = 1.0; + float a001 = float(_cv_isUseful(tex001.a)) * float(all(greaterThanEqual(vec3(1.05 / 15.0), abs(tex001.rgb - tex000.rgb)))); + float a010 = float(_cv_isUseful(tex010.a)) * float(all(greaterThanEqual(vec3(1.05 / 15.0), abs(tex010.rgb - tex000.rgb)))); + float a100 = float(_cv_isUseful(tex100.a)) * float(all(greaterThanEqual(vec3(1.05 / 15.0), abs(tex100.rgb - tex000.rgb)))); + float a011 = float(_cv_isUseful(tex011.a)) * float(all(greaterThanEqual(vec3(2.05 / 15.0), abs(tex011.rgb - tex000.rgb)))); + float a101 = float(_cv_isUseful(tex101.a)) * float(all(greaterThanEqual(vec3(2.05 / 15.0), abs(tex101.rgb - tex000.rgb)))); + float a110 = float(_cv_isUseful(tex110.a)) * float(all(greaterThanEqual(vec3(2.05 / 15.0), abs(tex110.rgb - tex000.rgb)))); + float a111 = float(_cv_isUseful(tex111.a)) * float(all(greaterThanEqual(vec3(3.05 / 15.0), abs(tex111.rgb - tex000.rgb)))); + + float w000 = a000 * abs(pos111.x * pos111.y * pos111.z); + float w001 = a001 * abs(pos110.x * pos110.y * pos110.z); + float w010 = a010 * abs(pos101.x * pos101.y * pos101.z); + float w011 = a011 * abs(pos100.x * pos100.y * pos100.z); + float w101 = a101 * abs(pos010.x * pos010.y * pos010.z); + float w110 = a110 * abs(pos001.x * pos001.y * pos001.z); + float w100 = a100 * abs(pos011.x * pos011.y * pos011.z); + float w111 = a111 * abs(pos000.x * pos000.y * pos000.z); float weight = w000 + w001 + w010 + w011 + w101 + w110 + w100 + w111; vec4 finalMix = weight == 0.0 ? vec4(0.0) : vec4((tex000.rgb * w000 + tex001.rgb * w001 + tex010.rgb * w010 + tex011.rgb * w011 + tex101.rgb * w101 + tex110.rgb * w110 + tex100.rgb * w100 + tex111.rgb * w111) / weight, 1.0); From 34978e0d473be830d6888fdd94960918926e0329 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Wed, 12 Jul 2023 11:24:29 +0700 Subject: [PATCH 39/69] Lazy region buffer allocation; test sparse allocator Additionally: respect useOcclusionData for sparse allocation --- .../light/color/LightDataAllocator.java | 220 ++++++++++++++++++ .../canvas/light/color/LightDataManager.java | 17 +- .../canvas/light/color/LightRegion.java | 10 +- .../canvas/light/color/LightRegionData.java | 35 ++- .../pipeline/config/ColoredLightsConfig.java | 2 + 5 files changed, 271 insertions(+), 13 deletions(-) create mode 100644 src/main/java/grondag/canvas/light/color/LightDataAllocator.java diff --git a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java new file mode 100644 index 000000000..a0259242c --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java @@ -0,0 +1,220 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.light.color; + +import it.unimi.dsi.fastutil.shorts.Short2LongOpenHashMap; +import it.unimi.dsi.fastutil.shorts.ShortArrayList; +import it.unimi.dsi.fastutil.shorts.ShortStack; + +import net.minecraft.client.Minecraft; + +import grondag.canvas.CanvasMod; + +public class LightDataAllocator { + // unsigned short hard limit, because we are putting addresses in the data texture. + // although, this number of maximum address correspond to about 1 GiB of memory + // of light data, so this is a reasonable "hard limit". + private static final int MAX_ADDRESSES = 65536; + + // size of one row as determined by the data size of one region (16^3). + // also represents row size of pointer header because we are putting addresses in the data texture. + private static final int ROW_SIZE = 4096; + + // max addresses divided by row size. + // the number of pointers might exceed number of address, since we assume most just point to empty address. + private static final int MIN_POINTER_ROWS = 16; + + // address for the static empty region. + static final short EMPTY_ADDRESS = 0; + + private static final int INITIAL_ADDRESS_COUNT = 128; + + private int pointerHeaderRows; + private int pointerExtent = -1; + private boolean requireTextureRemake; + + private int dynamicMaxAddresses = 0; + // 0 is reserved for empty address + private int nextAllocateAddress = 1; + private int addressCount = 1; + private int debug_prevAddressCount = 0; + private boolean debug_logResize = true; + + private final Short2LongOpenHashMap allocatedAddresses = new Short2LongOpenHashMap(); + private final ShortStack freedAddresses = new ShortArrayList(); + + LightDataAllocator() { + initPointerHeaderInfo(); + increaseAddressSize(INITIAL_ADDRESS_COUNT); + } + + private void initPointerHeaderInfo() { + final int viewDistance = Minecraft.getInstance().options.renderDistance().get(); + final int newPointerExtent = viewDistance * 2; + + if (newPointerExtent == pointerExtent) { + return; + } + + final int pointerCountReq = newPointerExtent * newPointerExtent * newPointerExtent; + + pointerExtent = newPointerExtent; + pointerHeaderRows = Math.max(MIN_POINTER_ROWS, pointerCountReq / ROW_SIZE + (pointerCountReq % ROW_SIZE != 0 ? 1 : 0)); + int maxPointers = pointerHeaderRows * ROW_SIZE; + + if (maxPointers < pointerCountReq) { + throw new IllegalStateException("Wrong light data allocator size due to logic error!"); + } + + requireTextureRemake = true; + } + + // currently, this only increase size + // TODO: shrink + private void increaseAddressSize(int newSize) { + final int cappedNewSize = Math.min(newSize, MAX_ADDRESSES); + + if (dynamicMaxAddresses >= cappedNewSize) { + return; + } + + if (debug_logResize) { + CanvasMod.LOG.info("Resized light data address capacity from " + dynamicMaxAddresses + " to " + cappedNewSize); + } + + dynamicMaxAddresses = cappedNewSize; + + requireTextureRemake = true; + } + + int allocateAddress(LightRegion region) { + if (!region.hasData) { + return EMPTY_ADDRESS; + } + + int regionAddress = region.texAllocation; + + if (regionAddress == EMPTY_ADDRESS) { + regionAddress = allocateAddressInner(region); + } + + // TODO: queue pointer for upload + final int pointerIndex = getPointerIndex(region); + + return regionAddress; + } + + private int allocateAddressInner(LightRegion region) { + assert region.hasData; + final short newAddress; + + // go through freed addresses first + // note: this is kinda bad when we reach MAX_ADDRESSES, but that's kinda edge case + if (!freedAddresses.isEmpty()) { + newAddress = freedAddresses.popShort(); + } else { + if (addressCount >= dynamicMaxAddresses && addressCount < MAX_ADDRESSES) { + // try resize first + increaseAddressSize(dynamicMaxAddresses * 2); + } + + if (addressCount < dynamicMaxAddresses) { + newAddress = (short) nextAllocateAddress; + nextAllocateAddress++; + addressCount++; + } else { + if (nextAllocateAddress >= dynamicMaxAddresses) { + // rolling pointer + nextAllocateAddress = EMPTY_ADDRESS + 1; + } + + final short castedNextAddress = (short) nextAllocateAddress; + + if (allocatedAddresses.containsKey(castedNextAddress)) { + final long oldOrigin = allocatedAddresses.get(castedNextAddress); + allocatedAddresses.remove(castedNextAddress); + + final LightRegion oldRegion = LightDataManager.INSTANCE.get(oldOrigin); + + if (oldRegion != null) { + oldRegion.texAllocation = EMPTY_ADDRESS; + } + } + + newAddress = castedNextAddress; + nextAllocateAddress++; + } + } + + region.texAllocation = newAddress; + allocatedAddresses.put(newAddress, region.origin); + + return newAddress; + } + + private int getPointerIndex(LightRegion region) { + final int xInExtent = region.originPos.getX() % pointerExtent; + final int yInExtent = region.originPos.getY() % pointerExtent; + final int zInExtent = region.originPos.getZ() % pointerExtent; + return xInExtent * pointerExtent * pointerExtent + yInExtent * pointerExtent + zInExtent; + } + + void freeAddress(LightRegion region) { + if (region.texAllocation != EMPTY_ADDRESS) { + final short oldAddress = region.texAllocation; + freedAddresses.push(oldAddress); + allocatedAddresses.remove(oldAddress); + region.texAllocation = EMPTY_ADDRESS; + } + } + + public void resizeRenderDistance() { + initPointerHeaderInfo(); + } + + public int dataRowStart() { + return pointerHeaderRows; + } + + public boolean requireTextureRemake() { + return requireTextureRemake; + } + + public int textureWidth() { + return ROW_SIZE; + } + + public int textureHeight() { + return pointerHeaderRows + dynamicMaxAddresses; + } + + void uploadPointers() { + // TODO either return bytebuffer or accept output texture? + } + + void debug_PrintAddressCount() { + if (addressCount != debug_prevAddressCount) { + CanvasMod.LOG.info("Light data allocator address count: " + addressCount); + } + + debug_prevAddressCount = addressCount; + } +} diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 38ad09125..b11281a94 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -57,6 +57,8 @@ public static void reload() { } else { INSTANCE.resize(image); } + + INSTANCE.useOcclusionData = Pipeline.config().coloredLights.useOcclusionData; } else { if (INSTANCE != null) { INSTANCE.close(); @@ -80,6 +82,9 @@ public static void update(BlockAndTintGetter blockView, int cameraX, int cameraY private final Long2ObjectMap allocated = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); private final Vector3i extentOrigin = new Vector3i(); + private final LightDataAllocator texAllocator; + + boolean useOcclusionData = false; private int extentGridMaskX; private int extentGridMaskY; @@ -99,6 +104,7 @@ public static void update(BlockAndTintGetter blockView, int cameraX, int cameraY ExtentIterable extentIterable = new ExtentIterable(); public LightDataManager(Image image) { + texAllocator = new LightDataAllocator(); allocated.defaultReturnValue(null); init(image); } @@ -182,6 +188,9 @@ private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, continue; } + // debug + texAllocator.allocateAddress(lightRegion); + boolean outsidePrev = false; final int x = lightRegion.lightData.regionOriginBlockX; @@ -195,7 +204,7 @@ private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, outsidePrev |= z < prevExtentBlockZ || z >= (prevExtentBlockZ + extentSizeZ); } - if (lightRegion.lightData.isDirty() || outsidePrev || extentWasResized || debugRedrawEveryFrame) { + if (lightRegion.lightData.hasBuffer() && (lightRegion.lightData.isDirty() || outsidePrev || extentWasResized || debugRedrawEveryFrame)) { // modulo into extent-grid texture.upload(x & extentGridMaskX, y & extentGridMaskY, z & extentGridMaskZ, lightRegion.lightData.getBuffer()); lightRegion.lightData.clearDirty(); @@ -204,6 +213,7 @@ private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, extentWasResized = false; cameraUninitialized = false; + texAllocator.debug_PrintAddressCount(); } LightRegion getFromBlock(BlockPos blockPos) { @@ -214,10 +224,15 @@ LightRegion getFromBlock(BlockPos blockPos) { return allocated.get(key); } + LightRegion get(long originKey) { + return allocated.get(originKey); + } + private void freeInner(BlockPos regionOrigin) { final LightRegion lightRegion = allocated.get(regionOrigin.asLong()); if (lightRegion != null && !lightRegion.isClosed()) { + texAllocator.freeAddress(lightRegion); lightRegion.close(); } diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 2e2a1f4db..8347816be 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -103,6 +103,7 @@ static short light(long entry) { final LightRegionData lightData; final long origin; + final BlockPos originPos; private final LightOp.BVec less = new LightOp.BVec(); private final BlockPos.MutableBlockPos sourcePos = new BlockPos.MutableBlockPos(); private final BlockPos.MutableBlockPos nodePos = new BlockPos.MutableBlockPos(); @@ -113,9 +114,12 @@ static short light(long entry) { private final LongPriorityQueue globalIncQueue = LongPriorityQueues.synchronize(new LongArrayFIFOQueue()); private final LongPriorityQueue globalDecQueue = LongPriorityQueues.synchronize(new LongArrayFIFOQueue()); + short texAllocation = LightDataAllocator.EMPTY_ADDRESS; + boolean hasData = false; private boolean needCheckEdges = true; LightRegion(BlockPos origin) { + this.originPos = new BlockPos(origin); this.origin = origin.asLong(); this.lightData = new LightRegionData(origin.getX(), origin.getY(), origin.getZ()); } @@ -140,6 +144,10 @@ public void checkBlock(BlockPos pos, BlockState blockState) { if (emitting) { Queues.enqueue(globalIncQueue, index, combined); } + + if (LightDataManager.INSTANCE.useOcclusionData) { + hasData = true; + } } } @@ -358,6 +366,7 @@ public boolean updateIncrease(BlockAndTintGetter blockView) { } if (incCount > 0) { + hasData = true; lightData.markAsDirty(); } @@ -395,7 +404,6 @@ private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourc private void checkEdges(BlockAndTintGetter blockView) { final int size = LightRegionData.Const.WIDTH; - final BlockPos originPos = BlockPos.of(origin); final BlockPos.MutableBlockPos searchPos = new BlockPos.MutableBlockPos(); final BlockPos.MutableBlockPos targetPos = new BlockPos.MutableBlockPos(); final int[] searchOffsets = new int[]{-1, size}; diff --git a/src/main/java/grondag/canvas/light/color/LightRegionData.java b/src/main/java/grondag/canvas/light/color/LightRegionData.java index 9af87f998..3aa75e0b9 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegionData.java +++ b/src/main/java/grondag/canvas/light/color/LightRegionData.java @@ -38,7 +38,7 @@ public static class Const { final int regionOriginBlockX; final int regionOriginBlockY; final int regionOriginBlockZ; - private final ByteBuffer buffer; + private ByteBuffer buffer = null; private boolean dirty = true; private boolean closed = false; @@ -46,6 +46,12 @@ public static class Const { this.regionOriginBlockX = regionOriginBlockX; this.regionOriginBlockY = regionOriginBlockY; this.regionOriginBlockZ = regionOriginBlockZ; + } + + private void allocateBuffer() { + if (buffer != null) { + throw new IllegalStateException("Trying to allocate light region buffer twice!"); + } buffer = MemoryUtil.memAlloc(LightDataTexture.Format.pixelBytes * Const.SIZE3D); @@ -63,26 +69,29 @@ public void clearDirty() { dirty = false; } - public void draw(short rgba) { - buffer.putShort(rgba); - } - public void close() { if (closed) { return; } - buffer.position(0); - // very important - MemoryUtil.memFree(buffer); + if (buffer != null) { + buffer.position(0); + // very important + MemoryUtil.memFree(buffer); + } + closed = true; } public short get(int index) { - return buffer.getShort(index); + return buffer == null ? 0 : buffer.getShort(index); } public void put(int index, short light) { + if (buffer == null) { + allocateBuffer(); + } + buffer.putShort(index, light); } @@ -118,9 +127,13 @@ public boolean withinExtents(int x, int y, int z) { && (z >= regionOriginBlockZ && z < regionOriginBlockZ + Const.WIDTH); } + boolean hasBuffer() { + return buffer != null; + } + ByteBuffer getBuffer() { - if (closed) { - throw new IllegalStateException("Attempting to access a closed light region buffer!"); + if (closed || buffer == null) { + throw new IllegalStateException("Attempting to access a closed or null light region buffer!"); } return buffer; diff --git a/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java b/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java index 7490da941..af628ee6b 100644 --- a/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java +++ b/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java @@ -30,10 +30,12 @@ public class ColoredLightsConfig extends AbstractConfig { public final NamedDependency lightImage; + public final boolean useOcclusionData; protected ColoredLightsConfig(ConfigContext ctx, JsonObject config) { super(ctx); lightImage = ctx.images.dependOn(ctx.dynamic.getString(config, "lightImage")); + useOcclusionData = ctx.dynamic.getBoolean(config, "useOcclusionData", false); } @Override From 9bc004407948703fa05f487d1937e7c20768da71 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Thu, 13 Jul 2023 00:09:48 +0700 Subject: [PATCH 40/69] Implement working sparse allocation - Light texture consists of: pointer-meta header, pointer storage, and data storage - Generates new texture when the storage (data or pointer) is doubled - Sampler bindings are now `int` suppliers to account for changing light texture ID - Pipeline declares occlusion data usage as it impacts number of allocation Possibly temporary: - Pipeline declares active radius for light execution. This won't be necessary if light execution is actually scalable - Reclaimable addresses are stored in an array-backed stack. This might be slow - Pointer-meta header might be moved to uniforms for simplicity NOT (yet) implemented: - Built-in light sampler for material programs - Prevent pipeline stalling by writing to a swap buffer - Proper registry for built-in textures available to use by program passes --- .../light/color/LightDataAllocator.java | 113 +++++++++++++----- .../canvas/light/color/LightDataManager.java | 87 ++++++++------ .../canvas/light/color/LightDataTexture.java | 67 +++++++---- .../grondag/canvas/light/color/LightOp.java | 6 +- .../canvas/light/color/LightRegion.java | 6 +- .../canvas/light/color/LightRegionAccess.java | 4 +- .../canvas/material/state/RenderState.java | 2 +- .../grondag/canvas/pipeline/Pipeline.java | 14 +-- .../canvas/pipeline/ProgramTextureData.java | 23 +++- .../pipeline/config/ColoredLightsConfig.java | 25 +--- .../canvas/pipeline/pass/ProgramPass.java | 2 +- .../java/grondag/canvas/shader/GlShader.java | 10 +- .../assets/frex/shaders/api/header.glsl | 3 +- .../assets/frex/shaders/api/light.glsl | 83 ++++++------- .../assets/frex/shaders/api/sampler.glsl | 4 - 15 files changed, 257 insertions(+), 192 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java index a0259242c..3a969de1f 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java +++ b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java @@ -20,9 +20,12 @@ package grondag.canvas.light.color; +import java.nio.ByteBuffer; + import it.unimi.dsi.fastutil.shorts.Short2LongOpenHashMap; import it.unimi.dsi.fastutil.shorts.ShortArrayList; import it.unimi.dsi.fastutil.shorts.ShortStack; +import org.lwjgl.system.MemoryUtil; import net.minecraft.client.Minecraft; @@ -36,19 +39,21 @@ public class LightDataAllocator { // size of one row as determined by the data size of one region (16^3). // also represents row size of pointer header because we are putting addresses in the data texture. - private static final int ROW_SIZE = 4096; - - // max addresses divided by row size. - // the number of pointers might exceed number of address, since we assume most just point to empty address. - private static final int MIN_POINTER_ROWS = 16; + private static final int ROW_SIZE = LightRegionData.Const.SIZE3D; // address for the static empty region. static final short EMPTY_ADDRESS = 0; private static final int INITIAL_ADDRESS_COUNT = 128; - private int pointerHeaderRows; + private static final int HEADER_META_ROWS = 1; + + private int pointerRows; + // note: pointer extent always cover the entire view distance, unlike light data manager extent private int pointerExtent = -1; + private boolean needUploadPointers = false; + private boolean needUploadMeta = false; + private ByteBuffer pointerBuffer; private boolean requireTextureRemake; private int dynamicMaxAddresses = 0; @@ -62,29 +67,40 @@ public class LightDataAllocator { private final ShortStack freedAddresses = new ShortArrayList(); LightDataAllocator() { - initPointerHeaderInfo(); increaseAddressSize(INITIAL_ADDRESS_COUNT); } - private void initPointerHeaderInfo() { - final int viewDistance = Minecraft.getInstance().options.renderDistance().get(); - final int newPointerExtent = viewDistance * 2; - + private void resizePointerBuffer(int newPointerExtent) { if (newPointerExtent == pointerExtent) { return; } - final int pointerCountReq = newPointerExtent * newPointerExtent * newPointerExtent; - pointerExtent = newPointerExtent; - pointerHeaderRows = Math.max(MIN_POINTER_ROWS, pointerCountReq / ROW_SIZE + (pointerCountReq % ROW_SIZE != 0 ? 1 : 0)); - int maxPointers = pointerHeaderRows * ROW_SIZE; - if (maxPointers < pointerCountReq) { - throw new IllegalStateException("Wrong light data allocator size due to logic error!"); + final int pointerCountReq = pointerExtent * pointerExtent * pointerExtent; + pointerRows = pointerCountReq / ROW_SIZE + ((pointerCountReq % ROW_SIZE == 0) ? 0 : 1); + + final ByteBuffer newPointerBuffer = MemoryUtil.memAlloc(pointerRows * ROW_SIZE * 2); + + if (pointerBuffer != null) { + pointerBuffer.position(0); + + // we copy because ..??? + while (pointerBuffer.position() < pointerBuffer.limit()) { + newPointerBuffer.putShort(pointerBuffer.getShort()); + } + + while (newPointerBuffer.position() < newPointerBuffer.limit()) { + newPointerBuffer.putShort((short) 0); + } + + pointerBuffer.position(0); + MemoryUtil.memFree(pointerBuffer); } + pointerBuffer = newPointerBuffer; requireTextureRemake = true; + needUploadMeta = true; } // currently, this only increase size @@ -110,19 +126,20 @@ int allocateAddress(LightRegion region) { return EMPTY_ADDRESS; } - int regionAddress = region.texAllocation; + short regionAddress = region.texAllocation; if (regionAddress == EMPTY_ADDRESS) { regionAddress = allocateAddressInner(region); } - // TODO: queue pointer for upload - final int pointerIndex = getPointerIndex(region); + final int pointerIndex = getPointerIndex(region) * 2; + pointerBuffer.putShort(pointerIndex, regionAddress); + needUploadPointers = true; return regionAddress; } - private int allocateAddressInner(LightRegion region) { + private short allocateAddressInner(LightRegion region) { assert region.hasData; final short newAddress; @@ -171,9 +188,10 @@ private int allocateAddressInner(LightRegion region) { } private int getPointerIndex(LightRegion region) { - final int xInExtent = region.originPos.getX() % pointerExtent; - final int yInExtent = region.originPos.getY() % pointerExtent; - final int zInExtent = region.originPos.getZ() % pointerExtent; + final int xInExtent = ((region.originPos.getX() / 16) % pointerExtent + pointerExtent) % pointerExtent; + final int yInExtent = ((region.originPos.getY() / 16) % pointerExtent + pointerExtent) % pointerExtent; + final int zInExtent = ((region.originPos.getZ() / 16) % pointerExtent + pointerExtent) % pointerExtent; + return xInExtent * pointerExtent * pointerExtent + yInExtent * pointerExtent + zInExtent; } @@ -186,15 +204,24 @@ void freeAddress(LightRegion region) { } } - public void resizeRenderDistance() { - initPointerHeaderInfo(); + void textureRemade() { + CanvasMod.LOG.info("Light texture was remade, new size : " + textureHeight()); + requireTextureRemake = false; + needUploadPointers = true; + needUploadMeta = true; } public int dataRowStart() { - return pointerHeaderRows; + return HEADER_META_ROWS + pointerRows; } - public boolean requireTextureRemake() { + public boolean checkInvalid() { + final var viewDistance = Minecraft.getInstance().options.renderDistance().get(); + + if (pointerExtent < viewDistance * 2) { + resizePointerBuffer(viewDistance * 2); + } + return requireTextureRemake; } @@ -203,11 +230,35 @@ public int textureWidth() { } public int textureHeight() { - return pointerHeaderRows + dynamicMaxAddresses; + return HEADER_META_ROWS + pointerRows + dynamicMaxAddresses; + } + + void uploadMetaIfNeeded(LightDataTexture texture) { + if (!needUploadMeta) { + return; + } + + needUploadMeta = false; + + final int count = 2; + ByteBuffer metaBuffer = MemoryUtil.memAlloc(count * 2); + metaBuffer.putShort((short) pointerExtent); + metaBuffer.putShort((short) pointerRows); + + texture.uploadDirect(0, 0, count, 1, metaBuffer); + + metaBuffer.position(0); + MemoryUtil.memFree(metaBuffer); } - void uploadPointers() { - // TODO either return bytebuffer or accept output texture? + void uploadPointersIfNeeded(LightDataTexture texture) { + if (!needUploadPointers) { + return; + } + + needUploadPointers = false; + + texture.upload(HEADER_META_ROWS, pointerRows, pointerBuffer); } void debug_PrintAddressCount() { diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index b11281a94..b801ef2ed 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -30,7 +30,6 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockAndTintGetter; -import grondag.canvas.pipeline.Image; import grondag.canvas.pipeline.Pipeline; import grondag.canvas.shader.data.ShaderDataManager; @@ -50,12 +49,12 @@ public static LightRegionAccess allocate(BlockPos regionOrigin) { public static void reload() { if (Pipeline.coloredLightsEnabled()) { assert Pipeline.config().coloredLights != null; - final var image = Pipeline.getImage(Pipeline.config().coloredLights.lightImage.name); + final var extentSizeRegions = Pipeline.config().coloredLights.maxRadiusChunks * 2; if (INSTANCE == null) { - INSTANCE = new LightDataManager(image); + INSTANCE = new LightDataManager(extentSizeRegions); } else { - INSTANCE.resize(image); + INSTANCE.resize(extentSizeRegions); } INSTANCE.useOcclusionData = Pipeline.config().coloredLights.useOcclusionData; @@ -79,6 +78,14 @@ public static void update(BlockAndTintGetter blockView, int cameraX, int cameraY } } + public static int texId() { + if (INSTANCE != null && INSTANCE.texture != null) { + return INSTANCE.texture.texId(); + } + + return 0; + } + private final Long2ObjectMap allocated = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); private final Vector3i extentOrigin = new Vector3i(); @@ -86,10 +93,6 @@ public static void update(BlockAndTintGetter blockView, int cameraX, int cameraY boolean useOcclusionData = false; - private int extentGridMaskX; - private int extentGridMaskY; - private int extentGridMaskZ; - private int extentSizeX; private int extentSizeY; private int extentSizeZ; @@ -103,32 +106,25 @@ public static void update(BlockAndTintGetter blockView, int cameraX, int cameraY private LightDataTexture texture; ExtentIterable extentIterable = new ExtentIterable(); - public LightDataManager(Image image) { + public LightDataManager(int newExtentSize) { texAllocator = new LightDataAllocator(); allocated.defaultReturnValue(null); - init(image); + init(newExtentSize); } - private void resize(Image image) { - init(image); + private void resize(int newExtentSize) { + init(newExtentSize); extentWasResized = true; } - private void init(Image image) { - // TODO: for some reason it's not working properly when width =/= depth (height is fine) - extentSizeXInRegions = image.config.width / 16; - extentSizeYInRegions = image.config.height / 16; - extentSizeZInRegions = image.config.depth / 16; - - extentGridMaskX = extentSizeMask(extentSizeXInRegions); - extentGridMaskY = extentSizeMask(extentSizeYInRegions); - extentGridMaskZ = extentSizeMask(extentSizeZInRegions); + private void init(int newExtentSize) { + extentSizeXInRegions = newExtentSize; + extentSizeYInRegions = newExtentSize; + extentSizeZInRegions = newExtentSize; extentSizeX = extentSizeInBlocks(extentSizeXInRegions); extentSizeY = extentSizeInBlocks(extentSizeYInRegions); extentSizeZ = extentSizeInBlocks(extentSizeZInRegions); - - texture = new LightDataTexture(image); } private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, int cameraZ) { @@ -180,8 +176,22 @@ private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, } } + boolean textureOrExtentChanged = extentWasResized; + + if (texAllocator.checkInvalid()) { + if (texture != null) { + texture.close(); + } + + texture = new LightDataTexture(texAllocator.textureWidth(), texAllocator.textureHeight()); + texAllocator.textureRemade(); + textureOrExtentChanged = true; + } + + texAllocator.uploadMetaIfNeeded(texture); + // TODO: swap texture in case of sparse, perhaps - for (long index:extentIterable) { + for (long index : extentIterable) { final LightRegion lightRegion = allocated.get(index); if (lightRegion == null || lightRegion.isClosed()) { @@ -193,24 +203,27 @@ private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, boolean outsidePrev = false; - final int x = lightRegion.lightData.regionOriginBlockX; - final int y = lightRegion.lightData.regionOriginBlockY; - final int z = lightRegion.lightData.regionOriginBlockZ; - - if (shouldRedraw) { + if (shouldRedraw && !textureOrExtentChanged) { // Redraw regions that just entered the current-frame extent - outsidePrev |= x < prevExtentBlockX || x >= (prevExtentBlockX + extentSizeX); - outsidePrev |= y < prevExtentBlockY || y >= (prevExtentBlockY + extentSizeY); - outsidePrev |= z < prevExtentBlockZ || z >= (prevExtentBlockZ + extentSizeZ); + outsidePrev |= lightRegion.lightData.regionOriginBlockX < prevExtentBlockX || lightRegion.lightData.regionOriginBlockX >= (prevExtentBlockX + extentSizeX); + outsidePrev |= lightRegion.lightData.regionOriginBlockY < prevExtentBlockY || lightRegion.lightData.regionOriginBlockY >= (prevExtentBlockY + extentSizeY); + outsidePrev |= lightRegion.lightData.regionOriginBlockZ < prevExtentBlockZ || lightRegion.lightData.regionOriginBlockZ >= (prevExtentBlockZ + extentSizeZ); } - if (lightRegion.lightData.hasBuffer() && (lightRegion.lightData.isDirty() || outsidePrev || extentWasResized || debugRedrawEveryFrame)) { - // modulo into extent-grid - texture.upload(x & extentGridMaskX, y & extentGridMaskY, z & extentGridMaskZ, lightRegion.lightData.getBuffer()); + if (lightRegion.lightData.hasBuffer() && (lightRegion.lightData.isDirty() || outsidePrev || textureOrExtentChanged || debugRedrawEveryFrame)) { + final int targetAddress = texAllocator.allocateAddress(lightRegion); + + if (targetAddress != LightDataAllocator.EMPTY_ADDRESS) { + final int targetRow = texAllocator.dataRowStart() + targetAddress; + texture.upload(targetRow, lightRegion.lightData.getBuffer()); + } + lightRegion.lightData.clearDirty(); } } + texAllocator.uploadPointersIfNeeded(texture); + extentWasResized = false; cameraUninitialized = false; texAllocator.debug_PrintAddressCount(); @@ -254,10 +267,6 @@ private int extentSizeInBlocks(int extentSize) { return extentSize * LightRegionData.Const.WIDTH; } - private int extentSizeMask(int extentSize) { - return extentSizeInBlocks(extentSize) - 1; - } - public void close() { texture.close(); diff --git a/src/main/java/grondag/canvas/light/color/LightDataTexture.java b/src/main/java/grondag/canvas/light/color/LightDataTexture.java index 52dd49177..ce1a14951 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataTexture.java +++ b/src/main/java/grondag/canvas/light/color/LightDataTexture.java @@ -22,51 +22,78 @@ import java.nio.ByteBuffer; +import org.lwjgl.opengl.GL11; + +import com.mojang.blaze3d.platform.TextureUtil; import com.mojang.blaze3d.systems.RenderSystem; -import grondag.canvas.pipeline.Image; import grondag.canvas.render.CanvasTextureState; import grondag.canvas.varia.GFX; public class LightDataTexture { + public static class Format { - public static int target = GFX.GL_TEXTURE_3D; + public static int target = GFX.GL_TEXTURE_2D; public static int pixelBytes = 2; public static int internalFormat = GFX.GL_RGBA4; public static int pixelFormat = GFX.GL_RGBA; public static int pixelDataType = GFX.GL_UNSIGNED_SHORT_4_4_4_4; } - private final Image image; + private final int glId; + private final int width; + private boolean closed = false; + + LightDataTexture(int width, int height) { + this.width = width; - LightDataTexture(Image image) { - this.image = image; + glId = TextureUtil.generateTextureId(); + CanvasTextureState.bindTexture(glId); - // ByteBuffer clearer = MemoryUtil.memAlloc(image.config.width * image.config.height * image.config.depth * Format.pixelBytes); - // - // while (clearer.position() < clearer.limit()) { - // clearer.putShort((short) 0); - // } + GFX.objectLabel(GL11.GL_TEXTURE, glId, "IMG colored_lights_data"); + + GFX.texParameter(Format.target, GFX.GL_TEXTURE_MIN_FILTER, GFX.GL_NEAREST); + GFX.texParameter(Format.target, GFX.GL_TEXTURE_MAG_FILTER, GFX.GL_NEAREST); + GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_S, GFX.GL_CLAMP_TO_EDGE); + GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_T, GFX.GL_CLAMP_TO_EDGE); + + GFX.texImage2D(Format.target, 0, Format.internalFormat, width, height, 0, Format.pixelFormat, Format.pixelDataType, (ByteBuffer) null); + } - // // clear?? NOTE: this is wrong - // upload(0, 0, 0, clearer); + public int texId() { + if (closed) { + return 0; + } - // clearer.position(0); - // MemoryUtil.memFree(clearer); + return glId; } public void close() { - // Image closing is already handled by pipeline manager + if (closed) { + return; + } + + TextureUtil.releaseTextureId(glId); + + closed = true; } - public void upload(int x, int y, int z, ByteBuffer buffer) { - upload(x, y, z, LightRegionData.Const.WIDTH, buffer); + public void upload(int row, ByteBuffer buffer) { + upload(row, 1, buffer); } - public void upload(int x, int y, int z, int regionSize, ByteBuffer buffer) { + public void upload(int rowStart, int rowCount, ByteBuffer buffer) { + uploadDirect(0, rowStart, width, rowCount, buffer); + } + + public void uploadDirect(int x, int y, int width, int height, ByteBuffer buffer) { + if (closed) { + throw new IllegalStateException("Uploading to a closed light texture!"); + } + RenderSystem.assertOnRenderThread(); - CanvasTextureState.bindTexture(LightDataTexture.Format.target, image.glId()); + CanvasTextureState.bindTexture(glId); // Gotta clean up some states, otherwise will cause memory access violation GFX.pixelStore(GFX.GL_UNPACK_SKIP_PIXELS, 0); @@ -77,6 +104,6 @@ public void upload(int x, int y, int z, int regionSize, ByteBuffer buffer) { // Importantly, reset the pointer without flip buffer.position(0); - GFX.glTexSubImage3D(Format.target, 0, x, y, z, regionSize, regionSize, regionSize, Format.pixelFormat, Format.pixelDataType, buffer); + GFX.glTexSubImage2D(Format.target, 0, x, y, width, height, Format.pixelFormat, Format.pixelDataType, buffer); } } diff --git a/src/main/java/grondag/canvas/light/color/LightOp.java b/src/main/java/grondag/canvas/light/color/LightOp.java index 04be9931c..ba24e1759 100644 --- a/src/main/java/grondag/canvas/light/color/LightOp.java +++ b/src/main/java/grondag/canvas/light/color/LightOp.java @@ -82,9 +82,9 @@ public static boolean lit(short light) { public static short max(short master, short sub) { final short max = (short) (Math.max(master & R.mask, sub & R.mask) - | Math.max(master & G.mask, sub & G.mask) - | Math.max(master & B.mask, sub & B.mask) - | master & 0xf); + | Math.max(master & G.mask, sub & G.mask) + | Math.max(master & B.mask, sub & B.mask) + | master & 0xf); return ensureUsefulness(max); } diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 8347816be..53aef9efa 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -68,7 +68,7 @@ static Side infer(BlockPos from, BlockPos to) { int y = to.getY() - from.getY(); int z = to.getZ() - from.getZ(); - for (Side side:Side.values()) { + for (Side side : Side.values()) { if (side.x == x && side.y == y && side.z == z) { return side; } @@ -198,7 +198,7 @@ boolean updateDecrease(BlockAndTintGetter blockView) { final BlockState sourceState = blockView.getBlockState(sourcePos); - for (var side:Side.values()) { + for (var side : Side.values()) { if (side.id == from) { continue; } @@ -315,7 +315,7 @@ public boolean updateIncrease(BlockAndTintGetter blockView) { final BlockState sourceState = blockView.getBlockState(sourcePos); - for (var side:Side.values()) { + for (var side : Side.values()) { if (side.id == from) { continue; } diff --git a/src/main/java/grondag/canvas/light/color/LightRegionAccess.java b/src/main/java/grondag/canvas/light/color/LightRegionAccess.java index 72b2d8d4c..ba6ca61e4 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegionAccess.java +++ b/src/main/java/grondag/canvas/light/color/LightRegionAccess.java @@ -27,11 +27,13 @@ public interface LightRegionAccess { LightRegionAccess EMPTY = new Empty(); void checkBlock(BlockPos pos, BlockState blockState); + boolean isClosed(); class Empty implements LightRegionAccess { @Override - public void checkBlock(BlockPos pos, BlockState blockState) { } + public void checkBlock(BlockPos pos, BlockState blockState) { + } @Override public boolean isClosed() { diff --git a/src/main/java/grondag/canvas/material/state/RenderState.java b/src/main/java/grondag/canvas/material/state/RenderState.java index b5346fdb1..2b1a59814 100644 --- a/src/main/java/grondag/canvas/material/state/RenderState.java +++ b/src/main/java/grondag/canvas/material/state/RenderState.java @@ -224,7 +224,7 @@ private void enableMaterial(int x, int y, int z) { // Activate non-frex material program textures for (int i = 0; i < Pipeline.config().materialProgram.samplerNames.length; i++) { final int bindTarget = Pipeline.materialTextures().texTargets[i]; - final int bind = Pipeline.materialTextures().texIds[i]; + final int bind = Pipeline.materialTextures().texIds[i].getAsInt(); CanvasTextureState.ensureTextureOfTextureUnit(TextureData.PROGRAM_SAMPLERS + i, bindTarget, bind); } diff --git a/src/main/java/grondag/canvas/pipeline/Pipeline.java b/src/main/java/grondag/canvas/pipeline/Pipeline.java index 9ef93a461..a22b24f3d 100644 --- a/src/main/java/grondag/canvas/pipeline/Pipeline.java +++ b/src/main/java/grondag/canvas/pipeline/Pipeline.java @@ -99,7 +99,6 @@ public class Pipeline { private static boolean advancedTerrainCulling; private static boolean coloredLightsEnabled; - private static final Vector3i lightVolumeSize = new Vector3i(); public static boolean shadowsEnabled() { return skyShadowFbo != null; @@ -109,10 +108,6 @@ public static boolean coloredLightsEnabled() { return coloredLightsEnabled; } - public static Vector3i lightVolumeSize() { - return lightVolumeSize; - } - public static boolean advancedTerrainCulling() { return advancedTerrainCulling; } @@ -218,14 +213,7 @@ static void activate(PrimaryFrameBuffer primary, int width, int height) { defaultZenithAngle = 0f; } - if (config.coloredLights != null) { - coloredLightsEnabled = true; - var image = config.coloredLights.lightImage.value(); - lightVolumeSize.set(image.width, image.height, image.depth); - } else { - coloredLightsEnabled = false; - lightVolumeSize.set(0); - } + coloredLightsEnabled = config.coloredLights != null; if (isFabulous) { final FabulousConfig fc = config.fabulosity; diff --git a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java index 7492bcf73..90ca68384 100644 --- a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java +++ b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java @@ -22,6 +22,9 @@ import static net.minecraft.client.renderer.entity.ItemRenderer.ENCHANTED_GLINT_ITEM; +import java.util.function.IntSupplier; +import java.util.function.Supplier; + import org.lwjgl.opengl.GL46; import net.minecraft.client.Minecraft; @@ -29,34 +32,42 @@ import net.minecraft.client.renderer.texture.TextureManager; import net.minecraft.resources.ResourceLocation; +import grondag.canvas.light.color.LightDataManager; import grondag.canvas.pipeline.config.ImageConfig; import grondag.canvas.pipeline.config.util.NamedDependency; public class ProgramTextureData { - public final int[] texIds; + public final IntSupplier[] texIds; public final int[] texTargets; public ProgramTextureData(NamedDependency[] samplerImages) { - texIds = new int[samplerImages.length]; + texIds = new IntSupplier[samplerImages.length]; texTargets = new int[samplerImages.length]; for (int i = 0; i < samplerImages.length; ++i) { final String imageName = samplerImages[i].name; - int imageBind = 0; + IntSupplier imageBind = () -> 0; int bindTarget = GL46.GL_TEXTURE_2D; - if (imageName.contains(":")) { + if (imageName.startsWith("frex:")) { + // TODO use a registry + if (imageName.equals("frex:textures/colored_lights")) { + imageBind = LightDataManager::texId; + } + } else if (imageName.contains(":")) { final AbstractTexture tex = tryLoadResourceTexture(new ResourceLocation(imageName)); if (tex != null) { - imageBind = tex.getId(); + final int id = tex.getId(); + imageBind = () -> id; } } else { final Image img = Pipeline.getImage(imageName); if (img != null) { - imageBind = img.glId(); + final int id = img.glId(); + imageBind = () -> id; bindTarget = img.config.target; } } diff --git a/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java b/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java index af628ee6b..e12fdc479 100644 --- a/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java +++ b/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java @@ -29,36 +29,17 @@ import grondag.canvas.pipeline.config.util.NamedDependency; public class ColoredLightsConfig extends AbstractConfig { - public final NamedDependency lightImage; + public final int maxRadiusChunks; public final boolean useOcclusionData; protected ColoredLightsConfig(ConfigContext ctx, JsonObject config) { super(ctx); - lightImage = ctx.images.dependOn(ctx.dynamic.getString(config, "lightImage")); + maxRadiusChunks = ctx.dynamic.getInt(config, "maxRadiusChunks", 4); useOcclusionData = ctx.dynamic.getBoolean(config, "useOcclusionData", false); } @Override public boolean validate() { - final var image = lightImage.value(); - - if (image != null) { - boolean valid = image.validate(); - - valid &= assertAndWarn(image.target == LightDataTexture.Format.target, "Invalid pipeline config for image %s. Light data image needs to target %s", image.name, GlSymbolLookup.reverseLookup(LightDataTexture.Format.target)); - valid &= assertAndWarn(image.internalFormat == LightDataTexture.Format.internalFormat, "Invalid pipeline config for image %s. Light data image needs to have internal format of %s", image.name, GlSymbolLookup.reverseLookup(LightDataTexture.Format.internalFormat)); - valid &= assertAndWarn(image.pixelFormat == LightDataTexture.Format.pixelFormat, "Invalid pipeline config for image %s. Light data image needs to have pixel format of %s", image.name, GlSymbolLookup.reverseLookup(LightDataTexture.Format.pixelFormat)); - valid &= assertAndWarn(image.pixelDataType == LightDataTexture.Format.pixelDataType, "Invalid pipeline config for image %s. Light data image needs to have pixel data type of %s", image.name, GlSymbolLookup.reverseLookup(LightDataTexture.Format.pixelDataType)); - valid &= assertAndWarn(image.width > 0 && image.height > 0 && image.depth > 0, "Invalid pipeline config for image %s. Light data image needs to have non-zero width, height, and depth", image.name); - valid &= assertAndWarn(image.width % 16 == 0 && image.height % 16 == 0 && image.depth % 16 == 0, "Invalid pipeline config for image %s. Light data image needs to have width, height, and depth that are multiples of 16", image.name); - - return valid; - } - - if (lightImage.name == null) { - return assertAndWarn(false, "Invalid pipeline light volume config. Light image is unspecified."); - } else { - return assertAndWarn(false, "Invalid pipeline light volume config. Image %s doesn't exist", lightImage.name); - } + return true; } } diff --git a/src/main/java/grondag/canvas/pipeline/pass/ProgramPass.java b/src/main/java/grondag/canvas/pipeline/pass/ProgramPass.java index 6ba7a8b53..a7ed90cd4 100644 --- a/src/main/java/grondag/canvas/pipeline/pass/ProgramPass.java +++ b/src/main/java/grondag/canvas/pipeline/pass/ProgramPass.java @@ -65,7 +65,7 @@ public void run(int width, int height) { final int slimit = textures.texIds.length; for (int i = 0; i < slimit; ++i) { - CanvasTextureState.ensureTextureOfTextureUnit(GFX.GL_TEXTURE0 + i, textures.texTargets[i], textures.texIds[i]); + CanvasTextureState.ensureTextureOfTextureUnit(GFX.GL_TEXTURE0 + i, textures.texTargets[i], textures.texIds[i].getAsInt()); } program.activate(); diff --git a/src/main/java/grondag/canvas/shader/GlShader.java b/src/main/java/grondag/canvas/shader/GlShader.java index dd81e0094..92b45ae89 100644 --- a/src/main/java/grondag/canvas/shader/GlShader.java +++ b/src/main/java/grondag/canvas/shader/GlShader.java @@ -270,11 +270,11 @@ private String getSource() { } if (Pipeline.coloredLightsEnabled()) { - var lightSize = Pipeline.lightVolumeSize(); - result = StringUtils.replace(result, "#define _CV_LIGHT_DATA_SIZE vec3(256.0)", String.format("#define _CV_LIGHT_DATA_SIZE vec3(%d, %d, %d)", lightSize.x, lightSize.y, lightSize.z)); - } else { - result = StringUtils.replace(result, "#define COLORED_LIGHTS_ENABLED", "//#define COLORED_LIGHTS_ENABLED"); - result = StringUtils.replace(result, "#define _CV_LIGHT_DATA_SIZE vec3(256.0)", "//#define _CV_LIGHT_DATA_SIZE vec3(256.0)"); + result = StringUtils.replace(result, "//#define COLORED_LIGHTS_ENABLED", "#define COLORED_LIGHTS_ENABLED"); + + if (Pipeline.config().coloredLights.useOcclusionData) { + result = StringUtils.replace(result, "//#define LIGHT_DATA_HAS_OCCLUSION", "#define LIGHT_DATA_HAS_OCCLUSION"); + } } result = StringUtils.replace(result, "#define _CV_MAX_SHADER_COUNT 0", "#define _CV_MAX_SHADER_COUNT " + MaterialConstants.MAX_SHADERS); diff --git a/src/main/resources/assets/frex/shaders/api/header.glsl b/src/main/resources/assets/frex/shaders/api/header.glsl index e2deac3fc..5322247d4 100644 --- a/src/main/resources/assets/frex/shaders/api/header.glsl +++ b/src/main/resources/assets/frex/shaders/api/header.glsl @@ -4,8 +4,7 @@ #define ANIMATED_FOLIAGE #define SHADOW_MAP_PRESENT #define SHADOW_MAP_SIZE 1024 -#define COLORED_LIGHTS_ENABLED -//#define SPARSE_LIGHT_DATA +//#define COLORED_LIGHTS_ENABLED //#define LIGHT_DATA_HAS_OCCLUSION #define _CV_LIGHT_DATA_COMPLEX_FILTER #define _CV_LIGHT_DATA_SIZE vec3(256.0) diff --git a/src/main/resources/assets/frex/shaders/api/light.glsl b/src/main/resources/assets/frex/shaders/api/light.glsl index 2911e4286..3c19a15ac 100644 --- a/src/main/resources/assets/frex/shaders/api/light.glsl +++ b/src/main/resources/assets/frex/shaders/api/light.glsl @@ -4,42 +4,44 @@ #ifdef COLORED_LIGHTS_ENABLED -#ifdef SPARSE_LIGHT_DATA -#define LightSampler sampler2D - -bool _cv_hasLightData(vec3 worldPos) { - return false; +int _cv_vec2short(vec4 source) { + ivec4 bytes = ivec4(source * 15.0); + return (bytes.r << 12) | (bytes.g << 8) | (bytes.b << 4) | bytes.a; } -vec2 _cv_lightCoords(vec3 worldPos) { - return vec2(0.0); -} +ivec2 _cv_lightPointer(sampler2D lightSampler, vec3 worldPos) { + // TODO use uniforms for meta + int pointerExtent = _cv_vec2short(texelFetch(lightSampler, ivec2(0, 0), 0)); + ivec3 local = ivec3(mod(worldPos / 16.0, float(pointerExtent))); + int index = local.x * pointerExtent * pointerExtent + local.y * pointerExtent + local.z; -ivec2 _cv_lightTexelCoords(vec3 worldPos) { - return ivec2(0); + // TODO use uniforms for meta, remove padding + return ivec2(index - (index / 4096) * 4096, index / 4096 + 1); } -#else -#define LightSampler sampler3D -bool _cv_hasLightData(vec3 worldPos) { - return clamp(worldPos, _cvu_world[_CV_LIGHT_DATA_ORIGIN].xyz, _cvu_world[_CV_LIGHT_DATA_ORIGIN].xyz + _CV_LIGHT_DATA_SIZE) == worldPos; +bool _cv_hasLightData(sampler2D lightSampler, vec3 worldPos) { + return _cv_vec2short(texelFetch(lightSampler, _cv_lightPointer(lightSampler, worldPos), 0)) != 0; } -vec3 _cv_lightCoords(vec3 worldPos) { - return mod(worldPos, _CV_LIGHT_DATA_SIZE) / _CV_LIGHT_DATA_SIZE; -} +ivec2 _cv_lightTexelCoords(sampler2D lightSampler, vec3 worldPos) { + int address = _cv_vec2short(texelFetch(lightSampler, _cv_lightPointer(lightSampler, worldPos), 0)); -ivec3 _cv_lightTexelCoords(vec3 worldPos) { - return ivec3(mod(clamp(worldPos, _cvu_world[_CV_LIGHT_DATA_ORIGIN].xyz, _cvu_world[_CV_LIGHT_DATA_ORIGIN].xyz + _CV_LIGHT_DATA_SIZE), _CV_LIGHT_DATA_SIZE)); + // TODO use uniforms for meta + int pointerRows = _cv_vec2short(texelFetch(lightSampler, ivec2(1, 0), 0)); + + // TODO use uniforms for meta, remove padding + int exactRow = pointerRows + 1 + address; + ivec3 local = ivec3(mod(worldPos, 16.0)); + + return ivec2(local.z * 16 * 16 + local.y * 16 + local.x, exactRow); } -#endif bool _cv_isUseful(float a) { return (int(a * 15.0) & 8) > 0; } -vec4 frx_getLightFiltered(LightSampler lightSampler, vec3 worldPos) { - if (!_cv_hasLightData(worldPos)) { +vec4 frx_getLightFiltered(sampler2D lightSampler, vec3 worldPos) { + if (!_cv_hasLightData(lightSampler, worldPos)) { return vec4(0.0); } @@ -51,14 +53,14 @@ vec4 frx_getLightFiltered(LightSampler lightSampler, vec3 worldPos) { const vec3 H = vec3(1.0); #endif - vec4 tex000 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(0.0, 0.0, 0.0)), 0); - vec4 tex001 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(0.0, 0.0, H.z)), 0); - vec4 tex010 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(0.0, H.y, 0.0)), 0); - vec4 tex011 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(0.0, H.y, H.z)), 0); - vec4 tex101 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(H.x, 0.0, H.z)), 0); - vec4 tex110 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(H.x, H.y, 0.0)), 0); - vec4 tex100 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(H.x, 0.0, 0.0)), 0); - vec4 tex111 = texelFetch(lightSampler, _cv_lightTexelCoords(pos + vec3(H.x, H.y, H.z)), 0); + vec4 tex000 = texelFetch(lightSampler, _cv_lightTexelCoords(lightSampler, pos + vec3(0.0, 0.0, 0.0)), 0); + vec4 tex001 = texelFetch(lightSampler, _cv_lightTexelCoords(lightSampler, pos + vec3(0.0, 0.0, H.z)), 0); + vec4 tex010 = texelFetch(lightSampler, _cv_lightTexelCoords(lightSampler, pos + vec3(0.0, H.y, 0.0)), 0); + vec4 tex011 = texelFetch(lightSampler, _cv_lightTexelCoords(lightSampler, pos + vec3(0.0, H.y, H.z)), 0); + vec4 tex101 = texelFetch(lightSampler, _cv_lightTexelCoords(lightSampler, pos + vec3(H.x, 0.0, H.z)), 0); + vec4 tex110 = texelFetch(lightSampler, _cv_lightTexelCoords(lightSampler, pos + vec3(H.x, H.y, 0.0)), 0); + vec4 tex100 = texelFetch(lightSampler, _cv_lightTexelCoords(lightSampler, pos + vec3(H.x, 0.0, 0.0)), 0); + vec4 tex111 = texelFetch(lightSampler, _cv_lightTexelCoords(lightSampler, pos + vec3(H.x, H.y, H.z)), 0); #ifdef _CV_LIGHT_DATA_COMPLEX_FILTER vec3 center = worldPos - pos; @@ -109,23 +111,22 @@ vec4 frx_getLightFiltered(LightSampler lightSampler, vec3 worldPos) { return finalMix; } -vec4 frx_getLightRaw(LightSampler lightSampler, vec3 worldPos) { - if (!_cv_hasLightData(worldPos)) { +vec4 frx_getLightRaw(sampler2D lightSampler, vec3 worldPos) { + if (!_cv_hasLightData(lightSampler, worldPos)) { return vec4(0.0); } - // TODO: use texture() for debug purpose. should be texelFetc() eventually - vec4 tex = texture(lightSampler, _cv_lightCoords(worldPos)); + vec4 tex = texelFetch(lightSampler, _cv_lightTexelCoords(lightSampler, worldPos), 0); return vec4(tex.rgb, float(_cv_isUseful(tex.a))); } -vec3 frx_getLight(LightSampler lightSampler, vec3 worldPos, vec3 fallback) { +vec3 frx_getLight(sampler2D lightSampler, vec3 worldPos, vec3 fallback) { vec4 light = frx_getLightFiltered(lightSampler, worldPos); return mix(fallback, light.rgb, light.a); } -bool frx_lightDataExists(vec3 worldPos) { - return _cv_hasLightData(worldPos); +bool frx_lightDataExists(sampler2D lightSampler, vec3 worldPos) { + return _cv_hasLightData(lightSampler, worldPos); } #ifdef LIGHT_DATA_HAS_OCCLUSION @@ -136,13 +137,13 @@ struct frx_LightData { bool isFullCube; }; -frx_LightData frx_getLightOcclusionData(LightSampler lightSampler, vec3 worldPos) { - if (!_cv_hasLightData(worldPos)) { +frx_LightData frx_getLightOcclusionData(sampler2D lightSampler, vec3 worldPos) { + if (!_cv_hasLightData(lightSampler, worldPos)) { return frx_LightData(vec4(0.0), false, false, false); } - vec4 tex = texelFetch(lightSampler, _cv_lightTexelCoords(worldPos)); - int flags = int(a * 15.0); + vec4 tex = texelFetch(lightSampler, _cv_lightTexelCoords(lightSampler, worldPos), 0); + int flags = int(tex.a * 15.0); bool isFullCube = (flags & 4) > 0; bool isOccluder = (flags & 2) > 0; diff --git a/src/main/resources/assets/frex/shaders/api/sampler.glsl b/src/main/resources/assets/frex/shaders/api/sampler.glsl index 56653396c..250ffede1 100644 --- a/src/main/resources/assets/frex/shaders/api/sampler.glsl +++ b/src/main/resources/assets/frex/shaders/api/sampler.glsl @@ -27,11 +27,7 @@ uniform sampler2DArray frxs_shadowMapTexture; #endif #ifdef COLORED_LIGHTS_ENABLED -#ifdef SPARSE_LIGHT_DATA uniform sampler2D frxs_lightData; -#else -uniform sampler3D frxs_lightData; -#endif #define frx_getLightFiltered(worldPos) frx_getLightFiltered(frxs_lightData, worldPos) #define frx_getLightRaw(worldPos) frx_getLightRaw(frxs_lightData, worldPos) From 95074207360f6aa98279757d8fc0a56eb781c6b3 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Thu, 13 Jul 2023 01:52:46 +0700 Subject: [PATCH 41/69] Fix pointer storage remapping and clear more often --- .../light/color/LightDataAllocator.java | 94 ++++++++++--------- .../canvas/light/color/LightDataManager.java | 2 - 2 files changed, 52 insertions(+), 44 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java index 3a969de1f..06be94fe2 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java +++ b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java @@ -28,6 +28,7 @@ import org.lwjgl.system.MemoryUtil; import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; import grondag.canvas.CanvasMod; @@ -78,27 +79,32 @@ private void resizePointerBuffer(int newPointerExtent) { pointerExtent = newPointerExtent; final int pointerCountReq = pointerExtent * pointerExtent * pointerExtent; + var prevRows = pointerRows; pointerRows = pointerCountReq / ROW_SIZE + ((pointerCountReq % ROW_SIZE == 0) ? 0 : 1); - final ByteBuffer newPointerBuffer = MemoryUtil.memAlloc(pointerRows * ROW_SIZE * 2); + if (debug_logResize) { + CanvasMod.LOG.info("Resized pointer storage capacity from " + prevRows + " to " + pointerRows); + } if (pointerBuffer != null) { pointerBuffer.position(0); + MemoryUtil.memFree(pointerBuffer); + } - // we copy because ..??? - while (pointerBuffer.position() < pointerBuffer.limit()) { - newPointerBuffer.putShort(pointerBuffer.getShort()); - } + pointerBuffer = MemoryUtil.memAlloc(pointerRows * ROW_SIZE * 2); - while (newPointerBuffer.position() < newPointerBuffer.limit()) { - newPointerBuffer.putShort((short) 0); - } + // reset each storage value + while (pointerBuffer.position() < pointerBuffer.limit()) { + pointerBuffer.putShort((short) 0); + } - pointerBuffer.position(0); - MemoryUtil.memFree(pointerBuffer); + // remap old pointers + final var searchPos = new BlockPos.MutableBlockPos(); + + for (var entry : allocatedAddresses.short2LongEntrySet()) { + setPointer(searchPos.set(entry.getLongValue()), entry.getShortKey()); } - pointerBuffer = newPointerBuffer; requireTextureRemake = true; needUploadMeta = true; } @@ -123,7 +129,7 @@ private void increaseAddressSize(int newSize) { int allocateAddress(LightRegion region) { if (!region.hasData) { - return EMPTY_ADDRESS; + return setAddress(region, EMPTY_ADDRESS); } short regionAddress = region.texAllocation; @@ -132,10 +138,6 @@ int allocateAddress(LightRegion region) { regionAddress = allocateAddressInner(region); } - final int pointerIndex = getPointerIndex(region) * 2; - pointerBuffer.putShort(pointerIndex, regionAddress); - needUploadPointers = true; - return regionAddress; } @@ -172,7 +174,7 @@ private short allocateAddressInner(LightRegion region) { final LightRegion oldRegion = LightDataManager.INSTANCE.get(oldOrigin); if (oldRegion != null) { - oldRegion.texAllocation = EMPTY_ADDRESS; + setAddress(oldRegion, EMPTY_ADDRESS); } } @@ -181,18 +183,29 @@ private short allocateAddressInner(LightRegion region) { } } + return setAddress(region, newAddress); + } + + private short setAddress(LightRegion region, short newAddress) { region.texAllocation = newAddress; - allocatedAddresses.put(newAddress, region.origin); + setPointer(region.originPos, newAddress); + + if (newAddress != EMPTY_ADDRESS) { + allocatedAddresses.put(newAddress, region.origin); + } return newAddress; } - private int getPointerIndex(LightRegion region) { - final int xInExtent = ((region.originPos.getX() / 16) % pointerExtent + pointerExtent) % pointerExtent; - final int yInExtent = ((region.originPos.getY() / 16) % pointerExtent + pointerExtent) % pointerExtent; - final int zInExtent = ((region.originPos.getZ() / 16) % pointerExtent + pointerExtent) % pointerExtent; - - return xInExtent * pointerExtent * pointerExtent + yInExtent * pointerExtent + zInExtent; + private void setPointer(BlockPos regionOrigin, short regionAddress) { + final int xInExtent = ((regionOrigin.getX() / 16) % pointerExtent + pointerExtent) % pointerExtent; + final int yInExtent = ((regionOrigin.getY() / 16) % pointerExtent + pointerExtent) % pointerExtent; + final int zInExtent = ((regionOrigin.getZ() / 16) % pointerExtent + pointerExtent) % pointerExtent; + final int pointerIndex = xInExtent * pointerExtent * pointerExtent + yInExtent * pointerExtent + zInExtent; + final int bufferIndex = pointerIndex * 2; + final short storedAddress = pointerBuffer.getShort(bufferIndex); + pointerBuffer.putShort(bufferIndex, regionAddress); + needUploadPointers |= storedAddress != regionAddress; } void freeAddress(LightRegion region) { @@ -200,7 +213,7 @@ void freeAddress(LightRegion region) { final short oldAddress = region.texAllocation; freedAddresses.push(oldAddress); allocatedAddresses.remove(oldAddress); - region.texAllocation = EMPTY_ADDRESS; + setAddress(region, EMPTY_ADDRESS); } } @@ -217,9 +230,10 @@ public int dataRowStart() { public boolean checkInvalid() { final var viewDistance = Minecraft.getInstance().options.renderDistance().get(); + final var expectedExtent = (viewDistance + 1) * 2; - if (pointerExtent < viewDistance * 2) { - resizePointerBuffer(viewDistance * 2); + if (pointerExtent < expectedExtent) { + resizePointerBuffer(expectedExtent); } return requireTextureRemake; @@ -233,25 +247,21 @@ public int textureHeight() { return HEADER_META_ROWS + pointerRows + dynamicMaxAddresses; } - void uploadMetaIfNeeded(LightDataTexture texture) { - if (!needUploadMeta) { - return; - } - - needUploadMeta = false; + void uploadPointersIfNeeded(LightDataTexture texture) { + if (needUploadMeta) { + needUploadMeta = false; - final int count = 2; - ByteBuffer metaBuffer = MemoryUtil.memAlloc(count * 2); - metaBuffer.putShort((short) pointerExtent); - metaBuffer.putShort((short) pointerRows); + final int count = 2; + ByteBuffer metaBuffer = MemoryUtil.memAlloc(count * 2); + metaBuffer.putShort((short) pointerExtent); + metaBuffer.putShort((short) pointerRows); - texture.uploadDirect(0, 0, count, 1, metaBuffer); + texture.uploadDirect(0, 0, count, 1, metaBuffer); - metaBuffer.position(0); - MemoryUtil.memFree(metaBuffer); - } + metaBuffer.position(0); + MemoryUtil.memFree(metaBuffer); + } - void uploadPointersIfNeeded(LightDataTexture texture) { if (!needUploadPointers) { return; } diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index b801ef2ed..7b533dad0 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -188,8 +188,6 @@ private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, textureOrExtentChanged = true; } - texAllocator.uploadMetaIfNeeded(texture); - // TODO: swap texture in case of sparse, perhaps for (long index : extentIterable) { final LightRegion lightRegion = allocated.get(index); From 5388f2f61873d45e3bb8065c2681d23bae3a652d Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Thu, 13 Jul 2023 02:40:43 +0700 Subject: [PATCH 42/69] Debug screen light allocation counter --- .../light/color/LightDataAllocator.java | 20 +++++++++---------- .../canvas/light/color/LightDataManager.java | 9 ++++++++- .../canvas/mixin/MixinDebugScreenOverlay.java | 3 +++ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java index 06be94fe2..881eebae9 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java +++ b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java @@ -31,8 +31,11 @@ import net.minecraft.core.BlockPos; import grondag.canvas.CanvasMod; +import grondag.canvas.pipeline.Pipeline; public class LightDataAllocator { + private static final boolean DEBUG_LOG_RESIZE = false; + // unsigned short hard limit, because we are putting addresses in the data texture. // although, this number of maximum address correspond to about 1 GiB of memory // of light data, so this is a reasonable "hard limit". @@ -61,8 +64,8 @@ public class LightDataAllocator { // 0 is reserved for empty address private int nextAllocateAddress = 1; private int addressCount = 1; - private int debug_prevAddressCount = 0; - private boolean debug_logResize = true; + + private float dataSize = 0f; private final Short2LongOpenHashMap allocatedAddresses = new Short2LongOpenHashMap(); private final ShortStack freedAddresses = new ShortArrayList(); @@ -82,7 +85,7 @@ private void resizePointerBuffer(int newPointerExtent) { var prevRows = pointerRows; pointerRows = pointerCountReq / ROW_SIZE + ((pointerCountReq % ROW_SIZE == 0) ? 0 : 1); - if (debug_logResize) { + if (DEBUG_LOG_RESIZE) { CanvasMod.LOG.info("Resized pointer storage capacity from " + prevRows + " to " + pointerRows); } @@ -118,7 +121,7 @@ private void increaseAddressSize(int newSize) { return; } - if (debug_logResize) { + if (DEBUG_LOG_RESIZE) { CanvasMod.LOG.info("Resized light data address capacity from " + dynamicMaxAddresses + " to " + cappedNewSize); } @@ -222,6 +225,7 @@ void textureRemade() { requireTextureRemake = false; needUploadPointers = true; needUploadMeta = true; + dataSize = (float) (textureWidth() * textureHeight() * 4d / (double) 0x100000); } public int dataRowStart() { @@ -271,11 +275,7 @@ void uploadPointersIfNeeded(LightDataTexture texture) { texture.upload(HEADER_META_ROWS, pointerRows, pointerBuffer); } - void debug_PrintAddressCount() { - if (addressCount != debug_prevAddressCount) { - CanvasMod.LOG.info("Light data allocator address count: " + addressCount); - } - - debug_prevAddressCount = addressCount; + String debugString() { + return String.format("Light %s (%d) %d/%d %5.1fMb", Pipeline.config().coloredLights.useOcclusionData ? "LO" : "L", pointerRows, addressCount, dynamicMaxAddresses, dataSize); } } diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 7b533dad0..243411e7f 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -86,6 +86,14 @@ public static int texId() { return 0; } + public static String debugString() { + if (INSTANCE != null) { + return INSTANCE.texAllocator.debugString(); + } + + return "Colored lights DISABLED"; + } + private final Long2ObjectMap allocated = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); private final Vector3i extentOrigin = new Vector3i(); @@ -224,7 +232,6 @@ private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, extentWasResized = false; cameraUninitialized = false; - texAllocator.debug_PrintAddressCount(); } LightRegion getFromBlock(BlockPos blockPos) { diff --git a/src/main/java/grondag/canvas/mixin/MixinDebugScreenOverlay.java b/src/main/java/grondag/canvas/mixin/MixinDebugScreenOverlay.java index 07ca582bf..61359d764 100644 --- a/src/main/java/grondag/canvas/mixin/MixinDebugScreenOverlay.java +++ b/src/main/java/grondag/canvas/mixin/MixinDebugScreenOverlay.java @@ -50,6 +50,7 @@ import grondag.canvas.buffer.render.TransferBuffers; import grondag.canvas.buffer.util.DirectBufferAllocator; import grondag.canvas.buffer.util.GlBufferAllocator; +import grondag.canvas.light.color.LightDataManager; import grondag.canvas.render.terrain.cluster.SlabAllocator; import grondag.canvas.render.world.CanvasWorldRenderer; import grondag.canvas.terrain.util.TerrainExecutor; @@ -218,6 +219,8 @@ private ArrayList onGetSystemInformation(Object[] elements) { result.add(worldRenderState.drawlistDebugSummary()); result.add(SlabAllocator.debugSummary()); + result.add(LightDataManager.debugString()); + return result; } } From 6058c58736757e2c86b3ea3bd3170bfc03026f74 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sat, 22 Jul 2023 14:24:57 +0700 Subject: [PATCH 43/69] Use uniforms for pointer meta --- .../light/color/LightDataAllocator.java | 28 +++++----------- .../grondag/canvas/shader/data/IntData.java | 4 ++- .../assets/canvas/shaders/internal/world.glsl | 4 ++- .../assets/frex/shaders/api/light.glsl | 32 +++++++------------ 4 files changed, 26 insertions(+), 42 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java index 881eebae9..66e1b3c81 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java +++ b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java @@ -32,6 +32,7 @@ import grondag.canvas.CanvasMod; import grondag.canvas.pipeline.Pipeline; +import grondag.canvas.shader.data.IntData; public class LightDataAllocator { private static final boolean DEBUG_LOG_RESIZE = false; @@ -50,8 +51,6 @@ public class LightDataAllocator { private static final int INITIAL_ADDRESS_COUNT = 128; - private static final int HEADER_META_ROWS = 1; - private int pointerRows; // note: pointer extent always cover the entire view distance, unlike light data manager extent private int pointerExtent = -1; @@ -229,7 +228,7 @@ void textureRemade() { } public int dataRowStart() { - return HEADER_META_ROWS + pointerRows; + return pointerRows; } public boolean checkInvalid() { @@ -248,31 +247,20 @@ public int textureWidth() { } public int textureHeight() { - return HEADER_META_ROWS + pointerRows + dynamicMaxAddresses; + return pointerRows + dynamicMaxAddresses; } void uploadPointersIfNeeded(LightDataTexture texture) { if (needUploadMeta) { needUploadMeta = false; - - final int count = 2; - ByteBuffer metaBuffer = MemoryUtil.memAlloc(count * 2); - metaBuffer.putShort((short) pointerExtent); - metaBuffer.putShort((short) pointerRows); - - texture.uploadDirect(0, 0, count, 1, metaBuffer); - - metaBuffer.position(0); - MemoryUtil.memFree(metaBuffer); + IntData.UINT_DATA.put(IntData.LIGHT_POINTER_EXTENT, pointerExtent); + IntData.UINT_DATA.put(IntData.LIGHT_DATA_FIRST_ROW, pointerRows); } - if (!needUploadPointers) { - return; + if (needUploadPointers) { + needUploadPointers = false; + texture.upload(0, pointerRows, pointerBuffer); } - - needUploadPointers = false; - - texture.upload(HEADER_META_ROWS, pointerRows, pointerBuffer); } String debugString() { diff --git a/src/main/java/grondag/canvas/shader/data/IntData.java b/src/main/java/grondag/canvas/shader/data/IntData.java index 0af71e204..aec75dc19 100644 --- a/src/main/java/grondag/canvas/shader/data/IntData.java +++ b/src/main/java/grondag/canvas/shader/data/IntData.java @@ -105,7 +105,9 @@ private IntData() { } static final BitPacker32.BooleanElement FLAG_BAD_OMEN = PLAYER_FLAGS.createBooleanElement(); static final BitPacker32.BooleanElement FLAG_HERO_OF_THE_VILLAGE = PLAYER_FLAGS.createBooleanElement(); - public static final int UINT_COUNT = 1; + public static final int UINT_COUNT = 3; public static final int RENDER_FRAMES = 0; + public static final int LIGHT_POINTER_EXTENT = 1; + public static final int LIGHT_DATA_FIRST_ROW = 2; public static final IntBuffer UINT_DATA = BufferUtils.createIntBuffer(UINT_COUNT); } diff --git a/src/main/resources/assets/canvas/shaders/internal/world.glsl b/src/main/resources/assets/canvas/shaders/internal/world.glsl index 2f8d37d69..f7ef02a2e 100644 --- a/src/main/resources/assets/canvas/shaders/internal/world.glsl +++ b/src/main/resources/assets/canvas/shaders/internal/world.glsl @@ -77,6 +77,8 @@ // UINT ARRAY #define _CV_RENDER_FRAMES 0 +#define _CV_LIGHT_POINTER_EXTENT 1 +#define _CV_LIGHT_DATA_FIRST_ROW 2 #define _CV_FLAG_HAS_SKYLIGHT 0 #define _CV_FLAG_IS_OVERWORLD 1 @@ -92,7 +94,7 @@ // update each frame uniform vec4[32] _cvu_world; -uniform uint[1] _cvu_world_uint; +uniform uint[3] _cvu_world_uint; uniform uint[4] _cvu_flags; #define _CV_MODEL_TO_WORLD 0 diff --git a/src/main/resources/assets/frex/shaders/api/light.glsl b/src/main/resources/assets/frex/shaders/api/light.glsl index 3c19a15ac..09362a724 100644 --- a/src/main/resources/assets/frex/shaders/api/light.glsl +++ b/src/main/resources/assets/frex/shaders/api/light.glsl @@ -1,36 +1,28 @@ +#include canvas:shaders/internal/world.glsl + /**************************************************************** * frex:shaders/api/light.glsl - Canvas Implementation ***************************************************************/ #ifdef COLORED_LIGHTS_ENABLED -int _cv_vec2short(vec4 source) { - ivec4 bytes = ivec4(source * 15.0); - return (bytes.r << 12) | (bytes.g << 8) | (bytes.b << 4) | bytes.a; -} - -ivec2 _cv_lightPointer(sampler2D lightSampler, vec3 worldPos) { - // TODO use uniforms for meta - int pointerExtent = _cv_vec2short(texelFetch(lightSampler, ivec2(0, 0), 0)); - ivec3 local = ivec3(mod(worldPos / 16.0, float(pointerExtent))); - int index = local.x * pointerExtent * pointerExtent + local.y * pointerExtent + local.z; +uint _cv_lightAddress(sampler2D lightSampler, vec3 worldPos) { + uint EXTENT = _cvu_world_uint[_CV_LIGHT_POINTER_EXTENT]; + uvec3 local = uvec3(mod(worldPos / 16.0, float(EXTENT))); + uint linearized = local.x * EXTENT * EXTENT + local.y * EXTENT + local.z; - // TODO use uniforms for meta, remove padding - return ivec2(index - (index / 4096) * 4096, index / 4096 + 1); + // interpret vec4 as a short + uint pointerRow = linearized / 4096u; + uvec4 address = uvec4(texelFetch(lightSampler, ivec2(linearized - pointerRow * 4096u, pointerRow), 0) * 15.0); + return (address.r << 12u) | (address.g << 8u) | (address.b << 4u) | address.a; } bool _cv_hasLightData(sampler2D lightSampler, vec3 worldPos) { - return _cv_vec2short(texelFetch(lightSampler, _cv_lightPointer(lightSampler, worldPos), 0)) != 0; + return _cv_lightAddress(lightSampler, worldPos) != 0u; } ivec2 _cv_lightTexelCoords(sampler2D lightSampler, vec3 worldPos) { - int address = _cv_vec2short(texelFetch(lightSampler, _cv_lightPointer(lightSampler, worldPos), 0)); - - // TODO use uniforms for meta - int pointerRows = _cv_vec2short(texelFetch(lightSampler, ivec2(1, 0), 0)); - - // TODO use uniforms for meta, remove padding - int exactRow = pointerRows + 1 + address; + uint exactRow = _cvu_world_uint[_CV_LIGHT_DATA_FIRST_ROW] + _cv_lightAddress(lightSampler, worldPos); ivec3 local = ivec3(mod(worldPos, 16.0)); return ivec2(local.z * 16 * 16 + local.y * 16 + local.x, exactRow); From 56c083552e7cd33ea0dc32b58b41a38b461acd8f Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sat, 22 Jul 2023 20:44:37 +0700 Subject: [PATCH 44/69] Deprecate light update extent; use Set-based queue Should have been done earlier: - Remove notions of extent-origin and redrawing when origin was moved - Redraw every allocated regions on texture remake instead of the above Experimentally: - Iterate through queue using native malloc "array" (slightly faster than regular array) Additionally: - Checkstyle fixes --- .../canvas/light/color/LightDataManager.java | 225 +++++++----------- .../canvas/light/color/LightDataTexture.java | 1 - .../canvas/light/color/LightRegion.java | 36 ++- .../canvas/light/color/LightRegionAccess.java | 6 + .../canvas/light/color/LightRegistry.java | 2 +- .../grondag/canvas/pipeline/Pipeline.java | 1 - .../canvas/pipeline/ProgramTextureData.java | 1 - .../pipeline/config/ColoredLightsConfig.java | 5 - .../render/world/CanvasWorldRenderer.java | 2 +- .../grondag/canvas/shader/data/FloatData.java | 3 - .../canvas/shader/data/ShaderDataManager.java | 8 - .../canvas/terrain/region/RenderRegion.java | 4 + .../assets/canvas/shaders/internal/world.glsl | 3 - 13 files changed, 128 insertions(+), 169 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 243411e7f..0778537e5 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -23,19 +23,17 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongIterable; -import it.unimi.dsi.fastutil.longs.LongIterator; -import org.joml.Vector3i; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.longs.LongSets; +import org.lwjgl.system.MemoryUtil; import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockAndTintGetter; import grondag.canvas.pipeline.Pipeline; -import grondag.canvas.shader.data.ShaderDataManager; public class LightDataManager { - private static final boolean debugRedrawEveryFrame = false; - static LightDataManager INSTANCE; public static LightRegionAccess allocate(BlockPos regionOrigin) { @@ -49,12 +47,9 @@ public static LightRegionAccess allocate(BlockPos regionOrigin) { public static void reload() { if (Pipeline.coloredLightsEnabled()) { assert Pipeline.config().coloredLights != null; - final var extentSizeRegions = Pipeline.config().coloredLights.maxRadiusChunks * 2; if (INSTANCE == null) { - INSTANCE = new LightDataManager(extentSizeRegions); - } else { - INSTANCE.resize(extentSizeRegions); + INSTANCE = new LightDataManager(); } INSTANCE.useOcclusionData = Pipeline.config().coloredLights.useOcclusionData; @@ -72,9 +67,9 @@ public static void free(BlockPos regionOrigin) { } } - public static void update(BlockAndTintGetter blockView, int cameraX, int cameraY, int cameraZ) { + public static void update(BlockAndTintGetter blockView) { if (INSTANCE != null) { - INSTANCE.updateInner(blockView, cameraX, cameraY, cameraZ); + INSTANCE.updateInner(blockView); } } @@ -94,97 +89,84 @@ public static String debugString() { return "Colored lights DISABLED"; } - private final Long2ObjectMap allocated = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>()); + private final Long2ObjectMap allocated = Long2ObjectMaps.synchronize(new Long2ObjectOpenHashMap<>(1024)); + + final LongSet publicUpdateQueue = LongSets.synchronize(new LongOpenHashSet()); + final LongSet publicDrawQueue = LongSets.synchronize(new LongOpenHashSet()); + private final LongSet decreaseQueue = new LongOpenHashSet(); + private final LongSet increaseQueue = new LongOpenHashSet(); - private final Vector3i extentOrigin = new Vector3i(); private final LightDataAllocator texAllocator; boolean useOcclusionData = false; - - private int extentSizeX; - private int extentSizeY; - private int extentSizeZ; - - private boolean cameraUninitialized = true; - private boolean extentWasResized = false; - - private int extentSizeXInRegions = 0; - private int extentSizeYInRegions = 0; - private int extentSizeZInRegions = 0; private LightDataTexture texture; - ExtentIterable extentIterable = new ExtentIterable(); - public LightDataManager(int newExtentSize) { + public LightDataManager() { texAllocator = new LightDataAllocator(); allocated.defaultReturnValue(null); - init(newExtentSize); - } - - private void resize(int newExtentSize) { - init(newExtentSize); - extentWasResized = true; } - private void init(int newExtentSize) { - extentSizeXInRegions = newExtentSize; - extentSizeYInRegions = newExtentSize; - extentSizeZInRegions = newExtentSize; - - extentSizeX = extentSizeInBlocks(extentSizeXInRegions); - extentSizeY = extentSizeInBlocks(extentSizeYInRegions); - extentSizeZ = extentSizeInBlocks(extentSizeZInRegions); - } - - private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, int cameraZ) { - final int regionSnapMask = ~LightRegionData.Const.WIDTH_MASK; - - final int prevExtentBlockX = extentOrigin.x; - final int prevExtentBlockY = extentOrigin.y; - final int prevExtentBlockZ = extentOrigin.z; + private void updateInner(BlockAndTintGetter blockView) { + synchronized (publicUpdateQueue) { + for (long index : publicUpdateQueue) { + decreaseQueue.add(index); + increaseQueue.add(index); + } - // snap camera position to the nearest region (chunk) - extentOrigin.x = (cameraX & regionSnapMask) - extentSizeInBlocks(extentSizeXInRegions / 2); - extentOrigin.y = (cameraY & regionSnapMask) - extentSizeInBlocks(extentSizeYInRegions / 2); - extentOrigin.z = (cameraZ & regionSnapMask) - extentSizeInBlocks(extentSizeZInRegions / 2); + publicUpdateQueue.clear(); + } - ShaderDataManager.updateLightVolumeOrigin(extentOrigin); + while (!decreaseQueue.isEmpty()) { + // hopefully faster with native op? + final int queueLen = decreaseQueue.size(); + final long queueIterator = MemoryUtil.nmemAlloc(8L * queueLen); - boolean extentMoved = !extentOrigin.equals(prevExtentBlockX, prevExtentBlockY, prevExtentBlockZ); + long i = 0L; - boolean needUpdate = true; + for (long index : decreaseQueue) { + MemoryUtil.memPutLong(queueIterator + i * 8L, index); + i++; + } - // TODO: account for extent-edge chunks? - // process all active regions' decrease queue until none is left - while (needUpdate) { - needUpdate = false; + decreaseQueue.clear(); - for (long index : extentIterable) { + for (i = 0; i < queueLen; i++) { + final long index = MemoryUtil.memGetLong(queueIterator + i * 8L); final LightRegion lightRegion = allocated.get(index); if (lightRegion != null && !lightRegion.isClosed()) { - needUpdate |= lightRegion.updateDecrease(blockView); + lightRegion.updateDecrease(blockView, decreaseQueue, increaseQueue); } } + + MemoryUtil.nmemFree(queueIterator); } - boolean shouldRedraw = !cameraUninitialized && extentMoved; + while (!increaseQueue.isEmpty()) { + // hopefully faster with native op? + final int queueLen = increaseQueue.size(); + final long queueIterator = MemoryUtil.nmemAlloc(8L * queueLen); + + long i = 0L; - needUpdate = true; + for (long index : increaseQueue) { + MemoryUtil.memPutLong(queueIterator + i * 8L, index); + i++; + } - // TODO: optimize active region traversal with queue - while (needUpdate) { - needUpdate = false; + increaseQueue.clear(); - for (long index : extentIterable) { + for (i = 0; i < queueLen; i++) { + final long index = MemoryUtil.memGetLong(queueIterator + i * 8L); final LightRegion lightRegion = allocated.get(index); if (lightRegion != null && !lightRegion.isClosed()) { - needUpdate |= lightRegion.updateIncrease(blockView); + lightRegion.updateIncrease(blockView, increaseQueue); } } - } - boolean textureOrExtentChanged = extentWasResized; + MemoryUtil.nmemFree(queueIterator); + } if (texAllocator.checkInvalid()) { if (texture != null) { @@ -193,45 +175,60 @@ private void updateInner(BlockAndTintGetter blockView, int cameraX, int cameraY, texture = new LightDataTexture(texAllocator.textureWidth(), texAllocator.textureHeight()); texAllocator.textureRemade(); - textureOrExtentChanged = true; - } - // TODO: swap texture in case of sparse, perhaps - for (long index : extentIterable) { - final LightRegion lightRegion = allocated.get(index); + publicDrawQueue.clear(); - if (lightRegion == null || lightRegion.isClosed()) { - continue; + // redraw + for (var lightRegion : allocated.values()) { + if (!lightRegion.isClosed()) { + drawInner(lightRegion, true); + } } + } else if (!publicDrawQueue.isEmpty()) { + final int queueLen; + final long queueIterator; + + long i = 0L; - // debug - texAllocator.allocateAddress(lightRegion); + synchronized (publicDrawQueue) { + // hopefully faster with native op? + queueLen = publicDrawQueue.size(); + queueIterator = MemoryUtil.nmemAlloc(8L * queueLen); - boolean outsidePrev = false; + for (long index : publicDrawQueue) { + MemoryUtil.memPutLong(queueIterator + i * 8L, index); + i++; + } - if (shouldRedraw && !textureOrExtentChanged) { - // Redraw regions that just entered the current-frame extent - outsidePrev |= lightRegion.lightData.regionOriginBlockX < prevExtentBlockX || lightRegion.lightData.regionOriginBlockX >= (prevExtentBlockX + extentSizeX); - outsidePrev |= lightRegion.lightData.regionOriginBlockY < prevExtentBlockY || lightRegion.lightData.regionOriginBlockY >= (prevExtentBlockY + extentSizeY); - outsidePrev |= lightRegion.lightData.regionOriginBlockZ < prevExtentBlockZ || lightRegion.lightData.regionOriginBlockZ >= (prevExtentBlockZ + extentSizeZ); + publicDrawQueue.clear(); } - if (lightRegion.lightData.hasBuffer() && (lightRegion.lightData.isDirty() || outsidePrev || textureOrExtentChanged || debugRedrawEveryFrame)) { - final int targetAddress = texAllocator.allocateAddress(lightRegion); + for (i = 0; i < queueLen; i++) { + final long index = MemoryUtil.memGetLong(queueIterator + i * 8L); + final LightRegion lightRegion = allocated.get(index); - if (targetAddress != LightDataAllocator.EMPTY_ADDRESS) { - final int targetRow = texAllocator.dataRowStart() + targetAddress; - texture.upload(targetRow, lightRegion.lightData.getBuffer()); + if (lightRegion != null && !lightRegion.isClosed()) { + drawInner(lightRegion, false); } - - lightRegion.lightData.clearDirty(); } + + MemoryUtil.nmemFree(queueIterator); } texAllocator.uploadPointersIfNeeded(texture); + } + + private void drawInner(LightRegion lightRegion, boolean redraw) { + if (lightRegion.lightData.hasBuffer() && (lightRegion.lightData.isDirty() || redraw)) { + final int targetAddress = texAllocator.allocateAddress(lightRegion); - extentWasResized = false; - cameraUninitialized = false; + if (targetAddress != LightDataAllocator.EMPTY_ADDRESS) { + final int targetRow = texAllocator.dataRowStart() + targetAddress; + texture.upload(targetRow, lightRegion.lightData.getBuffer()); + } + + lightRegion.lightData.clearDirty(); + } } LightRegion getFromBlock(BlockPos blockPos) { @@ -268,10 +265,6 @@ private LightRegion allocateInner(BlockPos regionOrigin) { return lightRegion; } - private int extentSizeInBlocks(int extentSize) { - return extentSize * LightRegionData.Const.WIDTH; - } - public void close() { texture.close(); @@ -285,38 +278,4 @@ public void close() { allocated.clear(); } } - - private class ExtentIterable implements LongIterable, LongIterator { - final int extent = extentSizeInBlocks(1); - - @Override - public LongIterator iterator() { - x = y = z = 0; - return this; - } - - int x, y, z; - - @Override - public long nextLong() { - final long value = BlockPos.asLong(extentOrigin.x + x * extent, extentOrigin.y + y * extent, extentOrigin.z + z * extent); - - if (++z >= extentSizeZInRegions) { - z = 0; - y++; - } - - if (y >= extentSizeYInRegions) { - y = 0; - x++; - } - - return value; - } - - @Override - public boolean hasNext() { - return x < extentSizeXInRegions; - } - } } diff --git a/src/main/java/grondag/canvas/light/color/LightDataTexture.java b/src/main/java/grondag/canvas/light/color/LightDataTexture.java index ce1a14951..54e7a3b8f 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataTexture.java +++ b/src/main/java/grondag/canvas/light/color/LightDataTexture.java @@ -31,7 +31,6 @@ import grondag.canvas.varia.GFX; public class LightDataTexture { - public static class Format { public static int target = GFX.GL_TEXTURE_2D; public static int pixelBytes = 2; diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 53aef9efa..640be9138 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -23,6 +23,7 @@ import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; import it.unimi.dsi.fastutil.longs.LongPriorityQueue; import it.unimi.dsi.fastutil.longs.LongPriorityQueues; +import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -151,6 +152,13 @@ public void checkBlock(BlockPos pos, BlockState blockState) { } } + @Override + public void submitChecks() { + if (!globalDecQueue.isEmpty() || !globalIncQueue.isEmpty()) { + LightDataManager.INSTANCE.publicUpdateQueue.add(origin); + } + } + private boolean occludeSide(BlockState state, Side dir, BlockAndTintGetter view, BlockPos pos) { // vanilla checks state.useShapeForLightOcclusion() but here it's always false for some reason. this is fine... @@ -161,7 +169,7 @@ private boolean occludeSide(BlockState state, Side dir, BlockAndTintGetter view, return Shapes.faceShapeOccludes(Shapes.empty(), state.getFaceOcclusionShape(view, pos, dir.vanilla)); } - boolean updateDecrease(BlockAndTintGetter blockView) { + void updateDecrease(BlockAndTintGetter blockView, LongSet neighborDecreaseQueue, LongSet neighborIncreaseQueue) { if (needCheckEdges) { checkEdges(blockView); needCheckEdges = false; @@ -169,7 +177,7 @@ boolean updateDecrease(BlockAndTintGetter blockView) { // faster exit when not necessary if (globalDecQueue.isEmpty()) { - return false; + return; } while (!globalDecQueue.isEmpty()) { @@ -180,7 +188,6 @@ boolean updateDecrease(BlockAndTintGetter blockView) { final LightOp.BVec removeMask = new LightOp.BVec(); int decCount = 0; - boolean accessedNeighborDecrease = false; while (!decQueue.isEmpty()) { decCount++; @@ -252,6 +259,10 @@ boolean updateDecrease(BlockAndTintGetter blockView) { dataAccess.put(nodeIndex, resultLight); Queues.enqueue(decreaseQueue, nodeIndex, nodeLight, side); + if (isNeighbor) { + neighborDecreaseQueue.add(neighbor.origin); + } + // restore obliterated light source if (restoreLightSource) { // defer putting light source as to not mess with decrease step @@ -261,8 +272,6 @@ boolean updateDecrease(BlockAndTintGetter blockView) { } else { repropLight = resultLight; } - - accessedNeighborDecrease |= isNeighbor; } else { repropLight = nodeLight; } @@ -270,24 +279,26 @@ boolean updateDecrease(BlockAndTintGetter blockView) { if (removeMask.and(less.not(), removeFlag).any() || restoreLightSource) { // increases queued in decrease may propagate to all directions as if a light source Queues.enqueue(increaseQueue, nodeIndex, repropLight); + + if (isNeighbor) { + neighborIncreaseQueue.add(neighbor.origin); + } } } } if (decCount > 0) { lightData.markAsDirty(); + LightDataManager.INSTANCE.publicDrawQueue.add(origin); } - - return accessedNeighborDecrease; } - public boolean updateIncrease(BlockAndTintGetter blockView) { + void updateIncrease(BlockAndTintGetter blockView, LongSet neighborIncreaseQueue) { while (!globalIncQueue.isEmpty()) { incQueue.enqueue(globalIncQueue.dequeueLong()); } int incCount = 0; - boolean accessedNeighbor = false; while (!incQueue.isEmpty()) { incCount++; @@ -360,7 +371,9 @@ public boolean updateIncrease(BlockAndTintGetter blockView) { dataAccess.put(nodeIndex, resultLight); Queues.enqueue(increaseQueue, nodeIndex, resultLight, side); - accessedNeighbor |= isNeighbor; + if (isNeighbor) { + neighborIncreaseQueue.add(neighbor.origin); + } } } } @@ -368,9 +381,8 @@ public boolean updateIncrease(BlockAndTintGetter blockView) { if (incCount > 0) { hasData = true; lightData.markAsDirty(); + LightDataManager.INSTANCE.publicDrawQueue.add(origin); } - - return accessedNeighbor; } private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourcePos, BlockPos.MutableBlockPos targetPos, Side side, BlockAndTintGetter blockView) { diff --git a/src/main/java/grondag/canvas/light/color/LightRegionAccess.java b/src/main/java/grondag/canvas/light/color/LightRegionAccess.java index ba6ca61e4..1a3c74367 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegionAccess.java +++ b/src/main/java/grondag/canvas/light/color/LightRegionAccess.java @@ -28,6 +28,8 @@ public interface LightRegionAccess { void checkBlock(BlockPos pos, BlockState blockState); + void submitChecks(); + boolean isClosed(); class Empty implements LightRegionAccess { @@ -35,6 +37,10 @@ class Empty implements LightRegionAccess { public void checkBlock(BlockPos pos, BlockState blockState) { } + @Override + public void submitChecks() { + } + @Override public boolean isClosed() { return true; diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java index 4e8a13356..12243301b 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegistry.java +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -102,7 +102,7 @@ private static class DummyWorld implements BlockGetter { private static BlockState state; private DummyWorld set(BlockState state) { - this.state = state; + DummyWorld.state = state; return this; } diff --git a/src/main/java/grondag/canvas/pipeline/Pipeline.java b/src/main/java/grondag/canvas/pipeline/Pipeline.java index a22b24f3d..6799912e6 100644 --- a/src/main/java/grondag/canvas/pipeline/Pipeline.java +++ b/src/main/java/grondag/canvas/pipeline/Pipeline.java @@ -24,7 +24,6 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import org.joml.Vector3i; import net.minecraft.client.GraphicsStatus; import net.minecraft.client.Minecraft; diff --git a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java index 90ca68384..79607c75b 100644 --- a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java +++ b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java @@ -23,7 +23,6 @@ import static net.minecraft.client.renderer.entity.ItemRenderer.ENCHANTED_GLINT_ITEM; import java.util.function.IntSupplier; -import java.util.function.Supplier; import org.lwjgl.opengl.GL46; diff --git a/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java b/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java index e12fdc479..f14cd006d 100644 --- a/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java +++ b/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java @@ -22,19 +22,14 @@ import blue.endless.jankson.JsonObject; -import grondag.canvas.light.color.LightDataTexture; -import grondag.canvas.pipeline.GlSymbolLookup; import grondag.canvas.pipeline.config.util.AbstractConfig; import grondag.canvas.pipeline.config.util.ConfigContext; -import grondag.canvas.pipeline.config.util.NamedDependency; public class ColoredLightsConfig extends AbstractConfig { - public final int maxRadiusChunks; public final boolean useOcclusionData; protected ColoredLightsConfig(ConfigContext ctx, JsonObject config) { super(ctx); - maxRadiusChunks = ctx.dynamic.getInt(config, "maxRadiusChunks", 4); useOcclusionData = ctx.dynamic.getBoolean(config, "useOcclusionData", false); } diff --git a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java index 3a1f282c0..51537f868 100644 --- a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java +++ b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java @@ -377,7 +377,7 @@ public void renderWorld(PoseStack viewMatrixStack, float tickDelta, long frameSt Lighting.setupLevel(MatrixData.viewMatrix); } - LightDataManager.update(world, (int) frameCameraX, (int) frameCameraY, (int) frameCameraZ); + LightDataManager.update(world); WorldRenderDraws.profileSwap(profiler, ProfilerGroup.StartWorld, "before_entities_event"); diff --git a/src/main/java/grondag/canvas/shader/data/FloatData.java b/src/main/java/grondag/canvas/shader/data/FloatData.java index 079f9deaf..4b4f6117c 100644 --- a/src/main/java/grondag/canvas/shader/data/FloatData.java +++ b/src/main/java/grondag/canvas/shader/data/FloatData.java @@ -112,7 +112,4 @@ private FloatData() { } static final int THUNDER_STRENGTH = VEC_WEATHER + 1; static final int SMOOTHED_RAIN_STRENGTH = VEC_WEATHER + 2; static final int SMOOTHED_THUNDER_STRENGTH = VEC_WEATHER + 3; - - static final int VEC_LIGHT_VOLUME_ORIGIN = 4 * 21; - static final int LIGHT_DATA_ORIGIN = VEC_LIGHT_VOLUME_ORIGIN; } diff --git a/src/main/java/grondag/canvas/shader/data/ShaderDataManager.java b/src/main/java/grondag/canvas/shader/data/ShaderDataManager.java index 5d2265d0a..bb17a941e 100644 --- a/src/main/java/grondag/canvas/shader/data/ShaderDataManager.java +++ b/src/main/java/grondag/canvas/shader/data/ShaderDataManager.java @@ -39,7 +39,6 @@ import static grondag.canvas.shader.data.FloatData.HELD_LIGHT_INTENSITY; import static grondag.canvas.shader.data.FloatData.HELD_LIGHT_OUTER_ANGLE; import static grondag.canvas.shader.data.FloatData.HELD_LIGHT_RED; -import static grondag.canvas.shader.data.FloatData.LIGHT_DATA_ORIGIN; import static grondag.canvas.shader.data.FloatData.MOON_SIZE; import static grondag.canvas.shader.data.FloatData.NIGHT_VISION_STRENGTH; import static grondag.canvas.shader.data.FloatData.PLAYER_MOOD; @@ -137,7 +136,6 @@ import org.joml.Matrix4f; import org.joml.Vector3f; -import org.joml.Vector3i; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack.Pose; @@ -649,12 +647,6 @@ public static void updateEmissiveColor(int color) { FLOAT_VECTOR_DATA.put(EMISSIVE_COLOR_BLUE, (color & 0xFF) / 255f); } - public static void updateLightVolumeOrigin(Vector3i lightOrigin) { - FLOAT_VECTOR_DATA.put(LIGHT_DATA_ORIGIN, lightOrigin.x); - FLOAT_VECTOR_DATA.put(LIGHT_DATA_ORIGIN + 1, lightOrigin.y); - FLOAT_VECTOR_DATA.put(LIGHT_DATA_ORIGIN + 2, lightOrigin.z); - } - private static void putViewVector(int index, float yaw, float pitch, Vector3f storeTo) { final Vec3 vec = Vec3.directionFromRotation(pitch, yaw); final float x = (float) vec.x; diff --git a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java index 4ba94d97d..5cee26030 100644 --- a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java +++ b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java @@ -471,6 +471,10 @@ private void buildTerrain(CanvasTerrainRenderContext context, RegionBuildState b } } + if (!lightRegion.isClosed()) { + lightRegion.submitChecks(); + } + buildState.prepareTranslucentIfNeeded(worldRenderState.sectorManager.cameraPos(), renderSector, collectors); if (ChunkRebuildCounters.ENABLED) { diff --git a/src/main/resources/assets/canvas/shaders/internal/world.glsl b/src/main/resources/assets/canvas/shaders/internal/world.glsl index f7ef02a2e..b0232eda1 100644 --- a/src/main/resources/assets/canvas/shaders/internal/world.glsl +++ b/src/main/resources/assets/canvas/shaders/internal/world.glsl @@ -72,9 +72,6 @@ // w = smoothed thunder strength #define _CV_WEATHER 20 -// w is unused -#define _CV_LIGHT_DATA_ORIGIN 21 - // UINT ARRAY #define _CV_RENDER_FRAMES 0 #define _CV_LIGHT_POINTER_EXTENT 1 From 967f26487cca9740bc7963a193f6d09a458a9864 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sun, 23 Jul 2023 02:41:08 +0700 Subject: [PATCH 45/69] Implement built-in sampler; Demo in abstract pipeline - Minor style change --- .../canvas/light/color/LightDataTexture.java | 24 +++++++++---------- .../canvas/light/color/LightRegionData.java | 8 +++---- .../canvas/material/state/RenderState.java | 14 +++++++---- .../canvas/pipeline/ProgramTextureData.java | 8 +++---- .../canvas/shader/data/ShaderUniforms.java | 1 + .../grondag/canvas/texture/TextureData.java | 4 ++-- .../assets/abstract/pipeline/base.json5 | 4 ++++ .../abstract/shaders/pipeline/abstract.frag | 9 ++++--- .../abstract/shaders/pipeline/abstract.vert | 8 +++++-- 9 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataTexture.java b/src/main/java/grondag/canvas/light/color/LightDataTexture.java index 54e7a3b8f..fe2171e8d 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataTexture.java +++ b/src/main/java/grondag/canvas/light/color/LightDataTexture.java @@ -32,11 +32,11 @@ public class LightDataTexture { public static class Format { - public static int target = GFX.GL_TEXTURE_2D; - public static int pixelBytes = 2; - public static int internalFormat = GFX.GL_RGBA4; - public static int pixelFormat = GFX.GL_RGBA; - public static int pixelDataType = GFX.GL_UNSIGNED_SHORT_4_4_4_4; + public static int TARGET = GFX.GL_TEXTURE_2D; + public static int PIXEL_BYTES = 2; + public static int INTERNAL_FORMAT = GFX.GL_RGBA4; + public static int PIXEL_FORMAT = GFX.GL_RGBA; + public static int TYPE = GFX.GL_UNSIGNED_SHORT_4_4_4_4; } private final int glId; @@ -49,14 +49,14 @@ public static class Format { glId = TextureUtil.generateTextureId(); CanvasTextureState.bindTexture(glId); - GFX.objectLabel(GL11.GL_TEXTURE, glId, "IMG colored_lights_data"); + GFX.objectLabel(GL11.GL_TEXTURE, glId, "IMG auto_colored_lights"); - GFX.texParameter(Format.target, GFX.GL_TEXTURE_MIN_FILTER, GFX.GL_NEAREST); - GFX.texParameter(Format.target, GFX.GL_TEXTURE_MAG_FILTER, GFX.GL_NEAREST); - GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_S, GFX.GL_CLAMP_TO_EDGE); - GFX.texParameter(Format.target, GFX.GL_TEXTURE_WRAP_T, GFX.GL_CLAMP_TO_EDGE); + GFX.texParameter(Format.TARGET, GFX.GL_TEXTURE_MIN_FILTER, GFX.GL_NEAREST); + GFX.texParameter(Format.TARGET, GFX.GL_TEXTURE_MAG_FILTER, GFX.GL_NEAREST); + GFX.texParameter(Format.TARGET, GFX.GL_TEXTURE_WRAP_S, GFX.GL_CLAMP_TO_EDGE); + GFX.texParameter(Format.TARGET, GFX.GL_TEXTURE_WRAP_T, GFX.GL_CLAMP_TO_EDGE); - GFX.texImage2D(Format.target, 0, Format.internalFormat, width, height, 0, Format.pixelFormat, Format.pixelDataType, (ByteBuffer) null); + GFX.texImage2D(Format.TARGET, 0, Format.INTERNAL_FORMAT, width, height, 0, Format.PIXEL_FORMAT, Format.TYPE, (ByteBuffer) null); } public int texId() { @@ -103,6 +103,6 @@ public void uploadDirect(int x, int y, int width, int height, ByteBuffer buffer) // Importantly, reset the pointer without flip buffer.position(0); - GFX.glTexSubImage2D(Format.target, 0, x, y, width, height, Format.pixelFormat, Format.pixelDataType, buffer); + GFX.glTexSubImage2D(Format.TARGET, 0, x, y, width, height, Format.PIXEL_FORMAT, Format.TYPE, buffer); } } diff --git a/src/main/java/grondag/canvas/light/color/LightRegionData.java b/src/main/java/grondag/canvas/light/color/LightRegionData.java index 3aa75e0b9..cc7e8b18f 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegionData.java +++ b/src/main/java/grondag/canvas/light/color/LightRegionData.java @@ -53,10 +53,10 @@ private void allocateBuffer() { throw new IllegalStateException("Trying to allocate light region buffer twice!"); } - buffer = MemoryUtil.memAlloc(LightDataTexture.Format.pixelBytes * Const.SIZE3D); + buffer = MemoryUtil.memAlloc(LightDataTexture.Format.PIXEL_BYTES * Const.SIZE3D); // clear manually ? - while (buffer.position() < LightDataTexture.Format.pixelBytes * Const.SIZE3D) { + while (buffer.position() < LightDataTexture.Format.PIXEL_BYTES * Const.SIZE3D) { buffer.putShort((short) 0); } } @@ -105,11 +105,11 @@ public int indexify(int x, int y, int z) { final int localZ = z - regionOriginBlockZ; // x and z are swapped because opengl - return ((localZ << (Const.WIDTH_SHIFT * 2)) | (localY << Const.WIDTH_SHIFT) | localX) * LightDataTexture.Format.pixelBytes; + return ((localZ << (Const.WIDTH_SHIFT * 2)) | (localY << Const.WIDTH_SHIFT) | localX) * LightDataTexture.Format.PIXEL_BYTES; } public void reverseIndexify(int index, BlockPos.MutableBlockPos result) { - index = index / LightDataTexture.Format.pixelBytes; + index = index / LightDataTexture.Format.PIXEL_BYTES; // x and z are swapped because opengl result.setX((index & Const.WIDTH_MASK) + regionOriginBlockX); diff --git a/src/main/java/grondag/canvas/material/state/RenderState.java b/src/main/java/grondag/canvas/material/state/RenderState.java index 2b1a59814..7ce0872aa 100644 --- a/src/main/java/grondag/canvas/material/state/RenderState.java +++ b/src/main/java/grondag/canvas/material/state/RenderState.java @@ -44,6 +44,8 @@ import io.vram.frex.api.texture.MaterialTexture; import grondag.canvas.config.Configurator; +import grondag.canvas.light.color.LightDataManager; +import grondag.canvas.light.color.LightDataTexture; import grondag.canvas.material.property.BinaryRenderState; import grondag.canvas.material.property.DecalRenderState; import grondag.canvas.material.property.DepthTestRenderState; @@ -214,10 +216,10 @@ private void enableMaterial(int x, int y, int z) { if (Pipeline.shadowMapDepth != -1) { CanvasTextureState.ensureTextureOfTextureUnit(TextureData.SHADOWMAP, GFX.GL_TEXTURE_2D_ARRAY, Pipeline.shadowMapDepth); CanvasTextureState.ensureTextureOfTextureUnit(TextureData.SHADOWMAP_TEXTURE, GFX.GL_TEXTURE_2D_ARRAY, Pipeline.shadowMapDepth); + } - // Set this back so nothing inadvertently tries to do stuff with array texture/shadowmap. - // Was seeing stray invalid operations errors in GL without. - CanvasTextureState.activeTextureUnit(TextureData.MC_SPRITE_ATLAS); + if (Pipeline.coloredLightsEnabled()) { + CanvasTextureState.ensureTextureOfTextureUnit(TextureData.COLORED_LIGHTS_DATA, LightDataTexture.Format.TARGET, LightDataManager.texId()); } if (Pipeline.config().materialProgram.samplerNames.length > 0) { @@ -227,10 +229,12 @@ private void enableMaterial(int x, int y, int z) { final int bind = Pipeline.materialTextures().texIds[i].getAsInt(); CanvasTextureState.ensureTextureOfTextureUnit(TextureData.PROGRAM_SAMPLERS + i, bindTarget, bind); } - - CanvasTextureState.activeTextureUnit(TextureData.MC_SPRITE_ATLAS); } + // Set this back so nothing inadvertently tries to do stuff with array texture/shadowmap. + // Was seeing stray invalid operations errors in GL without. + CanvasTextureState.activeTextureUnit(TextureData.MC_SPRITE_ATLAS); + texture.enable(blur); transparency.enable(); depthTest.enable(); diff --git a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java index 79607c75b..2be3c1ada 100644 --- a/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java +++ b/src/main/java/grondag/canvas/pipeline/ProgramTextureData.java @@ -49,11 +49,9 @@ public ProgramTextureData(NamedDependency[] samplerImages) { IntSupplier imageBind = () -> 0; int bindTarget = GL46.GL_TEXTURE_2D; - if (imageName.startsWith("frex:")) { - // TODO use a registry - if (imageName.equals("frex:textures/colored_lights")) { - imageBind = LightDataManager::texId; - } + // TODO: use a registry if there is more of these + if (imageName.equals("frex:textures/auto/colored_lights")) { + imageBind = LightDataManager::texId; } else if (imageName.contains(":")) { final AbstractTexture tex = tryLoadResourceTexture(new ResourceLocation(imageName)); diff --git a/src/main/java/grondag/canvas/shader/data/ShaderUniforms.java b/src/main/java/grondag/canvas/shader/data/ShaderUniforms.java index 2fabc6c55..06373211e 100644 --- a/src/main/java/grondag/canvas/shader/data/ShaderUniforms.java +++ b/src/main/java/grondag/canvas/shader/data/ShaderUniforms.java @@ -35,6 +35,7 @@ public class ShaderUniforms { program.uniformSampler("frxs_shadowMap", UniformRefreshFrequency.ON_LOAD, u -> u.set(TextureData.SHADOWMAP - GL21.GL_TEXTURE0)); program.uniformSampler("frxs_shadowMapTexture", UniformRefreshFrequency.ON_LOAD, u -> u.set(TextureData.SHADOWMAP_TEXTURE - GL21.GL_TEXTURE0)); + program.uniformSampler("frxs_lightData", UniformRefreshFrequency.ON_LOAD, u -> u.set(TextureData.COLORED_LIGHTS_DATA - GL21.GL_TEXTURE0)); //program.uniformSampler2d("frxs_dither", UniformRefreshFrequency.ON_LOAD, u -> u.set(TextureData.DITHER - GL21.GL_TEXTURE0)); diff --git a/src/main/java/grondag/canvas/texture/TextureData.java b/src/main/java/grondag/canvas/texture/TextureData.java index 0f3716111..292facbdc 100644 --- a/src/main/java/grondag/canvas/texture/TextureData.java +++ b/src/main/java/grondag/canvas/texture/TextureData.java @@ -31,7 +31,7 @@ public class TextureData { // want these outside of the range managed by Mojang's damn GlStateManager public static final int SHADOWMAP = GL21.GL_TEXTURE12; public static final int SHADOWMAP_TEXTURE = SHADOWMAP + 1; - public static final int HD_LIGHTMAP = SHADOWMAP_TEXTURE + 1; - public static final int MATERIAL_INFO = HD_LIGHTMAP + 1; + public static final int COLORED_LIGHTS_DATA = SHADOWMAP_TEXTURE + 1; + public static final int MATERIAL_INFO = COLORED_LIGHTS_DATA + 1; public static final int PROGRAM_SAMPLERS = MATERIAL_INFO + 1; } diff --git a/src/main/resources/resourcepacks/abstract/assets/abstract/pipeline/base.json5 b/src/main/resources/resourcepacks/abstract/assets/abstract/pipeline/base.json5 index dc2ef63f3..491966b25 100644 --- a/src/main/resources/resourcepacks/abstract/assets/abstract/pipeline/base.json5 +++ b/src/main/resources/resourcepacks/abstract/assets/abstract/pipeline/base.json5 @@ -10,6 +10,10 @@ rainSmoothingFrames: 500, glslVersion: 330, + coloredLights: { + useOcclusionData: false + }, + images: [ // color attachment for solid draws { diff --git a/src/main/resources/resourcepacks/abstract/assets/abstract/shaders/pipeline/abstract.frag b/src/main/resources/resourcepacks/abstract/assets/abstract/shaders/pipeline/abstract.frag index c510a97e9..0370378b5 100644 --- a/src/main/resources/resourcepacks/abstract/assets/abstract/shaders/pipeline/abstract.frag +++ b/src/main/resources/resourcepacks/abstract/assets/abstract/shaders/pipeline/abstract.frag @@ -7,6 +7,7 @@ #include frex:shaders/api/player.glsl #include frex:shaders/api/material.glsl #include frex:shaders/api/fragment.glsl +#include frex:shaders/api/sampler.glsl #include abstract:shaders/pipeline/glint.glsl #include abstract:basic_light_config #include abstract:handheld_light_config @@ -26,7 +27,8 @@ out vec4[2] fragColor; #if HANDHELD_LIGHT_RADIUS != 0 flat in float _cvInnerAngle; flat in float _cvOuterAngle; -in vec4 _cvViewVertex; +in vec3 _cvViewVertex; +in vec3 _cvWorldVertex; #endif /** @@ -96,8 +98,9 @@ void frx_pipelineFragment() { // ambient float skyCoord = frx_fragEnableDiffuse ? 0.03125 + (frx_fragLight.y - 0.03125) * 0.5 : frx_fragLight.y; - vec4 light = frx_fromGamma(texture(frxs_lightmap, vec2(frx_fragLight.x, skyCoord))); - light = mix(light, frx_emissiveColor, frx_fragEmissive); + vec4 light = frx_fromGamma(texture(frxs_lightmap, vec2(0.03125, skyCoord))); + light += frx_fromGamma(frx_getLightFiltered(_cvWorldVertex)); + light += frx_emissiveColor * frx_fragEmissive; #if HANDHELD_LIGHT_RADIUS != 0 vec4 held = frx_heldLight; diff --git a/src/main/resources/resourcepacks/abstract/assets/abstract/shaders/pipeline/abstract.vert b/src/main/resources/resourcepacks/abstract/assets/abstract/shaders/pipeline/abstract.vert index 3375eecc0..a46794db4 100644 --- a/src/main/resources/resourcepacks/abstract/assets/abstract/shaders/pipeline/abstract.vert +++ b/src/main/resources/resourcepacks/abstract/assets/abstract/shaders/pipeline/abstract.vert @@ -1,4 +1,5 @@ #include frex:shaders/api/view.glsl +#include frex:shaders/api/world.glsl #include frex:shaders/api/player.glsl #include abstract:handheld_light_config @@ -9,7 +10,8 @@ #if HANDHELD_LIGHT_RADIUS != 0 flat out float _cvInnerAngle; flat out float _cvOuterAngle; -out vec4 _cvViewVertex; +out vec3 _cvViewVertex; +out vec3 _cvWorldVertex; #endif out vec4 shadowPos; @@ -17,14 +19,16 @@ out vec4 shadowPos; void frx_pipelineVertex() { if (frx_isGui) { gl_Position = frx_guiViewProjectionMatrix * frx_vertex; + _cvWorldVertex = (frx_vertex.xyz * frx_normalModelMatrix) * 0.2 + frx_cameraPos; frx_distance = length(gl_Position.xyz); } else { frx_vertex += frx_modelToCamera; + _cvWorldVertex = frx_vertex.xyz + frx_cameraPos + frx_normal * 0.05; vec4 viewCoord = frx_viewMatrix * frx_vertex; frx_distance = length(viewCoord.xyz); gl_Position = frx_projectionMatrix * viewCoord; #if HANDHELD_LIGHT_RADIUS != 0 - _cvViewVertex = viewCoord; + _cvViewVertex = viewCoord.xyz; #endif shadowPos = frx_shadowViewMatrix * frx_vertex; From bbfea2f087adea2855a923220351b934615ee0c4 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sun, 23 Jul 2023 09:18:37 +0700 Subject: [PATCH 46/69] Block Light API defaults/examples Some light levels are adjusted to account for perceived luminance. Note that this is inaccurate. --- .../assets/minecraft/lights/block/blast_furnace.json | 7 +++++++ .../resources/assets/minecraft/lights/block/campfire.json | 7 +++++++ .../resources/assets/minecraft/lights/block/candle.json | 7 +++++++ .../assets/minecraft/lights/block/cave_vines.json | 7 +++++++ .../assets/minecraft/lights/block/cave_vines_plant.json | 7 +++++++ .../assets/minecraft/lights/block/enchanting_table.json | 7 +++++++ .../resources/assets/minecraft/lights/block/fire.json | 7 +++++++ .../resources/assets/minecraft/lights/block/furnace.json | 7 +++++++ .../assets/minecraft/lights/block/lava_cauldron.json | 7 +++++++ .../assets/minecraft/lights/block/nether_portal.json | 8 ++++++++ .../assets/minecraft/lights/block/sea_pickle.json | 7 +++++++ .../resources/assets/minecraft/lights/block/smoker.json | 7 +++++++ .../assets/minecraft/lights/block/soul_fire.json | 8 ++++++++ .../assets/minecraft/lights/fluid/flowing_lava.json | 7 +++++++ .../resources/assets/minecraft/lights/fluid/lava.json | 7 +++++++ 15 files changed, 107 insertions(+) create mode 100644 src/main/resources/assets/minecraft/lights/block/blast_furnace.json create mode 100644 src/main/resources/assets/minecraft/lights/block/campfire.json create mode 100644 src/main/resources/assets/minecraft/lights/block/candle.json create mode 100644 src/main/resources/assets/minecraft/lights/block/cave_vines.json create mode 100644 src/main/resources/assets/minecraft/lights/block/cave_vines_plant.json create mode 100644 src/main/resources/assets/minecraft/lights/block/enchanting_table.json create mode 100644 src/main/resources/assets/minecraft/lights/block/fire.json create mode 100644 src/main/resources/assets/minecraft/lights/block/furnace.json create mode 100644 src/main/resources/assets/minecraft/lights/block/lava_cauldron.json create mode 100644 src/main/resources/assets/minecraft/lights/block/nether_portal.json create mode 100644 src/main/resources/assets/minecraft/lights/block/sea_pickle.json create mode 100644 src/main/resources/assets/minecraft/lights/block/smoker.json create mode 100644 src/main/resources/assets/minecraft/lights/block/soul_fire.json create mode 100644 src/main/resources/assets/minecraft/lights/fluid/flowing_lava.json create mode 100644 src/main/resources/assets/minecraft/lights/fluid/lava.json diff --git a/src/main/resources/assets/minecraft/lights/block/blast_furnace.json b/src/main/resources/assets/minecraft/lights/block/blast_furnace.json new file mode 100644 index 000000000..e9a458490 --- /dev/null +++ b/src/main/resources/assets/minecraft/lights/block/blast_furnace.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} diff --git a/src/main/resources/assets/minecraft/lights/block/campfire.json b/src/main/resources/assets/minecraft/lights/block/campfire.json new file mode 100644 index 000000000..e9a458490 --- /dev/null +++ b/src/main/resources/assets/minecraft/lights/block/campfire.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} diff --git a/src/main/resources/assets/minecraft/lights/block/candle.json b/src/main/resources/assets/minecraft/lights/block/candle.json new file mode 100644 index 000000000..e9a458490 --- /dev/null +++ b/src/main/resources/assets/minecraft/lights/block/candle.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} diff --git a/src/main/resources/assets/minecraft/lights/block/cave_vines.json b/src/main/resources/assets/minecraft/lights/block/cave_vines.json new file mode 100644 index 000000000..e9a458490 --- /dev/null +++ b/src/main/resources/assets/minecraft/lights/block/cave_vines.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} diff --git a/src/main/resources/assets/minecraft/lights/block/cave_vines_plant.json b/src/main/resources/assets/minecraft/lights/block/cave_vines_plant.json new file mode 100644 index 000000000..e9a458490 --- /dev/null +++ b/src/main/resources/assets/minecraft/lights/block/cave_vines_plant.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} diff --git a/src/main/resources/assets/minecraft/lights/block/enchanting_table.json b/src/main/resources/assets/minecraft/lights/block/enchanting_table.json new file mode 100644 index 000000000..56ab6d7a0 --- /dev/null +++ b/src/main/resources/assets/minecraft/lights/block/enchanting_table.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 0.8, + "green": 1.0, + "blue": 1.0 + } +} diff --git a/src/main/resources/assets/minecraft/lights/block/fire.json b/src/main/resources/assets/minecraft/lights/block/fire.json new file mode 100644 index 000000000..e9a458490 --- /dev/null +++ b/src/main/resources/assets/minecraft/lights/block/fire.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} diff --git a/src/main/resources/assets/minecraft/lights/block/furnace.json b/src/main/resources/assets/minecraft/lights/block/furnace.json new file mode 100644 index 000000000..e9a458490 --- /dev/null +++ b/src/main/resources/assets/minecraft/lights/block/furnace.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} diff --git a/src/main/resources/assets/minecraft/lights/block/lava_cauldron.json b/src/main/resources/assets/minecraft/lights/block/lava_cauldron.json new file mode 100644 index 000000000..e9a458490 --- /dev/null +++ b/src/main/resources/assets/minecraft/lights/block/lava_cauldron.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} diff --git a/src/main/resources/assets/minecraft/lights/block/nether_portal.json b/src/main/resources/assets/minecraft/lights/block/nether_portal.json new file mode 100644 index 000000000..d8fa7268e --- /dev/null +++ b/src/main/resources/assets/minecraft/lights/block/nether_portal.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "lightLevel": 14.0, + "red": 1.0, + "green": 0.4, + "blue": 1.0 + } +} diff --git a/src/main/resources/assets/minecraft/lights/block/sea_pickle.json b/src/main/resources/assets/minecraft/lights/block/sea_pickle.json new file mode 100644 index 000000000..56ab6d7a0 --- /dev/null +++ b/src/main/resources/assets/minecraft/lights/block/sea_pickle.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 0.8, + "green": 1.0, + "blue": 1.0 + } +} diff --git a/src/main/resources/assets/minecraft/lights/block/smoker.json b/src/main/resources/assets/minecraft/lights/block/smoker.json new file mode 100644 index 000000000..e9a458490 --- /dev/null +++ b/src/main/resources/assets/minecraft/lights/block/smoker.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} diff --git a/src/main/resources/assets/minecraft/lights/block/soul_fire.json b/src/main/resources/assets/minecraft/lights/block/soul_fire.json new file mode 100644 index 000000000..01e134c9d --- /dev/null +++ b/src/main/resources/assets/minecraft/lights/block/soul_fire.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "lightLevel": 13.0, + "red": 0.6, + "green": 0.8, + "blue": 1.0 + } +} diff --git a/src/main/resources/assets/minecraft/lights/fluid/flowing_lava.json b/src/main/resources/assets/minecraft/lights/fluid/flowing_lava.json new file mode 100644 index 000000000..e9a458490 --- /dev/null +++ b/src/main/resources/assets/minecraft/lights/fluid/flowing_lava.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} diff --git a/src/main/resources/assets/minecraft/lights/fluid/lava.json b/src/main/resources/assets/minecraft/lights/fluid/lava.json new file mode 100644 index 000000000..e9a458490 --- /dev/null +++ b/src/main/resources/assets/minecraft/lights/fluid/lava.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} From b5a52f71b644dfe6a111f2077b59230841f1133e Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sat, 29 Jul 2023 12:43:12 +0700 Subject: [PATCH 47/69] Fix erroneous macro usage --- .../resources/assets/frex/shaders/api/sampler.glsl | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/resources/assets/frex/shaders/api/sampler.glsl b/src/main/resources/assets/frex/shaders/api/sampler.glsl index 250ffede1..5dcfceb7e 100644 --- a/src/main/resources/assets/frex/shaders/api/sampler.glsl +++ b/src/main/resources/assets/frex/shaders/api/sampler.glsl @@ -29,7 +29,15 @@ uniform sampler2DArray frxs_shadowMapTexture; #ifdef COLORED_LIGHTS_ENABLED uniform sampler2D frxs_lightData; -#define frx_getLightFiltered(worldPos) frx_getLightFiltered(frxs_lightData, worldPos) -#define frx_getLightRaw(worldPos) frx_getLightRaw(frxs_lightData, worldPos) -#define frx_getLight(worldPos, fallback) frx_getLight(frxs_lightData, worldPos, fallback) +vec4 frx_getLightFiltered(vec3 worldPos) { + return frx_getLightFiltered(frxs_lightData, worldPos); +} + +vec4 frx_getLightRaw(vec3 worldPos) { + return frx_getLightRaw(frxs_lightData, worldPos); +} + +vec3 frx_getLight(vec3 worldPos, vec3 fallback) { + return frx_getLight(frxs_lightData, worldPos, fallback); +} #endif From b5cdbf32ef7fa7bdf226efcfe1e1c65a4a236c53 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Wed, 27 Dec 2023 11:20:35 +0700 Subject: [PATCH 48/69] Fix emitter re-propagation (I thought I fixed this?) --- .../canvas/light/color/LightRegion.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 640be9138..7d04e063b 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -111,7 +111,7 @@ static short light(long entry) { private final LongArrayFIFOQueue incQueue = new LongArrayFIFOQueue(); private final LongArrayFIFOQueue decQueue = new LongArrayFIFOQueue(); - // This is bad and defeats the point of Canvas multithreading, maybe + // PERF: since increase queue is only honored if it's "latest", it can be optimized with a Map private final LongPriorityQueue globalIncQueue = LongPriorityQueues.synchronize(new LongArrayFIFOQueue()); private final LongPriorityQueue globalDecQueue = LongPriorityQueues.synchronize(new LongArrayFIFOQueue()); @@ -203,7 +203,8 @@ void updateDecrease(BlockAndTintGetter blockView, LongSet neighborDecreaseQueue, lightData.reverseIndexify(index, sourcePos); - final BlockState sourceState = blockView.getBlockState(sourcePos); + // unused for some reason + // final BlockState sourceState = blockView.getBlockState(sourcePos); for (var side : Side.values()) { if (side.id == from) { @@ -266,9 +267,7 @@ void updateDecrease(BlockAndTintGetter blockView, LongSet neighborDecreaseQueue, // restore obliterated light source if (restoreLightSource) { // defer putting light source as to not mess with decrease step - // take RGB of maximum and Alpha of registered - final short registered = LightRegistry.get(nodeState); - repropLight = LightOp.max(registered, resultLight); + repropLight = LightRegistry.get(nodeState); } else { repropLight = resultLight; } @@ -311,21 +310,30 @@ void updateIncrease(BlockAndTintGetter blockView, LongSet neighborIncreaseQueue) final short getLight = lightData.get(index); final short sourceLight; + final BlockState sourceState; + if (getLight != recordedLight) { if (LightOp.emitter(recordedLight)) { + lightData.reverseIndexify(index, sourcePos); + sourceState = blockView.getBlockState(sourcePos); + + if (LightRegistry.get(sourceState) != recordedLight) { + continue; + } + + // take max of current and recorded source sourceLight = LightOp.max(recordedLight, getLight); lightData.put(index, sourceLight); } else { continue; } } else { + lightData.reverseIndexify(index, sourcePos); + sourceState = blockView.getBlockState(sourcePos); + sourceLight = getLight; } - lightData.reverseIndexify(index, sourcePos); - - final BlockState sourceState = blockView.getBlockState(sourcePos); - for (var side : Side.values()) { if (side.id == from) { continue; From eced5f6c8fc9f4034fde245774765fb32a5e72a8 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Wed, 27 Dec 2023 14:51:28 +0700 Subject: [PATCH 49/69] Enable/disable colored lights via config and pipeline option --- .../java/grondag/canvas/config/CanvasConfigScreen.java | 9 +++++++++ src/main/java/grondag/canvas/config/ConfigData.java | 2 ++ src/main/java/grondag/canvas/config/Configurator.java | 3 +++ src/main/java/grondag/canvas/pipeline/Pipeline.java | 6 +++++- .../canvas/pipeline/config/ColoredLightsConfig.java | 2 ++ src/main/resources/assets/canvas/lang/en_us.json | 2 ++ 6 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/main/java/grondag/canvas/config/CanvasConfigScreen.java b/src/main/java/grondag/canvas/config/CanvasConfigScreen.java index e95a7d834..4a1899afe 100644 --- a/src/main/java/grondag/canvas/config/CanvasConfigScreen.java +++ b/src/main/java/grondag/canvas/config/CanvasConfigScreen.java @@ -116,6 +116,15 @@ protected void init() { DEFAULTS.semiFlatLighting, "config.canvas.help.semi_flat_lighting").listItem()); + list.addItem(optionSession.booleanOption("config.canvas.value.colored_lights", + () -> editing.coloredLights, + b -> { + reload |= Configurator.coloredLights != b; + editing.coloredLights = b; + }, + DEFAULTS.coloredLights, + "config.canvas.help.colored_lights").listItem()); + // TWEAKS final int indexTweaks = list.addCategory("config.canvas.category.tweaks"); diff --git a/src/main/java/grondag/canvas/config/ConfigData.java b/src/main/java/grondag/canvas/config/ConfigData.java index dd12a1a0c..8bd0b6ebd 100644 --- a/src/main/java/grondag/canvas/config/ConfigData.java +++ b/src/main/java/grondag/canvas/config/ConfigData.java @@ -55,6 +55,8 @@ class ConfigData { //boolean moreLightmap = true; @Comment("Models with flat lighting have smoother lighting (but no ambient occlusion).") boolean semiFlatLighting = true; + @Comment("Enable client-side colored block lights for pipelines that supports it. Will replace server lighting visually.") + boolean coloredLights = false; // TWEAKS @Comment("Adjusts quads on some vanilla models (like iron bars) to avoid z-fighting with neighbor blocks.") diff --git a/src/main/java/grondag/canvas/config/Configurator.java b/src/main/java/grondag/canvas/config/Configurator.java index 6662b7db4..ce8899b8a 100644 --- a/src/main/java/grondag/canvas/config/Configurator.java +++ b/src/main/java/grondag/canvas/config/Configurator.java @@ -40,6 +40,7 @@ public class Configurator { //public static boolean moreLightmap = DEFAULTS.moreLightmap; //public static int maxLightmapDelayFrames = DEFAULTS.maxLightmapDelayFrames; public static boolean semiFlatLighting = DEFAULTS.semiFlatLighting; + public static boolean coloredLights = DEFAULTS.coloredLights; public static boolean preventDepthFighting = DEFAULTS.preventDepthFighting; public static boolean clampExteriorVertices = DEFAULTS.clampExteriorVertices; @@ -136,6 +137,7 @@ static void readFromConfig(ConfigData config, boolean isStartup) { // lightmapNoise = config.lightmapNoise; lightSmoothing = config.lightSmoothing; semiFlatLighting = config.semiFlatLighting; + coloredLights = config.coloredLights; // disableVanillaChunkMatrix = config.disableVanillaChunkMatrix; preventDepthFighting = config.preventDepthFighting; @@ -202,6 +204,7 @@ static void writeToConfig(ConfigData config) { config.lightSmoothing = lightSmoothing; //config.moreLightmap = moreLightmap; config.semiFlatLighting = semiFlatLighting; + config.coloredLights = coloredLights; config.preventDepthFighting = preventDepthFighting; config.clampExteriorVertices = clampExteriorVertices; diff --git a/src/main/java/grondag/canvas/pipeline/Pipeline.java b/src/main/java/grondag/canvas/pipeline/Pipeline.java index 6799912e6..ca9ad7632 100644 --- a/src/main/java/grondag/canvas/pipeline/Pipeline.java +++ b/src/main/java/grondag/canvas/pipeline/Pipeline.java @@ -212,7 +212,11 @@ static void activate(PrimaryFrameBuffer primary, int width, int height) { defaultZenithAngle = 0f; } - coloredLightsEnabled = config.coloredLights != null; + if (config.coloredLights != null && Configurator.coloredLights) { + coloredLightsEnabled = config.coloredLights.enabled; + } else { + coloredLightsEnabled = false; + } if (isFabulous) { final FabulousConfig fc = config.fabulosity; diff --git a/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java b/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java index f14cd006d..6936c887e 100644 --- a/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java +++ b/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java @@ -26,10 +26,12 @@ import grondag.canvas.pipeline.config.util.ConfigContext; public class ColoredLightsConfig extends AbstractConfig { + public final boolean enabled; public final boolean useOcclusionData; protected ColoredLightsConfig(ConfigContext ctx, JsonObject config) { super(ctx); + enabled = ctx.dynamic.getBoolean(config, "enabled", true); useOcclusionData = ctx.dynamic.getBoolean(config, "useOcclusionData", false); } diff --git a/src/main/resources/assets/canvas/lang/en_us.json b/src/main/resources/assets/canvas/lang/en_us.json index c6ebb778a..47a2a6856 100644 --- a/src/main/resources/assets/canvas/lang/en_us.json +++ b/src/main/resources/assets/canvas/lang/en_us.json @@ -53,6 +53,8 @@ "config.canvas.help.lightmap_delay_frames": "Setting > 0 may give slightly;better FPS at cost of potential;flickering when lighting changes.", "config.canvas.value.semi_flat_lighting": "Semi-Flat Lightmap", "config.canvas.help.semi_flat_lighting": "Models with flat lighting have smoother lighting;(but no ambient occlusion).", + "config.canvas.value.colored_lights": "Colored Lights", + "config.canvas.help.colored_lights": "Enable client-side colored block lights for pipelines that supports it. Will replace server lighting visually.", "config.canvas.enum.ao_mode.normal": "Vanilla", "config.canvas.enum.ao_mode.subtle_always": "Subtle", "config.canvas.enum.ao_mode.subtle_block_light": "Subtle Torchlit", From aa9b355fda7b60e575f2d9fb2494f058c5f3ba8a Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Wed, 27 Dec 2023 15:20:57 +0700 Subject: [PATCH 50/69] Prevent light data texture size exceeding hardware limit --- .../light/color/LightDataAllocator.java | 76 +++++++++++++------ .../canvas/light/color/LightDataManager.java | 15 ++-- .../grondag/canvas/varia/CanvasGlHelper.java | 17 +++-- 3 files changed, 68 insertions(+), 40 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java index 66e1b3c81..6de69ef90 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java +++ b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java @@ -33,13 +33,14 @@ import grondag.canvas.CanvasMod; import grondag.canvas.pipeline.Pipeline; import grondag.canvas.shader.data.IntData; +import grondag.canvas.varia.CanvasGlHelper; public class LightDataAllocator { private static final boolean DEBUG_LOG_RESIZE = false; // unsigned short hard limit, because we are putting addresses in the data texture. - // although, this number of maximum address correspond to about 1 GiB of memory - // of light data, so this is a reasonable "hard limit". + // this corresponds to about 1 GiB of light data which is a reasonable hard limit, + // but in practice, the texture size limit will be reached far before this limit. private static final int MAX_ADDRESSES = 65536; // size of one row as determined by the data size of one region (16^3). @@ -59,9 +60,10 @@ public class LightDataAllocator { private ByteBuffer pointerBuffer; private boolean requireTextureRemake; + private int addressLimit = INITIAL_ADDRESS_COUNT; private int dynamicMaxAddresses = 0; // 0 is reserved for empty address - private int nextAllocateAddress = 1; + private short nextAllocateAddress = 1; private int addressCount = 1; private float dataSize = 0f; @@ -73,6 +75,25 @@ public class LightDataAllocator { increaseAddressSize(INITIAL_ADDRESS_COUNT); } + private void ensureWithinLimits() { + final int maxTextureHeight = CanvasGlHelper.maxTextureSize() == 0 ? 16384 : CanvasGlHelper.maxTextureSize(); + addressLimit = Math.max(INITIAL_ADDRESS_COUNT, Math.min(maxTextureHeight - pointerRows, MAX_ADDRESSES)); + + if (dynamicMaxAddresses > addressLimit) { + dynamicMaxAddresses = addressLimit; + requireTextureRemake = true; + } + + // Remove addresses exceeding limit + if (addressCount > dynamicMaxAddresses) { + for (short addressToRemove = (short) dynamicMaxAddresses; addressToRemove < addressCount; addressToRemove++) { + removeAddressIfAllocated(addressToRemove); + } + + addressCount = dynamicMaxAddresses; + } + } + private void resizePointerBuffer(int newPointerExtent) { if (newPointerExtent == pointerExtent) { return; @@ -83,6 +104,7 @@ private void resizePointerBuffer(int newPointerExtent) { final int pointerCountReq = pointerExtent * pointerExtent * pointerExtent; var prevRows = pointerRows; pointerRows = pointerCountReq / ROW_SIZE + ((pointerCountReq % ROW_SIZE == 0) ? 0 : 1); + ensureWithinLimits(); if (DEBUG_LOG_RESIZE) { CanvasMod.LOG.info("Resized pointer storage capacity from " + prevRows + " to " + pointerRows); @@ -112,11 +134,13 @@ private void resizePointerBuffer(int newPointerExtent) { } // currently, this only increase size - // TODO: shrink + // TODO: shrink (reclaim video memory) - not very necessary though? private void increaseAddressSize(int newSize) { - final int cappedNewSize = Math.min(newSize, MAX_ADDRESSES); + ensureWithinLimits(); + + final int cappedNewSize = Math.min(newSize, addressLimit); - if (dynamicMaxAddresses >= cappedNewSize) { + if (dynamicMaxAddresses == cappedNewSize) { return; } @@ -148,17 +172,16 @@ private short allocateAddressInner(LightRegion region) { final short newAddress; // go through freed addresses first - // note: this is kinda bad when we reach MAX_ADDRESSES, but that's kinda edge case if (!freedAddresses.isEmpty()) { newAddress = freedAddresses.popShort(); } else { - if (addressCount >= dynamicMaxAddresses && addressCount < MAX_ADDRESSES) { + if (addressCount == dynamicMaxAddresses) { // try resize first increaseAddressSize(dynamicMaxAddresses * 2); } if (addressCount < dynamicMaxAddresses) { - newAddress = (short) nextAllocateAddress; + newAddress = nextAllocateAddress; nextAllocateAddress++; addressCount++; } else { @@ -167,20 +190,9 @@ private short allocateAddressInner(LightRegion region) { nextAllocateAddress = EMPTY_ADDRESS + 1; } - final short castedNextAddress = (short) nextAllocateAddress; - - if (allocatedAddresses.containsKey(castedNextAddress)) { - final long oldOrigin = allocatedAddresses.get(castedNextAddress); - allocatedAddresses.remove(castedNextAddress); - - final LightRegion oldRegion = LightDataManager.INSTANCE.get(oldOrigin); - - if (oldRegion != null) { - setAddress(oldRegion, EMPTY_ADDRESS); - } - } - - newAddress = castedNextAddress; + final short nextAddress = nextAllocateAddress; + removeAddressIfAllocated(nextAddress); + newAddress = nextAddress; nextAllocateAddress++; } } @@ -219,8 +231,24 @@ void freeAddress(LightRegion region) { } } + void removeAddressIfAllocated(short addressToRemove) { + if (allocatedAddresses.containsKey(addressToRemove)) { + final long oldOrigin = allocatedAddresses.get(addressToRemove); + allocatedAddresses.remove(addressToRemove); + + final LightRegion oldRegion = LightDataManager.INSTANCE.get(oldOrigin); + + if (oldRegion != null) { + setAddress(oldRegion, EMPTY_ADDRESS); + } + } + } + void textureRemade() { - CanvasMod.LOG.info("Light texture was remade, new size : " + textureHeight()); + if (DEBUG_LOG_RESIZE) { + CanvasMod.LOG.info("Light texture was remade, new size : " + textureHeight()); + } + requireTextureRemake = false; needUploadPointers = true; needUploadMeta = true; diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 0778537e5..07c562544 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -45,19 +45,16 @@ public static LightRegionAccess allocate(BlockPos regionOrigin) { } public static void reload() { + if (INSTANCE != null) { + INSTANCE.close(); + INSTANCE = null; + } + if (Pipeline.coloredLightsEnabled()) { assert Pipeline.config().coloredLights != null; - if (INSTANCE == null) { - INSTANCE = new LightDataManager(); - } - + INSTANCE = new LightDataManager(); INSTANCE.useOcclusionData = Pipeline.config().coloredLights.useOcclusionData; - } else { - if (INSTANCE != null) { - INSTANCE.close(); - INSTANCE = null; - } } } diff --git a/src/main/java/grondag/canvas/varia/CanvasGlHelper.java b/src/main/java/grondag/canvas/varia/CanvasGlHelper.java index 712a759be..04f5186f2 100644 --- a/src/main/java/grondag/canvas/varia/CanvasGlHelper.java +++ b/src/main/java/grondag/canvas/varia/CanvasGlHelper.java @@ -36,6 +36,7 @@ public class CanvasGlHelper { private static boolean supportsKhrDebug = false; private static boolean supportsArbTextureCubeMapArray = false; private static boolean supportsArbConservativeDepth = false; + private static int maxTextureSize = 0; private static String maxGlVersion = "3.2"; @@ -55,6 +56,10 @@ public static boolean supportsArbConservativeDepth() { return supportsArbConservativeDepth; } + public static int maxTextureSize() { + return maxTextureSize; + } + public static String maxGlVersion() { return maxGlVersion; } @@ -70,6 +75,7 @@ public static void init() { supportsArbTextureCubeMapArray = caps.GL_ARB_texture_cube_map_array; supportsArbConservativeDepth = caps.GL_ARB_conservative_depth; maxGlVersion = maxGlVersion(caps); + maxTextureSize = GFX.getInteger(GFX.GL_MAX_TEXTURE_SIZE); if (Configurator.logMachineInfo) { logMachineInfo(caps); @@ -81,15 +87,12 @@ private static void logMachineInfo(GLCapabilities caps) { final Minecraft client = Minecraft.getInstance(); log.info("================== CANVAS RENDERER DEBUG INFORMATION =================="); - log.info(String.format(" Java: %s %dbit Canvas: %s", System.getProperty("java.version"), client.is64Bit() ? 64 : 32, CanvasMod.versionString)); + log.info(String.format(" Java: %s %dbit Canvas: %s LWJGL: %s", System.getProperty("java.version"), client.is64Bit() ? 64 : 32, CanvasMod.versionString, GLX._getLWJGLVersion())); log.info(String.format(" CPU: %s", GLX._getCpuInfo())); - log.info(String.format(" LWJGL: %s", GLX._getLWJGLVersion())); log.info(String.format(" OpenGL (Reported): %s", GLX.getOpenGLVersionString())); - log.info(String.format(" OpenGL (Available): %s", maxGlVersion)); - log.info(String.format(" glBufferStorage: %s", caps.glBufferStorage == 0 ? "N" : "Y")); - log.info(String.format(" KHR_debug: %s", supportsKhrDebug() ? "Y" : "N")); - log.info(String.format(" ARB_conservative_depth: %s", supportsArbConservativeDepth ? "Y" : "N")); - log.info(String.format(" ARB_texture_cube_map_array: %s", supportsArbTextureCubeMapArray ? "Y" : "N")); + log.info(String.format(" OpenGL (Available): %s Max texture size: %s", maxGlVersion, maxTextureSize)); + log.info(String.format(" glBufferStorage: %s KHR_debug: %s", caps.glBufferStorage == 0 ? "N" : "Y", supportsKhrDebug() ? "Y" : "N")); + log.info(String.format(" ARB_conservative_depth: %s ARB_texture_cube_map_array: %s", supportsArbConservativeDepth ? "Y" : "N", supportsArbTextureCubeMapArray ? "Y" : "N")); log.info(" (This message can be disabled by configuring logMachineInfo = false.)"); log.info("========================================================================"); } From 3d2fdef1cc22220d5898322feee8f25222a69192 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Thu, 28 Dec 2023 12:38:04 +0700 Subject: [PATCH 51/69] Try small optimize and add comments about redundancy, fix concurrency issue Note: simple deque is faster than Set, likely takes more memory --- .../light/color/LightDataAllocator.java | 2 +- .../canvas/light/color/LightDataManager.java | 71 ++++++------------- .../canvas/light/color/LightRegion.java | 55 +++++++------- .../render/world/CanvasWorldRenderer.java | 2 +- 4 files changed, 51 insertions(+), 79 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java index 6de69ef90..c32ee5573 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java +++ b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java @@ -292,6 +292,6 @@ void uploadPointersIfNeeded(LightDataTexture texture) { } String debugString() { - return String.format("Light %s (%d) %d/%d %5.1fMb", Pipeline.config().coloredLights.useOcclusionData ? "LO" : "L", pointerRows, addressCount, dynamicMaxAddresses, dataSize); + return String.format("ColoredLights%s (%d) %d/%d %5.1fMb", Pipeline.config().coloredLights.useOcclusionData ? "+Occlusion" : "", pointerRows, addressCount, dynamicMaxAddresses, dataSize); } } diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 07c562544..ff0e093c4 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -23,7 +23,9 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectMaps; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongPriorityQueue; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.longs.LongSets; import org.lwjgl.system.MemoryUtil; @@ -64,8 +66,9 @@ public static void free(BlockPos regionOrigin) { } } - public static void update(BlockAndTintGetter blockView) { + public static void update(BlockAndTintGetter blockView, Runnable profilerTask) { if (INSTANCE != null) { + profilerTask.run(); INSTANCE.updateInner(blockView); } } @@ -90,8 +93,8 @@ public static String debugString() { final LongSet publicUpdateQueue = LongSets.synchronize(new LongOpenHashSet()); final LongSet publicDrawQueue = LongSets.synchronize(new LongOpenHashSet()); - private final LongSet decreaseQueue = new LongOpenHashSet(); - private final LongSet increaseQueue = new LongOpenHashSet(); + private final LongPriorityQueue decreaseQueue = new LongArrayFIFOQueue(); + private final LongPriorityQueue increaseQueue = new LongArrayFIFOQueue(); private final LightDataAllocator texAllocator; @@ -106,63 +109,29 @@ public LightDataManager() { private void updateInner(BlockAndTintGetter blockView) { synchronized (publicUpdateQueue) { for (long index : publicUpdateQueue) { - decreaseQueue.add(index); - increaseQueue.add(index); + decreaseQueue.enqueue(index); + increaseQueue.enqueue(index); } publicUpdateQueue.clear(); } while (!decreaseQueue.isEmpty()) { - // hopefully faster with native op? - final int queueLen = decreaseQueue.size(); - final long queueIterator = MemoryUtil.nmemAlloc(8L * queueLen); + final long index = decreaseQueue.dequeueLong(); + final LightRegion lightRegion = allocated.get(index); - long i = 0L; - - for (long index : decreaseQueue) { - MemoryUtil.memPutLong(queueIterator + i * 8L, index); - i++; - } - - decreaseQueue.clear(); - - for (i = 0; i < queueLen; i++) { - final long index = MemoryUtil.memGetLong(queueIterator + i * 8L); - final LightRegion lightRegion = allocated.get(index); - - if (lightRegion != null && !lightRegion.isClosed()) { - lightRegion.updateDecrease(blockView, decreaseQueue, increaseQueue); - } + if (lightRegion != null && !lightRegion.isClosed()) { + lightRegion.updateDecrease(blockView, decreaseQueue, increaseQueue); } - - MemoryUtil.nmemFree(queueIterator); } while (!increaseQueue.isEmpty()) { - // hopefully faster with native op? - final int queueLen = increaseQueue.size(); - final long queueIterator = MemoryUtil.nmemAlloc(8L * queueLen); - - long i = 0L; - - for (long index : increaseQueue) { - MemoryUtil.memPutLong(queueIterator + i * 8L, index); - i++; - } - - increaseQueue.clear(); - - for (i = 0; i < queueLen; i++) { - final long index = MemoryUtil.memGetLong(queueIterator + i * 8L); - final LightRegion lightRegion = allocated.get(index); + final long index = increaseQueue.dequeueLong(); + final LightRegion lightRegion = allocated.get(index); - if (lightRegion != null && !lightRegion.isClosed()) { - lightRegion.updateIncrease(blockView, increaseQueue); - } + if (lightRegion != null && !lightRegion.isClosed()) { + lightRegion.updateIncrease(blockView, increaseQueue); } - - MemoryUtil.nmemFree(queueIterator); } if (texAllocator.checkInvalid()) { @@ -176,9 +145,11 @@ private void updateInner(BlockAndTintGetter blockView) { publicDrawQueue.clear(); // redraw - for (var lightRegion : allocated.values()) { - if (!lightRegion.isClosed()) { - drawInner(lightRegion, true); + synchronized (allocated) { + for (var lightRegion : allocated.values()) { + if (!lightRegion.isClosed()) { + drawInner(lightRegion, true); + } } } } else if (!publicDrawQueue.isEmpty()) { diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 7d04e063b..b3bbfcd86 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -23,7 +23,6 @@ import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; import it.unimi.dsi.fastutil.longs.LongPriorityQueue; import it.unimi.dsi.fastutil.longs.LongPriorityQueues; -import it.unimi.dsi.fastutil.longs.LongSet; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -169,12 +168,7 @@ private boolean occludeSide(BlockState state, Side dir, BlockAndTintGetter view, return Shapes.faceShapeOccludes(Shapes.empty(), state.getFaceOcclusionShape(view, pos, dir.vanilla)); } - void updateDecrease(BlockAndTintGetter blockView, LongSet neighborDecreaseQueue, LongSet neighborIncreaseQueue) { - if (needCheckEdges) { - checkEdges(blockView); - needCheckEdges = false; - } - + void updateDecrease(BlockAndTintGetter blockView, LongPriorityQueue neighborDecreaseQueue, LongPriorityQueue neighborIncreaseQueue) { // faster exit when not necessary if (globalDecQueue.isEmpty()) { return; @@ -187,11 +181,9 @@ void updateDecrease(BlockAndTintGetter blockView, LongSet neighborDecreaseQueue, final LightOp.BVec removeFlag = new LightOp.BVec(); final LightOp.BVec removeMask = new LightOp.BVec(); - int decCount = 0; + boolean didUpdate = false; while (!decQueue.isEmpty()) { - decCount++; - final long entry = decQueue.dequeueLong(); final int index = Queues.index(entry); final short sourcePrevLight = Queues.light(entry); @@ -201,6 +193,9 @@ void updateDecrease(BlockAndTintGetter blockView, LongSet neighborDecreaseQueue, final short sourceCurrentLight = lightData.get(index); removeFlag.lessThan(sourceCurrentLight, (short) 0x1110); + // NB: rarely happens, not worth the "if" + // if (!removeFlag.any()) continue; + lightData.reverseIndexify(index, sourcePos); // unused for some reason @@ -238,9 +233,8 @@ void updateDecrease(BlockAndTintGetter blockView, LongSet neighborDecreaseQueue, final int nodeIndex = dataAccess.indexify(nodePos); final short nodeLight = dataAccess.get(nodeIndex); - if (!LightOp.lit(nodeLight)) { - continue; - } + // Important: extremely high frequency redundancy filter (removes 99% of operations) + if (!LightOp.lit(nodeLight)) continue; final BlockState nodeState = blockView.getBlockState(nodePos); @@ -257,11 +251,18 @@ void updateDecrease(BlockAndTintGetter blockView, LongSet neighborDecreaseQueue, if (removeMask.any()) { final short resultLight = LightOp.remove(nodeLight, removeMask); + + // high frequency redundancy when removing next to a different colored light, low otherwise + if (resultLight == nodeLight) continue; + dataAccess.put(nodeIndex, resultLight); Queues.enqueue(decreaseQueue, nodeIndex, nodeLight, side); + // congrats, the queued update was not redundant! + didUpdate = true; + if (isNeighbor) { - neighborDecreaseQueue.add(neighbor.origin); + neighborDecreaseQueue.enqueue(neighbor.origin); } // restore obliterated light source @@ -280,28 +281,29 @@ void updateDecrease(BlockAndTintGetter blockView, LongSet neighborDecreaseQueue, Queues.enqueue(increaseQueue, nodeIndex, repropLight); if (isNeighbor) { - neighborIncreaseQueue.add(neighbor.origin); + neighborIncreaseQueue.enqueue(neighbor.origin); } } } } - if (decCount > 0) { + if (didUpdate) { lightData.markAsDirty(); LightDataManager.INSTANCE.publicDrawQueue.add(origin); } } - void updateIncrease(BlockAndTintGetter blockView, LongSet neighborIncreaseQueue) { + void updateIncrease(BlockAndTintGetter blockView, LongPriorityQueue neighborIncreaseQueue) { + if (needCheckEdges) { + needCheckEdges = false; + checkEdges(blockView); + } + while (!globalIncQueue.isEmpty()) { incQueue.enqueue(globalIncQueue.dequeueLong()); } - int incCount = 0; - while (!incQueue.isEmpty()) { - incCount++; - final long entry = incQueue.dequeueLong(); final int index = Queues.index(entry); final short recordedLight = Queues.light(entry); @@ -380,17 +382,16 @@ void updateIncrease(BlockAndTintGetter blockView, LongSet neighborIncreaseQueue) Queues.enqueue(increaseQueue, nodeIndex, resultLight, side); if (isNeighbor) { - neighborIncreaseQueue.add(neighbor.origin); + neighborIncreaseQueue.enqueue(neighbor.origin); } } } } - if (incCount > 0) { - hasData = true; - lightData.markAsDirty(); - LightDataManager.INSTANCE.publicDrawQueue.add(origin); - } + // If we reached here we should draw + hasData = true; + lightData.markAsDirty(); + LightDataManager.INSTANCE.publicDrawQueue.add(origin); } private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourcePos, BlockPos.MutableBlockPos targetPos, Side side, BlockAndTintGetter blockView) { diff --git a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java index 51537f868..9b853e340 100644 --- a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java +++ b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java @@ -377,7 +377,7 @@ public void renderWorld(PoseStack viewMatrixStack, float tickDelta, long frameSt Lighting.setupLevel(MatrixData.viewMatrix); } - LightDataManager.update(world); + LightDataManager.update(world, () -> WorldRenderDraws.profileSwap(profiler, ProfilerGroup.StartWorld, "colored_lights")); WorldRenderDraws.profileSwap(profiler, ProfilerGroup.StartWorld, "before_entities_event"); From 9543aaae144ef533ac45028f187f9ba8b21b9279 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Thu, 28 Dec 2023 12:40:16 +0700 Subject: [PATCH 52/69] Add colored lights update profiler --- .../canvas/light/color/LightDataManager.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index ff0e093c4..90d188fec 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -33,6 +33,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockAndTintGetter; +import grondag.canvas.CanvasMod; import grondag.canvas.pipeline.Pipeline; public class LightDataManager { @@ -69,7 +70,9 @@ public static void free(BlockPos regionOrigin) { public static void update(BlockAndTintGetter blockView, Runnable profilerTask) { if (INSTANCE != null) { profilerTask.run(); + INSTANCE.profiler.start(); INSTANCE.updateInner(blockView); + INSTANCE.profiler.end(); } } @@ -101,6 +104,7 @@ public static String debugString() { boolean useOcclusionData = false; private LightDataTexture texture; + private DebugProfiler profiler = new ActiveProfiler(); public LightDataManager() { texAllocator = new LightDataAllocator(); allocated.defaultReturnValue(null); @@ -246,4 +250,56 @@ public void close() { allocated.clear(); } } + + private interface DebugProfiler { + void start(); + void end(); + } + + private static class EmptyProfiler implements DebugProfiler { + @Override + public void start() { + } + + @Override + public void end() { + } + } + + private final class ActiveProfiler implements DebugProfiler { + private long minUpdateTimeNanos = Long.MAX_VALUE; + private long maxUpdateTimeNanos = 0; + private long totalUpdateTimeNanos = 0; + private long totalUpdatePerformed = 0; + private long startTimeNanos = 0; + private long startTimeOverall = 0; + private boolean init = false; + + @Override + public void start() { + startTimeNanos = System.nanoTime(); + + if (!init) { + startTimeOverall = startTimeNanos; + init = true; + } + } + + @Override + public void end() { + final long elapsedNanos = System.nanoTime() - startTimeNanos; + minUpdateTimeNanos = Math.min(elapsedNanos, minUpdateTimeNanos); + maxUpdateTimeNanos = Math.max(elapsedNanos, maxUpdateTimeNanos); + totalUpdateTimeNanos += elapsedNanos; + totalUpdatePerformed ++; + + if (System.nanoTime() - startTimeOverall > 10000000000L) { + CanvasMod.LOG.info("Colored Lights profiler output:"); + CanvasMod.LOG.info("min update time: " + ((float) minUpdateTimeNanos / 1000000.0f) + "ms"); + CanvasMod.LOG.info("max update time: " + ((float) maxUpdateTimeNanos / 1000000.0f) + "ms"); + CanvasMod.LOG.info("avg. update time: " + (((float) totalUpdateTimeNanos / (float) totalUpdatePerformed) / 1000000.0f) + "ms (over 10 seconds)"); + LightDataManager.this.profiler = new EmptyProfiler(); + } + } + } } From 927fa3823c861bbcf0622725b53231a8b6a55b6c Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Thu, 28 Dec 2023 14:21:53 +0700 Subject: [PATCH 53/69] Employ urgency system based on vanilla --- .../canvas/light/color/LightDataManager.java | 56 ++++++++++++++++++- .../canvas/light/color/LightRegion.java | 5 ++ .../canvas/light/color/LightRegionAccess.java | 6 ++ .../render/world/CanvasWorldRenderer.java | 2 +- .../canvas/terrain/region/RenderRegion.java | 4 ++ 5 files changed, 69 insertions(+), 4 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 90d188fec..8eb2b90d6 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -33,6 +33,8 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.level.BlockAndTintGetter; +import io.vram.frex.api.config.FlawlessFrames; + import grondag.canvas.CanvasMod; import grondag.canvas.pipeline.Pipeline; @@ -67,11 +69,11 @@ public static void free(BlockPos regionOrigin) { } } - public static void update(BlockAndTintGetter blockView, Runnable profilerTask) { + public static void update(BlockAndTintGetter blockView, long deadlineNanos, Runnable profilerTask) { if (INSTANCE != null) { profilerTask.run(); INSTANCE.profiler.start(); - INSTANCE.updateInner(blockView); + INSTANCE.updateInner(blockView, FlawlessFrames.isActive() ? Long.MAX_VALUE : deadlineNanos); INSTANCE.profiler.end(); } } @@ -99,6 +101,10 @@ public static String debugString() { private final LongPriorityQueue decreaseQueue = new LongArrayFIFOQueue(); private final LongPriorityQueue increaseQueue = new LongArrayFIFOQueue(); + final LongSet publicUrgentUpdateQueue = LongSets.synchronize(new LongOpenHashSet()); + private final LongPriorityQueue urgentDecreaseQueue = new LongArrayFIFOQueue(); + private final LongPriorityQueue urgentIncreaseQueue = new LongArrayFIFOQueue(); + private final LightDataAllocator texAllocator; boolean useOcclusionData = false; @@ -110,7 +116,7 @@ public LightDataManager() { allocated.defaultReturnValue(null); } - private void updateInner(BlockAndTintGetter blockView) { + private void executeRegularUpdates(BlockAndTintGetter blockView, int minimumUpdates, long deadlineNanos) { synchronized (publicUpdateQueue) { for (long index : publicUpdateQueue) { decreaseQueue.enqueue(index); @@ -120,6 +126,8 @@ private void updateInner(BlockAndTintGetter blockView) { publicUpdateQueue.clear(); } + int count = 0; + while (!decreaseQueue.isEmpty()) { final long index = decreaseQueue.dequeueLong(); final LightRegion lightRegion = allocated.get(index); @@ -127,8 +135,14 @@ private void updateInner(BlockAndTintGetter blockView) { if (lightRegion != null && !lightRegion.isClosed()) { lightRegion.updateDecrease(blockView, decreaseQueue, increaseQueue); } + + if (++count > minimumUpdates && System.nanoTime() > deadlineNanos) { + break; + } } + count = 0; + while (!increaseQueue.isEmpty()) { final long index = increaseQueue.dequeueLong(); final LightRegion lightRegion = allocated.get(index); @@ -136,6 +150,42 @@ private void updateInner(BlockAndTintGetter blockView) { if (lightRegion != null && !lightRegion.isClosed()) { lightRegion.updateIncrease(blockView, increaseQueue); } + + if (++count > minimumUpdates && System.nanoTime() > deadlineNanos) { + break; + } + } + } + + private void updateInner(BlockAndTintGetter blockView, long deadlineNanos) { + synchronized (publicUrgentUpdateQueue) { + for (long index : publicUrgentUpdateQueue) { + urgentDecreaseQueue.enqueue(index); + urgentIncreaseQueue.enqueue(index); + publicUpdateQueue.remove(index); + } + + publicUrgentUpdateQueue.clear(); + } + + while (!urgentDecreaseQueue.isEmpty()) { + final long index = urgentDecreaseQueue.dequeueLong(); + final LightRegion lightRegion = allocated.get(index); + + if (lightRegion != null && !lightRegion.isClosed()) { + lightRegion.updateDecrease(blockView, urgentDecreaseQueue, urgentIncreaseQueue); + } + } + + executeRegularUpdates(blockView, 7, deadlineNanos); + + while (!urgentIncreaseQueue.isEmpty()) { + final long index = urgentIncreaseQueue.dequeueLong(); + final LightRegion lightRegion = allocated.get(index); + + if (lightRegion != null && !lightRegion.isClosed()) { + lightRegion.updateIncrease(blockView, urgentIncreaseQueue); + } } if (texAllocator.checkInvalid()) { diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index b3bbfcd86..dc20ac0ec 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -158,6 +158,11 @@ public void submitChecks() { } } + @Override + public void markUrgent() { + LightDataManager.INSTANCE.publicUrgentUpdateQueue.add(origin); + } + private boolean occludeSide(BlockState state, Side dir, BlockAndTintGetter view, BlockPos pos) { // vanilla checks state.useShapeForLightOcclusion() but here it's always false for some reason. this is fine... diff --git a/src/main/java/grondag/canvas/light/color/LightRegionAccess.java b/src/main/java/grondag/canvas/light/color/LightRegionAccess.java index 1a3c74367..8e84cf181 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegionAccess.java +++ b/src/main/java/grondag/canvas/light/color/LightRegionAccess.java @@ -30,6 +30,8 @@ public interface LightRegionAccess { void submitChecks(); + void markUrgent(); + boolean isClosed(); class Empty implements LightRegionAccess { @@ -41,6 +43,10 @@ public void checkBlock(BlockPos pos, BlockState blockState) { public void submitChecks() { } + @Override + public void markUrgent() { + } + @Override public boolean isClosed() { return true; diff --git a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java index 9b853e340..f15c26554 100644 --- a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java +++ b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java @@ -377,7 +377,7 @@ public void renderWorld(PoseStack viewMatrixStack, float tickDelta, long frameSt Lighting.setupLevel(MatrixData.viewMatrix); } - LightDataManager.update(world, () -> WorldRenderDraws.profileSwap(profiler, ProfilerGroup.StartWorld, "colored_lights")); + LightDataManager.update(world, System.nanoTime() + 2000000, () -> WorldRenderDraws.profileSwap(profiler, ProfilerGroup.StartWorld, "colored_lights")); WorldRenderDraws.profileSwap(profiler, ProfilerGroup.StartWorld, "before_entities_event"); diff --git a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java index 5cee26030..1087079a0 100644 --- a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java +++ b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java @@ -189,6 +189,10 @@ public void markForBuild(boolean isImportant) { final boolean neededRebuild = needsRebuild; needsRebuild = true; needsImportantRebuild = isImportant | (neededRebuild && needsImportantRebuild); + + if (needsImportantRebuild && !lightRegion.isClosed()) { + lightRegion.markUrgent(); + } } /** From e7a29aa97ddc342bf239d4a6d32d561258f38066 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Thu, 28 Dec 2023 15:20:13 +0700 Subject: [PATCH 54/69] Add default light color resources --- .../assets/minecraft/lights/block/blast_furnace.json | 8 ++++++++ .../assets/minecraft/lights/block/campfire.json | 8 ++++++++ .../assets/minecraft/lights/block/cave_vines.json | 8 ++++++++ .../assets/minecraft/lights/block/cave_vines_plant.json | 8 ++++++++ .../minecraft/lights/block/deepslate_redstone_ore.json | 7 +++++++ .../assets/minecraft/lights/block/enchanting_table.json | 8 ++++++++ .../assets/minecraft/lights/block/end_gateway.json | 7 +++++++ .../assets/minecraft/lights/block/end_portal.json | 7 +++++++ .../assets/minecraft/lights/block/fire.json | 8 ++++++++ .../assets/minecraft/lights/block/furnace.json | 8 ++++++++ .../assets/minecraft/lights/block/glowstone.json | 7 +++++++ .../assets/minecraft/lights/block/jack_o_lantern.json | 8 ++++++++ .../assets/minecraft/lights/block/lantern.json | 7 +++++++ .../assets/minecraft/lights/block/lava_cauldron.json | 8 ++++++++ .../assets/minecraft/lights/block/light.json | 8 ++++++++ .../assets/minecraft/lights/block/magma_block.json | 8 ++++++++ .../assets/minecraft/lights/block/nether_portal.json | 7 +++++++ .../assets/minecraft/lights/block/ochre_froglight.json | 7 +++++++ .../minecraft/lights/block/pearlescent_froglight.json | 7 +++++++ .../assets/minecraft/lights/block/redstone_ore.json | 7 +++++++ .../assets/minecraft/lights/block/redstone_torch.json | 7 +++++++ .../assets/minecraft/lights/block/sea_lantern.json | 8 ++++++++ .../assets/minecraft/lights/block/sea_pickle.json | 8 ++++++++ .../assets/minecraft/lights/block/shroomlight.json | 8 ++++++++ .../assets/minecraft/lights/block/smoker.json | 8 ++++++++ .../assets/minecraft/lights/block/soul_fire.json | 8 ++++++++ .../assets/minecraft/lights/block/soul_lantern.json | 8 ++++++++ .../assets/minecraft/lights/block/soul_torch.json | 8 ++++++++ .../assets/minecraft/lights/block/torch.json | 8 ++++++++ .../assets/minecraft/lights/block/verdant_froglight.json | 7 +++++++ .../assets/minecraft/lights/fluid/flowing_lava.json | 8 ++++++++ .../assets/minecraft/lights/fluid/lava.json | 8 ++++++++ 32 files changed, 245 insertions(+) create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/blast_furnace.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/campfire.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/cave_vines.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/cave_vines_plant.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/deepslate_redstone_ore.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/enchanting_table.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/end_gateway.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/end_portal.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/fire.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/furnace.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/glowstone.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/jack_o_lantern.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/lantern.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/lava_cauldron.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/light.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/magma_block.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/nether_portal.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/ochre_froglight.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/pearlescent_froglight.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/redstone_ore.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/redstone_torch.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/sea_lantern.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/sea_pickle.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/shroomlight.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/smoker.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/soul_fire.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/soul_lantern.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/soul_torch.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/torch.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/verdant_froglight.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/fluid/flowing_lava.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/fluid/lava.json diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/blast_furnace.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/blast_furnace.json new file mode 100644 index 000000000..99cbe9ba9 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/blast_furnace.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/campfire.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/campfire.json new file mode 100644 index 000000000..99cbe9ba9 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/campfire.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/cave_vines.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/cave_vines.json new file mode 100644 index 000000000..99cbe9ba9 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/cave_vines.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/cave_vines_plant.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/cave_vines_plant.json new file mode 100644 index 000000000..99cbe9ba9 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/cave_vines_plant.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/deepslate_redstone_ore.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/deepslate_redstone_ore.json new file mode 100644 index 000000000..2de115850 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/deepslate_redstone_ore.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 0.3, + "blue": 0.3 + } +} diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/enchanting_table.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/enchanting_table.json new file mode 100644 index 000000000..7f7d19652 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/enchanting_table.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 0.8, + "green": 1.0, + "blue": 1.0 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/end_gateway.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/end_gateway.json new file mode 100644 index 000000000..ac49c2abe --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/end_gateway.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 0.6, + "blue": 1.0 + } +} diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/end_portal.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/end_portal.json new file mode 100644 index 000000000..ac49c2abe --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/end_portal.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 0.6, + "blue": 1.0 + } +} diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/fire.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/fire.json new file mode 100644 index 000000000..99cbe9ba9 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/fire.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/furnace.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/furnace.json new file mode 100644 index 000000000..99cbe9ba9 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/furnace.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/glowstone.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/glowstone.json new file mode 100644 index 000000000..90c551d76 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/glowstone.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/jack_o_lantern.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/jack_o_lantern.json new file mode 100644 index 000000000..99cbe9ba9 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/jack_o_lantern.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/lantern.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/lantern.json new file mode 100644 index 000000000..90c551d76 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/lantern.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/lava_cauldron.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/lava_cauldron.json new file mode 100644 index 000000000..99cbe9ba9 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/lava_cauldron.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/light.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/light.json new file mode 100644 index 000000000..99cbe9ba9 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/light.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/magma_block.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/magma_block.json new file mode 100644 index 000000000..97a63a40d --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/magma_block.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 0.8, + "blue": 0.3 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/nether_portal.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/nether_portal.json new file mode 100644 index 000000000..ac49c2abe --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/nether_portal.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 0.6, + "blue": 1.0 + } +} diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/ochre_froglight.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/ochre_froglight.json new file mode 100644 index 000000000..e75c61089 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/ochre_froglight.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.6 + } +} diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/pearlescent_froglight.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/pearlescent_froglight.json new file mode 100644 index 000000000..c96316349 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/pearlescent_froglight.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 0.6, + "blue": 0.9 + } +} diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/redstone_ore.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/redstone_ore.json new file mode 100644 index 000000000..2de115850 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/redstone_ore.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 0.3, + "blue": 0.3 + } +} diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/redstone_torch.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/redstone_torch.json new file mode 100644 index 000000000..2de115850 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/redstone_torch.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 0.3, + "blue": 0.3 + } +} diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/sea_lantern.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/sea_lantern.json new file mode 100644 index 000000000..7f7d19652 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/sea_lantern.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 0.8, + "green": 1.0, + "blue": 1.0 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/sea_pickle.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/sea_pickle.json new file mode 100644 index 000000000..7f7d19652 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/sea_pickle.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 0.8, + "green": 1.0, + "blue": 1.0 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/shroomlight.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/shroomlight.json new file mode 100644 index 000000000..99cbe9ba9 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/shroomlight.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/smoker.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/smoker.json new file mode 100644 index 000000000..99cbe9ba9 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/smoker.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/soul_fire.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/soul_fire.json new file mode 100644 index 000000000..f56044686 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/soul_fire.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 0.6, + "green": 0.8, + "blue": 1.0 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/soul_lantern.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/soul_lantern.json new file mode 100644 index 000000000..f56044686 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/soul_lantern.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 0.6, + "green": 0.8, + "blue": 1.0 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/soul_torch.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/soul_torch.json new file mode 100644 index 000000000..f56044686 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/soul_torch.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 0.6, + "green": 0.8, + "blue": 1.0 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/torch.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/torch.json new file mode 100644 index 000000000..99cbe9ba9 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/torch.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/verdant_froglight.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/verdant_froglight.json new file mode 100644 index 000000000..0a14c0503 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/block/verdant_froglight.json @@ -0,0 +1,7 @@ +{ + "defaultLight": { + "red": 0.6, + "green": 1.0, + "blue": 0.7 + } +} diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/fluid/flowing_lava.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/fluid/flowing_lava.json new file mode 100644 index 000000000..99cbe9ba9 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/fluid/flowing_lava.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/fluid/lava.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/fluid/lava.json new file mode 100644 index 000000000..99cbe9ba9 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/fluid/lava.json @@ -0,0 +1,8 @@ +{ + "defaultLight": { + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} + From 453c8b3cbaa05ebc2786ebfacaa8d85f3483a289 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Fri, 29 Dec 2023 06:47:16 +0700 Subject: [PATCH 55/69] Critical: Fix false pointer clearance --- .../light/color/LightDataAllocator.java | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java index c32ee5573..fb02c2932 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java +++ b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java @@ -155,7 +155,7 @@ private void increaseAddressSize(int newSize) { int allocateAddress(LightRegion region) { if (!region.hasData) { - return setAddress(region, EMPTY_ADDRESS); + return clearAddress(region); } short regionAddress = region.texAllocation; @@ -200,13 +200,21 @@ private short allocateAddressInner(LightRegion region) { return setAddress(region, newAddress); } + private short clearAddress(LightRegion region) { + if (region.texAllocation != EMPTY_ADDRESS) { + clearPointerIfWas(region.originPos, region.texAllocation); + region.texAllocation = EMPTY_ADDRESS; + } + + return EMPTY_ADDRESS; + } + private short setAddress(LightRegion region, short newAddress) { + assert newAddress != EMPTY_ADDRESS; + region.texAllocation = newAddress; setPointer(region.originPos, newAddress); - - if (newAddress != EMPTY_ADDRESS) { - allocatedAddresses.put(newAddress, region.origin); - } + allocatedAddresses.put(newAddress, region.origin); return newAddress; } @@ -222,12 +230,26 @@ private void setPointer(BlockPos regionOrigin, short regionAddress) { needUploadPointers |= storedAddress != regionAddress; } + private void clearPointerIfWas(BlockPos regionOrigin, short oldAddress) { + final int xInExtent = ((regionOrigin.getX() / 16) % pointerExtent + pointerExtent) % pointerExtent; + final int yInExtent = ((regionOrigin.getY() / 16) % pointerExtent + pointerExtent) % pointerExtent; + final int zInExtent = ((regionOrigin.getZ() / 16) % pointerExtent + pointerExtent) % pointerExtent; + final int pointerIndex = xInExtent * pointerExtent * pointerExtent + yInExtent * pointerExtent + zInExtent; + final int bufferIndex = pointerIndex * 2; + final short storedAddress = pointerBuffer.getShort(bufferIndex); + + if (storedAddress == oldAddress) { + pointerBuffer.putShort(bufferIndex, EMPTY_ADDRESS); + needUploadPointers = true; + } + } + void freeAddress(LightRegion region) { if (region.texAllocation != EMPTY_ADDRESS) { final short oldAddress = region.texAllocation; freedAddresses.push(oldAddress); allocatedAddresses.remove(oldAddress); - setAddress(region, EMPTY_ADDRESS); + clearAddress(region); } } @@ -239,7 +261,7 @@ void removeAddressIfAllocated(short addressToRemove) { final LightRegion oldRegion = LightDataManager.INSTANCE.get(oldOrigin); if (oldRegion != null) { - setAddress(oldRegion, EMPTY_ADDRESS); + clearAddress(oldRegion); } } } From 462c0591281f2f58694632cd0627908f9124c10f Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Fri, 29 Dec 2023 07:04:15 +0700 Subject: [PATCH 56/69] Try reduce pointer space overlap with more extent padding --- .../light/color/LightDataAllocator.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java index fb02c2932..f461cf9dd 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java +++ b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java @@ -47,6 +47,9 @@ public class LightDataAllocator { // also represents row size of pointer header because we are putting addresses in the data texture. private static final int ROW_SIZE = LightRegionData.Const.SIZE3D; + // padding to prevent overlap. set to minimum achievable without error + private static final int EXTENT_PADDING = 2; + // address for the static empty region. static final short EMPTY_ADDRESS = 0; @@ -200,6 +203,7 @@ private short allocateAddressInner(LightRegion region) { return setAddress(region, newAddress); } + // MAINTENANCE NOTICE: this function is a special casing of setAddress(LightRegion, short) private short clearAddress(LightRegion region) { if (region.texAllocation != EMPTY_ADDRESS) { clearPointerIfWas(region.originPos, region.texAllocation); @@ -219,23 +223,24 @@ private short setAddress(LightRegion region, short newAddress) { return newAddress; } - private void setPointer(BlockPos regionOrigin, short regionAddress) { + private int getBufferIndex(BlockPos regionOrigin) { final int xInExtent = ((regionOrigin.getX() / 16) % pointerExtent + pointerExtent) % pointerExtent; final int yInExtent = ((regionOrigin.getY() / 16) % pointerExtent + pointerExtent) % pointerExtent; final int zInExtent = ((regionOrigin.getZ() / 16) % pointerExtent + pointerExtent) % pointerExtent; final int pointerIndex = xInExtent * pointerExtent * pointerExtent + yInExtent * pointerExtent + zInExtent; - final int bufferIndex = pointerIndex * 2; + return pointerIndex * 2; + } + + private void setPointer(BlockPos regionOrigin, short regionAddress) { + final int bufferIndex = getBufferIndex(regionOrigin); final short storedAddress = pointerBuffer.getShort(bufferIndex); pointerBuffer.putShort(bufferIndex, regionAddress); needUploadPointers |= storedAddress != regionAddress; } + // MAINTENANCE NOTICE: this function is a special casing of setPointer(BlockPos, short) private void clearPointerIfWas(BlockPos regionOrigin, short oldAddress) { - final int xInExtent = ((regionOrigin.getX() / 16) % pointerExtent + pointerExtent) % pointerExtent; - final int yInExtent = ((regionOrigin.getY() / 16) % pointerExtent + pointerExtent) % pointerExtent; - final int zInExtent = ((regionOrigin.getZ() / 16) % pointerExtent + pointerExtent) % pointerExtent; - final int pointerIndex = xInExtent * pointerExtent * pointerExtent + yInExtent * pointerExtent + zInExtent; - final int bufferIndex = pointerIndex * 2; + final int bufferIndex = getBufferIndex(regionOrigin); final short storedAddress = pointerBuffer.getShort(bufferIndex); if (storedAddress == oldAddress) { @@ -283,7 +288,7 @@ public int dataRowStart() { public boolean checkInvalid() { final var viewDistance = Minecraft.getInstance().options.renderDistance().get(); - final var expectedExtent = (viewDistance + 1) * 2; + final var expectedExtent = (viewDistance + EXTENT_PADDING) * 2; if (pointerExtent < expectedExtent) { resizePointerBuffer(expectedExtent); From 606765f691b77cd923384b4ef305c23c0f173cc0 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Fri, 29 Dec 2023 12:50:59 +0700 Subject: [PATCH 57/69] Robust reload on pipeline state change - Revert LightDataManager.reload() change from 2 days ago --- .../grondag/canvas/apiimpl/CanvasState.java | 81 ++++++++++++++----- .../canvas/config/CanvasConfigScreen.java | 2 +- .../grondag/canvas/config/ConfigManager.java | 6 +- .../canvas/config/PipelineOptionScreen.java | 10 +-- .../canvas/light/color/LightDataManager.java | 19 +++-- .../render/world/CanvasWorldRenderer.java | 2 +- .../canvas/shader/data/AccessibilityData.java | 2 +- .../resources/assets/canvas/lang/en_us.json | 3 +- 8 files changed, 83 insertions(+), 42 deletions(-) diff --git a/src/main/java/grondag/canvas/apiimpl/CanvasState.java b/src/main/java/grondag/canvas/apiimpl/CanvasState.java index 9888d85fd..534add67c 100644 --- a/src/main/java/grondag/canvas/apiimpl/CanvasState.java +++ b/src/main/java/grondag/canvas/apiimpl/CanvasState.java @@ -34,6 +34,7 @@ import grondag.canvas.material.property.TextureMaterialState; import grondag.canvas.perf.ChunkRebuildCounters; import grondag.canvas.perf.Timekeeper; +import grondag.canvas.pipeline.Pipeline; import grondag.canvas.pipeline.PipelineManager; import grondag.canvas.pipeline.config.PipelineLoader; import grondag.canvas.shader.GlMaterialProgramManager; @@ -46,27 +47,69 @@ import grondag.canvas.terrain.util.ChunkColorCache; public class CanvasState { - public static void recompileIfNeeded(boolean forceRecompile) { - while (CanvasMod.RECOMPILE.consumeClick()) { - forceRecompile = true; + public static void handleRecompileKeybind() { + final boolean recompilePressed = CanvasMod.RECOMPILE.consumeClick(); + + while (true) { + // consume all clicks + if (!CanvasMod.RECOMPILE.consumeClick()) { + break; + } + } + + if (recompilePressed) { + recompile(false); + } + } + + public static void recompile() { + recompile(false); + } + + private static int loopCounter = 0; + + private static boolean recompilePipeline() { + final boolean prevColorLightsState = Pipeline.coloredLightsEnabled(); + final boolean prevAdvancedCullingState = Pipeline.advancedTerrainCulling(); + + PipelineLoader.reload(Minecraft.getInstance().getResourceManager()); + PipelineManager.reload(); + + final boolean coloredLightsChanged = Pipeline.coloredLightsEnabled() && !prevColorLightsState; + final boolean requireCullingRebuild = Pipeline.advancedTerrainCulling() && !prevAdvancedCullingState; + + return coloredLightsChanged || requireCullingRebuild; + } + + private static void recompile(boolean alreadyReloaded) { + CanvasMod.LOG.info(I18n.get("info.canvas.recompile")); + + final boolean requireReload = recompilePipeline(); + + if (!alreadyReloaded && loopCounter < 2 && requireReload) { + CanvasMod.LOG.info(I18n.get("info.canvas.recompile_needs_reload")); + loopCounter++; + Minecraft.getInstance().levelRenderer.allChanged(); + return; } - if (forceRecompile) { - CanvasMod.LOG.info(I18n.get("info.canvas.recompile")); - PipelineLoader.reload(Minecraft.getInstance().getResourceManager()); - PipelineManager.reload(); - LightDataManager.reload(); - PreReleaseShaderCompat.reload(); - MaterialProgram.reload(); - GlShaderManager.INSTANCE.reload(); - GlProgramManager.INSTANCE.reload(); - GlMaterialProgramManager.INSTANCE.reload(); - // LightmapHdTexture.reload(); - // LightmapHd.reload(); - TextureMaterialState.reload(); - ShaderDataManager.reload(); - Timekeeper.configOrPipelineReload(); + LightDataManager.reload(); + PreReleaseShaderCompat.reload(); + MaterialProgram.reload(); + GlShaderManager.INSTANCE.reload(); + GlProgramManager.INSTANCE.reload(); + GlMaterialProgramManager.INSTANCE.reload(); + // LightmapHdTexture.reload(); + // LightmapHd.reload(); + TextureMaterialState.reload(); + ShaderDataManager.reload(); + Timekeeper.configOrPipelineReload(); + + if (loopCounter > 1) { + CanvasMod.LOG.warn("Reloading recursively twice or more. This isn't supposed to happen."); } + + loopCounter = 0; } public static void reload() { @@ -79,6 +122,6 @@ public static void reload() { ChunkColorCache.invalidate(); AoFace.clampExteriorVertices(Configurator.clampExteriorVertices); - recompileIfNeeded(true); + recompile(true); } } diff --git a/src/main/java/grondag/canvas/config/CanvasConfigScreen.java b/src/main/java/grondag/canvas/config/CanvasConfigScreen.java index 4a1899afe..d5e523acd 100644 --- a/src/main/java/grondag/canvas/config/CanvasConfigScreen.java +++ b/src/main/java/grondag/canvas/config/CanvasConfigScreen.java @@ -528,7 +528,7 @@ private void save() { Timekeeper.configOrPipelineReload(); } - ConfigManager.saveUserInput(reload ? RELOAD_EVERYTHING : DONT_RELOAD); + ConfigManager.saveCanvasConfig(reload ? RELOAD_EVERYTHING : DONT_RELOAD); if (requiresRestart) { this.minecraft.setScreen(new ConfigRestartScreen(this.parent)); diff --git a/src/main/java/grondag/canvas/config/ConfigManager.java b/src/main/java/grondag/canvas/config/ConfigManager.java index e2a4755a4..bb85107fb 100644 --- a/src/main/java/grondag/canvas/config/ConfigManager.java +++ b/src/main/java/grondag/canvas/config/ConfigManager.java @@ -120,7 +120,7 @@ public static void savePipelineOptions(OptionConfig[] options) { CanvasMod.LOG.error("Error loading pipeline config. Using default values."); } - CanvasState.recompileIfNeeded(true); + CanvasState.recompile(); } private static void saveConfig() { @@ -163,12 +163,12 @@ static void loadConfig() { Configurator.readFromConfig(config, true); } - static void saveUserInput(Reload reload) { + static void saveCanvasConfig(Reload reload) { saveConfig(); switch (reload) { case RELOAD_EVERYTHING -> Minecraft.getInstance().levelRenderer.allChanged(); - case RELOAD_PIPELINE -> CanvasState.recompileIfNeeded(true); + case RELOAD_PIPELINE -> CanvasState.recompile(); case DONT_RELOAD -> { } } } diff --git a/src/main/java/grondag/canvas/config/PipelineOptionScreen.java b/src/main/java/grondag/canvas/config/PipelineOptionScreen.java index b5c108c3c..43332edc2 100644 --- a/src/main/java/grondag/canvas/config/PipelineOptionScreen.java +++ b/src/main/java/grondag/canvas/config/PipelineOptionScreen.java @@ -20,7 +20,6 @@ package grondag.canvas.config; -import static grondag.canvas.config.ConfigManager.Reload.RELOAD_EVERYTHING; import static grondag.canvas.config.ConfigManager.Reload.RELOAD_PIPELINE; import java.util.List; @@ -40,7 +39,6 @@ import grondag.canvas.config.gui.BaseButton; import grondag.canvas.config.gui.BaseScreen; import grondag.canvas.config.gui.ListWidget; -import grondag.canvas.pipeline.Pipeline; import grondag.canvas.pipeline.config.PipelineConfig; import grondag.canvas.pipeline.config.PipelineConfigBuilder; import grondag.canvas.pipeline.config.PipelineLoader; @@ -118,14 +116,8 @@ protected void init() { } private void savePipelineSelection(ResourceLocation newPipelineId) { - final var newPipeline = PipelineLoader.get(newPipelineId.toString()); - - boolean shadowsChanged = Pipeline.shadowsEnabled() != newPipeline.shadowsEnabled; - boolean coloredLightsChanged = Pipeline.coloredLightsEnabled() != newPipeline.coloredLightsEnabled; - boolean needRegionsReloaded = (shadowsChanged && !Configurator.advancedTerrainCulling) || coloredLightsChanged; - Configurator.pipelineId = newPipelineId.toString(); - ConfigManager.saveUserInput(needRegionsReloaded ? RELOAD_EVERYTHING : RELOAD_PIPELINE); + ConfigManager.saveCanvasConfig(RELOAD_PIPELINE); } private void save() { diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 8eb2b90d6..268cf1d75 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -50,16 +50,21 @@ public static LightRegionAccess allocate(BlockPos regionOrigin) { } public static void reload() { - if (INSTANCE != null) { - INSTANCE.close(); - INSTANCE = null; - } - if (Pipeline.coloredLightsEnabled()) { assert Pipeline.config().coloredLights != null; - INSTANCE = new LightDataManager(); + // NB: this is a shader recompile, not chunk storage reload. DON'T destroy existing instance. + if (INSTANCE == null) { + INSTANCE = new LightDataManager(); + } + INSTANCE.useOcclusionData = Pipeline.config().coloredLights.useOcclusionData; + } else { + // If colored lights state change, we can clean this up as the next state change will trigger chunk storage reload. + if (INSTANCE != null) { + INSTANCE.close(); + INSTANCE = null; + } } } @@ -341,7 +346,7 @@ public void end() { minUpdateTimeNanos = Math.min(elapsedNanos, minUpdateTimeNanos); maxUpdateTimeNanos = Math.max(elapsedNanos, maxUpdateTimeNanos); totalUpdateTimeNanos += elapsedNanos; - totalUpdatePerformed ++; + totalUpdatePerformed++; if (System.nanoTime() - startTimeOverall > 10000000000L) { CanvasMod.LOG.info("Colored Lights profiler output:"); diff --git a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java index f15c26554..a4aad2813 100644 --- a/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java +++ b/src/main/java/grondag/canvas/render/world/CanvasWorldRenderer.java @@ -851,7 +851,7 @@ public void renderLevel(PoseStack viewMatrixStack, float tickDelta, long frameSt BufferSynchronizer.checkPoint(); DirectBufferAllocator.update(); TransferBuffers.update(); - CanvasState.recompileIfNeeded(false); + CanvasState.handleRecompileKeybind(); FlawlessFramesController.handleToggle(); if (wasFabulous != Pipeline.isFabulous()) { diff --git a/src/main/java/grondag/canvas/shader/data/AccessibilityData.java b/src/main/java/grondag/canvas/shader/data/AccessibilityData.java index 87c592916..f95285cce 100644 --- a/src/main/java/grondag/canvas/shader/data/AccessibilityData.java +++ b/src/main/java/grondag/canvas/shader/data/AccessibilityData.java @@ -41,7 +41,7 @@ public class AccessibilityData { public static void onCloseOptionScreen() { if (AccessibilityData.checkChanged() && Minecraft.getInstance().level != null) { - CanvasState.recompileIfNeeded(true); + CanvasState.recompile(); } } diff --git a/src/main/resources/assets/canvas/lang/en_us.json b/src/main/resources/assets/canvas/lang/en_us.json index 47a2a6856..2d61bab03 100644 --- a/src/main/resources/assets/canvas/lang/en_us.json +++ b/src/main/resources/assets/canvas/lang/en_us.json @@ -204,5 +204,6 @@ "config.canvas.help.trace_texture_load": "Log significant events of texture/sprite atlas loading.;For debugging use. Will spam the log.", "config.canvas.value.bloom_toggle": "Enable Bloom", "config.canvas.help.bloom_toggle": "Renders glow effect around light sources.;Modest impact on performance.", - "info.canvas.recompile": "Recompiling shaders" + "info.canvas.recompile": "Recompiling shaders", + "info.canvas.recompile_needs_reload": "Found a state change that requires reload during recompilation. Reloading renderer..." } From 413764d2edfee163606b8b35d3a538e48ae8c25e Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Fri, 29 Dec 2023 14:16:14 +0700 Subject: [PATCH 58/69] Mark light region urgency differently, better result --- .../java/grondag/canvas/light/color/LightRegion.java | 12 ++++++++++-- .../grondag/canvas/terrain/region/RenderRegion.java | 8 ++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index dc20ac0ec..774f0481f 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -151,16 +151,24 @@ public void checkBlock(BlockPos pos, BlockState blockState) { } } + private boolean urgent = false; + @Override public void submitChecks() { if (!globalDecQueue.isEmpty() || !globalIncQueue.isEmpty()) { - LightDataManager.INSTANCE.publicUpdateQueue.add(origin); + if (urgent) { + LightDataManager.INSTANCE.publicUrgentUpdateQueue.add(origin); + } else { + LightDataManager.INSTANCE.publicUpdateQueue.add(origin); + } } + + urgent = false; } @Override public void markUrgent() { - LightDataManager.INSTANCE.publicUrgentUpdateQueue.add(origin); + urgent = true; } private boolean occludeSide(BlockState state, Side dir, BlockAndTintGetter view, BlockPos pos) { diff --git a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java index 1087079a0..d73074d70 100644 --- a/src/main/java/grondag/canvas/terrain/region/RenderRegion.java +++ b/src/main/java/grondag/canvas/terrain/region/RenderRegion.java @@ -189,10 +189,6 @@ public void markForBuild(boolean isImportant) { final boolean neededRebuild = needsRebuild; needsRebuild = true; needsImportantRebuild = isImportant | (neededRebuild && needsImportantRebuild); - - if (needsImportantRebuild && !lightRegion.isClosed()) { - lightRegion.markUrgent(); - } } /** @@ -547,6 +543,10 @@ public void rebuildOnMainThread() { final RegionBuildState newBuildState = captureAndSetBuildState(context, origin.isNear()); context.encoder.updateSector(renderSector, origin); + if (!lightRegion.isClosed()) { + lightRegion.markUrgent(); + } + buildTerrain(context, newBuildState); if (ChunkRebuildCounters.ENABLED) { From f2f338c7baa78092ca5bba725c9c666682436b9a Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sun, 31 Dec 2023 09:22:14 +0700 Subject: [PATCH 59/69] Fix 1-tick emitter replacement (fire to nether portal) - Shorten profiler output --- .../canvas/light/color/LightDataManager.java | 10 ++++++---- .../canvas/light/color/LightRegion.java | 18 ++++++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 268cf1d75..4009aa2b8 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -28,6 +28,7 @@ import it.unimi.dsi.fastutil.longs.LongPriorityQueue; import it.unimi.dsi.fastutil.longs.LongSet; import it.unimi.dsi.fastutil.longs.LongSets; +import org.apache.logging.log4j.Level; import org.lwjgl.system.MemoryUtil; import net.minecraft.core.BlockPos; @@ -349,10 +350,11 @@ public void end() { totalUpdatePerformed++; if (System.nanoTime() - startTimeOverall > 10000000000L) { - CanvasMod.LOG.info("Colored Lights profiler output:"); - CanvasMod.LOG.info("min update time: " + ((float) minUpdateTimeNanos / 1000000.0f) + "ms"); - CanvasMod.LOG.info("max update time: " + ((float) maxUpdateTimeNanos / 1000000.0f) + "ms"); - CanvasMod.LOG.info("avg. update time: " + (((float) totalUpdateTimeNanos / (float) totalUpdatePerformed) / 1000000.0f) + "ms (over 10 seconds)"); + final float minTime = (float) minUpdateTimeNanos / 1000000.0f; + final float maxTime = (float) maxUpdateTimeNanos / 1000000.0f; + final float avgTime = ((float) totalUpdateTimeNanos / (float) totalUpdatePerformed) / 1000000.0f; + // final String score = avgTime <= 2.5 ? "GOOD" : (avgTime <= 6.0 ? "OKAY" : "BAD"); + CanvasMod.LOG.printf(Level.INFO, "Colored Lights frame time statistics: min %.2G ms; max %.2f ms; avg. %.2f ms; over 10 seconds (%d frames)", minTime, maxTime, avgTime, totalUpdatePerformed); LightDataManager.this.profiler = new EmptyProfiler(); } } diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 774f0481f..f0ea6668b 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -136,15 +136,25 @@ public void checkBlock(BlockPos pos, BlockState blockState) { final boolean occluding = blockState.canOcclude(); final boolean emitting = LightOp.emitter(registeredLight); - if ((emitting && getLight != registeredLight) || LightOp.emitter(getLight) || LightOp.occluder(getLight) != occluding || (LightOp.lit(getLight) && occluding)) { - final short combined = emitting ? LightOp.max(registeredLight, getLight) : registeredLight; - lightData.put(index, combined); + // Equivalent of "hard reset" on this particular block pos, so we want it to pass specific checks + final boolean replaceEmitter = emitting && getLight != registeredLight; + final boolean removeEmitter = LightOp.emitter(getLight); + final boolean replaceOccluder = LightOp.occluder(getLight) != occluding; + final boolean occludeLitBlock = LightOp.lit(getLight) && occluding; + + if (replaceEmitter || removeEmitter || replaceOccluder || occludeLitBlock) { + // If emitter, light with be placed later so put 0 here for removal purpose. Otherwise, put occlusion data here + lightData.put(index, emitting ? (short) 0 : registeredLight); + + // Removal queue, since we're doing "hard reset" Queues.enqueue(globalDecQueue, index, getLight); + // Emission queue if (emitting) { - Queues.enqueue(globalIncQueue, index, combined); + Queues.enqueue(globalIncQueue, index, registeredLight); } + // At this point, no light data yet, but we might have occlusion data if (LightDataManager.INSTANCE.useOcclusionData) { hasData = true; } From eb009dd979710ec43b35a4d9da249b3812c4eda5 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Thu, 4 Jan 2024 17:06:33 +0700 Subject: [PATCH 60/69] Disable colored light filtering "integrity check" by default --- .../canvas/config/CanvasConfigScreen.java | 6 ++ .../grondag/canvas/config/ConfigData.java | 2 + .../grondag/canvas/config/Configurator.java | 3 + .../java/grondag/canvas/shader/GlShader.java | 4 + .../resources/assets/canvas/lang/en_us.json | 3 + .../assets/frex/shaders/api/header.glsl | 1 + .../assets/frex/shaders/api/light.glsl | 99 +++++++++++-------- 7 files changed, 75 insertions(+), 43 deletions(-) diff --git a/src/main/java/grondag/canvas/config/CanvasConfigScreen.java b/src/main/java/grondag/canvas/config/CanvasConfigScreen.java index d5e523acd..1c82efbc1 100644 --- a/src/main/java/grondag/canvas/config/CanvasConfigScreen.java +++ b/src/main/java/grondag/canvas/config/CanvasConfigScreen.java @@ -493,6 +493,12 @@ protected void init() { DEFAULTS.traceTextureLoad, "config.canvas.help.trace_texture_load").listItem()); + list.addItem(optionSession.booleanOption("config.canvas.value.debug_shader_flag", + () -> editing.debugShaderFlag, + b -> editing.debugShaderFlag = b, + DEFAULTS.debugShaderFlag, + "config.canvas.help.debug_shader_flag").listItem()); + if (sideW > 0) { final ListWidget tabs = new ListWidget(1, list.getY(), sideW, list.getHeight(), true); addRenderableWidget(tabs); diff --git a/src/main/java/grondag/canvas/config/ConfigData.java b/src/main/java/grondag/canvas/config/ConfigData.java index 8bd0b6ebd..09a4b554d 100644 --- a/src/main/java/grondag/canvas/config/ConfigData.java +++ b/src/main/java/grondag/canvas/config/ConfigData.java @@ -153,6 +153,8 @@ class ConfigData { boolean debugSpriteAtlas = false; @Comment("Log significant events of texture/sprite atlas loading. For debugging use. Will spam the log.") boolean traceTextureLoad = false; + @Comment("Enables debug flag in the shader. Only intended for internal Canvas development purposes.") + boolean debugShaderFlag = false; // GSON doesn't do this automatically public void clearNulls() { diff --git a/src/main/java/grondag/canvas/config/Configurator.java b/src/main/java/grondag/canvas/config/Configurator.java index ce8899b8a..0a22e2b93 100644 --- a/src/main/java/grondag/canvas/config/Configurator.java +++ b/src/main/java/grondag/canvas/config/Configurator.java @@ -96,6 +96,7 @@ public class Configurator { public static boolean cullBackfacingTerrain = DEFAULTS.cullBackfacingTerrain; public static boolean debugSpriteAtlas = DEFAULTS.debugSpriteAtlas; public static boolean traceTextureLoad = DEFAULTS.traceTextureLoad; + public static boolean debugShaderFlag = DEFAULTS.debugShaderFlag; // @LangKey("config.acuity_fancy_fluids") // @Comment({"Enable fancy water and lava rendering.", @@ -187,6 +188,7 @@ static void readFromConfig(ConfigData config, boolean isStartup) { cullBackfacingTerrain = config.cullBackfacingTerrain; debugSpriteAtlas = config.debugSpriteAtlas; traceTextureLoad = config.traceTextureLoad; + debugShaderFlag = config.debugShaderFlag; } static void writeToConfig(ConfigData config) { @@ -253,5 +255,6 @@ static void writeToConfig(ConfigData config) { config.cullBackfacingTerrain = cullBackfacingTerrain; config.debugSpriteAtlas = debugSpriteAtlas; config.traceTextureLoad = traceTextureLoad; + config.debugShaderFlag = debugShaderFlag; } } diff --git a/src/main/java/grondag/canvas/shader/GlShader.java b/src/main/java/grondag/canvas/shader/GlShader.java index f141ea3c3..4b30b66f4 100644 --- a/src/main/java/grondag/canvas/shader/GlShader.java +++ b/src/main/java/grondag/canvas/shader/GlShader.java @@ -277,6 +277,10 @@ private String getSource() { } } + if (Configurator.debugShaderFlag) { + result = StringUtils.replace(result, "//#define _CV_DEBUG", "#define _CV_DEBUG"); + } + result = StringUtils.replace(result, "#define _CV_MAX_SHADER_COUNT 0", "#define _CV_MAX_SHADER_COUNT " + MaterialConstants.MAX_SHADERS); // prepend GLSL version diff --git a/src/main/resources/assets/canvas/lang/en_us.json b/src/main/resources/assets/canvas/lang/en_us.json index 2d61bab03..a9eed5bdf 100644 --- a/src/main/resources/assets/canvas/lang/en_us.json +++ b/src/main/resources/assets/canvas/lang/en_us.json @@ -152,6 +152,9 @@ "config.canvas.help.profiler_detail_level": "Profiler level of detail. 0=Collapse all, 1=Expand program passes, 2=Expand all", "config.canvas.value.profiler_overlay_scale": "Profiler Overlay Scale", "config.canvas.help.profiler_overlay_scale": "Size of the profiler overlay relative to GUI scale.", + "config.canvas.value.debug_shader_flag": "Shader Debug Flag", + "config.canvas.help.debug_shader_flag": "Enables debug flag in the shader. Only intended for internal Canvas development purposes.", + "key.canvas.debug_toggle": "Toggle Debug View", "key.canvas.debug_prev": "Debug Previous Image", "key.canvas.debug_next": "Debug Next Image", diff --git a/src/main/resources/assets/frex/shaders/api/header.glsl b/src/main/resources/assets/frex/shaders/api/header.glsl index 5322247d4..e6ed3c926 100644 --- a/src/main/resources/assets/frex/shaders/api/header.glsl +++ b/src/main/resources/assets/frex/shaders/api/header.glsl @@ -11,3 +11,4 @@ #define MATERIAL_TARGET_UNKNOWN //#define DEPTH_PASS //#define PBR_ENABLED +//#define _CV_DEBUG \ No newline at end of file diff --git a/src/main/resources/assets/frex/shaders/api/light.glsl b/src/main/resources/assets/frex/shaders/api/light.glsl index 09362a724..df7339431 100644 --- a/src/main/resources/assets/frex/shaders/api/light.glsl +++ b/src/main/resources/assets/frex/shaders/api/light.glsl @@ -38,13 +38,14 @@ vec4 frx_getLightFiltered(sampler2D lightSampler, vec3 worldPos) { } #ifdef _CV_LIGHT_DATA_COMPLEX_FILTER - vec3 pos = floor(worldPos) + vec3(0.5); - vec3 H = sign(fract(worldPos) - vec3(0.5)); + vec3 pos = floor(worldPos) + vec3(0.5); + vec3 H = sign(fract(worldPos) - vec3(0.5)); #else - vec3 pos = worldPos - vec3(0.5); - const vec3 H = vec3(1.0); + vec3 pos = worldPos - vec3(0.5); + const vec3 H = vec3(1.0); #endif + // sample 2x2x2 area vec4 tex000 = texelFetch(lightSampler, _cv_lightTexelCoords(lightSampler, pos + vec3(0.0, 0.0, 0.0)), 0); vec4 tex001 = texelFetch(lightSampler, _cv_lightTexelCoords(lightSampler, pos + vec3(0.0, 0.0, H.z)), 0); vec4 tex010 = texelFetch(lightSampler, _cv_lightTexelCoords(lightSampler, pos + vec3(0.0, H.y, 0.0)), 0); @@ -55,49 +56,61 @@ vec4 frx_getLightFiltered(sampler2D lightSampler, vec3 worldPos) { vec4 tex111 = texelFetch(lightSampler, _cv_lightTexelCoords(lightSampler, pos + vec3(H.x, H.y, H.z)), 0); #ifdef _CV_LIGHT_DATA_COMPLEX_FILTER - vec3 center = worldPos - pos; - vec3 pos000 = vec3(0.0, 0.0, 0.0) - center; - vec3 pos001 = vec3(0.0, 0.0, H.z) - center; - vec3 pos010 = vec3(0.0, H.y, 0.0) - center; - vec3 pos011 = vec3(0.0, H.y, H.z) - center; - vec3 pos101 = vec3(H.x, 0.0, H.z) - center; - vec3 pos110 = vec3(H.x, H.y, 0.0) - center; - vec3 pos100 = vec3(H.x, 0.0, 0.0) - center; - vec3 pos111 = vec3(H.x, H.y, H.z) - center; - - // origin filter - float a000 = 1.0; - float a001 = float(_cv_isUseful(tex001.a)) * float(all(greaterThanEqual(vec3(1.05 / 15.0), abs(tex001.rgb - tex000.rgb)))); - float a010 = float(_cv_isUseful(tex010.a)) * float(all(greaterThanEqual(vec3(1.05 / 15.0), abs(tex010.rgb - tex000.rgb)))); - float a100 = float(_cv_isUseful(tex100.a)) * float(all(greaterThanEqual(vec3(1.05 / 15.0), abs(tex100.rgb - tex000.rgb)))); - float a011 = float(_cv_isUseful(tex011.a)) * float(all(greaterThanEqual(vec3(2.05 / 15.0), abs(tex011.rgb - tex000.rgb)))); - float a101 = float(_cv_isUseful(tex101.a)) * float(all(greaterThanEqual(vec3(2.05 / 15.0), abs(tex101.rgb - tex000.rgb)))); - float a110 = float(_cv_isUseful(tex110.a)) * float(all(greaterThanEqual(vec3(2.05 / 15.0), abs(tex110.rgb - tex000.rgb)))); - float a111 = float(_cv_isUseful(tex111.a)) * float(all(greaterThanEqual(vec3(3.05 / 15.0), abs(tex111.rgb - tex000.rgb)))); - - float w000 = a000 * abs(pos111.x * pos111.y * pos111.z); - float w001 = a001 * abs(pos110.x * pos110.y * pos110.z); - float w010 = a010 * abs(pos101.x * pos101.y * pos101.z); - float w011 = a011 * abs(pos100.x * pos100.y * pos100.z); - float w101 = a101 * abs(pos010.x * pos010.y * pos010.z); - float w110 = a110 * abs(pos001.x * pos001.y * pos001.z); - float w100 = a100 * abs(pos011.x * pos011.y * pos011.z); - float w111 = a111 * abs(pos000.x * pos000.y * pos000.z); - - float weight = w000 + w001 + w010 + w011 + w101 + w110 + w100 + w111; - vec4 finalMix = weight == 0.0 ? vec4(0.0) : vec4((tex000.rgb * w000 + tex001.rgb * w001 + tex010.rgb * w010 + tex011.rgb * w011 + tex101.rgb * w101 + tex110.rgb * w110 + tex100.rgb * w100 + tex111.rgb * w111) / weight, 1.0); + vec3 center = worldPos - pos; + vec3 pos000 = vec3(0.0, 0.0, 0.0) - center; + vec3 pos001 = vec3(0.0, 0.0, H.z) - center; + vec3 pos010 = vec3(0.0, H.y, 0.0) - center; + vec3 pos011 = vec3(0.0, H.y, H.z) - center; + vec3 pos101 = vec3(H.x, 0.0, H.z) - center; + vec3 pos110 = vec3(H.x, H.y, 0.0) - center; + vec3 pos100 = vec3(H.x, 0.0, 0.0) - center; + vec3 pos111 = vec3(H.x, H.y, H.z) - center; + + float a000 = 1.0; // origin + float a001 = float(_cv_isUseful(tex001.a)); + float a010 = float(_cv_isUseful(tex010.a)); + float a100 = float(_cv_isUseful(tex100.a)); + float a011 = float(_cv_isUseful(tex011.a)); + float a101 = float(_cv_isUseful(tex101.a)); + float a110 = float(_cv_isUseful(tex110.a)); + float a111 = float(_cv_isUseful(tex111.a)); + + #ifdef _CV_DEBUG + // filters out "irrelevant" data from the current blending result. + // in theory it should make the resulting light more accurate. in practice, this makes propagation errors stand out more. + // as our implementation is not error-free, this is more detrimental than useful to the users. use for debugging. + a001 *= float(all(greaterThanEqual(vec3(1.05 / 15.0), abs(tex001.rgb - tex000.rgb)))); + a010 *= float(all(greaterThanEqual(vec3(1.05 / 15.0), abs(tex010.rgb - tex000.rgb)))); + a100 *= float(all(greaterThanEqual(vec3(1.05 / 15.0), abs(tex100.rgb - tex000.rgb)))); + a011 *= float(all(greaterThanEqual(vec3(2.05 / 15.0), abs(tex011.rgb - tex000.rgb)))); + a101 *= float(all(greaterThanEqual(vec3(2.05 / 15.0), abs(tex101.rgb - tex000.rgb)))); + a110 *= float(all(greaterThanEqual(vec3(2.05 / 15.0), abs(tex110.rgb - tex000.rgb)))); + a111 *= float(all(greaterThanEqual(vec3(3.05 / 15.0), abs(tex111.rgb - tex000.rgb)))); + #endif + + float w000 = a000 * abs(pos111.x * pos111.y * pos111.z); + float w001 = a001 * abs(pos110.x * pos110.y * pos110.z); + float w010 = a010 * abs(pos101.x * pos101.y * pos101.z); + float w011 = a011 * abs(pos100.x * pos100.y * pos100.z); + float w101 = a101 * abs(pos010.x * pos010.y * pos010.z); + float w110 = a110 * abs(pos001.x * pos001.y * pos001.z); + float w100 = a100 * abs(pos011.x * pos011.y * pos011.z); + float w111 = a111 * abs(pos000.x * pos000.y * pos000.z); + + float weight = w000 + w001 + w010 + w011 + w101 + w110 + w100 + w111; + vec4 finalMix = weight == 0.0 ? vec4(0.0) : vec4((tex000.rgb * w000 + tex001.rgb * w001 + tex010.rgb * w010 + tex011.rgb * w011 + tex101.rgb * w101 + tex110.rgb * w110 + tex100.rgb * w100 + tex111.rgb * w111) / weight, 1.0); #else - vec3 fac = fract(pos); + vec3 fac = fract(pos); - vec3 mix001 = mix(tex000.rgb, tex001.rgb, fac.z); - vec3 mix011 = mix(tex010.rgb, tex011.rgb, fac.z); - vec3 mix010 = mix(mix001, mix011, fac.y); + vec3 mix001 = mix(tex000.rgb, tex001.rgb, fac.z); + vec3 mix011 = mix(tex010.rgb, tex011.rgb, fac.z); + vec3 mix010 = mix(mix001, mix011, fac.y); - vec3 mix101 = mix(tex100.rgb, tex101.rgb, fac.z); - vec3 mix111 = mix(tex110.rgb, tex111.rgb, fac.z); - vec3 mix110 = mix(mix101, mix111, fac.y); + vec3 mix101 = mix(tex100.rgb, tex101.rgb, fac.z); + vec3 mix111 = mix(tex110.rgb, tex111.rgb, fac.z); + vec3 mix110 = mix(mix101, mix111, fac.y); - vec4 finalMix = vec4(mix(mix010, mix110, fac.x), 1.0); + vec4 finalMix = vec4(mix(mix010, mix110, fac.x), 1.0); #endif return finalMix; From fa8363d6564be3d667c48b91748fdc3137d2f0f4 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Thu, 4 Jan 2024 17:33:24 +0700 Subject: [PATCH 61/69] Differentiate shader recompile and full reload in config screen --- .../canvas/config/CanvasConfigScreen.java | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/main/java/grondag/canvas/config/CanvasConfigScreen.java b/src/main/java/grondag/canvas/config/CanvasConfigScreen.java index 1c82efbc1..f5bfd5e8c 100644 --- a/src/main/java/grondag/canvas/config/CanvasConfigScreen.java +++ b/src/main/java/grondag/canvas/config/CanvasConfigScreen.java @@ -23,6 +23,7 @@ import static grondag.canvas.config.ConfigManager.DEFAULTS; import static grondag.canvas.config.ConfigManager.Reload.DONT_RELOAD; import static grondag.canvas.config.ConfigManager.Reload.RELOAD_EVERYTHING; +import static grondag.canvas.config.ConfigManager.Reload.RELOAD_PIPELINE; import java.util.List; @@ -47,6 +48,7 @@ public class CanvasConfigScreen extends BaseScreen { private boolean reload; + private boolean recompile; private boolean reloadTimekeeper; private boolean requiresRestart; @@ -59,6 +61,7 @@ public CanvasConfigScreen(Screen parent) { super(parent, Component.translatable("config.canvas.title")); reload = false; + recompile = false; reloadTimekeeper = false; requiresRestart = false; @@ -95,7 +98,7 @@ protected void init() { list.addItem(optionSession.booleanOption("config.canvas.value.wavy_grass", () -> editing.wavyGrass, b -> { - reload |= Configurator.wavyGrass != b; + recompile |= Configurator.wavyGrass != b; editing.wavyGrass = b; }, DEFAULTS.wavyGrass, @@ -341,7 +344,7 @@ protected void init() { list.addItem(optionSession.booleanOption("config.canvas.value.preprocess_shader_source", () -> editing.preprocessShaderSource, b -> { - reload |= Configurator.preprocessShaderSource != b; + recompile |= Configurator.preprocessShaderSource != b; editing.preprocessShaderSource = b; }, DEFAULTS.preprocessShaderSource, @@ -495,7 +498,10 @@ protected void init() { list.addItem(optionSession.booleanOption("config.canvas.value.debug_shader_flag", () -> editing.debugShaderFlag, - b -> editing.debugShaderFlag = b, + b -> { + recompile |= Configurator.debugShaderFlag != b; + editing.debugShaderFlag = b; + }, DEFAULTS.debugShaderFlag, "config.canvas.help.debug_shader_flag").listItem()); @@ -534,7 +540,17 @@ private void save() { Timekeeper.configOrPipelineReload(); } - ConfigManager.saveCanvasConfig(reload ? RELOAD_EVERYTHING : DONT_RELOAD); + final ConfigManager.Reload configReload; + + if (reload) { + configReload = RELOAD_EVERYTHING; + } else if (recompile) { + configReload = RELOAD_PIPELINE; + } else { + configReload = DONT_RELOAD; + } + + ConfigManager.saveCanvasConfig(configReload); if (requiresRestart) { this.minecraft.setScreen(new ConfigRestartScreen(this.parent)); From 96ae9645f6dc97711f19641cd785570abd90b6b5 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Fri, 5 Jan 2024 07:11:22 +0700 Subject: [PATCH 62/69] (cont. from prev commit) Don't reload timekeeper twice --- .../java/grondag/canvas/config/CanvasConfigScreen.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/grondag/canvas/config/CanvasConfigScreen.java b/src/main/java/grondag/canvas/config/CanvasConfigScreen.java index f5bfd5e8c..88bdb34dd 100644 --- a/src/main/java/grondag/canvas/config/CanvasConfigScreen.java +++ b/src/main/java/grondag/canvas/config/CanvasConfigScreen.java @@ -535,11 +535,6 @@ private void save() { Configurator.readFromConfig(editing); - // for now Config reload does reload everything including Timekeeper - if (reloadTimekeeper && !reload) { - Timekeeper.configOrPipelineReload(); - } - final ConfigManager.Reload configReload; if (reload) { @@ -547,6 +542,11 @@ private void save() { } else if (recompile) { configReload = RELOAD_PIPELINE; } else { + // the other cases already cover timekeeper reload at the moment. + if (reloadTimekeeper) { + Timekeeper.configOrPipelineReload(); + } + configReload = DONT_RELOAD; } From 73ff9a1e5e827a965bedfd437b4162ca0390ffe0 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Fri, 5 Jan 2024 10:27:15 +0700 Subject: [PATCH 63/69] Prevent drawing light texture out of bounds --- .../canvas/light/color/LightDataAllocator.java | 14 ++++++++++++-- .../canvas/light/color/LightDataManager.java | 5 +++++ .../canvas/light/color/LightDataTexture.java | 7 +++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java index f461cf9dd..955aa30ec 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataAllocator.java +++ b/src/main/java/grondag/canvas/light/color/LightDataAllocator.java @@ -64,7 +64,9 @@ public class LightDataAllocator { private boolean requireTextureRemake; private int addressLimit = INITIAL_ADDRESS_COUNT; - private int dynamicMaxAddresses = 0; + // dummy value, need immediate initialization + private int dynamicMaxAddresses = 1; + // 0 is reserved for empty address private short nextAllocateAddress = 1; private int addressCount = 1; @@ -94,6 +96,10 @@ private void ensureWithinLimits() { } addressCount = dynamicMaxAddresses; + + if (nextAllocateAddress > addressCount) { + nextAllocateAddress = EMPTY_ADDRESS + 1; + } } } @@ -188,7 +194,7 @@ private short allocateAddressInner(LightRegion region) { nextAllocateAddress++; addressCount++; } else { - if (nextAllocateAddress >= dynamicMaxAddresses) { + if (nextAllocateAddress >= addressCount) { // rolling pointer nextAllocateAddress = EMPTY_ADDRESS + 1; } @@ -297,6 +303,10 @@ public boolean checkInvalid() { return requireTextureRemake; } + public boolean isInvalid() { + return requireTextureRemake; + } + public int textureWidth() { return ROW_SIZE; } diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 4009aa2b8..6c27536a7 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -250,6 +250,11 @@ private void drawInner(LightRegion lightRegion, boolean redraw) { if (lightRegion.lightData.hasBuffer() && (lightRegion.lightData.isDirty() || redraw)) { final int targetAddress = texAllocator.allocateAddress(lightRegion); + if (texAllocator.isInvalid()) { + // don't draw to invalid texture, but continue looping to allocate the rest of the region queue + return; + } + if (targetAddress != LightDataAllocator.EMPTY_ADDRESS) { final int targetRow = texAllocator.dataRowStart() + targetAddress; texture.upload(targetRow, lightRegion.lightData.getBuffer()); diff --git a/src/main/java/grondag/canvas/light/color/LightDataTexture.java b/src/main/java/grondag/canvas/light/color/LightDataTexture.java index fe2171e8d..6b5306acc 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataTexture.java +++ b/src/main/java/grondag/canvas/light/color/LightDataTexture.java @@ -27,6 +27,7 @@ import com.mojang.blaze3d.platform.TextureUtil; import com.mojang.blaze3d.systems.RenderSystem; +import grondag.canvas.CanvasMod; import grondag.canvas.render.CanvasTextureState; import grondag.canvas.varia.GFX; @@ -41,10 +42,12 @@ public static class Format { private final int glId; private final int width; + private final int height; private boolean closed = false; LightDataTexture(int width, int height) { this.width = width; + this.height = height; glId = TextureUtil.generateTextureId(); CanvasTextureState.bindTexture(glId); @@ -90,6 +93,10 @@ public void uploadDirect(int x, int y, int width, int height, ByteBuffer buffer) throw new IllegalStateException("Uploading to a closed light texture!"); } + if (x + width > this.width || y + height > this.height) { + CanvasMod.LOG.warn("Drawing light texture out of bounds"); + } + RenderSystem.assertOnRenderThread(); CanvasTextureState.bindTexture(glId); From 1c120b7bd93d231c8db5fe564cd4077a741bcbe7 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sat, 6 Jan 2024 13:51:29 +0700 Subject: [PATCH 64/69] NEW: Implement virtual light system --- .../canvas/light/color/LightDataManager.java | 17 ++- .../grondag/canvas/light/color/LightOp.java | 8 + .../canvas/light/color/LightRegion.java | 56 ++++--- .../canvas/light/color/LightRegionAccess.java | 4 +- .../grondag/canvas/light/color/LightView.java | 35 +++++ .../light/color/VirtualLightManager.java | 141 ++++++++++++++++++ 6 files changed, 225 insertions(+), 36 deletions(-) create mode 100644 src/main/java/grondag/canvas/light/color/LightView.java create mode 100644 src/main/java/grondag/canvas/light/color/VirtualLightManager.java diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 6c27536a7..66a53b701 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -112,6 +112,7 @@ public static String debugString() { private final LongPriorityQueue urgentIncreaseQueue = new LongArrayFIFOQueue(); private final LightDataAllocator texAllocator; + private final VirtualLightManager lightView; boolean useOcclusionData = false; private LightDataTexture texture; @@ -119,6 +120,7 @@ public static String debugString() { private DebugProfiler profiler = new ActiveProfiler(); public LightDataManager() { texAllocator = new LightDataAllocator(); + lightView = new VirtualLightManager(); allocated.defaultReturnValue(null); } @@ -139,7 +141,7 @@ private void executeRegularUpdates(BlockAndTintGetter blockView, int minimumUpda final LightRegion lightRegion = allocated.get(index); if (lightRegion != null && !lightRegion.isClosed()) { - lightRegion.updateDecrease(blockView, decreaseQueue, increaseQueue); + lightRegion.updateDecrease(lightView, decreaseQueue, increaseQueue); } if (++count > minimumUpdates && System.nanoTime() > deadlineNanos) { @@ -154,7 +156,7 @@ private void executeRegularUpdates(BlockAndTintGetter blockView, int minimumUpda final LightRegion lightRegion = allocated.get(index); if (lightRegion != null && !lightRegion.isClosed()) { - lightRegion.updateIncrease(blockView, increaseQueue); + lightRegion.updateIncrease(lightView, increaseQueue); } if (++count > minimumUpdates && System.nanoTime() > deadlineNanos) { @@ -164,6 +166,8 @@ private void executeRegularUpdates(BlockAndTintGetter blockView, int minimumUpda } private void updateInner(BlockAndTintGetter blockView, long deadlineNanos) { + lightView.startFrame(blockView); + synchronized (publicUrgentUpdateQueue) { for (long index : publicUrgentUpdateQueue) { urgentDecreaseQueue.enqueue(index); @@ -179,7 +183,7 @@ private void updateInner(BlockAndTintGetter blockView, long deadlineNanos) { final LightRegion lightRegion = allocated.get(index); if (lightRegion != null && !lightRegion.isClosed()) { - lightRegion.updateDecrease(blockView, urgentDecreaseQueue, urgentIncreaseQueue); + lightRegion.updateDecrease(lightView, urgentDecreaseQueue, urgentIncreaseQueue); } } @@ -190,7 +194,7 @@ private void updateInner(BlockAndTintGetter blockView, long deadlineNanos) { final LightRegion lightRegion = allocated.get(index); if (lightRegion != null && !lightRegion.isClosed()) { - lightRegion.updateIncrease(blockView, urgentIncreaseQueue); + lightRegion.updateIncrease(lightView, urgentIncreaseQueue); } } @@ -244,6 +248,7 @@ private void updateInner(BlockAndTintGetter blockView, long deadlineNanos) { } texAllocator.uploadPointersIfNeeded(texture); + lightView.endFrame(); } private void drawInner(LightRegion lightRegion, boolean redraw) { @@ -276,6 +281,10 @@ LightRegion get(long originKey) { return allocated.get(originKey); } + LightView lightView() { + return lightView; + } + private void freeInner(BlockPos regionOrigin) { final LightRegion lightRegion = allocated.get(regionOrigin.asLong()); diff --git a/src/main/java/grondag/canvas/light/color/LightOp.java b/src/main/java/grondag/canvas/light/color/LightOp.java index ba24e1759..131fd4a80 100644 --- a/src/main/java/grondag/canvas/light/color/LightOp.java +++ b/src/main/java/grondag/canvas/light/color/LightOp.java @@ -55,6 +55,14 @@ public static short encodeLight(int pureLight, boolean isFull, boolean isEmitter return ensureUsefulness((short) (pureLight | encodeAlpha(isFull, isEmitter, isOccluding))); } + public static short pure(short light) { + return (short) (light & 0xfff0); + } + + public static short makeEmitter(short light) { + return (short) (light | EMITTER_FLAG); + } + private static int encodeAlpha(boolean isFull, boolean isEmitter, boolean isOccluding) { return (isFull ? FULL_FLAG : 0) | (isEmitter ? EMITTER_FLAG : 0) | (isOccluding ? OCCLUDER_FLAG : 0); } diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index f0ea6668b..4733ab86f 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -23,10 +23,10 @@ import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; import it.unimi.dsi.fastutil.longs.LongPriorityQueue; import it.unimi.dsi.fastutil.longs.LongPriorityQueues; +import org.jetbrains.annotations.Nullable; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.phys.shapes.Shapes; @@ -125,15 +125,16 @@ static short light(long entry) { } @Override - public void checkBlock(BlockPos pos, BlockState blockState) { + public void checkBlock(BlockPos pos, @Nullable BlockState blockState) { if (!lightData.withinExtents(pos)) { return; } - final short registeredLight = LightRegistry.get(blockState); final int index = lightData.indexify(pos); + final short registeredLight = LightDataManager.INSTANCE.lightView().getRegistered(pos, blockState); + final boolean occluding = LightOp.occluder(registeredLight); + final short getLight = lightData.get(index); - final boolean occluding = blockState.canOcclude(); final boolean emitting = LightOp.emitter(registeredLight); // Equivalent of "hard reset" on this particular block pos, so we want it to pass specific checks @@ -181,17 +182,18 @@ public void markUrgent() { urgent = true; } - private boolean occludeSide(BlockState state, Side dir, BlockAndTintGetter view, BlockPos pos) { + private boolean occludeSide(Side dir, LightView view, BlockPos pos) { // vanilla checks state.useShapeForLightOcclusion() but here it's always false for some reason. this is fine... + var state = view.baseView().getBlockState(pos); if (!state.canOcclude()) { return false; } - return Shapes.faceShapeOccludes(Shapes.empty(), state.getFaceOcclusionShape(view, pos, dir.vanilla)); + return Shapes.faceShapeOccludes(Shapes.empty(), state.getFaceOcclusionShape(view.baseView(), pos, dir.vanilla)); } - void updateDecrease(BlockAndTintGetter blockView, LongPriorityQueue neighborDecreaseQueue, LongPriorityQueue neighborIncreaseQueue) { + void updateDecrease(LightView view, LongPriorityQueue neighborDecreaseQueue, LongPriorityQueue neighborIncreaseQueue) { // faster exit when not necessary if (globalDecQueue.isEmpty()) { return; @@ -259,17 +261,16 @@ void updateDecrease(BlockAndTintGetter blockView, LongPriorityQueue neighborDecr // Important: extremely high frequency redundancy filter (removes 99% of operations) if (!LightOp.lit(nodeLight)) continue; - final BlockState nodeState = blockView.getBlockState(nodePos); - // check neighbor occlusion for decrease - if (!LightOp.emitter(nodeLight) && occludeSide(nodeState, side.opposite, blockView, nodePos)) { + if (!LightOp.emitter(nodeLight) && occludeSide(side.opposite, view, nodePos)) { continue; } // only propagate removal according to removeFlag removeMask.and(less.lessThan(nodeLight, sourcePrevLight), removeFlag); - final boolean restoreLightSource = removeMask.any() && LightOp.emitter(nodeLight); + final short registered = view.getRegistered(nodePos); + final boolean restoreLightSource = removeMask.any() && LightOp.emitter(registered); final short repropLight; if (removeMask.any()) { @@ -291,7 +292,7 @@ void updateDecrease(BlockAndTintGetter blockView, LongPriorityQueue neighborDecr // restore obliterated light source if (restoreLightSource) { // defer putting light source as to not mess with decrease step - repropLight = LightRegistry.get(nodeState); + repropLight = registered; } else { repropLight = resultLight; } @@ -316,10 +317,10 @@ void updateDecrease(BlockAndTintGetter blockView, LongPriorityQueue neighborDecr } } - void updateIncrease(BlockAndTintGetter blockView, LongPriorityQueue neighborIncreaseQueue) { + void updateIncrease(LightView view, LongPriorityQueue neighborIncreaseQueue) { if (needCheckEdges) { needCheckEdges = false; - checkEdges(blockView); + checkEdges(view); } while (!globalIncQueue.isEmpty()) { @@ -335,14 +336,11 @@ void updateIncrease(BlockAndTintGetter blockView, LongPriorityQueue neighborIncr final short getLight = lightData.get(index); final short sourceLight; - final BlockState sourceState; - if (getLight != recordedLight) { if (LightOp.emitter(recordedLight)) { lightData.reverseIndexify(index, sourcePos); - sourceState = blockView.getBlockState(sourcePos); - if (LightRegistry.get(sourceState) != recordedLight) { + if (view.getRegistered(sourcePos) != recordedLight) { continue; } @@ -354,7 +352,6 @@ void updateIncrease(BlockAndTintGetter blockView, LongPriorityQueue neighborIncr } } else { lightData.reverseIndexify(index, sourcePos); - sourceState = blockView.getBlockState(sourcePos); sourceLight = getLight; } @@ -365,7 +362,7 @@ void updateIncrease(BlockAndTintGetter blockView, LongPriorityQueue neighborIncr } // check self occlusion for increase - if (!LightOp.emitter(sourceLight) && occludeSide(sourceState, side, blockView, sourcePos)) { + if (!LightOp.emitter(sourceLight) && occludeSide(side, view, sourcePos)) { continue; } @@ -392,10 +389,9 @@ void updateIncrease(BlockAndTintGetter blockView, LongPriorityQueue neighborIncr final int nodeIndex = dataAccess.indexify(nodePos); final short nodeLight = dataAccess.get(nodeIndex); - final BlockState nodeState = blockView.getBlockState(nodePos); // check neighbor occlusion for increase - if (occludeSide(nodeState, side.opposite, blockView, nodePos)) { + if (occludeSide(side.opposite, view, nodePos)) { continue; } @@ -417,24 +413,22 @@ void updateIncrease(BlockAndTintGetter blockView, LongPriorityQueue neighborIncr LightDataManager.INSTANCE.publicDrawQueue.add(origin); } - private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourcePos, BlockPos.MutableBlockPos targetPos, Side side, BlockAndTintGetter blockView) { + private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourcePos, BlockPos.MutableBlockPos targetPos, Side side, LightView view) { final int sourceIndex = neighbor.lightData.indexify(sourcePos); final short sourceLight = neighbor.lightData.get(sourceIndex); - final BlockState sourceState = blockView.getBlockState(sourcePos); if (LightOp.lit(sourceLight)) { // TODO: generalize for all increase process, with check-neighbor flag // check self occlusion for increase - if (!LightOp.emitter(sourceLight) && occludeSide(sourceState, side, blockView, sourcePos)) { + if (!LightOp.emitter(sourceLight) && occludeSide(side, view, sourcePos)) { return; } final int targetIndex = lightData.indexify(targetPos); final short targetLight = lightData.get(targetIndex); - final BlockState nodeState = blockView.getBlockState(targetPos); // check neighbor occlusion for increase - if (occludeSide(nodeState, side.opposite, blockView, targetPos)) { + if (occludeSide(side.opposite, view, targetPos)) { return; } @@ -446,7 +440,7 @@ private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourc } } - private void checkEdges(BlockAndTintGetter blockView) { + private void checkEdges(LightView view) { final int size = LightRegionData.Const.WIDTH; final BlockPos.MutableBlockPos searchPos = new BlockPos.MutableBlockPos(); final BlockPos.MutableBlockPos targetPos = new BlockPos.MutableBlockPos(); @@ -470,7 +464,7 @@ private void checkEdges(BlockAndTintGetter blockView) { for (int z = 0; z < size; z++) { searchPos.setWithOffset(originPos, x, y, z); targetPos.setWithOffset(originPos, xTarget, y, z); - checkEdgeBlock(neighbor, searchPos, targetPos, side, blockView); + checkEdgeBlock(neighbor, searchPos, targetPos, side, view); } } } @@ -493,7 +487,7 @@ private void checkEdges(BlockAndTintGetter blockView) { for (int x = 0; x < size; x++) { searchPos.setWithOffset(originPos, x, y, z); targetPos.setWithOffset(originPos, x, yTarget, z); - checkEdgeBlock(neighbor, searchPos, targetPos, side, blockView); + checkEdgeBlock(neighbor, searchPos, targetPos, side, view); } } } @@ -515,7 +509,7 @@ private void checkEdges(BlockAndTintGetter blockView) { for (int y = 0; y < size; y++) { searchPos.setWithOffset(originPos, x, y, z); targetPos.setWithOffset(originPos, x, y, zTarget); - checkEdgeBlock(neighbor, searchPos, targetPos, side, blockView); + checkEdgeBlock(neighbor, searchPos, targetPos, side, view); } } } diff --git a/src/main/java/grondag/canvas/light/color/LightRegionAccess.java b/src/main/java/grondag/canvas/light/color/LightRegionAccess.java index 8e84cf181..98ccfbfb8 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegionAccess.java +++ b/src/main/java/grondag/canvas/light/color/LightRegionAccess.java @@ -20,13 +20,15 @@ package grondag.canvas.light.color; +import org.jetbrains.annotations.Nullable; + import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.state.BlockState; public interface LightRegionAccess { LightRegionAccess EMPTY = new Empty(); - void checkBlock(BlockPos pos, BlockState blockState); + void checkBlock(BlockPos pos, @Nullable BlockState blockState); void submitChecks(); diff --git a/src/main/java/grondag/canvas/light/color/LightView.java b/src/main/java/grondag/canvas/light/color/LightView.java new file mode 100644 index 000000000..4cc86f325 --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/LightView.java @@ -0,0 +1,35 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.light.color; + +import org.jetbrains.annotations.Nullable; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.block.state.BlockState; + +public interface LightView { + BlockAndTintGetter baseView(); + short getRegistered(BlockPos pos); + short getRegistered(BlockPos pos, @Nullable BlockState state); + void placeVirtualLight(BlockPos blockPos, short light); + void removeVirtualLight(BlockPos blockPos, short light); +} diff --git a/src/main/java/grondag/canvas/light/color/VirtualLightManager.java b/src/main/java/grondag/canvas/light/color/VirtualLightManager.java new file mode 100644 index 000000000..b9d2ebe9b --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/VirtualLightManager.java @@ -0,0 +1,141 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.light.color; + +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.shorts.ShortArrayList; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.block.state.BlockState; + +import grondag.canvas.CanvasMod; + +public class VirtualLightManager implements LightView { + private BlockAndTintGetter baseView = null; + + private final Long2ObjectOpenHashMap positions = new Long2ObjectOpenHashMap<>(); + private final LongArrayFIFOQueue queue = new LongArrayFIFOQueue(); + private final ObjectOpenHashSet checkQueue = new ObjectOpenHashSet<>(); + + VirtualLightManager() { + // dummy test + // placeVirtualLight(new BlockPos(-278, 43, 6), LightOp.encodeLight(0x0, 0xf, 0x0, false, true, false)); + } + + void startFrame(BlockAndTintGetter blockView) { + baseView = blockView; + update(); + } + + void endFrame() { + // TODO: more elegant solution? + baseView = null; + } + + @Override + public BlockAndTintGetter baseView() { + return baseView; + } + + @Override + public short getRegistered(BlockPos pos) { + var registered = baseView == null ? 0 : LightRegistry.get(baseView.getBlockState(pos)); + return combineWithBlockLight(registered, getVirtualLight(pos)); + } + + @Override + public short getRegistered(BlockPos pos, @Nullable BlockState state) { + // MAINTENANCE NOTICE: this function is a special casing of getRegistered(BlockPos) + var registered = state != null ? LightRegistry.get(state) : (baseView == null ? 0 : LightRegistry.get(baseView.getBlockState(pos))); + return combineWithBlockLight(registered, getVirtualLight(pos)); + } + + @Override + public void placeVirtualLight(BlockPos blockPos, short light) { + final long pos = blockPos.asLong(); + final var list = positions.computeIfAbsent(pos, l -> new ShortArrayList()); + list.add(encodeVirtualLight(light)); + queue.enqueue(pos); + } + + @Override + public void removeVirtualLight(BlockPos blockPos, short light) { + final long pos = blockPos.asLong(); + final var list = positions.get(pos); + final int i = list == null ? -1 : list.indexOf(encodeVirtualLight(light)); + + if (i != -1) { + list.removeShort(i); + queue.enqueue(pos); + } + } + + private short getVirtualLight(BlockPos blockPos) { + ShortArrayList lights = positions.get(blockPos.asLong()); + + if (lights != null) { + short combined = 0; + + for (short light:lights) { + combined = LightOp.max(light, combined); + } + + return combined; + } + + return 0; + } + + private void update() { + while (!queue.isEmpty()) { + long pos = queue.dequeueLong(); + var region = LightDataManager.INSTANCE.get(pos); + + if (region != null) { + var blockPos = BlockPos.of(pos); + region.checkBlock(blockPos, null); + checkQueue.add(region); + } else if (positions.containsKey(pos)) { + // there are virtual lights (placed and never removed) but the region doesn't exist + CanvasMod.LOG.warn("Trying to update virtual lights on a null region. ID:" + pos); + } + } + + for (var region:checkQueue) { + region.markUrgent(); + region.submitChecks(); + } + + checkQueue.clear(); + } + + private static short encodeVirtualLight(short light) { + return LightOp.encodeLight(LightOp.pure(light), false, true, false); + } + + private static short combineWithBlockLight(short block, short virtual) { + return LightOp.makeEmitter(LightOp.max(block, virtual)); + } +} From bf97937cb97426b14e08e23c6fed09e9b08183f7 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sun, 7 Jan 2024 13:01:55 +0700 Subject: [PATCH 65/69] Implement item-based virtual lighting for entity held light and item entity - Rename VirtualLightManager to LightLevel - Make item light encoding consistent in LightRegistry and everywhere - Reformat --- .../main/resources/mixins.canvas.client.json | 3 + .../grondag/canvas/apiimpl/CanvasState.java | 6 +- .../canvas/config/CanvasConfigScreen.java | 9 + .../grondag/canvas/config/ConfigData.java | 4 +- .../grondag/canvas/config/Configurator.java | 3 + .../canvas/light/color/LightDataManager.java | 39 ++-- .../canvas/light/color/LightLevel.java | 176 ++++++++++++++++++ .../{LightView.java => LightLevelAccess.java} | 16 +- .../canvas/light/color/LightRegion.java | 38 ++-- .../canvas/light/color/LightRegistry.java | 30 ++- .../light/color/VirtualLightManager.java | 141 -------------- .../color/entity/EntityLightProvider.java | 29 +++ .../color/entity/EntityLightTracker.java | 149 +++++++++++++++ .../canvas/mixin/MixinClientLevel.java | 44 +++++ .../grondag/canvas/mixin/MixinItemEntity.java | 52 ++++++ .../canvas/mixin/MixinLivingEntity.java | 66 +++++++ .../resources/assets/canvas/lang/en_us.json | 4 +- 17 files changed, 615 insertions(+), 194 deletions(-) create mode 100644 src/main/java/grondag/canvas/light/color/LightLevel.java rename src/main/java/grondag/canvas/light/color/{LightView.java => LightLevelAccess.java} (80%) delete mode 100644 src/main/java/grondag/canvas/light/color/VirtualLightManager.java create mode 100644 src/main/java/grondag/canvas/light/color/entity/EntityLightProvider.java create mode 100644 src/main/java/grondag/canvas/light/color/entity/EntityLightTracker.java create mode 100644 src/main/java/grondag/canvas/mixin/MixinClientLevel.java create mode 100644 src/main/java/grondag/canvas/mixin/MixinItemEntity.java create mode 100644 src/main/java/grondag/canvas/mixin/MixinLivingEntity.java diff --git a/fabric/src/main/resources/mixins.canvas.client.json b/fabric/src/main/resources/mixins.canvas.client.json index 313c9038a..ebb9e6cd6 100644 --- a/fabric/src/main/resources/mixins.canvas.client.json +++ b/fabric/src/main/resources/mixins.canvas.client.json @@ -15,6 +15,7 @@ "MixinBufferUploader", "MixinChunkRenderDispatcher", "MixinClientChunkCache", + "MixinClientLevel", "MixinCompiledChunk", "MixinCyclingOption", "MixinDebugScreenOverlay", @@ -26,11 +27,13 @@ "MixinGui", "MixinGuiGraphics", "MixinHumanoidArmorLayer", + "MixinItemEntity", "MixinItemFrameRenderer", "MixinItemRenderer", "MixinLevelChunk", "MixinLevelRenderer", "MixinLightTexture", + "MixinLivingEntity", "MixinMinecraft", "MixinModelBlockRenderer", "MixinNativeImage", diff --git a/src/main/java/grondag/canvas/apiimpl/CanvasState.java b/src/main/java/grondag/canvas/apiimpl/CanvasState.java index 534add67c..785a3e50b 100644 --- a/src/main/java/grondag/canvas/apiimpl/CanvasState.java +++ b/src/main/java/grondag/canvas/apiimpl/CanvasState.java @@ -81,19 +81,19 @@ private static boolean recompilePipeline() { return coloredLightsChanged || requireCullingRebuild; } - private static void recompile(boolean alreadyReloaded) { + private static void recompile(boolean wasReloaded) { CanvasMod.LOG.info(I18n.get("info.canvas.recompile")); final boolean requireReload = recompilePipeline(); - if (!alreadyReloaded && loopCounter < 2 && requireReload) { + if (!wasReloaded && loopCounter < 2 && requireReload) { CanvasMod.LOG.info(I18n.get("info.canvas.recompile_needs_reload")); loopCounter++; Minecraft.getInstance().levelRenderer.allChanged(); return; } - LightDataManager.reload(); + LightDataManager.reload(wasReloaded); PreReleaseShaderCompat.reload(); MaterialProgram.reload(); GlShaderManager.INSTANCE.reload(); diff --git a/src/main/java/grondag/canvas/config/CanvasConfigScreen.java b/src/main/java/grondag/canvas/config/CanvasConfigScreen.java index 88bdb34dd..6bbd08914 100644 --- a/src/main/java/grondag/canvas/config/CanvasConfigScreen.java +++ b/src/main/java/grondag/canvas/config/CanvasConfigScreen.java @@ -128,6 +128,15 @@ protected void init() { DEFAULTS.coloredLights, "config.canvas.help.colored_lights").listItem()); + list.addItem(optionSession.booleanOption("config.canvas.value.entity_light_source", + () -> editing.entityLightSource, + b -> { + reload |= Configurator.entityLightSource != b; + editing.entityLightSource = b; + }, + DEFAULTS.entityLightSource, + "config.canvas.help.entity_light_source").listItem()); + // TWEAKS final int indexTweaks = list.addCategory("config.canvas.category.tweaks"); diff --git a/src/main/java/grondag/canvas/config/ConfigData.java b/src/main/java/grondag/canvas/config/ConfigData.java index 09a4b554d..54ec8176b 100644 --- a/src/main/java/grondag/canvas/config/ConfigData.java +++ b/src/main/java/grondag/canvas/config/ConfigData.java @@ -55,8 +55,10 @@ class ConfigData { //boolean moreLightmap = true; @Comment("Models with flat lighting have smoother lighting (but no ambient occlusion).") boolean semiFlatLighting = true; - @Comment("Enable client-side colored block lights for pipelines that supports it. Will replace server lighting visually.") + @Comment("Enable colored block lights on pipelines that support it. Replaces vanilla lighting but only visually.") boolean coloredLights = false; + @Comment("Enable entity as dynamic light sources. Requires colored lights.") + boolean entityLightSource = false; // TWEAKS @Comment("Adjusts quads on some vanilla models (like iron bars) to avoid z-fighting with neighbor blocks.") diff --git a/src/main/java/grondag/canvas/config/Configurator.java b/src/main/java/grondag/canvas/config/Configurator.java index 0a22e2b93..020dfdebd 100644 --- a/src/main/java/grondag/canvas/config/Configurator.java +++ b/src/main/java/grondag/canvas/config/Configurator.java @@ -41,6 +41,7 @@ public class Configurator { //public static int maxLightmapDelayFrames = DEFAULTS.maxLightmapDelayFrames; public static boolean semiFlatLighting = DEFAULTS.semiFlatLighting; public static boolean coloredLights = DEFAULTS.coloredLights; + public static boolean entityLightSource = DEFAULTS.entityLightSource; public static boolean preventDepthFighting = DEFAULTS.preventDepthFighting; public static boolean clampExteriorVertices = DEFAULTS.clampExteriorVertices; @@ -139,6 +140,7 @@ static void readFromConfig(ConfigData config, boolean isStartup) { lightSmoothing = config.lightSmoothing; semiFlatLighting = config.semiFlatLighting; coloredLights = config.coloredLights; + entityLightSource = config.entityLightSource; // disableVanillaChunkMatrix = config.disableVanillaChunkMatrix; preventDepthFighting = config.preventDepthFighting; @@ -207,6 +209,7 @@ static void writeToConfig(ConfigData config) { //config.moreLightmap = moreLightmap; config.semiFlatLighting = semiFlatLighting; config.coloredLights = coloredLights; + config.entityLightSource = entityLightSource; config.preventDepthFighting = preventDepthFighting; config.clampExteriorVertices = clampExteriorVertices; diff --git a/src/main/java/grondag/canvas/light/color/LightDataManager.java b/src/main/java/grondag/canvas/light/color/LightDataManager.java index 66a53b701..44a7d9aa2 100644 --- a/src/main/java/grondag/canvas/light/color/LightDataManager.java +++ b/src/main/java/grondag/canvas/light/color/LightDataManager.java @@ -31,8 +31,8 @@ import org.apache.logging.log4j.Level; import org.lwjgl.system.MemoryUtil; +import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.core.BlockPos; -import net.minecraft.world.level.BlockAndTintGetter; import io.vram.frex.api.config.FlawlessFrames; @@ -50,13 +50,16 @@ public static LightRegionAccess allocate(BlockPos regionOrigin) { return INSTANCE.allocateInner(regionOrigin); } - public static void reload() { + public static void reload(boolean chunkReload) { if (Pipeline.coloredLightsEnabled()) { assert Pipeline.config().coloredLights != null; // NB: this is a shader recompile, not chunk storage reload. DON'T destroy existing instance. if (INSTANCE == null) { INSTANCE = new LightDataManager(); + } else if (chunkReload) { + // if the chunk storage was reloaded, the light level object needs to be reloaded as well + INSTANCE.lightLevel.reload(); } INSTANCE.useOcclusionData = Pipeline.config().coloredLights.useOcclusionData; @@ -75,11 +78,11 @@ public static void free(BlockPos regionOrigin) { } } - public static void update(BlockAndTintGetter blockView, long deadlineNanos, Runnable profilerTask) { + public static void update(ClientLevel level, long deadlineNanos, Runnable profilerTask) { if (INSTANCE != null) { profilerTask.run(); INSTANCE.profiler.start(); - INSTANCE.updateInner(blockView, FlawlessFrames.isActive() ? Long.MAX_VALUE : deadlineNanos); + INSTANCE.updateInner(level, FlawlessFrames.isActive() ? Long.MAX_VALUE : deadlineNanos); INSTANCE.profiler.end(); } } @@ -112,19 +115,20 @@ public static String debugString() { private final LongPriorityQueue urgentIncreaseQueue = new LongArrayFIFOQueue(); private final LightDataAllocator texAllocator; - private final VirtualLightManager lightView; + private final LightLevel lightLevel; boolean useOcclusionData = false; private LightDataTexture texture; private DebugProfiler profiler = new ActiveProfiler(); + public LightDataManager() { texAllocator = new LightDataAllocator(); - lightView = new VirtualLightManager(); + lightLevel = new LightLevel(); allocated.defaultReturnValue(null); } - private void executeRegularUpdates(BlockAndTintGetter blockView, int minimumUpdates, long deadlineNanos) { + private void executeRegularUpdates(int minimumUpdates, long deadlineNanos) { synchronized (publicUpdateQueue) { for (long index : publicUpdateQueue) { decreaseQueue.enqueue(index); @@ -141,7 +145,7 @@ private void executeRegularUpdates(BlockAndTintGetter blockView, int minimumUpda final LightRegion lightRegion = allocated.get(index); if (lightRegion != null && !lightRegion.isClosed()) { - lightRegion.updateDecrease(lightView, decreaseQueue, increaseQueue); + lightRegion.updateDecrease(lightLevel, decreaseQueue, increaseQueue); } if (++count > minimumUpdates && System.nanoTime() > deadlineNanos) { @@ -156,7 +160,7 @@ private void executeRegularUpdates(BlockAndTintGetter blockView, int minimumUpda final LightRegion lightRegion = allocated.get(index); if (lightRegion != null && !lightRegion.isClosed()) { - lightRegion.updateIncrease(lightView, increaseQueue); + lightRegion.updateIncrease(lightLevel, increaseQueue); } if (++count > minimumUpdates && System.nanoTime() > deadlineNanos) { @@ -165,8 +169,8 @@ private void executeRegularUpdates(BlockAndTintGetter blockView, int minimumUpda } } - private void updateInner(BlockAndTintGetter blockView, long deadlineNanos) { - lightView.startFrame(blockView); + private void updateInner(ClientLevel level, long deadlineNanos) { + lightLevel.updateOnStartFrame(level); synchronized (publicUrgentUpdateQueue) { for (long index : publicUrgentUpdateQueue) { @@ -183,18 +187,18 @@ private void updateInner(BlockAndTintGetter blockView, long deadlineNanos) { final LightRegion lightRegion = allocated.get(index); if (lightRegion != null && !lightRegion.isClosed()) { - lightRegion.updateDecrease(lightView, urgentDecreaseQueue, urgentIncreaseQueue); + lightRegion.updateDecrease(lightLevel, urgentDecreaseQueue, urgentIncreaseQueue); } } - executeRegularUpdates(blockView, 7, deadlineNanos); + executeRegularUpdates(7, deadlineNanos); while (!urgentIncreaseQueue.isEmpty()) { final long index = urgentIncreaseQueue.dequeueLong(); final LightRegion lightRegion = allocated.get(index); if (lightRegion != null && !lightRegion.isClosed()) { - lightRegion.updateIncrease(lightView, urgentIncreaseQueue); + lightRegion.updateIncrease(lightLevel, urgentIncreaseQueue); } } @@ -248,7 +252,6 @@ private void updateInner(BlockAndTintGetter blockView, long deadlineNanos) { } texAllocator.uploadPointersIfNeeded(texture); - lightView.endFrame(); } private void drawInner(LightRegion lightRegion, boolean redraw) { @@ -281,8 +284,8 @@ LightRegion get(long originKey) { return allocated.get(originKey); } - LightView lightView() { - return lightView; + LightLevelAccess lightLevel() { + return lightLevel; } private void freeInner(BlockPos regionOrigin) { @@ -309,6 +312,7 @@ private LightRegion allocateInner(BlockPos regionOrigin) { public void close() { texture.close(); + lightLevel.close(); synchronized (allocated) { for (var lightRegion : allocated.values()) { @@ -323,6 +327,7 @@ public void close() { private interface DebugProfiler { void start(); + void end(); } diff --git a/src/main/java/grondag/canvas/light/color/LightLevel.java b/src/main/java/grondag/canvas/light/color/LightLevel.java new file mode 100644 index 000000000..fc209faad --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/LightLevel.java @@ -0,0 +1,176 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.light.color; + +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import it.unimi.dsi.fastutil.shorts.ShortArrayList; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockAndTintGetter; +import net.minecraft.world.level.block.state.BlockState; + +import grondag.canvas.CanvasMod; +import grondag.canvas.config.Configurator; +import grondag.canvas.light.color.entity.EntityLightTracker; + +class LightLevel implements LightLevelAccess { + private BlockAndTintGetter baseLevel = null; + private EntityLightTracker entityLightTracker = null; + + private final Long2ObjectOpenHashMap virtualLights = new Long2ObjectOpenHashMap<>(); + private final LongArrayFIFOQueue virtualQueue = new LongArrayFIFOQueue(); + private final ObjectOpenHashSet virtualCheckQueue = new ObjectOpenHashSet<>(); + + LightLevel() { + reload(); + + // dummy test + // placeVirtualLight(new BlockPos(-278, 43, 6), LightOp.encodeLight(0x0, 0xf, 0x0, false, true, false)); + } + + public void reload() { + if (Configurator.entityLightSource) { + if (entityLightTracker == null) { + entityLightTracker = new EntityLightTracker(this); + } else { + entityLightTracker.reload(); + } + } else { + if (entityLightTracker != null) { + entityLightTracker.close(false); + } + + entityLightTracker = null; + } + } + + void updateOnStartFrame(ClientLevel level) { + if (baseLevel != level) { + baseLevel = level; + + if (entityLightTracker != null) { + entityLightTracker.reload(); + } + } + + if (entityLightTracker != null) { + entityLightTracker.update(level); + } + + updateInner(); + } + + @Override + public BlockAndTintGetter level() { + return baseLevel; + } + + @Override + public short getRegistered(BlockPos pos) { + var registered = baseLevel == null ? 0 : LightRegistry.get(baseLevel.getBlockState(pos)); + return combineWithBlockLight(registered, getVirtualLight(pos)); + } + + @Override + public short getRegistered(BlockPos pos, @Nullable BlockState state) { + // MAINTENANCE NOTICE: this function is a special casing of getRegistered(BlockPos) + var registered = state != null ? LightRegistry.get(state) : (baseLevel == null ? 0 : LightRegistry.get(baseLevel.getBlockState(pos))); + return combineWithBlockLight(registered, getVirtualLight(pos)); + } + + @Override + public void placeVirtualLight(BlockPos blockPos, short light) { + final long pos = blockPos.asLong(); + final var list = virtualLights.computeIfAbsent(pos, l -> new ShortArrayList()); + list.add(encodeVirtualLight(light)); + virtualQueue.enqueue(pos); + } + + @Override + public void removeVirtualLight(BlockPos blockPos, short light) { + final long pos = blockPos.asLong(); + final var list = virtualLights.get(pos); + final int i = list == null ? -1 : list.indexOf(encodeVirtualLight(light)); + + if (i != -1) { + list.removeShort(i); + virtualQueue.enqueue(pos); + } + } + + void close() { + if (entityLightTracker != null) { + entityLightTracker.close(true); + entityLightTracker = null; + } + } + + private short getVirtualLight(BlockPos blockPos) { + ShortArrayList lights = virtualLights.get(blockPos.asLong()); + + if (lights != null) { + short combined = 0; + + for (short light : lights) { + combined = LightOp.max(light, combined); + } + + return combined; + } + + return 0; + } + + private void updateInner() { + while (!virtualQueue.isEmpty()) { + long pos = virtualQueue.dequeueLong(); + var blockPos = BlockPos.of(pos); + var region = LightDataManager.INSTANCE.getFromBlock(blockPos); + + if (region != null) { + region.checkBlock(blockPos, null); + virtualCheckQueue.add(region); + } else if (virtualLights.containsKey(pos)) { + // there are virtual lights (placed and never removed) but the region doesn't exist + CanvasMod.LOG.warn("Trying to update virtual lights on a null region. ID:" + pos); + } + } + + for (var region : virtualCheckQueue) { + region.markUrgent(); + region.submitChecks(); + } + + virtualCheckQueue.clear(); + } + + public static short encodeVirtualLight(short light) { + return LightOp.encodeLight(LightOp.pure(light), false, true, false); + } + + private static short combineWithBlockLight(short block, short virtual) { + return LightOp.makeEmitter(LightOp.max(block, virtual)); + } +} diff --git a/src/main/java/grondag/canvas/light/color/LightView.java b/src/main/java/grondag/canvas/light/color/LightLevelAccess.java similarity index 80% rename from src/main/java/grondag/canvas/light/color/LightView.java rename to src/main/java/grondag/canvas/light/color/LightLevelAccess.java index 4cc86f325..b834d2301 100644 --- a/src/main/java/grondag/canvas/light/color/LightView.java +++ b/src/main/java/grondag/canvas/light/color/LightLevelAccess.java @@ -26,10 +26,14 @@ import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; -public interface LightView { - BlockAndTintGetter baseView(); - short getRegistered(BlockPos pos); - short getRegistered(BlockPos pos, @Nullable BlockState state); - void placeVirtualLight(BlockPos blockPos, short light); - void removeVirtualLight(BlockPos blockPos, short light); +public interface LightLevelAccess { + BlockAndTintGetter level(); + + short getRegistered(BlockPos pos); + + short getRegistered(BlockPos pos, @Nullable BlockState state); + + void placeVirtualLight(BlockPos blockPos, short light); + + void removeVirtualLight(BlockPos blockPos, short light); } diff --git a/src/main/java/grondag/canvas/light/color/LightRegion.java b/src/main/java/grondag/canvas/light/color/LightRegion.java index 4733ab86f..cde412be9 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegion.java +++ b/src/main/java/grondag/canvas/light/color/LightRegion.java @@ -131,7 +131,7 @@ public void checkBlock(BlockPos pos, @Nullable BlockState blockState) { } final int index = lightData.indexify(pos); - final short registeredLight = LightDataManager.INSTANCE.lightView().getRegistered(pos, blockState); + final short registeredLight = LightDataManager.INSTANCE.lightLevel().getRegistered(pos, blockState); final boolean occluding = LightOp.occluder(registeredLight); final short getLight = lightData.get(index); @@ -182,18 +182,18 @@ public void markUrgent() { urgent = true; } - private boolean occludeSide(Side dir, LightView view, BlockPos pos) { + private boolean occludeSide(Side dir, LightLevelAccess lightLevel, BlockPos pos) { // vanilla checks state.useShapeForLightOcclusion() but here it's always false for some reason. this is fine... - var state = view.baseView().getBlockState(pos); + var state = lightLevel.level().getBlockState(pos); if (!state.canOcclude()) { return false; } - return Shapes.faceShapeOccludes(Shapes.empty(), state.getFaceOcclusionShape(view.baseView(), pos, dir.vanilla)); + return Shapes.faceShapeOccludes(Shapes.empty(), state.getFaceOcclusionShape(lightLevel.level(), pos, dir.vanilla)); } - void updateDecrease(LightView view, LongPriorityQueue neighborDecreaseQueue, LongPriorityQueue neighborIncreaseQueue) { + void updateDecrease(LightLevelAccess lightLevel, LongPriorityQueue neighborDecreaseQueue, LongPriorityQueue neighborIncreaseQueue) { // faster exit when not necessary if (globalDecQueue.isEmpty()) { return; @@ -262,14 +262,14 @@ void updateDecrease(LightView view, LongPriorityQueue neighborDecreaseQueue, Lon if (!LightOp.lit(nodeLight)) continue; // check neighbor occlusion for decrease - if (!LightOp.emitter(nodeLight) && occludeSide(side.opposite, view, nodePos)) { + if (!LightOp.emitter(nodeLight) && occludeSide(side.opposite, lightLevel, nodePos)) { continue; } // only propagate removal according to removeFlag removeMask.and(less.lessThan(nodeLight, sourcePrevLight), removeFlag); - final short registered = view.getRegistered(nodePos); + final short registered = lightLevel.getRegistered(nodePos); final boolean restoreLightSource = removeMask.any() && LightOp.emitter(registered); final short repropLight; @@ -317,10 +317,10 @@ void updateDecrease(LightView view, LongPriorityQueue neighborDecreaseQueue, Lon } } - void updateIncrease(LightView view, LongPriorityQueue neighborIncreaseQueue) { + void updateIncrease(LightLevelAccess lightLevel, LongPriorityQueue neighborIncreaseQueue) { if (needCheckEdges) { needCheckEdges = false; - checkEdges(view); + checkEdges(lightLevel); } while (!globalIncQueue.isEmpty()) { @@ -340,7 +340,7 @@ void updateIncrease(LightView view, LongPriorityQueue neighborIncreaseQueue) { if (LightOp.emitter(recordedLight)) { lightData.reverseIndexify(index, sourcePos); - if (view.getRegistered(sourcePos) != recordedLight) { + if (lightLevel.getRegistered(sourcePos) != recordedLight) { continue; } @@ -362,7 +362,7 @@ void updateIncrease(LightView view, LongPriorityQueue neighborIncreaseQueue) { } // check self occlusion for increase - if (!LightOp.emitter(sourceLight) && occludeSide(side, view, sourcePos)) { + if (!LightOp.emitter(sourceLight) && occludeSide(side, lightLevel, sourcePos)) { continue; } @@ -391,7 +391,7 @@ void updateIncrease(LightView view, LongPriorityQueue neighborIncreaseQueue) { final short nodeLight = dataAccess.get(nodeIndex); // check neighbor occlusion for increase - if (occludeSide(side.opposite, view, nodePos)) { + if (occludeSide(side.opposite, lightLevel, nodePos)) { continue; } @@ -413,14 +413,14 @@ void updateIncrease(LightView view, LongPriorityQueue neighborIncreaseQueue) { LightDataManager.INSTANCE.publicDrawQueue.add(origin); } - private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourcePos, BlockPos.MutableBlockPos targetPos, Side side, LightView view) { + private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourcePos, BlockPos.MutableBlockPos targetPos, Side side, LightLevelAccess lightLevel) { final int sourceIndex = neighbor.lightData.indexify(sourcePos); final short sourceLight = neighbor.lightData.get(sourceIndex); if (LightOp.lit(sourceLight)) { // TODO: generalize for all increase process, with check-neighbor flag // check self occlusion for increase - if (!LightOp.emitter(sourceLight) && occludeSide(side, view, sourcePos)) { + if (!LightOp.emitter(sourceLight) && occludeSide(side, lightLevel, sourcePos)) { return; } @@ -428,7 +428,7 @@ private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourc final short targetLight = lightData.get(targetIndex); // check neighbor occlusion for increase - if (occludeSide(side.opposite, view, targetPos)) { + if (occludeSide(side.opposite, lightLevel, targetPos)) { return; } @@ -440,7 +440,7 @@ private void checkEdgeBlock(LightRegion neighbor, BlockPos.MutableBlockPos sourc } } - private void checkEdges(LightView view) { + private void checkEdges(LightLevelAccess lightLevel) { final int size = LightRegionData.Const.WIDTH; final BlockPos.MutableBlockPos searchPos = new BlockPos.MutableBlockPos(); final BlockPos.MutableBlockPos targetPos = new BlockPos.MutableBlockPos(); @@ -464,7 +464,7 @@ private void checkEdges(LightView view) { for (int z = 0; z < size; z++) { searchPos.setWithOffset(originPos, x, y, z); targetPos.setWithOffset(originPos, xTarget, y, z); - checkEdgeBlock(neighbor, searchPos, targetPos, side, view); + checkEdgeBlock(neighbor, searchPos, targetPos, side, lightLevel); } } } @@ -487,7 +487,7 @@ private void checkEdges(LightView view) { for (int x = 0; x < size; x++) { searchPos.setWithOffset(originPos, x, y, z); targetPos.setWithOffset(originPos, x, yTarget, z); - checkEdgeBlock(neighbor, searchPos, targetPos, side, view); + checkEdgeBlock(neighbor, searchPos, targetPos, side, lightLevel); } } } @@ -509,7 +509,7 @@ private void checkEdges(LightView view) { for (int y = 0; y < size; y++) { searchPos.setWithOffset(originPos, x, y, z); targetPos.setWithOffset(originPos, x, y, zTarget); - checkEdgeBlock(neighbor, searchPos, targetPos, side, view); + checkEdgeBlock(neighbor, searchPos, targetPos, side, lightLevel); } } } diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java index 12243301b..ffe4f5026 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegistry.java +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -80,21 +80,39 @@ private static short generate(BlockState blockState) { final ItemStack stack = new ItemStack(blockState.getBlock(), 1); final ItemLight itemLight = ItemLight.get(stack); - if (itemLight == null) { + if (itemLight == ItemLight.NONE) { return defaultLight; } float maxValue = Math.max(itemLight.red(), Math.max(itemLight.green(), itemLight.blue())); - if (maxValue <= 0) { + if (maxValue <= 0 || itemLight.intensity() <= 0) { return defaultLight; } - final int r = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.red() / maxValue)); - final int g = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.green() / maxValue)); - final int b = org.joml.Math.clamp(1, 15, Math.round(lightLevel * itemLight.blue() / maxValue)); + return encodeItem(itemLight, lightLevel, isFullCube, blockState.canOcclude()); + } + + public static short encodeItem(ItemLight light) { + return encodeItem(light, -1, false, false); + } + + public static short encodeItem(ItemLight light, int lightLevel, boolean isFull, boolean isOccluding) { + if (light == null || light == ItemLight.NONE) { + return 0; + } + + if (lightLevel < 0) { + lightLevel = 15; + } + + final int r = org.joml.Math.clamp(0, 15, Math.round(((float) lightLevel) * light.intensity() * light.red())); + final int g = org.joml.Math.clamp(0, 15, Math.round(((float) lightLevel) * light.intensity() * light.green())); + final int b = org.joml.Math.clamp(0, 15, Math.round(((float) lightLevel) * light.intensity() * light.blue())); + + var result = LightOp.encode(r, g, b, 0); - return LightOp.encodeLight(r, g, b, isFullCube, true, blockState.canOcclude()); + return LightOp.encodeLight(result, isFull, LightOp.lit(result), isOccluding); } private static class DummyWorld implements BlockGetter { diff --git a/src/main/java/grondag/canvas/light/color/VirtualLightManager.java b/src/main/java/grondag/canvas/light/color/VirtualLightManager.java deleted file mode 100644 index b9d2ebe9b..000000000 --- a/src/main/java/grondag/canvas/light/color/VirtualLightManager.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * This file is part of Canvas Renderer and is licensed to the project under - * terms that are compatible with the GNU Lesser General Public License. - * See the NOTICE file distributed with this work for additional information - * regarding copyright ownership and licensing. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package grondag.canvas.light.color; - -import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; -import it.unimi.dsi.fastutil.shorts.ShortArrayList; -import org.jetbrains.annotations.Nullable; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.level.BlockAndTintGetter; -import net.minecraft.world.level.block.state.BlockState; - -import grondag.canvas.CanvasMod; - -public class VirtualLightManager implements LightView { - private BlockAndTintGetter baseView = null; - - private final Long2ObjectOpenHashMap positions = new Long2ObjectOpenHashMap<>(); - private final LongArrayFIFOQueue queue = new LongArrayFIFOQueue(); - private final ObjectOpenHashSet checkQueue = new ObjectOpenHashSet<>(); - - VirtualLightManager() { - // dummy test - // placeVirtualLight(new BlockPos(-278, 43, 6), LightOp.encodeLight(0x0, 0xf, 0x0, false, true, false)); - } - - void startFrame(BlockAndTintGetter blockView) { - baseView = blockView; - update(); - } - - void endFrame() { - // TODO: more elegant solution? - baseView = null; - } - - @Override - public BlockAndTintGetter baseView() { - return baseView; - } - - @Override - public short getRegistered(BlockPos pos) { - var registered = baseView == null ? 0 : LightRegistry.get(baseView.getBlockState(pos)); - return combineWithBlockLight(registered, getVirtualLight(pos)); - } - - @Override - public short getRegistered(BlockPos pos, @Nullable BlockState state) { - // MAINTENANCE NOTICE: this function is a special casing of getRegistered(BlockPos) - var registered = state != null ? LightRegistry.get(state) : (baseView == null ? 0 : LightRegistry.get(baseView.getBlockState(pos))); - return combineWithBlockLight(registered, getVirtualLight(pos)); - } - - @Override - public void placeVirtualLight(BlockPos blockPos, short light) { - final long pos = blockPos.asLong(); - final var list = positions.computeIfAbsent(pos, l -> new ShortArrayList()); - list.add(encodeVirtualLight(light)); - queue.enqueue(pos); - } - - @Override - public void removeVirtualLight(BlockPos blockPos, short light) { - final long pos = blockPos.asLong(); - final var list = positions.get(pos); - final int i = list == null ? -1 : list.indexOf(encodeVirtualLight(light)); - - if (i != -1) { - list.removeShort(i); - queue.enqueue(pos); - } - } - - private short getVirtualLight(BlockPos blockPos) { - ShortArrayList lights = positions.get(blockPos.asLong()); - - if (lights != null) { - short combined = 0; - - for (short light:lights) { - combined = LightOp.max(light, combined); - } - - return combined; - } - - return 0; - } - - private void update() { - while (!queue.isEmpty()) { - long pos = queue.dequeueLong(); - var region = LightDataManager.INSTANCE.get(pos); - - if (region != null) { - var blockPos = BlockPos.of(pos); - region.checkBlock(blockPos, null); - checkQueue.add(region); - } else if (positions.containsKey(pos)) { - // there are virtual lights (placed and never removed) but the region doesn't exist - CanvasMod.LOG.warn("Trying to update virtual lights on a null region. ID:" + pos); - } - } - - for (var region:checkQueue) { - region.markUrgent(); - region.submitChecks(); - } - - checkQueue.clear(); - } - - private static short encodeVirtualLight(short light) { - return LightOp.encodeLight(LightOp.pure(light), false, true, false); - } - - private static short combineWithBlockLight(short block, short virtual) { - return LightOp.makeEmitter(LightOp.max(block, virtual)); - } -} diff --git a/src/main/java/grondag/canvas/light/color/entity/EntityLightProvider.java b/src/main/java/grondag/canvas/light/color/entity/EntityLightProvider.java new file mode 100644 index 000000000..4bc208dfa --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/entity/EntityLightProvider.java @@ -0,0 +1,29 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.light.color.entity; + +import net.minecraft.core.BlockPos; + +public interface EntityLightProvider { + BlockPos canvas_getPos(); + + short canvas_getLight(); +} diff --git a/src/main/java/grondag/canvas/light/color/entity/EntityLightTracker.java b/src/main/java/grondag/canvas/light/color/entity/EntityLightTracker.java new file mode 100644 index 000000000..c885c81de --- /dev/null +++ b/src/main/java/grondag/canvas/light/color/entity/EntityLightTracker.java @@ -0,0 +1,149 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.light.color.entity; + +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; + +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; + +import grondag.canvas.light.color.LightLevelAccess; +import grondag.canvas.light.color.LightOp; + +public class EntityLightTracker { + private static EntityLightTracker INSTANCE; + private final Int2ObjectOpenHashMap entities = new Int2ObjectOpenHashMap<>(); + private final LightLevelAccess lightLevel; + private boolean requiresInitialization = true; + + public EntityLightTracker(LightLevelAccess lightLevel) { + INSTANCE = this; + this.lightLevel = lightLevel; + } + + public void update(ClientLevel level) { + if (requiresInitialization) { + requiresInitialization = false; + + var entities = level.entitiesForRendering(); + + for (var entity:entities) { + trackAny(entity); + } + } + + for (var entity : entities.values()) { + entity.update(); + } + } + + public static void levelAddsEntity(Entity entity) { + if (INSTANCE != null && !INSTANCE.requiresInitialization) { + INSTANCE.trackAny(entity); + } + } + + public static void levelRemovesEntity(int id) { + if (INSTANCE != null && !INSTANCE.requiresInitialization) { + INSTANCE.removeAny(id); + } + } + + public void reload() { + requiresInitialization = true; + // might be called twice due to multiple hook (setLevel and allChanged). idempotent. + removeAll(); + } + + public void close(boolean lightLevelIsClosing) { + if (!lightLevelIsClosing) { + removeAll(); + } + + if (INSTANCE == this) { + INSTANCE = null; + } + } + + private void removeAll() { + // see notes on reload() + for (var entity:entities.values()) { + entity.removeLight(); + } + + entities.clear(); + } + + private void trackAny(Entity entity) { + try { + EntityLightProvider lightProvider = (EntityLightProvider) entity; + entities.put(entity.getId(), new TrackedEntity(lightProvider)); + } catch (ClassCastException ignored) { + // eat 'em + } + } + + private void removeAny(int id) { + if (entities.containsKey(id)) { + entities.remove(id).removeLight(); + } + } + + private class TrackedEntity { + final EntityLightProvider entity; + private final BlockPos.MutableBlockPos lastTrackedPos = new BlockPos.MutableBlockPos(); + private short lastTrackedLight = 0; + + TrackedEntity(EntityLightProvider entity) { + this.entity = entity; + lastTrackedPos.set(entity.canvas_getPos()); + } + + void update() { + final BlockPos pos = entity.canvas_getPos(); + final short light = entity.canvas_getLight(); + final boolean changedLight = lastTrackedLight != light; + final boolean changedPos = LightOp.emitter(light) && !lastTrackedPos.equals(pos); + + if (changedLight || changedPos) { + if (LightOp.emitter(lastTrackedLight)) { + lightLevel.removeVirtualLight(lastTrackedPos, lastTrackedLight); + } + + if (LightOp.emitter(light)) { + lightLevel.placeVirtualLight(pos, light); + } + + System.out.println("Changed," + changedLight + "," + changedPos + "," + entity); + } + + lastTrackedLight = light; + lastTrackedPos.set(pos); + } + + public void removeLight() { + if (LightOp.emitter(lastTrackedLight)) { + lightLevel.removeVirtualLight(entity.canvas_getPos(), lastTrackedLight); + } + } + } +} diff --git a/src/main/java/grondag/canvas/mixin/MixinClientLevel.java b/src/main/java/grondag/canvas/mixin/MixinClientLevel.java new file mode 100644 index 000000000..ef89ad967 --- /dev/null +++ b/src/main/java/grondag/canvas/mixin/MixinClientLevel.java @@ -0,0 +1,44 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.world.entity.Entity; + +import grondag.canvas.light.color.entity.EntityLightTracker; + +@Mixin(ClientLevel.class) +public class MixinClientLevel { + @Inject(method = "addEntity", at = @At("TAIL")) + void onAddEntity(int i, Entity entity, CallbackInfo ci) { + EntityLightTracker.levelAddsEntity(entity); + } + + @Inject(method = "removeEntity", at = @At("HEAD")) + void onRemoveEntity(int id, Entity.RemovalReason removalReason, CallbackInfo ci) { + EntityLightTracker.levelRemovesEntity(id); + } +} diff --git a/src/main/java/grondag/canvas/mixin/MixinItemEntity.java b/src/main/java/grondag/canvas/mixin/MixinItemEntity.java new file mode 100644 index 000000000..7e50e265c --- /dev/null +++ b/src/main/java/grondag/canvas/mixin/MixinItemEntity.java @@ -0,0 +1,52 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.mixin; + +import org.spongepowered.asm.mixin.Mixin; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.item.ItemEntity; + +import io.vram.frex.api.light.ItemLight; + +import grondag.canvas.light.color.LightRegistry; +import grondag.canvas.light.color.entity.EntityLightProvider; + +@Mixin(ItemEntity.class) +public class MixinItemEntity implements EntityLightProvider { + @Override + public BlockPos canvas_getPos() { + return ((ItemEntity) (Object) this).blockPosition(); + } + + @Override + public short canvas_getLight() { + final var itemEntity = (ItemEntity) (Object) this; + + var light = ItemLight.get(itemEntity.getItem()); + + if (!light.worksInFluid() && itemEntity.isUnderWater()) { + light = ItemLight.NONE; + } + + return LightRegistry.encodeItem(light); + } +} diff --git a/src/main/java/grondag/canvas/mixin/MixinLivingEntity.java b/src/main/java/grondag/canvas/mixin/MixinLivingEntity.java new file mode 100644 index 000000000..5aa2fd1e4 --- /dev/null +++ b/src/main/java/grondag/canvas/mixin/MixinLivingEntity.java @@ -0,0 +1,66 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; + +import io.vram.frex.api.light.HeldItemLightListener; +import io.vram.frex.api.light.ItemLight; + +import grondag.canvas.light.color.LightRegistry; +import grondag.canvas.light.color.entity.EntityLightProvider; + +@Mixin(LivingEntity.class) +public abstract class MixinLivingEntity implements EntityLightProvider { + @Shadow public abstract void remove(Entity.RemovalReason removalReason); + + @Override + public BlockPos canvas_getPos() { + return ((LivingEntity) (Object) this).blockPosition(); + } + + @Override + public short canvas_getLight() { + final LivingEntity livingEntity = (LivingEntity) (Object) this; + + final var mainItem = livingEntity.getMainHandItem(); + final var mainLight = ItemLight.get(mainItem); + + var light = HeldItemLightListener.apply(livingEntity, mainItem, mainLight); + + if (light.equals(ItemLight.NONE)) { + final var offItem = livingEntity.getOffhandItem(); + final var offLight = ItemLight.get(offItem); + light = HeldItemLightListener.apply(livingEntity, offItem, offLight); + } + + if (!light.worksInFluid() && livingEntity.isUnderWater()) { + light = ItemLight.NONE; + } + + return LightRegistry.encodeItem(light); + } +} diff --git a/src/main/resources/assets/canvas/lang/en_us.json b/src/main/resources/assets/canvas/lang/en_us.json index a9eed5bdf..8724e6228 100644 --- a/src/main/resources/assets/canvas/lang/en_us.json +++ b/src/main/resources/assets/canvas/lang/en_us.json @@ -54,7 +54,9 @@ "config.canvas.value.semi_flat_lighting": "Semi-Flat Lightmap", "config.canvas.help.semi_flat_lighting": "Models with flat lighting have smoother lighting;(but no ambient occlusion).", "config.canvas.value.colored_lights": "Colored Lights", - "config.canvas.help.colored_lights": "Enable client-side colored block lights for pipelines that supports it. Will replace server lighting visually.", + "config.canvas.help.colored_lights": "Enable colored block lights on pipelines that support it. Replaces vanilla lighting but only visually.", + "config.canvas.value.entity_light_source": "Entity Light Source", + "config.canvas.help.entity_light_source": "Enable entity as dynamic light sources. Requires colored lights.", "config.canvas.enum.ao_mode.normal": "Vanilla", "config.canvas.enum.ao_mode.subtle_always": "Subtle", "config.canvas.enum.ao_mode.subtle_block_light": "Subtle Torchlit", From cde4831912debc7ffb37af345fd86153e5273142 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Sun, 7 Jan 2024 23:24:54 +0700 Subject: [PATCH 66/69] Fewer mixin approach, turn off local player's virtual light for now --- .../main/resources/mixins.canvas.client.json | 2 - .../{entity => }/EntityLightTracker.java | 107 ++++++++++++++---- .../canvas/light/color/LightLevel.java | 1 - .../color/entity/EntityLightProvider.java | 29 ----- .../canvas/mixin/MixinClientLevel.java | 2 +- .../grondag/canvas/mixin/MixinItemEntity.java | 52 --------- .../canvas/mixin/MixinLivingEntity.java | 66 ----------- 7 files changed, 86 insertions(+), 173 deletions(-) rename src/main/java/grondag/canvas/light/color/{entity => }/EntityLightTracker.java (50%) delete mode 100644 src/main/java/grondag/canvas/light/color/entity/EntityLightProvider.java delete mode 100644 src/main/java/grondag/canvas/mixin/MixinItemEntity.java delete mode 100644 src/main/java/grondag/canvas/mixin/MixinLivingEntity.java diff --git a/fabric/src/main/resources/mixins.canvas.client.json b/fabric/src/main/resources/mixins.canvas.client.json index ebb9e6cd6..155c34be3 100644 --- a/fabric/src/main/resources/mixins.canvas.client.json +++ b/fabric/src/main/resources/mixins.canvas.client.json @@ -27,13 +27,11 @@ "MixinGui", "MixinGuiGraphics", "MixinHumanoidArmorLayer", - "MixinItemEntity", "MixinItemFrameRenderer", "MixinItemRenderer", "MixinLevelChunk", "MixinLevelRenderer", "MixinLightTexture", - "MixinLivingEntity", "MixinMinecraft", "MixinModelBlockRenderer", "MixinNativeImage", diff --git a/src/main/java/grondag/canvas/light/color/entity/EntityLightTracker.java b/src/main/java/grondag/canvas/light/color/EntityLightTracker.java similarity index 50% rename from src/main/java/grondag/canvas/light/color/entity/EntityLightTracker.java rename to src/main/java/grondag/canvas/light/color/EntityLightTracker.java index c885c81de..5c6593217 100644 --- a/src/main/java/grondag/canvas/light/color/entity/EntityLightTracker.java +++ b/src/main/java/grondag/canvas/light/color/EntityLightTracker.java @@ -18,29 +18,36 @@ * along with this program. If not, see . */ -package grondag.canvas.light.color.entity; +package grondag.canvas.light.color; + +import java.util.function.Function; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.LocalPlayer; import net.minecraft.core.BlockPos; import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.level.entity.EntityAccess; -import grondag.canvas.light.color.LightLevelAccess; -import grondag.canvas.light.color.LightOp; +import io.vram.frex.api.light.HeldItemLightListener; +import io.vram.frex.api.light.ItemLight; public class EntityLightTracker { private static EntityLightTracker INSTANCE; - private final Int2ObjectOpenHashMap entities = new Int2ObjectOpenHashMap<>(); + private final Int2ObjectOpenHashMap> entities = new Int2ObjectOpenHashMap<>(); private final LightLevelAccess lightLevel; private boolean requiresInitialization = true; - public EntityLightTracker(LightLevelAccess lightLevel) { + EntityLightTracker(LightLevelAccess lightLevel) { INSTANCE = this; this.lightLevel = lightLevel; } - public void update(ClientLevel level) { + void update(ClientLevel level) { if (requiresInitialization) { requiresInitialization = false; @@ -68,13 +75,13 @@ public static void levelRemovesEntity(int id) { } } - public void reload() { + void reload() { requiresInitialization = true; // might be called twice due to multiple hook (setLevel and allChanged). idempotent. removeAll(); } - public void close(boolean lightLevelIsClosing) { + void close(boolean lightLevelIsClosing) { if (!lightLevelIsClosing) { removeAll(); } @@ -94,12 +101,21 @@ private void removeAll() { } private void trackAny(Entity entity) { - try { - EntityLightProvider lightProvider = (EntityLightProvider) entity; - entities.put(entity.getId(), new TrackedEntity(lightProvider)); - } catch (ClassCastException ignored) { - // eat 'em + final TrackedEntity trackedEntity; + + // supported entity types. TODO: json / api based entity mapping + if (entity == Minecraft.getInstance().player) { + // we already have shader held light. TODO: json / api support + return; + } else if (entity instanceof LivingEntity livingEntity) { + trackedEntity = new TrackedEntity<>(livingEntity, new HeldLightSupplier()); + } else if (entity instanceof ItemEntity itemEntity) { + trackedEntity = new TrackedEntity<>(itemEntity, new ItemLightSupplier()); + } else { + return; } + + entities.put(entity.getId(), trackedEntity); } private void removeAny(int id) { @@ -108,19 +124,68 @@ private void removeAny(int id) { } } - private class TrackedEntity { - final EntityLightProvider entity; + // Unused at the moment + private static class ThirdPersonSupplier implements Function { + private final HeldLightSupplier heldLightSupplier = new HeldLightSupplier(); + + @Override + public Short apply(LocalPlayer localPlayer) { + final boolean firstPerson = Minecraft.getInstance().options.getCameraType().isFirstPerson(); + return firstPerson ? 0 : heldLightSupplier.apply(localPlayer); + } + } + + private static class ItemLightSupplier implements Function { + @Override + public Short apply(ItemEntity itemEntity) { + ItemLight light = ItemLight.get(itemEntity.getItem()); + + if (!light.worksInFluid() && itemEntity.isUnderWater()) { + light = ItemLight.NONE; + } + + return LightRegistry.encodeItem(light); + } + } + + private static class HeldLightSupplier implements Function { + @Override + public Short apply(LivingEntity livingEntity) { + final var mainItem = livingEntity.getMainHandItem(); + final var mainLight = ItemLight.get(mainItem); + + var light = HeldItemLightListener.apply(livingEntity, mainItem, mainLight); + + if (light.equals(ItemLight.NONE)) { + final var offItem = livingEntity.getOffhandItem(); + final var offLight = ItemLight.get(offItem); + light = HeldItemLightListener.apply(livingEntity, offItem, offLight); + } + + if (!light.worksInFluid() && livingEntity.isUnderWater()) { + light = ItemLight.NONE; + } + + return LightRegistry.encodeItem(light); + } + } + + private class TrackedEntity { + private final E entity; + private final Function lightSupplier; + private final BlockPos.MutableBlockPos lastTrackedPos = new BlockPos.MutableBlockPos(); private short lastTrackedLight = 0; - TrackedEntity(EntityLightProvider entity) { + TrackedEntity(E entity, Function lightSupplier) { this.entity = entity; - lastTrackedPos.set(entity.canvas_getPos()); + this.lightSupplier = lightSupplier; + lastTrackedPos.set(entity.blockPosition()); } void update() { - final BlockPos pos = entity.canvas_getPos(); - final short light = entity.canvas_getLight(); + final BlockPos pos = entity.blockPosition(); + final short light = lightSupplier.apply(entity); final boolean changedLight = lastTrackedLight != light; final boolean changedPos = LightOp.emitter(light) && !lastTrackedPos.equals(pos); @@ -132,8 +197,6 @@ void update() { if (LightOp.emitter(light)) { lightLevel.placeVirtualLight(pos, light); } - - System.out.println("Changed," + changedLight + "," + changedPos + "," + entity); } lastTrackedLight = light; @@ -142,7 +205,7 @@ void update() { public void removeLight() { if (LightOp.emitter(lastTrackedLight)) { - lightLevel.removeVirtualLight(entity.canvas_getPos(), lastTrackedLight); + lightLevel.removeVirtualLight(entity.blockPosition(), lastTrackedLight); } } } diff --git a/src/main/java/grondag/canvas/light/color/LightLevel.java b/src/main/java/grondag/canvas/light/color/LightLevel.java index fc209faad..e2c7fdfea 100644 --- a/src/main/java/grondag/canvas/light/color/LightLevel.java +++ b/src/main/java/grondag/canvas/light/color/LightLevel.java @@ -33,7 +33,6 @@ import grondag.canvas.CanvasMod; import grondag.canvas.config.Configurator; -import grondag.canvas.light.color.entity.EntityLightTracker; class LightLevel implements LightLevelAccess { private BlockAndTintGetter baseLevel = null; diff --git a/src/main/java/grondag/canvas/light/color/entity/EntityLightProvider.java b/src/main/java/grondag/canvas/light/color/entity/EntityLightProvider.java deleted file mode 100644 index 4bc208dfa..000000000 --- a/src/main/java/grondag/canvas/light/color/entity/EntityLightProvider.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of Canvas Renderer and is licensed to the project under - * terms that are compatible with the GNU Lesser General Public License. - * See the NOTICE file distributed with this work for additional information - * regarding copyright ownership and licensing. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package grondag.canvas.light.color.entity; - -import net.minecraft.core.BlockPos; - -public interface EntityLightProvider { - BlockPos canvas_getPos(); - - short canvas_getLight(); -} diff --git a/src/main/java/grondag/canvas/mixin/MixinClientLevel.java b/src/main/java/grondag/canvas/mixin/MixinClientLevel.java index ef89ad967..67011d2b9 100644 --- a/src/main/java/grondag/canvas/mixin/MixinClientLevel.java +++ b/src/main/java/grondag/canvas/mixin/MixinClientLevel.java @@ -28,7 +28,7 @@ import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.world.entity.Entity; -import grondag.canvas.light.color.entity.EntityLightTracker; +import grondag.canvas.light.color.EntityLightTracker; @Mixin(ClientLevel.class) public class MixinClientLevel { diff --git a/src/main/java/grondag/canvas/mixin/MixinItemEntity.java b/src/main/java/grondag/canvas/mixin/MixinItemEntity.java deleted file mode 100644 index 7e50e265c..000000000 --- a/src/main/java/grondag/canvas/mixin/MixinItemEntity.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of Canvas Renderer and is licensed to the project under - * terms that are compatible with the GNU Lesser General Public License. - * See the NOTICE file distributed with this work for additional information - * regarding copyright ownership and licensing. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package grondag.canvas.mixin; - -import org.spongepowered.asm.mixin.Mixin; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.entity.item.ItemEntity; - -import io.vram.frex.api.light.ItemLight; - -import grondag.canvas.light.color.LightRegistry; -import grondag.canvas.light.color.entity.EntityLightProvider; - -@Mixin(ItemEntity.class) -public class MixinItemEntity implements EntityLightProvider { - @Override - public BlockPos canvas_getPos() { - return ((ItemEntity) (Object) this).blockPosition(); - } - - @Override - public short canvas_getLight() { - final var itemEntity = (ItemEntity) (Object) this; - - var light = ItemLight.get(itemEntity.getItem()); - - if (!light.worksInFluid() && itemEntity.isUnderWater()) { - light = ItemLight.NONE; - } - - return LightRegistry.encodeItem(light); - } -} diff --git a/src/main/java/grondag/canvas/mixin/MixinLivingEntity.java b/src/main/java/grondag/canvas/mixin/MixinLivingEntity.java deleted file mode 100644 index 5aa2fd1e4..000000000 --- a/src/main/java/grondag/canvas/mixin/MixinLivingEntity.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * This file is part of Canvas Renderer and is licensed to the project under - * terms that are compatible with the GNU Lesser General Public License. - * See the NOTICE file distributed with this work for additional information - * regarding copyright ownership and licensing. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package grondag.canvas.mixin; - -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -import net.minecraft.core.BlockPos; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.LivingEntity; - -import io.vram.frex.api.light.HeldItemLightListener; -import io.vram.frex.api.light.ItemLight; - -import grondag.canvas.light.color.LightRegistry; -import grondag.canvas.light.color.entity.EntityLightProvider; - -@Mixin(LivingEntity.class) -public abstract class MixinLivingEntity implements EntityLightProvider { - @Shadow public abstract void remove(Entity.RemovalReason removalReason); - - @Override - public BlockPos canvas_getPos() { - return ((LivingEntity) (Object) this).blockPosition(); - } - - @Override - public short canvas_getLight() { - final LivingEntity livingEntity = (LivingEntity) (Object) this; - - final var mainItem = livingEntity.getMainHandItem(); - final var mainLight = ItemLight.get(mainItem); - - var light = HeldItemLightListener.apply(livingEntity, mainItem, mainLight); - - if (light.equals(ItemLight.NONE)) { - final var offItem = livingEntity.getOffhandItem(); - final var offLight = ItemLight.get(offItem); - light = HeldItemLightListener.apply(livingEntity, offItem, offLight); - } - - if (!light.worksInFluid() && livingEntity.isUnderWater()) { - light = ItemLight.NONE; - } - - return LightRegistry.encodeItem(light); - } -} From f5f78daec95305782534ed176c5f65f304f81122 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Mon, 8 Jan 2024 01:59:31 +0700 Subject: [PATCH 67/69] Entity JSON light loader, refactor CachedBlockLight - Entity JSON light is a simple light without predicate - CachedBlockLight -> FloodFillBlockLight, make all values consistent - `blaze` and `glow_squid` light in canvas/default resource pack - EntityLightProvider API (unused for now) --- .../grondag/canvas/light/api/BlockLight.java | 23 ++- .../canvas/light/api/EntityLightProvider.java | 31 ++++ .../light/api/impl/BlockLightLoader.java | 137 ++++++++---------- .../light/api/impl/FloodFillBlockLight.java | 114 +++++++++++++++ .../light/color/EntityLightTracker.java | 18 ++- .../canvas/light/color/LightRegistry.java | 9 +- .../assets/minecraft/lights/entity/blaze.json | 9 ++ .../minecraft/lights/entity/glow_squid.json | 9 ++ 8 files changed, 252 insertions(+), 98 deletions(-) create mode 100644 src/main/java/grondag/canvas/light/api/EntityLightProvider.java create mode 100644 src/main/java/grondag/canvas/light/api/impl/FloodFillBlockLight.java create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/entity/blaze.json create mode 100644 src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/entity/glow_squid.json diff --git a/src/main/java/grondag/canvas/light/api/BlockLight.java b/src/main/java/grondag/canvas/light/api/BlockLight.java index a834c2ae7..cb1a46bb7 100644 --- a/src/main/java/grondag/canvas/light/api/BlockLight.java +++ b/src/main/java/grondag/canvas/light/api/BlockLight.java @@ -20,10 +20,10 @@ package grondag.canvas.light.api; +import grondag.canvas.light.api.impl.FloodFillBlockLight; + /** * BlockLight API draft. - * - *

Similar to Material, this needs to be constructed by a factory provided by implementation. */ public interface BlockLight { /** @@ -35,34 +35,33 @@ public interface BlockLight { *

Typical value is in range 0-15. Value outside of this range is implementation-specific. * *

In JSON format, defaults to the vanilla registered light level when missing. - * Importantly, light level is attached to blocks, so for fluid states - * (not their block counterpart) the default is always 0. - * - * @return Raw light level value + * Importantly, light level is attached to block states. Fluid states will attempt + * to default to their block state counterpart. */ float lightLevel(); /** * Red intensity. Behavior of values outside of range 0-1 is undefined. * In JSON format, defaults to 0 when missing. - * - * @return Raw red intensity */ float red(); /** * Green intensity. Behavior of values outside of range 0-1 is undefined. * In JSON format, defaults to 0 when missing. - * - * @return Raw green intensity */ float green(); /** * Blue intensity. Behavior of values outside of range 0-1 is undefined. * In JSON format, defaults to 0 when missing. - * - * @return Raw blue intensity */ float blue(); + + /** + * Constructs an implementation consistent instance of BlockLight. + */ + static BlockLight of(float lightLevel, float red, float green, float blue) { + return new FloodFillBlockLight(lightLevel, red, green, blue, true); + } } diff --git a/src/main/java/grondag/canvas/light/api/EntityLightProvider.java b/src/main/java/grondag/canvas/light/api/EntityLightProvider.java new file mode 100644 index 000000000..4210fec06 --- /dev/null +++ b/src/main/java/grondag/canvas/light/api/EntityLightProvider.java @@ -0,0 +1,31 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.light.api; + +/** + * Implement this interface to treat a particular entity as a dynamic light source. + */ +public interface EntityLightProvider { + /** + * The block light value of this entity in the current frame. + */ + BlockLight getBlockLight(); +} diff --git a/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java b/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java index d3085efa3..9be14e9a2 100644 --- a/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java +++ b/src/main/java/grondag/canvas/light/api/impl/BlockLightLoader.java @@ -32,6 +32,7 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.util.GsonHelper; +import net.minecraft.world.entity.EntityType; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateHolder; @@ -39,29 +40,33 @@ import net.minecraft.world.level.material.FluidState; import grondag.canvas.CanvasMod; -import grondag.canvas.light.api.BlockLight; -import grondag.canvas.light.color.LightOp; public class BlockLightLoader { - public static BlockLightLoader INSTANCE; - private static final CachedBlockLight DEFAULT_LIGHT = new CachedBlockLight(0f, 0f, 0f, 0f, false); + public static final FloodFillBlockLight DEFAULT_LIGHT = new FloodFillBlockLight(0f, 0f, 0f, 0f, false); - public final IdentityHashMap blockLights = new IdentityHashMap<>(); - public final IdentityHashMap fluidLights = new IdentityHashMap<>(); + public static final IdentityHashMap BLOCK_LIGHTS = new IdentityHashMap<>(); + public static final IdentityHashMap FLUID_LIGHTS = new IdentityHashMap<>(); + public static final IdentityHashMap, FloodFillBlockLight> ENTITY_LIGHTS = new IdentityHashMap<>(); public static void reload(ResourceManager manager) { - INSTANCE = new BlockLightLoader(); + BLOCK_LIGHTS.clear(); + FLUID_LIGHTS.clear(); + ENTITY_LIGHTS.clear(); for (Block block : BuiltInRegistries.BLOCK) { - INSTANCE.loadBlock(manager, block); + loadBlock(manager, block); } for (Fluid fluid : BuiltInRegistries.FLUID) { - INSTANCE.loadFluid(manager, fluid); + loadFluid(manager, fluid); + } + + for (EntityType type : BuiltInRegistries.ENTITY_TYPE) { + loadEntity(manager, type); } } - private void loadBlock(ResourceManager manager, Block block) { + private static void loadBlock(ResourceManager manager, Block block) { final ResourceLocation blockId = BuiltInRegistries.BLOCK.getKey(block); final ResourceLocation id = new ResourceLocation(blockId.getNamespace(), "lights/block/" + blockId.getPath() + ".json"); @@ -70,14 +75,14 @@ private void loadBlock(ResourceManager manager, Block block) { final var res = manager.getResource(id); if (res.isPresent()) { - deserialize(block.getStateDefinition().getPossibleStates(), id, new InputStreamReader(res.get().open(), StandardCharsets.UTF_8), blockLights); + deserialize(block.getStateDefinition().getPossibleStates(), id, new InputStreamReader(res.get().open(), StandardCharsets.UTF_8), BLOCK_LIGHTS); } } catch (final Exception e) { CanvasMod.LOG.info("Unable to load block light map " + id.toString() + " due to exception " + e.toString()); } } - private void loadFluid(ResourceManager manager, Fluid fluid) { + private static void loadFluid(ResourceManager manager, Fluid fluid) { final ResourceLocation blockId = BuiltInRegistries.FLUID.getKey(fluid); final ResourceLocation id = new ResourceLocation(blockId.getNamespace(), "lights/fluid/" + blockId.getPath() + ".json"); @@ -86,20 +91,35 @@ private void loadFluid(ResourceManager manager, Fluid fluid) { final var res = manager.getResource(id); if (res.isPresent()) { - deserialize(fluid.getStateDefinition().getPossibleStates(), id, new InputStreamReader(res.get().open(), StandardCharsets.UTF_8), fluidLights); + deserialize(fluid.getStateDefinition().getPossibleStates(), id, new InputStreamReader(res.get().open(), StandardCharsets.UTF_8), FLUID_LIGHTS); } } catch (final Exception e) { CanvasMod.LOG.info("Unable to load fluid light map " + id.toString() + " due to exception " + e.toString()); } } - public static > void deserialize(List states, ResourceLocation idForLog, InputStreamReader reader, IdentityHashMap map) { + private static void loadEntity(ResourceManager manager, EntityType entityType) { + final ResourceLocation entityId = BuiltInRegistries.ENTITY_TYPE.getKey(entityType); + final ResourceLocation id = new ResourceLocation(entityId.getNamespace(), "lights/entity/" + entityId.getPath() + ".json"); + + try { + final var res = manager.getResource(id); + + if (res.isPresent()) { + deserialize(entityType, id, new InputStreamReader(res.get().open(), StandardCharsets.UTF_8), ENTITY_LIGHTS); + } + } catch (final Exception e) { + CanvasMod.LOG.info("Unable to load block light map " + id.toString() + " due to exception " + e.toString()); + } + } + + private static > void deserialize(List states, ResourceLocation idForLog, InputStreamReader reader, IdentityHashMap map) { try { final JsonObject json = GsonHelper.parse(reader); final String idString = idForLog.toString(); - final CachedBlockLight globalDefaultLight = DEFAULT_LIGHT; - final CachedBlockLight defaultLight; + final FloodFillBlockLight globalDefaultLight = DEFAULT_LIGHT; + final FloodFillBlockLight defaultLight; if (json.has("defaultLight")) { defaultLight = loadLight(json.get("defaultLight").getAsJsonObject(), globalDefaultLight); @@ -119,7 +139,7 @@ private void loadFluid(ResourceManager manager, Fluid fluid) { } for (final T state : states) { - CachedBlockLight result = defaultLight; + FloodFillBlockLight result = defaultLight; if (!result.levelIsSet && state instanceof BlockState blockState) { result = result.withLevel(blockState.getLightEmission()); @@ -139,7 +159,27 @@ private void loadFluid(ResourceManager manager, Fluid fluid) { } } - public static CachedBlockLight loadLight(JsonObject obj, CachedBlockLight defaultValue) { + private static void deserialize(T type, ResourceLocation idForLog, InputStreamReader reader, IdentityHashMap map) { + try { + final JsonObject json = GsonHelper.parse(reader); + final FloodFillBlockLight globalDefaultLight = BlockLightLoader.DEFAULT_LIGHT; + final FloodFillBlockLight result; + + if (json.has("defaultLight")) { + result = BlockLightLoader.loadLight(json.get("defaultLight").getAsJsonObject(), globalDefaultLight); + } else { + result = BlockLightLoader.loadLight(json, globalDefaultLight); + } + + if (!result.equals(globalDefaultLight) && result.levelIsSet) { + map.put(type, result); + } + } catch (final Exception e) { + CanvasMod.LOG.warn("Unable to load lights for " + idForLog.toString() + " due to unhandled exception:", e); + } + } + + private static FloodFillBlockLight loadLight(JsonObject obj, FloodFillBlockLight defaultValue) { if (obj == null) { return defaultValue; } @@ -149,12 +189,13 @@ public static CachedBlockLight loadLight(JsonObject obj, CachedBlockLight defaul final var greenObj = obj.get("green"); final var blueObj = obj.get("blue"); - final float lightLevel = lightLevelObj == null ? defaultValue.lightLevel() : lightLevelObj.getAsFloat(); + final float defaultLightLevel = defaultValue.levelIsSet ? defaultValue.lightLevel() : 15f; + final float lightLevel = lightLevelObj == null ? defaultLightLevel : lightLevelObj.getAsFloat(); final float red = redObj == null ? defaultValue.red() : redObj.getAsFloat(); final float green = greenObj == null ? defaultValue.green() : greenObj.getAsFloat(); final float blue = blueObj == null ? defaultValue.blue() : blueObj.getAsFloat(); - final boolean levelIsSet = lightLevelObj == null ? defaultValue.levelIsSet() : true; - final var result = new CachedBlockLight(lightLevel, red, green, blue, levelIsSet); + final boolean levelIsSet = lightLevelObj != null || defaultValue.levelIsSet; + final var result = new FloodFillBlockLight(lightLevel, red, green, blue, levelIsSet); if (result.equals(defaultValue)) { return defaultValue; @@ -162,58 +203,4 @@ public static CachedBlockLight loadLight(JsonObject obj, CachedBlockLight defaul return result; } } - - private static int clampLight(float light) { - return org.joml.Math.clamp(0, 15, Math.round(light)); - } - - public static record CachedBlockLight(float lightLevel, float red, float green, float blue, short value, boolean levelIsSet) implements BlockLight { - CachedBlockLight(float lightLevel, float red, float green, float blue, boolean levelIsSet) { - this(lightLevel, red, green, blue, computeValue(lightLevel, red, green, blue), levelIsSet); - } - - public CachedBlockLight withLevel(float lightEmission) { - if (this.lightLevel == lightEmission && this.levelIsSet) { - return this; - } else { - return new CachedBlockLight(lightEmission, red, green, blue, true); - } - } - - static short computeValue(float lightLevel, float red, float green, float blue) { - final int blockRadius = lightLevel == 0f ? 0 : org.joml.Math.clamp(1, 15, Math.round(lightLevel)); - return LightOp.encode(clampLight(blockRadius * red), clampLight(blockRadius * green), clampLight(blockRadius * blue), 0); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (obj == null || getClass() != obj.getClass()) { - return false; - } - - CachedBlockLight that = (CachedBlockLight) obj; - - if (that.lightLevel != lightLevel) { - return false; - } - - if (that.red != red) { - return false; - } - - if (that.green != green) { - return false; - } - - if (that.blue != blue) { - return false; - } - - return value == that.value; - } - } } diff --git a/src/main/java/grondag/canvas/light/api/impl/FloodFillBlockLight.java b/src/main/java/grondag/canvas/light/api/impl/FloodFillBlockLight.java new file mode 100644 index 000000000..8babe3596 --- /dev/null +++ b/src/main/java/grondag/canvas/light/api/impl/FloodFillBlockLight.java @@ -0,0 +1,114 @@ +/* + * This file is part of Canvas Renderer and is licensed to the project under + * terms that are compatible with the GNU Lesser General Public License. + * See the NOTICE file distributed with this work for additional information + * regarding copyright ownership and licensing. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package grondag.canvas.light.api.impl; + +import grondag.canvas.light.api.BlockLight; +import grondag.canvas.light.color.LightOp; + +public final class FloodFillBlockLight implements BlockLight { + public final short value; + public final boolean levelIsSet; + public final float red, green, blue, lightLevel; + + public FloodFillBlockLight(short value, boolean levelIsSet) { + this.value = value; + this.levelIsSet = levelIsSet; + this.lightLevel = Math.max(LightOp.R.of(value), Math.max(LightOp.G.of(value), LightOp.B.of(value))); + this.red = LightOp.R.of(value) / 15.0f; + this.green = LightOp.G.of(value) / 15.0f; + this.blue = LightOp.B.of(value) / 15.0f; + } + + public FloodFillBlockLight(float lightLevel, float red, float green, float blue, boolean levelIsSet) { + this(computeValue(lightLevel, red, green, blue), levelIsSet); + } + + public FloodFillBlockLight withLevel(float lightEmission) { + if (this.lightLevel() == lightEmission && this.levelIsSet) { + return this; + } else { + return new FloodFillBlockLight(lightEmission, red(), green(), blue(), true); + } + } + + static short computeValue(float lightLevel, float red, float green, float blue) { + final int blockRadius = lightLevel == 0f ? 0 : org.joml.Math.clamp(1, 15, Math.round(lightLevel)); + return LightOp.encode(clampLight(blockRadius * red), clampLight(blockRadius * green), clampLight(blockRadius * blue), 0); + } + + private static int clampLight(float light) { + return org.joml.Math.clamp(0, 15, Math.round(light)); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (!(obj instanceof BlockLight that)) { + return false; + } + + if (this.lightLevel() == 0f && that.lightLevel() == 0f) { + // both are completely dark regardless of color + return true; + } + + if (obj instanceof FloodFillBlockLight floodFillBlockLight) { + return this.value == floodFillBlockLight.value && this.levelIsSet == floodFillBlockLight.levelIsSet; + } + + if (that.red() != this.red()) { + return false; + } + + if (that.green() != this.green()) { + return false; + } + + if (that.blue() != this.blue()) { + return false; + } + + return that.lightLevel() == this.lightLevel(); + } + + @Override + public float lightLevel() { + return lightLevel; + } + + @Override + public float red() { + return red; + } + + @Override + public float green() { + return green; + } + + @Override + public float blue() { + return blue; + } +} diff --git a/src/main/java/grondag/canvas/light/color/EntityLightTracker.java b/src/main/java/grondag/canvas/light/color/EntityLightTracker.java index 5c6593217..f660124e9 100644 --- a/src/main/java/grondag/canvas/light/color/EntityLightTracker.java +++ b/src/main/java/grondag/canvas/light/color/EntityLightTracker.java @@ -36,6 +36,8 @@ import io.vram.frex.api.light.HeldItemLightListener; import io.vram.frex.api.light.ItemLight; +import grondag.canvas.light.api.impl.BlockLightLoader; + public class EntityLightTracker { private static EntityLightTracker INSTANCE; private final Int2ObjectOpenHashMap> entities = new Int2ObjectOpenHashMap<>(); @@ -103,9 +105,11 @@ private void removeAll() { private void trackAny(Entity entity) { final TrackedEntity trackedEntity; - // supported entity types. TODO: json / api based entity mapping - if (entity == Minecraft.getInstance().player) { - // we already have shader held light. TODO: json / api support + if (BlockLightLoader.ENTITY_LIGHTS.containsKey(entity.getType())) { + final short loadedLight = BlockLightLoader.ENTITY_LIGHTS.get(entity.getType()).value; + trackedEntity = new TrackedEntity<>(entity, e -> loadedLight); + } else if (entity == Minecraft.getInstance().player) { + // we already have shader held light return; } else if (entity instanceof LivingEntity livingEntity) { trackedEntity = new TrackedEntity<>(livingEntity, new HeldLightSupplier()); @@ -187,14 +191,14 @@ void update() { final BlockPos pos = entity.blockPosition(); final short light = lightSupplier.apply(entity); final boolean changedLight = lastTrackedLight != light; - final boolean changedPos = LightOp.emitter(light) && !lastTrackedPos.equals(pos); + final boolean changedPos = LightOp.lit(light) && !lastTrackedPos.equals(pos); if (changedLight || changedPos) { - if (LightOp.emitter(lastTrackedLight)) { + if (LightOp.lit(lastTrackedLight)) { lightLevel.removeVirtualLight(lastTrackedPos, lastTrackedLight); } - if (LightOp.emitter(light)) { + if (LightOp.lit(light)) { lightLevel.placeVirtualLight(pos, light); } } @@ -204,7 +208,7 @@ void update() { } public void removeLight() { - if (LightOp.emitter(lastTrackedLight)) { + if (LightOp.lit(lastTrackedLight)) { lightLevel.removeVirtualLight(entity.blockPosition(), lastTrackedLight); } } diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java index ffe4f5026..ab8d1d473 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegistry.java +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -37,6 +37,7 @@ import io.vram.frex.api.light.ItemLight; import grondag.canvas.light.api.impl.BlockLightLoader; +import grondag.canvas.light.api.impl.FloodFillBlockLight; public class LightRegistry { private static final ConcurrentHashMap cachedLights = new ConcurrentHashMap<>(); @@ -58,18 +59,18 @@ private static short generate(BlockState blockState) { final int lightLevel = blockState.getLightEmission(); final short defaultLight = LightOp.encodeLight(lightLevel, lightLevel, lightLevel, isFullCube, lightLevel > 0, blockState.canOcclude()); - BlockLightLoader.CachedBlockLight apiLight = BlockLightLoader.INSTANCE.blockLights.get(blockState); + FloodFillBlockLight apiLight = BlockLightLoader.BLOCK_LIGHTS.get(blockState); if (apiLight == null && !blockState.getFluidState().isEmpty()) { - apiLight = BlockLightLoader.INSTANCE.fluidLights.get(blockState.getFluidState()); + apiLight = BlockLightLoader.FLUID_LIGHTS.get(blockState.getFluidState()); } if (apiLight != null) { - if (!apiLight.levelIsSet()) { + if (!apiLight.levelIsSet) { apiLight = apiLight.withLevel(blockState.getLightEmission()); } - return LightOp.encodeLight(apiLight.value(), isFullCube, apiLight.value() != 0, blockState.canOcclude()); + return LightOp.encodeLight(apiLight.value, isFullCube, apiLight.value != 0, blockState.canOcclude()); } if (lightLevel < 1) { diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/entity/blaze.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/entity/blaze.json new file mode 100644 index 000000000..475d51552 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/entity/blaze.json @@ -0,0 +1,9 @@ +{ + "defaultLight": { + "lightLevel": 8.0, + "red": 1.0, + "green": 1.0, + "blue": 0.8 + } +} + diff --git a/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/entity/glow_squid.json b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/entity/glow_squid.json new file mode 100644 index 000000000..19a304290 --- /dev/null +++ b/src/main/resources/resourcepacks/canvas_default/assets/minecraft/lights/entity/glow_squid.json @@ -0,0 +1,9 @@ +{ + "defaultLight": { + "lightLevel": 8.0, + "red": 0.6, + "green": 1.0, + "blue": 0.9 + } +} + From bf67085aa0a4f55b89891a8ff409b9c45cb78392 Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Mon, 8 Jan 2024 03:42:45 +0700 Subject: [PATCH 68/69] Let pipeline disallow virtual lights, more robust reload - More robust LightLevel and EntityLightTracker reload - More robust CanvasState requireReload --- .../grondag/canvas/apiimpl/CanvasState.java | 14 ++-- .../grondag/canvas/config/ConfigData.java | 2 +- .../light/color/EntityLightTracker.java | 18 ++--- .../canvas/light/color/LightLevel.java | 72 +++++++++++++++---- .../pipeline/config/ColoredLightsConfig.java | 16 +++++ .../resources/assets/canvas/lang/en_us.json | 2 +- 6 files changed, 96 insertions(+), 28 deletions(-) diff --git a/src/main/java/grondag/canvas/apiimpl/CanvasState.java b/src/main/java/grondag/canvas/apiimpl/CanvasState.java index 785a3e50b..1323a0a93 100644 --- a/src/main/java/grondag/canvas/apiimpl/CanvasState.java +++ b/src/main/java/grondag/canvas/apiimpl/CanvasState.java @@ -20,6 +20,8 @@ package grondag.canvas.apiimpl; +import java.util.Objects; + import net.minecraft.client.Minecraft; import net.minecraft.client.resources.language.I18n; @@ -69,16 +71,18 @@ public static void recompile() { private static int loopCounter = 0; private static boolean recompilePipeline() { - final boolean prevColorLightsState = Pipeline.coloredLightsEnabled(); - final boolean prevAdvancedCullingState = Pipeline.advancedTerrainCulling(); + final var prev_coloredLightsConfig = Pipeline.config().coloredLights; + final boolean coloredLights_wasDisabled = !Pipeline.coloredLightsEnabled(); + final boolean advancedCulling_wasDisabled = !Pipeline.advancedTerrainCulling(); PipelineLoader.reload(Minecraft.getInstance().getResourceManager()); PipelineManager.reload(); - final boolean coloredLightsChanged = Pipeline.coloredLightsEnabled() && !prevColorLightsState; - final boolean requireCullingRebuild = Pipeline.advancedTerrainCulling() && !prevAdvancedCullingState; + final boolean coloredLightsConfigChanged = !Objects.equals(Pipeline.config().coloredLights, prev_coloredLightsConfig); + final boolean coloredLights_requiresRebuild = Pipeline.coloredLightsEnabled() && (coloredLightsConfigChanged || coloredLights_wasDisabled); + final boolean culling_requiresRebuild = Pipeline.advancedTerrainCulling() && advancedCulling_wasDisabled; - return coloredLightsChanged || requireCullingRebuild; + return coloredLights_requiresRebuild || culling_requiresRebuild; } private static void recompile(boolean wasReloaded) { diff --git a/src/main/java/grondag/canvas/config/ConfigData.java b/src/main/java/grondag/canvas/config/ConfigData.java index 54ec8176b..d20ec6409 100644 --- a/src/main/java/grondag/canvas/config/ConfigData.java +++ b/src/main/java/grondag/canvas/config/ConfigData.java @@ -57,7 +57,7 @@ class ConfigData { boolean semiFlatLighting = true; @Comment("Enable colored block lights on pipelines that support it. Replaces vanilla lighting but only visually.") boolean coloredLights = false; - @Comment("Enable entity as dynamic light sources. Requires colored lights.") + @Comment("Enable entity as dynamic light sources. Requires colored lights and supporting pipeline.") boolean entityLightSource = false; // TWEAKS diff --git a/src/main/java/grondag/canvas/light/color/EntityLightTracker.java b/src/main/java/grondag/canvas/light/color/EntityLightTracker.java index f660124e9..f4668bac2 100644 --- a/src/main/java/grondag/canvas/light/color/EntityLightTracker.java +++ b/src/main/java/grondag/canvas/light/color/EntityLightTracker.java @@ -77,15 +77,15 @@ public static void levelRemovesEntity(int id) { } } - void reload() { + void reload(boolean clearLights) { requiresInitialization = true; // might be called twice due to multiple hook (setLevel and allChanged). idempotent. - removeAll(); + removeAll(clearLights); } - void close(boolean lightLevelIsClosing) { - if (!lightLevelIsClosing) { - removeAll(); + void close(boolean clear) { + if (clear) { + removeAll(true); } if (INSTANCE == this) { @@ -93,10 +93,12 @@ void close(boolean lightLevelIsClosing) { } } - private void removeAll() { + private void removeAll(boolean clearLights) { // see notes on reload() - for (var entity:entities.values()) { - entity.removeLight(); + if (clearLights) { + for (var entity : entities.values()) { + entity.removeLight(); + } } entities.clear(); diff --git a/src/main/java/grondag/canvas/light/color/LightLevel.java b/src/main/java/grondag/canvas/light/color/LightLevel.java index e2c7fdfea..5d0e21e99 100644 --- a/src/main/java/grondag/canvas/light/color/LightLevel.java +++ b/src/main/java/grondag/canvas/light/color/LightLevel.java @@ -33,6 +33,7 @@ import grondag.canvas.CanvasMod; import grondag.canvas.config.Configurator; +import grondag.canvas.pipeline.Pipeline; class LightLevel implements LightLevelAccess { private BlockAndTintGetter baseLevel = null; @@ -42,6 +43,13 @@ class LightLevel implements LightLevelAccess { private final LongArrayFIFOQueue virtualQueue = new LongArrayFIFOQueue(); private final ObjectOpenHashSet virtualCheckQueue = new ObjectOpenHashSet<>(); + private static final Operator DO_NOTHING = (pos, light) -> { }; + private static final Getter GET_ZERO = pos -> (short) 0; + + private Operator placer = DO_NOTHING; + private Operator remover = DO_NOTHING; + private Getter getter = GET_ZERO; + LightLevel() { reload(); @@ -50,19 +58,38 @@ class LightLevel implements LightLevelAccess { } public void reload() { - if (Configurator.entityLightSource) { + assert Pipeline.config().coloredLights != null; + + final boolean virtualAllowed = Pipeline.config().coloredLights.allowVirtual; + + if (Configurator.entityLightSource && virtualAllowed) { if (entityLightTracker == null) { entityLightTracker = new EntityLightTracker(this); } else { - entityLightTracker.reload(); + // no need to clear as we are nuking everything + entityLightTracker.reload(false); } } else { if (entityLightTracker != null) { + // no need to clear as we are nuking everything entityLightTracker.close(false); } entityLightTracker = null; } + + // NB: implementations are expected to repopulate every time chunk is reloaded + virtualLights.clear(); + + if (virtualAllowed) { + placer = PLACE_VIRTUAL; + remover = REMOVE_VIRTUAL; + getter = GET_VIRTUAL; + } else { + placer = DO_NOTHING; + remover = DO_NOTHING; + getter = GET_ZERO; + } } void updateOnStartFrame(ClientLevel level) { @@ -70,7 +97,7 @@ void updateOnStartFrame(ClientLevel level) { baseLevel = level; if (entityLightTracker != null) { - entityLightTracker.reload(); + entityLightTracker.reload(true); } } @@ -89,26 +116,34 @@ public BlockAndTintGetter level() { @Override public short getRegistered(BlockPos pos) { var registered = baseLevel == null ? 0 : LightRegistry.get(baseLevel.getBlockState(pos)); - return combineWithBlockLight(registered, getVirtualLight(pos)); + return combineWithBlockLight(registered, getter.apply(pos)); } @Override public short getRegistered(BlockPos pos, @Nullable BlockState state) { // MAINTENANCE NOTICE: this function is a special casing of getRegistered(BlockPos) var registered = state != null ? LightRegistry.get(state) : (baseLevel == null ? 0 : LightRegistry.get(baseLevel.getBlockState(pos))); - return combineWithBlockLight(registered, getVirtualLight(pos)); + return combineWithBlockLight(registered, getter.apply(pos)); } @Override public void placeVirtualLight(BlockPos blockPos, short light) { + placer.apply(blockPos, light); + } + + @Override + public void removeVirtualLight(BlockPos blockPos, short light) { + remover.apply(blockPos, light); + } + + private final Operator PLACE_VIRTUAL = (blockPos, light) -> { final long pos = blockPos.asLong(); final var list = virtualLights.computeIfAbsent(pos, l -> new ShortArrayList()); list.add(encodeVirtualLight(light)); virtualQueue.enqueue(pos); - } + }; - @Override - public void removeVirtualLight(BlockPos blockPos, short light) { + private final Operator REMOVE_VIRTUAL = (blockPos, light) -> { final long pos = blockPos.asLong(); final var list = virtualLights.get(pos); final int i = list == null ? -1 : list.indexOf(encodeVirtualLight(light)); @@ -117,16 +152,17 @@ public void removeVirtualLight(BlockPos blockPos, short light) { list.removeShort(i); virtualQueue.enqueue(pos); } - } + }; void close() { if (entityLightTracker != null) { - entityLightTracker.close(true); + // no need to clear as we are nuking everything + entityLightTracker.close(false); entityLightTracker = null; } } - private short getVirtualLight(BlockPos blockPos) { + private final Getter GET_VIRTUAL = blockPos -> { ShortArrayList lights = virtualLights.get(blockPos.asLong()); if (lights != null) { @@ -139,8 +175,8 @@ private short getVirtualLight(BlockPos blockPos) { return combined; } - return 0; - } + return (short) 0; + }; private void updateInner() { while (!virtualQueue.isEmpty()) { @@ -172,4 +208,14 @@ public static short encodeVirtualLight(short light) { private static short combineWithBlockLight(short block, short virtual) { return LightOp.makeEmitter(LightOp.max(block, virtual)); } + + @FunctionalInterface + private interface Operator { + void apply(BlockPos pos, short light); + } + + @FunctionalInterface + private interface Getter { + short apply(BlockPos pos); + } } diff --git a/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java b/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java index 6936c887e..b39288fa8 100644 --- a/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java +++ b/src/main/java/grondag/canvas/pipeline/config/ColoredLightsConfig.java @@ -27,11 +27,13 @@ public class ColoredLightsConfig extends AbstractConfig { public final boolean enabled; + public final boolean allowVirtual; public final boolean useOcclusionData; protected ColoredLightsConfig(ConfigContext ctx, JsonObject config) { super(ctx); enabled = ctx.dynamic.getBoolean(config, "enabled", true); + allowVirtual = ctx.dynamic.getBoolean(config, "allowVirtual", true); useOcclusionData = ctx.dynamic.getBoolean(config, "useOcclusionData", false); } @@ -39,4 +41,18 @@ protected ColoredLightsConfig(ConfigContext ctx, JsonObject config) { public boolean validate() { return true; } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + final ColoredLightsConfig that = (ColoredLightsConfig) obj; + return enabled == that.enabled && allowVirtual == that.allowVirtual && useOcclusionData == that.useOcclusionData; + } } diff --git a/src/main/resources/assets/canvas/lang/en_us.json b/src/main/resources/assets/canvas/lang/en_us.json index 8724e6228..d16540aae 100644 --- a/src/main/resources/assets/canvas/lang/en_us.json +++ b/src/main/resources/assets/canvas/lang/en_us.json @@ -56,7 +56,7 @@ "config.canvas.value.colored_lights": "Colored Lights", "config.canvas.help.colored_lights": "Enable colored block lights on pipelines that support it. Replaces vanilla lighting but only visually.", "config.canvas.value.entity_light_source": "Entity Light Source", - "config.canvas.help.entity_light_source": "Enable entity as dynamic light sources. Requires colored lights.", + "config.canvas.help.entity_light_source": "Enable entity as dynamic light sources. Requires colored lights and supporting pipeline.", "config.canvas.enum.ao_mode.normal": "Vanilla", "config.canvas.enum.ao_mode.subtle_always": "Subtle", "config.canvas.enum.ao_mode.subtle_block_light": "Subtle Torchlit", From 4afb98b92a49d56478da71583b680bf6936aa88a Mon Sep 17 00:00:00 2001 From: spiralhalo Date: Mon, 8 Jan 2024 14:57:58 +0700 Subject: [PATCH 69/69] Clamp item light intensity when converting to block light Our flood fill-based model doesn't support HDR, so it's better to preserve the color hue instead of clipping to white on high intensity. --- .../java/grondag/canvas/light/color/LightRegistry.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/grondag/canvas/light/color/LightRegistry.java b/src/main/java/grondag/canvas/light/color/LightRegistry.java index ab8d1d473..c9c42fcba 100644 --- a/src/main/java/grondag/canvas/light/color/LightRegistry.java +++ b/src/main/java/grondag/canvas/light/color/LightRegistry.java @@ -107,9 +107,11 @@ public static short encodeItem(ItemLight light, int lightLevel, boolean isFull, lightLevel = 15; } - final int r = org.joml.Math.clamp(0, 15, Math.round(((float) lightLevel) * light.intensity() * light.red())); - final int g = org.joml.Math.clamp(0, 15, Math.round(((float) lightLevel) * light.intensity() * light.green())); - final int b = org.joml.Math.clamp(0, 15, Math.round(((float) lightLevel) * light.intensity() * light.blue())); + final float postLevel = (float) Math.min(15, lightLevel) * org.joml.Math.clamp(0.0f, 1.0f, light.intensity()); + + final int r = org.joml.Math.clamp(0, 15, Math.round(postLevel * light.red())); + final int g = org.joml.Math.clamp(0, 15, Math.round(postLevel * light.green())); + final int b = org.joml.Math.clamp(0, 15, Math.round(postLevel * light.blue())); var result = LightOp.encode(r, g, b, 0);