This commit is contained in:
mcrcortex
2025-12-14 23:44:46 +10:00
parent 03d971385c
commit c7cf4a74d5
9 changed files with 221 additions and 143 deletions

View File

@@ -64,6 +64,6 @@ public class VoxyClient implements ClientModInitializer {
}
public static boolean disableSodiumChunkRender() {
return getOcclusionDebugState() != 0;
return false;// getOcclusionDebugState() != 0;
}
}

View File

@@ -376,7 +376,7 @@ public class VoxyRenderSystem {
return base.mulLocal(
makeProjectionMatrix(0.05f, Minecraft.getInstance().gameRenderer.getDepthFar()).invert(),
new Matrix4f()
).mulLocal(makeProjectionMatrix(VoxyClient.getOcclusionDebugState()<=1?16f:0.1f, 16*3000));
).mulLocal(makeProjectionMatrix(VoxyClient.disableSodiumChunkRender()?0.1f:16f, 16*3000));
}
private boolean frexStillHasWork() {

View File

@@ -0,0 +1,128 @@
package me.cortex.voxy.client.core.model;
import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue;
import me.cortex.voxy.common.util.MemoryBuffer;
import net.caffeinemc.mods.sodium.client.util.color.ColorSRGB;
import org.lwjgl.system.MemoryUtil;
import java.util.Arrays;
import static me.cortex.voxy.client.core.model.ModelFactory.LAYERS;
import static me.cortex.voxy.client.core.model.ModelFactory.MODEL_TEXTURE_SIZE;
public class MipGen {
static {
if (MODEL_TEXTURE_SIZE>16) throw new IllegalStateException("TODO: THIS MUST BE UPDATED, IT CURRENTLY ASSUMES 16 OR SMALLER SIZE");
}
private static final short[] SCRATCH = new short[MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE];
private static final ByteArrayFIFOQueue QUEUE = new ByteArrayFIFOQueue(MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE);
private static long getOffset(int bx, int by, int i) {
bx += i&(MODEL_TEXTURE_SIZE-1);
by += i/MODEL_TEXTURE_SIZE;
return bx+by*MODEL_TEXTURE_SIZE*3;
}
private static void solidify(long baseAddr, byte msk) {
for (int idx = 0; idx < 6; idx++) {
if (((msk>>idx)&1)==0) continue;
int bx = (idx>>1)*MODEL_TEXTURE_SIZE;
int by = (idx&1)*MODEL_TEXTURE_SIZE;
long cAddr = baseAddr + (long)(bx+by*MODEL_TEXTURE_SIZE*3)*4;
Arrays.fill(SCRATCH, (short) -1);
for (int y = 0; y<MODEL_TEXTURE_SIZE;y++) {
for (int x = 0; x<MODEL_TEXTURE_SIZE;x++) {
int colour = MemoryUtil.memGetInt(cAddr+(x+y*MODEL_TEXTURE_SIZE*3)*4);
if ((colour&0xFF000000)!=0) {
int pos = x+y*MODEL_TEXTURE_SIZE;
SCRATCH[pos] = ((short)pos);
QUEUE.enqueue((byte) pos);
}
}
}
while (!QUEUE.isEmpty()) {
int pos = Byte.toUnsignedInt(QUEUE.dequeueByte());
int x = pos&(MODEL_TEXTURE_SIZE-1);
int y = pos/MODEL_TEXTURE_SIZE;//this better be turned into a bitshift
short newVal = (short) (SCRATCH[pos]+(short) 0x0100);
for (int D = 3; D!=-1; D--) {
int d = 2*(D&1)-1;
int x2 = x+(((D&2)==2)?d:0);
int y2 = y+(((D&2)==0)?d:0);
if (x2<0||x2>=MODEL_TEXTURE_SIZE||y2<0||y2>=MODEL_TEXTURE_SIZE) continue;
int pos2 = x2+y2*MODEL_TEXTURE_SIZE;
if ((newVal&0xFF00)<(SCRATCH[pos2]&0xFF00)) {
SCRATCH[pos2] = newVal;
QUEUE.enqueue((byte) pos2);
}
}
}
for (int i = 0; i < MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE; i++) {
int d = Short.toUnsignedInt(SCRATCH[i]);
if ((d&0xFF00)!=0) {
int c = MemoryUtil.memGetInt(baseAddr+getOffset(bx, by, d&0xFF)*4)&0x00FFFFFF;
MemoryUtil.memPutInt(baseAddr+getOffset(bx, by, i)*4, c);
}
}
}
}
public static void putTextures(boolean darkened, ColourDepthTextureData[] textures, MemoryBuffer into) {
//if (MODEL_TEXTURE_SIZE != 16) {throw new IllegalStateException("THIS METHOD MUST BE REDONE IF THIS CONST CHANGES");}
//TODO: need to use a write mask to see what pixels must be used to contribute to mipping
// as in, using the depth/stencil info, check if pixel was written to, if so, use that pixel when blending, else dont
final long addr = into.address;
final int LENGTH_B = MODEL_TEXTURE_SIZE*3;
byte solidMsk = 0;
for (int i = 0; i < 6; i++) {
int x = (i>>1)*MODEL_TEXTURE_SIZE;
int y = (i&1)*MODEL_TEXTURE_SIZE;
int j = 0;
boolean anyTransparent = false;
for (int t : textures[i].colour()) {
int o = ((y+(j>>LAYERS))*LENGTH_B + ((j&(MODEL_TEXTURE_SIZE-1))+x))*4; j++;//LAYERS here is just cause faster
//t = ((t&0xFF000000)==0)?0x00_FF_00_FF:t;//great for testing
MemoryUtil.memPutInt(addr+o, t);
anyTransparent |= ((t&0xFF000000)==0);
}
solidMsk |= (anyTransparent?1:0)<<i;
}
if (!darkened) {
solidify(addr, solidMsk);
}
//Mip the scratch
long dAddr = addr;
for (int i = 0; i < LAYERS-1; i++) {
long sAddr = dAddr;
dAddr += (MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*3*2*4)>>(i<<1);//is.. i*2 because shrink both MODEL_TEXTURE_SIZE by >>i so is 2*i total shift
int width = (MODEL_TEXTURE_SIZE*3)>>(i+1);
int sWidth = (MODEL_TEXTURE_SIZE*3)>>i;
int height = (MODEL_TEXTURE_SIZE*2)>>(i+1);
//TODO: OPTIMZIE THIS
for (int px = 0; px < width; px++) {
for (int py = 0; py < height; py++) {
long bp = sAddr + (px*2 + py*2*sWidth)*4;
int C00 = MemoryUtil.memGetInt(bp);
int C01 = MemoryUtil.memGetInt(bp+sWidth*4);
int C10 = MemoryUtil.memGetInt(bp+4);
int C11 = MemoryUtil.memGetInt(bp+sWidth*4+4);
MemoryUtil.memPutInt(dAddr + (px+py*width) * 4L, TextureUtils.mipColours(darkened, C00, C01, C10, C11));
}
}
}
/*
*/
}
public static void generateMipmaps(long[] textures, int size) {
}
}

View File

@@ -63,6 +63,7 @@ import static org.lwjgl.opengl.GL11.*;
// this _quarters_ the memory requirements for the texture atlas!!! WHICH IS HUGE saving
public class ModelFactory {
public static final int MODEL_TEXTURE_SIZE = 16;
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
// the fluid state in the mipper
@@ -158,6 +159,7 @@ public class ModelFactory {
private final MemoryBuffer rawData;
public boolean isShaded;
public boolean hasDarkenedTextures;
public RawBakeResult(int blockId, BlockState blockState, MemoryBuffer rawData) {
this.blockId = blockId;
@@ -219,7 +221,9 @@ public class ModelFactory {
RawBakeResult result = new RawBakeResult(blockId, blockState);
int allocation = this.downstream.download(MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*2*4*6, ptr -> this.rawBakeResults.add(result.cpyBuf(ptr)));
result.isShaded = this.bakery.renderToStream(blockState, this.downstream.getBufferId(), allocation);
int flags = this.bakery.renderToStream(blockState, this.downstream.getBufferId(), allocation);
result.hasDarkenedTextures = (flags&2)!=0;
result.isShaded = (flags&1)!=0;
return true;
}
@@ -245,7 +249,7 @@ public class ModelFactory {
}
}
result.rawData.free();
var bakeResult = this.processTextureBakeResult(result.blockId, result.blockState, textureData, result.isShaded);
var bakeResult = this.processTextureBakeResult(result.blockId, result.blockState, textureData, result.isShaded, result.hasDarkenedTextures);
if (bakeResult!=null) {
this.uploadResults.add(bakeResult);
}
@@ -337,7 +341,7 @@ public class ModelFactory {
}
}
private ModelBakeResultUpload processTextureBakeResult(int blockId, BlockState blockState, ColourDepthTextureData[] textureData, boolean isShaded) {
private ModelBakeResultUpload processTextureBakeResult(int blockId, BlockState blockState, ColourDepthTextureData[] textureData, boolean isShaded, boolean darkenedTinting) {
if (this.idMappings[blockId] != -1) {
//This should be impossible to reach as it means that multiple bakes for the same blockId happened and where inflight at the same time!
throw new IllegalStateException("Block id already added: " + blockId + " for state: " + blockState);
@@ -636,7 +640,7 @@ public class ModelFactory {
//TODO callback to inject extra data into the model data
this.putTextures(textureData, uploadResult.texture);
MipGen.putTextures(darkenedTinting, textureData, uploadResult.texture);
//glGenerateTextureMipmap(this.textures.id);
@@ -734,7 +738,7 @@ public class ModelFactory {
// if it is, need to add it to a list and mark it as biome colour dependent or something then the shader
// will either use the uint as an index or a direct colour multiplier
private static int captureColourConstant(BlockColor colorProvider, BlockState state, Biome biome) {
return colorProvider.getColor(state, new BlockAndTintGetter() {
var getter = new BlockAndTintGetter() {
@Override
public float getShade(Direction direction, boolean shaded) {
return 0;
@@ -780,12 +784,16 @@ public class ModelFactory {
public int getMinY() {
return 0;
}
}, BlockPos.ZERO, 0);
};
//Multiple layer bs to do with flower beds
int c = colorProvider.getColor(state, getter, BlockPos.ZERO, 0);
if (c!=-1) return c;
return colorProvider.getColor(state, getter, BlockPos.ZERO, 1);
}
private static boolean isBiomeDependentColour(BlockColor colorProvider, BlockState state) {
boolean[] biomeDependent = new boolean[1];
colorProvider.getColor(state, new BlockAndTintGetter() {
var getter = new BlockAndTintGetter() {
@Override
public float getShade(Direction direction, boolean shaded) {
return 0;
@@ -832,7 +840,9 @@ public class ModelFactory {
public int getMinY() {
return 0;
}
}, BlockPos.ZERO, 0);
};
colorProvider.getColor(state, getter, BlockPos.ZERO, 0);
colorProvider.getColor(state, getter, BlockPos.ZERO, 1);
return biomeDependent[0];
}
@@ -881,57 +891,6 @@ public class ModelFactory {
}
private static int computeSizeWithMips(int size) {
int total = 0;
for (;size!=0;size>>=1) total += size*size;
return total;
}
private static final MemoryBuffer SCRATCH_TEX = new MemoryBuffer((2L*3*computeSizeWithMips(MODEL_TEXTURE_SIZE))*4);
private static final int LAYERS = Integer.numberOfTrailingZeros(MODEL_TEXTURE_SIZE);
//TODO: redo to batch blit, instead of 6 seperate blits, and also fix mipping
private void putTextures(ColourDepthTextureData[] textures, MemoryBuffer into) {
//if (MODEL_TEXTURE_SIZE != 16) {throw new IllegalStateException("THIS METHOD MUST BE REDONE IF THIS CONST CHANGES");}
//TODO: need to use a write mask to see what pixels must be used to contribute to mipping
// as in, using the depth/stencil info, check if pixel was written to, if so, use that pixel when blending, else dont
final long addr = into.address;
final int LENGTH_B = MODEL_TEXTURE_SIZE*3;
for (int i = 0; i < 6; i++) {
int x = (i>>1)*MODEL_TEXTURE_SIZE;
int y = (i&1)*MODEL_TEXTURE_SIZE;
int j = 0;
for (int t : textures[i].colour()) {
int o = ((y+(j>>LAYERS))*LENGTH_B + ((j&(MODEL_TEXTURE_SIZE-1))+x))*4; j++;//LAYERS here is just cause faster
MemoryUtil.memPutInt(addr+o, t);
}
}
//Mip the scratch
long dAddr = addr;
for (int i = 0; i < LAYERS-1; i++) {
long sAddr = dAddr;
dAddr += (MODEL_TEXTURE_SIZE*MODEL_TEXTURE_SIZE*3*2*4)>>(i<<1);//is.. i*2 because shrink both MODEL_TEXTURE_SIZE by >>i so is 2*i total shift
int width = (MODEL_TEXTURE_SIZE*3)>>(i+1);
int sWidth = (MODEL_TEXTURE_SIZE*3)>>i;
int height = (MODEL_TEXTURE_SIZE*2)>>(i+1);
//TODO: OPTIMZIE THIS
for (int px = 0; px < width; px++) {
for (int py = 0; py < height; py++) {
long bp = sAddr + (px*2 + py*2*sWidth)*4;
int C00 = MemoryUtil.memGetInt(bp);
int C01 = MemoryUtil.memGetInt(bp+sWidth*4);
int C10 = MemoryUtil.memGetInt(bp+4);
int C11 = MemoryUtil.memGetInt(bp+sWidth*4+4);
MemoryUtil.memPutInt(dAddr + (px+py*width) * 4L, TextureUtils.mipColours(C00, C01, C10, C11));
}
}
}
/*
*/
}
public void free() {
this.bakery.free();
this.downstream.free();
@@ -954,4 +913,11 @@ public class ModelFactory {
size += this.biomeQueue.size();
return size;
}
private static int computeSizeWithMips(int size) {
int total = 0;
for (;size!=0;size>>=1) total += size*size;
return total;
}
}

View File

@@ -2,6 +2,7 @@ package me.cortex.voxy.client.core.model;
import net.caffeinemc.mods.sodium.client.util.color.ColorSRGB;
import net.minecraft.client.renderer.texture.MipmapGenerator;
import net.minecraft.util.ARGB;
//Texturing utils to manipulate data from the model bakery
public class TextureUtils {
@@ -26,6 +27,7 @@ public class TextureUtils {
public static final int WRITE_CHECK_STENCIL = 1;
public static final int WRITE_CHECK_DEPTH = 2;
public static final int WRITE_CHECK_ALPHA = 3;
private static boolean wasPixelWritten(ColourDepthTextureData data, int mode, int index) {
if (mode == WRITE_CHECK_STENCIL) {
return (data.depth()[index] & 0xFF) != 0;
@@ -195,70 +197,42 @@ public class TextureUtils {
}
public static int mipColours(int one, int two, int three, int four) {
if (false) {
return 0;
//return MipmapGenerator.alphaBlend(one, two, three, four, false);
} else {
return weightedAverageColor(weightedAverageColor(one, two), weightedAverageColor(three, four));
public static int mipColours(boolean darkend, int C00, int C01, int C10, int C11) {
darkend = !darkend;//Invert to make it easier
float r = 0.0f;
float g = 0.0f;
float b = 0.0f;
float a = 0.0f;
if (darkend || (C00 >>> 24) != 0) {
r += ColorSRGB.srgbToLinear((C00 >> 0) & 0xFF);
g += ColorSRGB.srgbToLinear((C00 >> 8) & 0xFF);
b += ColorSRGB.srgbToLinear((C00 >> 16) & 0xFF);
a += darkend ? (C00 >>> 24) : ColorSRGB.srgbToLinear(C00 >>> 24);
}
if (darkend || (C01 >>> 24) != 0) {
r += ColorSRGB.srgbToLinear((C01 >> 0) & 0xFF);
g += ColorSRGB.srgbToLinear((C01 >> 8) & 0xFF);
b += ColorSRGB.srgbToLinear((C01 >> 16) & 0xFF);
a += darkend ? (C01 >>> 24) : ColorSRGB.srgbToLinear(C01 >>> 24);
}
if (darkend || (C10 >>> 24) != 0) {
r += ColorSRGB.srgbToLinear((C10 >> 0) & 0xFF);
g += ColorSRGB.srgbToLinear((C10 >> 8) & 0xFF);
b += ColorSRGB.srgbToLinear((C10 >> 16) & 0xFF);
a += darkend ? (C10 >>> 24) : ColorSRGB.srgbToLinear(C10 >>> 24);
}
if (darkend || (C11 >>> 24) != 0) {
r += ColorSRGB.srgbToLinear((C11 >> 0) & 0xFF);
g += ColorSRGB.srgbToLinear((C11 >> 8) & 0xFF);
b += ColorSRGB.srgbToLinear((C11 >> 16) & 0xFF);
a += darkend ? (C11 >>> 24) : ColorSRGB.srgbToLinear(C11 >>> 24);
}
//TODO: FIXME!!! ITS READING IT AS ABGR??? isnt the format RGBA??
private static int weightedAverageColor(int a, int b) {
//We specifically want the entire other component if the alpha is zero
// this prevents black mips from generating due to A) non filled colours, and B) when the sampler samples everything it doesnt detonate
if ((a&0xFF000000) == 0) {
//return (b&0x00FFFFFF)|((b>>>2)&0x3FC00000);
return b;
}
if ((b&0xFF000000) == 0) {
//return (a&0x00FFFFFF)|((a>>>2)&0x3FC00000);
return a;
}
if (((a^b)&0xFF000000)==0) {
return ColorSRGB.linearToSrgb(
addHalfLinear(0, a,b),
addHalfLinear(8, a,b),
addHalfLinear(16, a,b),
a>>>24);
}
{
int A = (a>>>24);
int B = (a>>>24);
float mul = 1.0F / (float)(A+B);
float wA = A * mul;
float wB = B * mul;
return ColorSRGB.linearToSrgb(
addMulLinear(0, a,b,wA,wB),
addMulLinear(8, a,b,wA,wB),
addMulLinear(16, a,b,wA,wB)
, (A + B)/2);
}
}
private static float addHalfLinear(int shift, int a, int b) {
return addMulLinear(shift, a, b, 0.5f, 0.5f);
}
private static float addMulLinear(int shift, int a, int b, float mulA, float mulB) {
return Math.fma(ColorSRGB.srgbToLinear((a>>shift)&0xFF),mulA, ColorSRGB.srgbToLinear((b>>shift)&0xFF)*mulB);
r / 4,
g / 4,
b / 4,
darkend ? ((int) a) / 4 : ARGB.linearToSrgbChannel(a / 4)
);
}
}

View File

@@ -170,7 +170,7 @@ public class ModelTextureBakery {
}
public boolean renderToStream(BlockState state, int streamBuffer, int streamOffset) {
public int renderToStream(BlockState state, int streamBuffer, int streamOffset) {
this.capture.clear();
boolean isBlock = true;
ChunkSectionLayer layer;
@@ -221,10 +221,12 @@ public class ModelTextureBakery {
}
boolean isAnyShaded = false;
boolean isAnyDarkend = false;
if (isBlock) {
this.vc.reset();
this.bakeBlockModel(state, layer);
isAnyShaded |= this.vc.anyShaded;
isAnyDarkend |= this.vc.anyDarkendTex;
if (!this.vc.isEmpty()) {//only render if there... is shit to render
//Setup for continual emission
@@ -267,6 +269,7 @@ public class ModelTextureBakery {
this.bakeFluidState(state, layer, i);
if (this.vc.isEmpty()) continue;
isAnyShaded |= this.vc.anyShaded;
isAnyDarkend |= this.vc.anyDarkendTex;
BudgetBufferRenderer.setup(this.vc.getAddress(), this.vc.quadCount(), blockTextureId);
glViewport((i % 3) * this.width, (i / 3) * this.height, this.width, this.height);
@@ -331,7 +334,7 @@ public class ModelTextureBakery {
GL14.glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
return isAnyShaded;
return (isAnyShaded?1:0)|(isAnyDarkend?2:0);
}

View File

@@ -4,6 +4,7 @@ package me.cortex.voxy.client.core.model.bakery;
import me.cortex.voxy.common.util.MemoryBuffer;
import net.minecraft.client.model.geom.builders.UVPair;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.texture.MipmapStrategy;
import org.lwjgl.system.MemoryUtil;
import static me.cortex.voxy.client.core.model.bakery.BudgetBufferRenderer.VERTEX_FORMAT_SIZE;
@@ -17,6 +18,7 @@ public final class ReuseVertexConsumer implements VertexConsumer {
private int defaultMeta;
public boolean anyShaded;
public boolean anyDarkendTex;
public ReuseVertexConsumer() {
this.reset();
@@ -82,6 +84,7 @@ public final class ReuseVertexConsumer implements VertexConsumer {
public ReuseVertexConsumer quad(BakedQuad quad, int metadata) {
this.anyShaded |= quad.shade();
this.anyDarkendTex |= quad.sprite().contents().mipmapStrategy == MipmapStrategy.DARK_CUTOUT;
this.ensureCanPut();
for (int i = 0; i < 4; i++) {
var pos = quad.position(i);
@@ -109,6 +112,7 @@ public final class ReuseVertexConsumer implements VertexConsumer {
public ReuseVertexConsumer reset() {
this.anyShaded = false;
this.anyDarkendTex = false;
this.defaultMeta = 0;//RESET THE DEFAULT META
this.count = 0;
this.ptr = this.buffer.address - VERTEX_FORMAT_SIZE;//the thing is first time this gets incremented by FORMAT_STRIDE

View File

@@ -166,6 +166,7 @@ void main() {
//Also, small quad is really fking over the mipping level somehow
#ifndef TRANSLUCENT
if (useDiscard() && (textureLod(blockModelAtlas, texPos, 0).a <= 0.1f)) {
//if (useDiscard() && (colour.a <= 0.1f)) {
#else
if (textureLod(blockModelAtlas, texPos, 0).a == 0.0f) {
#endif

View File

@@ -14,6 +14,8 @@ accessible field net/minecraft/world/level/chunk/PalettedContainer data Lnet/min
accessible field net/minecraft/world/level/chunk/PalettedContainer$Data palette Lnet/minecraft/world/level/chunk/Palette;
accessible field net/minecraft/world/level/chunk/PalettedContainer$Data storage Lnet/minecraft/util/BitStorage;
accessible field net/minecraft/client/renderer/texture/SpriteContents mipmapStrategy Lnet/minecraft/client/renderer/texture/MipmapStrategy;
accessible method net/minecraft/client/renderer/GameRenderer getFov (Lnet/minecraft/client/Camera;FZ)F
accessible method net/minecraft/client/multiplayer/ClientChunkCache$Storage getChunk (I)Lnet/minecraft/world/level/chunk/LevelChunk;