From 6b72cb27941f067d1dece49c854b314c18a4cae9 Mon Sep 17 00:00:00 2001 From: G9third Date: Thu, 2 Jul 2026 05:56:35 -0400 Subject: [PATCH 1/2] Fix multiversion LOD biome tint sampling position --- .../voxy/client/core/model/ModelFactory.java | 69 +++++++++++++++++++ .../rendering/building/RenderDataFactory.java | 31 +++++++-- 2 files changed, 96 insertions(+), 4 deletions(-) diff --git a/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java b/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java index 1e9f154de..6f97d24a6 100644 --- a/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java +++ b/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java @@ -64,6 +64,14 @@ // this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving public class ModelFactory { public static final int MODEL_TEXTURE_SIZE = 16; + + // Position-aware biome tint compatibility mode. + // Biome-tinted LOD models use a fixed 512-entry RGB333 palette, and + // RenderDataFactory writes the palette index produced from BlockColors + // at the actual LOD sample world position into the quad biome-id bits. + public static final boolean USE_POSITION_TINT_PALETTE = true; + public static final int POSITION_TINT_PALETTE_SIZE = 512; + public static final int LAYERS = Integer.numberOfTrailingZeros(MODEL_TEXTURE_SIZE); //TODO: replace the fluid BlockState with a client model id integer of the fluidState, requires looking up @@ -649,6 +657,16 @@ private ModelBakeResultUpload processTextureBakeResult(int blockId, BlockState b MemoryUtil.memPutInt(uploadPtr, -1);//Set the default to nothing so that its faster on the gpu } else if (!isBiomeColourDependent) { MemoryUtil.memPutInt(uploadPtr, entry.tintingColour); + } else if (USE_POSITION_TINT_PALETTE) { + // Position-aware tinting mode: use the quad biome-id bits as an RGB333 palette index. + // RenderDataFactory computes that palette index from the actual world position. + int colourIndex = this.modelsRequiringBiomeColours.size() * POSITION_TINT_PALETTE_SIZE; + MemoryUtil.memPutInt(uploadPtr, colourIndex); + this.modelsRequiringBiomeColours.add(new Pair<>(modelId, blockState)); + + uploadResult.biomeUploadIndex = colourIndex; + long clrUploadPtr = (uploadResult.biomeUpload = new MemoryBuffer(4L * POSITION_TINT_PALETTE_SIZE)).address; + writePositionTintPalette(clrUploadPtr); } else { //Populate the list of biomes for the model state int biomeIndex = this.modelsRequiringBiomeColours.size() * this.biomes.size(); @@ -730,6 +748,53 @@ public int getMinBuildHeight() { return Math.clamp(state.getLightEmission(),0,15); } + private static void writePositionTintPalette(long ptr) { + for (int i = 0; i < POSITION_TINT_PALETTE_SIZE; i++) { + MemoryUtil.memPutInt(ptr + i * 4L, positionTintPaletteColour(i) | 0xFF000000); + } + } + + private static int positionTintPaletteColour(int index) { + int r = (index >> 6) & 7; + int g = (index >> 3) & 7; + int b = index & 7; + return ((r * 255 / 7) << 16) | ((g * 255 / 7) << 8) | (b * 255 / 7); + } + + public int getPositionTintPaletteIndex(int blockId, BlockPos pos) { + if (!USE_POSITION_TINT_PALETTE) { + return -1; + } + var level = Minecraft.getInstance().level; + if (level == null) { + return -1; + } + BlockState state = this.mapper.getBlockStateFromBlockId(blockId); + int color; + try { + color = Minecraft.getInstance().getBlockColors().getColor(state, level, pos, 0); + if (color == -1) { + color = Minecraft.getInstance().getBlockColors().getColor(state, level, pos, 1); + } + } catch (Throwable throwable) { + return -1; + } + if (color == -1) { + return -1; + } + return quantizePositionTintColour(color); + } + + private static int quantizePositionTintColour(int color) { + int r = (color >> 16) & 255; + int g = (color >> 8) & 255; + int b = color & 255; + int r3 = (r * 7 + 127) / 255; + int g3 = (g * 7 + 127) / 255; + int b3 = (b * 7 + 127) / 255; + return (r3 << 6) | (g3 << 3) | b3; + } + private static final class BiomeUploadResult implements ResultUploader { private final MemoryBuffer biomeColourBuffer; private final MemoryBuffer modelBiomeIndexPairs; @@ -781,6 +846,10 @@ private BiomeUploadResult addBiome0(int id, Biome biome) { return null; } + // In position-tint-palette mode, biome-coloured model LUTs are fixed RGB333 palettes. + // New biome registrations do not require rebuilding/reuploading those model colour LUTs. + if (USE_POSITION_TINT_PALETTE) return null; + if (this.modelsRequiringBiomeColours.isEmpty()) return null; var result = new BiomeUploadResult(this.biomes.size(), this.modelsRequiringBiomeColours.size()); diff --git a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory.java b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory.java index c290c3f00..ce6131cfa 100644 --- a/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory.java +++ b/src/main/java/me/cortex/voxy/client/core/rendering/building/RenderDataFactory.java @@ -11,6 +11,7 @@ import me.cortex.voxy.common.world.WorldSection; import me.cortex.voxy.common.world.other.Mapper; import me.cortex.voxy.commonImpl.VoxyCommon; +import net.minecraft.core.BlockPos; import org.lwjgl.system.MemoryUtil; import java.util.Arrays; @@ -201,9 +202,18 @@ private static long getQuadTyping(long metadata) {//2 bits return 0b111L&(0b000_000_010_100L>>(ModelQueries._isTranslucent(metadata)*6+ModelQueries._isDoubleSided(metadata)*3)); } - private static long packPartialQuadData(int modelId, long state, long metadata) { + private long packPartialQuadData(int modelId, long state, long metadata, WorldSection section, int localIndex) { //This uses hardcoded data to shuffle things long lightAndBiome = (state&((0x1FFL<<47)|(0xFFL<<56)))>>>1; + + if (ModelFactory.USE_POSITION_TINT_PALETTE && ModelQueries.isBiomeColoured(metadata)) { + int paletteIndex = this.modelMan.getPositionTintPaletteIndex(Mapper.getBlockId(state), getWorldSamplePos(section, localIndex)); + if (paletteIndex >= 0) { + lightAndBiome &= ~(0x1FFL << 46); + lightAndBiome |= (Integer.toUnsignedLong(paletteIndex) & 0x1FFL) << 46; + } + } + lightAndBiome &= ~(ModelQueries._notIsBiomeColoured(metadata) * (0x1FFL << 46));//46 not 47 because is already shifted by 1 THIS WASTED 4 HOURS ;-; aaaaaAAAAAA lightAndBiome &= ~(ModelQueries._isFullyOpaque(metadata)*(0xFFL << 55));//If its fully opaque it always uses neighbor light? @@ -213,7 +223,20 @@ private static long packPartialQuadData(int modelId, long state, long metadata) return quadData; } - private int prepareSectionData(final long[] rawSectionData) { + private static BlockPos getWorldSamplePos(WorldSection section, int localIndex) { + int localX = localIndex & 31; + int localZ = (localIndex >> 5) & 31; + int localY = (localIndex >> 10) & 31; + int scale = 1 << section.lvl; + int offset = scale >> 1; + return new BlockPos( + ((section.x << 5) + localX) * scale + offset, + ((section.y << 5) + localY) * scale + offset, + ((section.z << 5) + localZ) * scale + offset + ); + } + + private int prepareSectionData(final long[] rawSectionData, WorldSection section) { final var sectionData = this.sectionData; final var rawModelIds = this.modelMan._unsafeRawAccess(); long opaque = 0; @@ -242,7 +265,7 @@ private int prepareSectionData(final long[] rawSectionData) { long modelMetadata = this.modelMan.getModelMetadataFromClientId(modelId); - sectionData[i * 2] = packPartialQuadData(modelId, block, modelMetadata); + sectionData[i * 2] = packPartialQuadData(modelId, block, modelMetadata, section, i); sectionData[i * 2 + 1] = modelMetadata; notEmpty |= 1L << j; @@ -1727,7 +1750,7 @@ public BuiltSection generateMesh(WorldSection section) { Arrays.fill(this.fluidMasks, 0); //Prepare everything - int neighborMskAndFlags = this.prepareSectionData(section._unsafeGetRawDataArray()); + int neighborMskAndFlags = this.prepareSectionData(section._unsafeGetRawDataArray(), section); if ((neighborMskAndFlags&(1<<31))!=0) {//We failed to get everything so throw exception throw new IdNotYetComputedException(neighborMskAndFlags&((1<<20)-1), true); } From b3ecf6756a130c983adace5415c6101085c3c732 Mon Sep 17 00:00:00 2001 From: G9third Date: Thu, 2 Jul 2026 06:46:30 -0400 Subject: [PATCH 2/2] Share client-position tint palette --- .../voxy/client/core/model/ModelFactory.java | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java b/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java index 6f97d24a6..9e3edaf32 100644 --- a/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java +++ b/src/main/java/me/cortex/voxy/client/core/model/ModelFactory.java @@ -66,11 +66,13 @@ public class ModelFactory { public static final int MODEL_TEXTURE_SIZE = 16; // Position-aware biome tint compatibility mode. - // Biome-tinted LOD models use a fixed 512-entry RGB333 palette, and + // Biome-tinted LOD models share one fixed 512-entry RGB333 palette. // RenderDataFactory writes the palette index produced from BlockColors // at the actual LOD sample world position into the quad biome-id bits. public static final boolean USE_POSITION_TINT_PALETTE = true; public static final int POSITION_TINT_PALETTE_SIZE = 512; + public static final int POSITION_TINT_PALETTE_INDEX = 0; + public static final int POSITION_TINT_FALLBACK_INDEX = POSITION_TINT_PALETTE_SIZE - 1; public static final int LAYERS = Integer.numberOfTrailingZeros(MODEL_TEXTURE_SIZE); @@ -139,6 +141,8 @@ public ModelEntry(ColourDepthTextureData[] textures, int fluidBlockStateId, int private final ConcurrentLinkedDeque uploadResults = new ConcurrentLinkedDeque<>(); + private boolean positionTintPaletteUploaded = false; + private Object2IntMap customBlockStateIdMapping; //TODO: NOTE!!! is it worth even uploading as a 16x16 texture, since automatic lod selection... doing 8x8 textures might be perfectly ok!!! @@ -659,14 +663,10 @@ private ModelBakeResultUpload processTextureBakeResult(int blockId, BlockState b MemoryUtil.memPutInt(uploadPtr, entry.tintingColour); } else if (USE_POSITION_TINT_PALETTE) { // Position-aware tinting mode: use the quad biome-id bits as an RGB333 palette index. - // RenderDataFactory computes that palette index from the actual world position. - int colourIndex = this.modelsRequiringBiomeColours.size() * POSITION_TINT_PALETTE_SIZE; - MemoryUtil.memPutInt(uploadPtr, colourIndex); - this.modelsRequiringBiomeColours.add(new Pair<>(modelId, blockState)); - - uploadResult.biomeUploadIndex = colourIndex; - long clrUploadPtr = (uploadResult.biomeUpload = new MemoryBuffer(4L * POSITION_TINT_PALETTE_SIZE)).address; - writePositionTintPalette(clrUploadPtr); + // All tinted models point at the same shared palette; RenderDataFactory computes + // the per-quad palette index from the actual LOD sample world position. + MemoryUtil.memPutInt(uploadPtr, POSITION_TINT_PALETTE_INDEX); + this.queuePositionTintPaletteUpload(uploadResult); } else { //Populate the list of biomes for the model state int biomeIndex = this.modelsRequiringBiomeColours.size() * this.biomes.size(); @@ -748,6 +748,16 @@ public int getMinBuildHeight() { return Math.clamp(state.getLightEmission(),0,15); } + private void queuePositionTintPaletteUpload(ModelBakeResultUpload uploadResult) { + if (this.positionTintPaletteUploaded) { + return; + } + this.positionTintPaletteUploaded = true; + uploadResult.biomeUploadIndex = POSITION_TINT_PALETTE_INDEX; + long clrUploadPtr = (uploadResult.biomeUpload = new MemoryBuffer(4L * POSITION_TINT_PALETTE_SIZE)).address; + writePositionTintPalette(clrUploadPtr); + } + private static void writePositionTintPalette(long ptr) { for (int i = 0; i < POSITION_TINT_PALETTE_SIZE; i++) { MemoryUtil.memPutInt(ptr + i * 4L, positionTintPaletteColour(i) | 0xFF000000); @@ -767,7 +777,7 @@ public int getPositionTintPaletteIndex(int blockId, BlockPos pos) { } var level = Minecraft.getInstance().level; if (level == null) { - return -1; + return POSITION_TINT_FALLBACK_INDEX; } BlockState state = this.mapper.getBlockStateFromBlockId(blockId); int color; @@ -777,10 +787,10 @@ public int getPositionTintPaletteIndex(int blockId, BlockPos pos) { color = Minecraft.getInstance().getBlockColors().getColor(state, level, pos, 1); } } catch (Throwable throwable) { - return -1; + return POSITION_TINT_FALLBACK_INDEX; } if (color == -1) { - return -1; + return POSITION_TINT_FALLBACK_INDEX; } return quantizePositionTintColour(color); }