From 777db1db72e4d02fb3f0551d729ab80d743efc66 Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 12 Aug 2024 17:58:50 +0200 Subject: [PATCH 01/12] gather translucency and transparency data on sprite contents --- .../textures/mipmaps/SpriteContentsMixin.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/mipmaps/SpriteContentsMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/mipmaps/SpriteContentsMixin.java index c59a26dec7..6da7917dc3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/mipmaps/SpriteContentsMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/mipmaps/SpriteContentsMixin.java @@ -26,6 +26,12 @@ public class SpriteContentsMixin { @Final private NativeImage originalImage; + @Unique + public boolean hasTransparentPixels = false; + + @Unique + public boolean hasTranslucentPixels = false; + // While Fabric allows us to @Inject into the constructor here, that's just a specific detail of FabricMC's mixin // fork. Upstream Mixin doesn't allow arbitrary @Inject usage in constructor. However, we can use @ModifyVariable // just fine, in a way that hopefully doesn't conflict with other mods. @@ -51,7 +57,7 @@ public class SpriteContentsMixin { * black color does not leak over into sampling. */ @Unique - private static void sodium$fillInTransparentPixelColors(NativeImage nativeImage) { + private void sodium$fillInTransparentPixelColors(NativeImage nativeImage) { final long ppPixel = NativeImageHelper.getPointerRGBA(nativeImage); final int pixelCount = nativeImage.getHeight() * nativeImage.getWidth(); @@ -79,9 +85,18 @@ public class SpriteContentsMixin { b += ColorSRGB.srgbToLinear(FastColor.ABGR32.blue(color)) * weight; totalWeight += weight; + + // track if this image has transparent or even translucent pixels + if (alpha < 255) { + this.hasTranslucentPixels = true; + } + } else { + this.hasTransparentPixels = true; } } + this.hasTransparentPixels |= this.hasTranslucentPixels; + // Bail if none of the pixels are semi-transparent. if (totalWeight == 0.0f) { return; From bcdadc226123325f1bea0550c56f1d82832a33dd Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 13 Aug 2024 05:13:54 +0200 Subject: [PATCH 02/12] hook up block renderer to check sprite contents for material downgrade --- .../chunk/compile/pipeline/BlockRenderer.java | 44 +++++++++++++++---- .../pipeline/SpriteContentsExtension.java | 7 +++ .../textures/mipmaps/SpriteContentsMixin.java | 23 +++++++--- 3 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/SpriteContentsExtension.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java index df6851a805..dddbbd63c0 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java @@ -9,6 +9,7 @@ import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadOrientation; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildBuffers; import net.caffeinemc.mods.sodium.client.render.chunk.compile.buffers.ChunkModelBuilder; +import net.caffeinemc.mods.sodium.client.render.chunk.terrain.DefaultTerrainRenderPasses; import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.DefaultMaterials; import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.Material; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TranslucentGeometryCollector; @@ -28,6 +29,7 @@ import net.fabricmc.fabric.api.util.TriState; import net.minecraft.client.renderer.ItemBlockRenderTypes; import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.resources.model.BakedModel; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.state.BlockState; @@ -128,11 +130,9 @@ protected void processQuad(MutableQuadViewImpl quad) { material = DefaultMaterials.forRenderLayer(blendMode.blockRenderLayer == null ? type : blendMode.blockRenderLayer); } - ChunkModelBuilder builder = this.buffers.get(material); - this.colorizeQuad(quad, colorIndex); this.shadeQuad(quad, lightMode, emissive, shadeMode); - this.bufferQuad(quad, this.quadLightData.br, material, builder); + this.bufferQuad(quad, this.quadLightData.br, material); } private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) { @@ -152,11 +152,16 @@ private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) { } } - private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material material, ChunkModelBuilder modelBuilder) { + private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material material) { ModelQuadOrientation orientation = defaultLightMode == LightMode.FLAT ? ModelQuadOrientation.NORMAL : ModelQuadOrientation.orientByBrightness(brightnesses, quad); ChunkVertexEncoder.Vertex[] vertices = this.vertices; Vector3f offset = this.posOffset; + float minU = 1; + float minV = 1; + float maxU = 0; + float maxV = 0; + for (int dstIndex = 0; dstIndex < 4; dstIndex++) { int srcIndex = orientation.getVertexIndex(dstIndex); @@ -169,21 +174,44 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material // Due to our vertex format, the alpha from the quad color is ignored entirely. out.color = ColorARGB.toABGR(quad.color(srcIndex), brightnesses[srcIndex]); - out.u = quad.u(srcIndex); - out.v = quad.v(srcIndex); + var u = quad.u(srcIndex); + var v = quad.v(srcIndex); + out.u = u; + out.v = v; + minU = Math.min(minU, u); + minV = Math.min(minV, v); + maxU = Math.max(maxU, u); + maxV = Math.max(maxV, v); out.light = quad.lightmap(srcIndex); } + // TODO: actually use the uv bounding box to check each quad + var atlasSprite = SpriteFinderCache.forBlockAtlas().find(quad.getTexU(0), quad.getTexV(0)); + material = getDowngradedMaterial(atlasSprite, material); + ModelQuadFacing normalFace = quad.normalFace(); if (material.isTranslucent() && this.collector != null) { this.collector.appendQuad(quad.getFaceNormal(), vertices, normalFace); } - ChunkMeshBufferBuilder vertexBuffer = modelBuilder.getVertexBuffer(normalFace); + ChunkModelBuilder builder = this.buffers.get(material); + + ChunkMeshBufferBuilder vertexBuffer = builder.getVertexBuffer(normalFace); vertexBuffer.push(vertices, material); - modelBuilder.addSprite(SpriteFinderCache.forBlockAtlas().find(quad.getTexU(0), quad.getTexV(0))); + builder.addSprite(atlasSprite); + } + + private Material getDowngradedMaterial(TextureAtlasSprite sprite, Material material) { + var contents = (SpriteContentsExtension)(sprite.contents()); + if (material.pass == DefaultTerrainRenderPasses.TRANSLUCENT && !contents.sodium$hasTranslucentPixels()) { + material = DefaultMaterials.CUTOUT_MIPPED; + } + if (material.pass == DefaultTerrainRenderPasses.CUTOUT && !contents.sodium$hasTransparentPixels()) { + material = DefaultMaterials.SOLID; + } + return material; } } \ No newline at end of file diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/SpriteContentsExtension.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/SpriteContentsExtension.java new file mode 100644 index 0000000000..e54f894587 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/SpriteContentsExtension.java @@ -0,0 +1,7 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline; + +public interface SpriteContentsExtension { + boolean sodium$hasTransparentPixels(); + + boolean sodium$hasTranslucentPixels(); +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/mipmaps/SpriteContentsMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/mipmaps/SpriteContentsMixin.java index 6da7917dc3..900652a175 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/mipmaps/SpriteContentsMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/mipmaps/SpriteContentsMixin.java @@ -1,6 +1,7 @@ package net.caffeinemc.mods.sodium.mixin.features.textures.mipmaps; import com.mojang.blaze3d.platform.NativeImage; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline.SpriteContentsExtension; import net.caffeinemc.mods.sodium.client.util.NativeImageHelper; import net.caffeinemc.mods.sodium.client.util.color.ColorSRGB; import net.minecraft.client.renderer.texture.SpriteContents; @@ -20,17 +21,17 @@ * This Mixin is ported from Iris at MixinTextureAtlasSprite. */ @Mixin(SpriteContents.class) -public class SpriteContentsMixin { +public class SpriteContentsMixin implements SpriteContentsExtension { @Mutable @Shadow @Final private NativeImage originalImage; @Unique - public boolean hasTransparentPixels = false; + public boolean sodium$hasTransparentPixels = false; @Unique - public boolean hasTranslucentPixels = false; + public boolean sodium$hasTranslucentPixels = false; // While Fabric allows us to @Inject into the constructor here, that's just a specific detail of FabricMC's mixin // fork. Upstream Mixin doesn't allow arbitrary @Inject usage in constructor. However, we can use @ModifyVariable @@ -88,14 +89,14 @@ public class SpriteContentsMixin { // track if this image has transparent or even translucent pixels if (alpha < 255) { - this.hasTranslucentPixels = true; + this.sodium$hasTranslucentPixels = true; } } else { - this.hasTransparentPixels = true; + this.sodium$hasTransparentPixels = true; } } - this.hasTransparentPixels |= this.hasTranslucentPixels; + this.sodium$hasTransparentPixels |= this.sodium$hasTranslucentPixels; // Bail if none of the pixels are semi-transparent. if (totalWeight == 0.0f) { @@ -122,4 +123,14 @@ public class SpriteContentsMixin { } } } + + @Override + public boolean sodium$hasTransparentPixels() { + return this.sodium$hasTransparentPixels; + } + + @Override + public boolean sodium$hasTranslucentPixels() { + return this.sodium$hasTranslucentPixels; + } } \ No newline at end of file From aa93df1e45da897edd4c2acf3b92288bb0ce385c Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 15 Aug 2024 04:02:45 +0200 Subject: [PATCH 03/12] cleanup unused per-quad scanning, this is likely too hard to implement and not performant. Mods should instead use FRAPI or Forge APIs to assign multiple materials to the quads in their models such that each quad only renders as the most demanding material it requires. --- .../chunk/compile/pipeline/BlockRenderer.java | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java index dddbbd63c0..9978101f93 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java @@ -157,11 +157,6 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material ChunkVertexEncoder.Vertex[] vertices = this.vertices; Vector3f offset = this.posOffset; - float minU = 1; - float minV = 1; - float maxU = 0; - float maxV = 0; - for (int dstIndex = 0; dstIndex < 4; dstIndex++) { int srcIndex = orientation.getVertexIndex(dstIndex); @@ -174,19 +169,12 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material // Due to our vertex format, the alpha from the quad color is ignored entirely. out.color = ColorARGB.toABGR(quad.color(srcIndex), brightnesses[srcIndex]); - var u = quad.u(srcIndex); - var v = quad.v(srcIndex); - out.u = u; - out.v = v; - minU = Math.min(minU, u); - minV = Math.min(minV, v); - maxU = Math.max(maxU, u); - maxV = Math.max(maxV, v); + out.u = quad.u(srcIndex); + out.v = quad.v(srcIndex); out.light = quad.lightmap(srcIndex); } - // TODO: actually use the uv bounding box to check each quad var atlasSprite = SpriteFinderCache.forBlockAtlas().find(quad.getTexU(0), quad.getTexV(0)); material = getDowngradedMaterial(atlasSprite, material); @@ -205,7 +193,7 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material } private Material getDowngradedMaterial(TextureAtlasSprite sprite, Material material) { - var contents = (SpriteContentsExtension)(sprite.contents()); + var contents = (SpriteContentsExtension) (sprite.contents()); if (material.pass == DefaultTerrainRenderPasses.TRANSLUCENT && !contents.sodium$hasTranslucentPixels()) { material = DefaultMaterials.CUTOUT_MIPPED; } From c258eb1c9806026ed7a07fcf59257dcab0e2a42c Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 15 Aug 2024 15:54:56 +0200 Subject: [PATCH 04/12] move scanner into its own mixin, change injection of both mixins to not conflict by using WrapOperation, change the downgrade method to use passes, but then also add code to change the material bits' alpha cutoff parameter --- .../chunk/compile/ChunkBuildBuffers.java | 4 ++ .../chunk/compile/pipeline/BlockRenderer.java | 36 ++++++---- .../builder/ChunkMeshBufferBuilder.java | 6 +- .../vertex/format/ChunkVertexEncoder.java | 4 +- .../format/impl/CompactChunkVertex.java | 4 +- .../textures/mipmaps/SpriteContentsMixin.java | 57 +++------------- .../textures/scan/SpriteContentsMixin.java | 66 +++++++++++++++++++ common/src/main/resources/sodium.mixins.json | 1 + 8 files changed, 112 insertions(+), 66 deletions(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers.java index d506ba3237..eae5389deb 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildBuffers.java @@ -51,6 +51,10 @@ public ChunkModelBuilder get(Material material) { return this.builders.get(material.pass); } + public ChunkModelBuilder get(TerrainRenderPass pass) { + return this.builders.get(pass); + } + /** * Creates immutable baked chunk meshes from all non-empty scratch buffers. This is used after all blocks * have been rendered to pass the finished meshes over to the graphics card. This function can be called multiple diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java index 9978101f93..89045eb5a2 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java @@ -10,8 +10,11 @@ import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildBuffers; import net.caffeinemc.mods.sodium.client.render.chunk.compile.buffers.ChunkModelBuilder; import net.caffeinemc.mods.sodium.client.render.chunk.terrain.DefaultTerrainRenderPasses; +import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass; import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.DefaultMaterials; import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.Material; +import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.parameters.AlphaCutoffParameter; +import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.parameters.MaterialParameters; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TranslucentGeometryCollector; import net.caffeinemc.mods.sodium.client.render.chunk.vertex.builder.ChunkMeshBufferBuilder; import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexEncoder; @@ -176,30 +179,39 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material } var atlasSprite = SpriteFinderCache.forBlockAtlas().find(quad.getTexU(0), quad.getTexV(0)); - material = getDowngradedMaterial(atlasSprite, material); + var pass = getDowngradedPass(atlasSprite, material.pass); + var materialBits = material.bits(); ModelQuadFacing normalFace = quad.normalFace(); - if (material.isTranslucent() && this.collector != null) { + if (pass.isTranslucent() && this.collector != null) { this.collector.appendQuad(quad.getFaceNormal(), vertices, normalFace); } - ChunkModelBuilder builder = this.buffers.get(material); + ChunkModelBuilder builder = this.buffers.get(pass); + + if (material == DefaultMaterials.TRANSLUCENT && pass == DefaultTerrainRenderPasses.CUTOUT) { + materialBits = MaterialParameters.pack( + material.mipped + ? AlphaCutoffParameter.HALF : AlphaCutoffParameter.ONE_TENTH, + material.mipped); + } ChunkMeshBufferBuilder vertexBuffer = builder.getVertexBuffer(normalFace); - vertexBuffer.push(vertices, material); + vertexBuffer.push(vertices, materialBits); builder.addSprite(atlasSprite); } - private Material getDowngradedMaterial(TextureAtlasSprite sprite, Material material) { - var contents = (SpriteContentsExtension) (sprite.contents()); - if (material.pass == DefaultTerrainRenderPasses.TRANSLUCENT && !contents.sodium$hasTranslucentPixels()) { - material = DefaultMaterials.CUTOUT_MIPPED; - } - if (material.pass == DefaultTerrainRenderPasses.CUTOUT && !contents.sodium$hasTransparentPixels()) { - material = DefaultMaterials.SOLID; + private static TerrainRenderPass getDowngradedPass(TextureAtlasSprite sprite, TerrainRenderPass pass) { + if (sprite.contents() instanceof SpriteContentsExtension contents) { + if (pass == DefaultTerrainRenderPasses.TRANSLUCENT && !contents.sodium$hasTranslucentPixels()) { + pass = DefaultTerrainRenderPasses.CUTOUT; + } + if (pass == DefaultTerrainRenderPasses.CUTOUT && !contents.sodium$hasTransparentPixels()) { + pass = DefaultTerrainRenderPasses.SOLID; + } } - return material; + return pass; } } \ No newline at end of file diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/builder/ChunkMeshBufferBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/builder/ChunkMeshBufferBuilder.java index d6155d27ba..574701eda2 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/builder/ChunkMeshBufferBuilder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/builder/ChunkMeshBufferBuilder.java @@ -28,6 +28,10 @@ public ChunkMeshBufferBuilder(ChunkVertexType vertexType, int initialCapacity) { } public void push(ChunkVertexEncoder.Vertex[] vertices, Material material) { + this.push(vertices, material.bits()); + } + + public void push(ChunkVertexEncoder.Vertex[] vertices, int materialBits) { var vertexCount = vertices.length; if (this.count + vertexCount >= this.capacity) { @@ -35,7 +39,7 @@ public void push(ChunkVertexEncoder.Vertex[] vertices, Material material) { } this.encoder.write(MemoryUtil.memAddress(this.buffer, this.count * this.stride), - material, vertices, this.sectionIndex); + materialBits, vertices, this.sectionIndex); this.count += vertexCount; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/ChunkVertexEncoder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/ChunkVertexEncoder.java index 3db1859cc0..8f4322c9de 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/ChunkVertexEncoder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/ChunkVertexEncoder.java @@ -1,9 +1,7 @@ package net.caffeinemc.mods.sodium.client.render.chunk.vertex.format; -import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.Material; - public interface ChunkVertexEncoder { - long write(long ptr, Material material, Vertex[] vertices, int sectionIndex); + long write(long ptr, int materialBits, Vertex[] vertices, int sectionIndex); class Vertex { public float x; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/impl/CompactChunkVertex.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/impl/CompactChunkVertex.java index 67dba7bdad..d204ccf53e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/impl/CompactChunkVertex.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/vertex/format/impl/CompactChunkVertex.java @@ -32,7 +32,7 @@ public GlVertexFormat getVertexFormat() { @Override public ChunkVertexEncoder getEncoder() { - return (ptr, material, vertices, section) -> { + return (ptr, materialBits, vertices, section) -> { // Calculate the center point of the texture region which is mapped to the quad float texCentroidU = 0.0f; float texCentroidV = 0.0f; @@ -61,7 +61,7 @@ public ChunkVertexEncoder getEncoder() { MemoryUtil.memPutInt(ptr + 4L, packPositionLo(x, y, z)); MemoryUtil.memPutInt(ptr + 8L, vertex.color); MemoryUtil.memPutInt(ptr + 12L, packTexture(u, v)); - MemoryUtil.memPutInt(ptr + 16L, packLightAndData(light, material.bits(), section)); + MemoryUtil.memPutInt(ptr + 16L, packLightAndData(light, materialBits, section)); ptr += STRIDE; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/mipmaps/SpriteContentsMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/mipmaps/SpriteContentsMixin.java index 900652a175..88c52c0a44 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/mipmaps/SpriteContentsMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/mipmaps/SpriteContentsMixin.java @@ -1,52 +1,32 @@ package net.caffeinemc.mods.sodium.mixin.features.textures.mipmaps; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.mojang.blaze3d.platform.NativeImage; -import net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline.SpriteContentsExtension; import net.caffeinemc.mods.sodium.client.util.NativeImageHelper; import net.caffeinemc.mods.sodium.client.util.color.ColorSRGB; import net.minecraft.client.renderer.texture.SpriteContents; -import net.minecraft.resources.ResourceLocation; import net.minecraft.util.FastColor; import org.lwjgl.system.MemoryUtil; import org.objectweb.asm.Opcodes; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Mutable; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; /** * This Mixin is ported from Iris at MixinTextureAtlasSprite. */ @Mixin(SpriteContents.class) -public class SpriteContentsMixin implements SpriteContentsExtension { +public class SpriteContentsMixin { @Mutable @Shadow @Final private NativeImage originalImage; - @Unique - public boolean sodium$hasTransparentPixels = false; - - @Unique - public boolean sodium$hasTranslucentPixels = false; - - // While Fabric allows us to @Inject into the constructor here, that's just a specific detail of FabricMC's mixin - // fork. Upstream Mixin doesn't allow arbitrary @Inject usage in constructor. However, we can use @ModifyVariable - // just fine, in a way that hopefully doesn't conflict with other mods. - // - // By doing this, we can work with upstream Mixin as well, as is used on Forge. While we don't officially - // support Forge, since this works well on Fabric too, it's fine to ensure that the diff between Fabric and Forge - // can remain minimal. Being less dependent on specific details of Fabric is good, since it means we can be more - // cross-platform. - @Redirect(method = "", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/texture/SpriteContents;originalImage:Lcom/mojang/blaze3d/platform/NativeImage;", opcode = Opcodes.PUTFIELD)) - private void sodium$beforeGenerateMipLevels(SpriteContents instance, NativeImage nativeImage, ResourceLocation identifier) { - // We're injecting after the "info" field has been set, so this is safe even though we're in a constructor. + @WrapOperation(method = "", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/texture/SpriteContents;originalImage:Lcom/mojang/blaze3d/platform/NativeImage;", opcode = Opcodes.PUTFIELD)) + private void sodium$beforeGenerateMipLevels(SpriteContents instance, NativeImage nativeImage, Operation original) { sodium$fillInTransparentPixelColors(nativeImage); - this.originalImage = nativeImage; + original.call(instance, nativeImage); } /** @@ -58,7 +38,7 @@ public class SpriteContentsMixin implements SpriteContentsExtension { * black color does not leak over into sampling. */ @Unique - private void sodium$fillInTransparentPixelColors(NativeImage nativeImage) { + private static void sodium$fillInTransparentPixelColors(NativeImage nativeImage) { final long ppPixel = NativeImageHelper.getPointerRGBA(nativeImage); final int pixelCount = nativeImage.getHeight() * nativeImage.getWidth(); @@ -71,7 +51,7 @@ public class SpriteContentsMixin implements SpriteContentsExtension { float totalWeight = 0.0f; for (int pixelIndex = 0; pixelIndex < pixelCount; pixelIndex++) { - long pPixel = ppPixel + (pixelIndex * 4); + long pPixel = ppPixel + (pixelIndex * 4L); int color = MemoryUtil.memGetInt(pPixel); int alpha = FastColor.ABGR32.alpha(color); @@ -86,18 +66,9 @@ public class SpriteContentsMixin implements SpriteContentsExtension { b += ColorSRGB.srgbToLinear(FastColor.ABGR32.blue(color)) * weight; totalWeight += weight; - - // track if this image has transparent or even translucent pixels - if (alpha < 255) { - this.sodium$hasTranslucentPixels = true; - } - } else { - this.sodium$hasTransparentPixels = true; } } - this.sodium$hasTransparentPixels |= this.sodium$hasTranslucentPixels; - // Bail if none of the pixels are semi-transparent. if (totalWeight == 0.0f) { return; @@ -123,14 +94,4 @@ public class SpriteContentsMixin implements SpriteContentsExtension { } } } - - @Override - public boolean sodium$hasTransparentPixels() { - return this.sodium$hasTransparentPixels; - } - - @Override - public boolean sodium$hasTranslucentPixels() { - return this.sodium$hasTranslucentPixels; - } } \ No newline at end of file diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java new file mode 100644 index 0000000000..fa0d17af7a --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java @@ -0,0 +1,66 @@ +package net.caffeinemc.mods.sodium.mixin.features.textures.scan; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.mojang.blaze3d.platform.NativeImage; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline.SpriteContentsExtension; +import net.caffeinemc.mods.sodium.client.util.NativeImageHelper; +import net.minecraft.client.renderer.texture.SpriteContents; +import net.minecraft.util.FastColor; +import org.lwjgl.system.MemoryUtil; +import org.objectweb.asm.Opcodes; +import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(SpriteContents.class) +public class SpriteContentsMixin implements SpriteContentsExtension { + @Mutable + @Shadow + @Final + private NativeImage originalImage; + + @Unique + public boolean sodium$hasTransparentPixels = false; + + @Unique + public boolean sodium$hasTranslucentPixels = false; + + @WrapOperation(method = "", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/texture/SpriteContents;originalImage:Lcom/mojang/blaze3d/platform/NativeImage;", opcode = Opcodes.PUTFIELD)) + private void sodium$beforeGenerateMipLevels(SpriteContents instance, NativeImage nativeImage, Operation original) { + sodium$scanSpriteContents(nativeImage); + + original.call(instance, nativeImage); + } + + @Unique + private void sodium$scanSpriteContents(NativeImage nativeImage) { + final long ppPixel = NativeImageHelper.getPointerRGBA(nativeImage); + final int pixelCount = nativeImage.getHeight() * nativeImage.getWidth(); + + for (int pixelIndex = 0; pixelIndex < pixelCount; pixelIndex++) { + long pPixel = ppPixel + (pixelIndex * 4L); + + int color = MemoryUtil.memGetInt(pPixel); + int alpha = FastColor.ABGR32.alpha(color); + + // track if this image has transparent or even translucent pixels + if (alpha == 0) { + this.sodium$hasTransparentPixels = true; + } else if (alpha < 255) { + this.sodium$hasTranslucentPixels = true; + } + } + + this.sodium$hasTransparentPixels |= this.sodium$hasTranslucentPixels; + } + + @Override + public boolean sodium$hasTransparentPixels() { + return this.sodium$hasTransparentPixels; + } + + @Override + public boolean sodium$hasTranslucentPixels() { + return this.sodium$hasTranslucentPixels; + } +} diff --git a/common/src/main/resources/sodium.mixins.json b/common/src/main/resources/sodium.mixins.json index 7dbaeb8053..455c919fe8 100644 --- a/common/src/main/resources/sodium.mixins.json +++ b/common/src/main/resources/sodium.mixins.json @@ -82,6 +82,7 @@ "features.textures.animations.upload.SpriteContentsInterpolationMixin", "features.textures.mipmaps.MipmapGeneratorMixin", "features.textures.mipmaps.SpriteContentsMixin", + "features.textures.scan.SpriteContentsMixin", "features.world.biome.BiomeMixin", "workarounds.context_creation.WindowMixin", "workarounds.event_loop.RenderSystemMixin" From 77752599e7062e2963fa8a59362a4d416e589f10 Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 15 Aug 2024 18:15:42 +0200 Subject: [PATCH 05/12] use one tenth alpha test for determining transparency, remove ternary that gives different (but functionally identical) alpha cutoff parameters --- .../client/render/chunk/compile/pipeline/BlockRenderer.java | 6 ++---- .../mixin/features/textures/scan/SpriteContentsMixin.java | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java index 89045eb5a2..7ec0c0dede 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java @@ -191,10 +191,8 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material ChunkModelBuilder builder = this.buffers.get(pass); if (material == DefaultMaterials.TRANSLUCENT && pass == DefaultTerrainRenderPasses.CUTOUT) { - materialBits = MaterialParameters.pack( - material.mipped - ? AlphaCutoffParameter.HALF : AlphaCutoffParameter.ONE_TENTH, - material.mipped); + // ONE_TENTH and HALF are functionally the same so it doesn't matter which one we take here + materialBits = MaterialParameters.pack(AlphaCutoffParameter.ONE_TENTH, material.mipped); } ChunkMeshBufferBuilder vertexBuffer = builder.getVertexBuffer(normalFace); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java index fa0d17af7a..35a3a9009b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java @@ -44,7 +44,7 @@ public class SpriteContentsMixin implements SpriteContentsExtension { int alpha = FastColor.ABGR32.alpha(color); // track if this image has transparent or even translucent pixels - if (alpha == 0) { + if (alpha <= 25) { // 0.1 * 255 this.sodium$hasTransparentPixels = true; } else if (alpha < 255) { this.sodium$hasTranslucentPixels = true; From 87a46c67b0aac3f1636fb30d19cd30bc997a709f Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 15 Aug 2024 19:28:10 +0200 Subject: [PATCH 06/12] fix issues with wrong sprite being used by using the center of the quad's UV coordinates and by sanity checking the quad against the sprite before downgrading --- .../chunk/compile/pipeline/BlockRenderer.java | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java index 7ec0c0dede..22fd5cf222 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java @@ -160,6 +160,9 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material ChunkVertexEncoder.Vertex[] vertices = this.vertices; Vector3f offset = this.posOffset; + float uSum = 0.0f; + float vSum = 0.0f; + for (int dstIndex = 0; dstIndex < 4; dstIndex++) { int srcIndex = orientation.getVertexIndex(dstIndex); @@ -172,14 +175,33 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material // Due to our vertex format, the alpha from the quad color is ignored entirely. out.color = ColorARGB.toABGR(quad.color(srcIndex), brightnesses[srcIndex]); - out.u = quad.u(srcIndex); - out.v = quad.v(srcIndex); + uSum += out.u = quad.u(srcIndex); + vSum += out.v = quad.v(srcIndex); out.light = quad.lightmap(srcIndex); } - var atlasSprite = SpriteFinderCache.forBlockAtlas().find(quad.getTexU(0), quad.getTexV(0)); - var pass = getDowngradedPass(atlasSprite, material.pass); + var atlasSprite = SpriteFinderCache.forBlockAtlas().find(uSum / 4.0f, vSum / 4.0f); + + // sanity check that the quad's UVs are within the sprite's bounds + var spriteUMin = atlasSprite.getU0(); + var spriteUMax = atlasSprite.getU1(); + var spriteVMin = atlasSprite.getV0(); + var spriteVMax = atlasSprite.getV1(); + var doDowngrade = true; + + for (int i = 0; i < 4; i++) { + var u = vertices[i].u; + var v = vertices[i].v; + if (u < spriteUMin || u > spriteUMax || v < spriteVMin || v > spriteVMax) { + doDowngrade = false; + } + } + + var pass = material.pass; + if (doDowngrade) { + pass = getDowngradedPass(atlasSprite, pass); + } var materialBits = material.bits(); ModelQuadFacing normalFace = quad.normalFace(); @@ -190,7 +212,7 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material ChunkModelBuilder builder = this.buffers.get(pass); - if (material == DefaultMaterials.TRANSLUCENT && pass == DefaultTerrainRenderPasses.CUTOUT) { + if (doDowngrade && material == DefaultMaterials.TRANSLUCENT && pass == DefaultTerrainRenderPasses.CUTOUT) { // ONE_TENTH and HALF are functionally the same so it doesn't matter which one we take here materialBits = MaterialParameters.pack(AlphaCutoffParameter.ONE_TENTH, material.mipped); } From b51cddae6a666badb91baa3fe21892ea9d64b7a0 Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 15 Aug 2024 21:00:22 +0200 Subject: [PATCH 07/12] extract quad UV validation logic into separate method --- .../chunk/compile/pipeline/BlockRenderer.java | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java index 22fd5cf222..4fb0734cc3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java @@ -182,21 +182,7 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material } var atlasSprite = SpriteFinderCache.forBlockAtlas().find(uSum / 4.0f, vSum / 4.0f); - - // sanity check that the quad's UVs are within the sprite's bounds - var spriteUMin = atlasSprite.getU0(); - var spriteUMax = atlasSprite.getU1(); - var spriteVMin = atlasSprite.getV0(); - var spriteVMax = atlasSprite.getV1(); - var doDowngrade = true; - - for (int i = 0; i < 4; i++) { - var u = vertices[i].u; - var v = vertices[i].v; - if (u < spriteUMin || u > spriteUMax || v < spriteVMin || v > spriteVMax) { - doDowngrade = false; - } - } + boolean doDowngrade = validateQuadUVs(atlasSprite); var pass = material.pass; if (doDowngrade) { @@ -223,6 +209,24 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material builder.addSprite(atlasSprite); } + private boolean validateQuadUVs(TextureAtlasSprite atlasSprite) { + // sanity check that the quad's UVs are within the sprite's bounds + var spriteUMin = atlasSprite.getU0(); + var spriteUMax = atlasSprite.getU1(); + var spriteVMin = atlasSprite.getV0(); + var spriteVMax = atlasSprite.getV1(); + + for (int i = 0; i < 4; i++) { + var u = this.vertices[i].u; + var v = this.vertices[i].v; + if (u < spriteUMin || u > spriteUMax || v < spriteVMin || v > spriteVMax) { + return false; + } + } + + return true; + } + private static TerrainRenderPass getDowngradedPass(TextureAtlasSprite sprite, TerrainRenderPass pass) { if (sprite.contents() instanceof SpriteContentsExtension contents) { if (pass == DefaultTerrainRenderPasses.TRANSLUCENT && !contents.sodium$hasTranslucentPixels()) { From 7d2f1dc9a283101781431f38f21bcbca05d8c9f9 Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 15 Aug 2024 22:30:13 +0200 Subject: [PATCH 08/12] only do downgrade if quad is not translucent and there's non-opaque vertexes --- .../chunk/compile/pipeline/BlockRenderer.java | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java index 9cd2676743..a7671131d7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java @@ -158,8 +158,12 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material ChunkVertexEncoder.Vertex[] vertices = this.vertices; Vector3f offset = this.posOffset; + var pass = material.pass; + var attemptDowngrade = true; + float uSum = 0.0f; float vSum = 0.0f; + int alphaSum = 0; for (int dstIndex = 0; dstIndex < 4; dstIndex++) { int srcIndex = orientation.getVertexIndex(dstIndex); @@ -170,7 +174,10 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material out.z = quad.z(srcIndex) + offset.z; // FRAPI uses ARGB color format; convert to ABGR. - out.color = ColorARGB.toABGR(quad.color(srcIndex)); + var color = quad.color(srcIndex); + alphaSum += ColorARGB.unpackAlpha(color); + + out.color = ColorARGB.toABGR(color); out.ao = brightnesses[srcIndex]; uSum += out.u = quad.u(srcIndex); @@ -180,12 +187,20 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material } var atlasSprite = SpriteFinderCache.forBlockAtlas().find(uSum / 4.0f, vSum / 4.0f); - boolean doDowngrade = validateQuadUVs(atlasSprite); - var pass = material.pass; - if (doDowngrade) { + // don't do downgrade if some vertex is not fully opaque + if (pass.isTranslucent() && alphaSum < 4 * 255) { + attemptDowngrade = false; + } + + if (attemptDowngrade) { + attemptDowngrade = validateQuadUVs(atlasSprite); + } + + if (attemptDowngrade) { pass = getDowngradedPass(atlasSprite, pass); } + var materialBits = material.bits(); ModelQuadFacing normalFace = quad.normalFace(); @@ -196,7 +211,7 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material ChunkModelBuilder builder = this.buffers.get(pass); - if (doDowngrade && material == DefaultMaterials.TRANSLUCENT && pass == DefaultTerrainRenderPasses.CUTOUT) { + if (attemptDowngrade && material == DefaultMaterials.TRANSLUCENT && pass == DefaultTerrainRenderPasses.CUTOUT) { // ONE_TENTH and HALF are functionally the same so it doesn't matter which one we take here materialBits = MaterialParameters.pack(AlphaCutoffParameter.ONE_TENTH, material.mipped); } From b1656b84fa46017690454e11413eb442f479795c Mon Sep 17 00:00:00 2001 From: douira Date: Fri, 16 Aug 2024 03:53:39 +0200 Subject: [PATCH 09/12] restructure material downgrade in bufferQuad into a separate method, address review comments in the sprite contents mixin --- .../chunk/compile/pipeline/BlockRenderer.java | 65 +++++++++++-------- .../textures/scan/SpriteContentsMixin.java | 12 +++- 2 files changed, 47 insertions(+), 30 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java index a7671131d7..43ef1ecad8 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java @@ -1,5 +1,6 @@ package net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline; +import net.caffeinemc.mods.sodium.api.util.ColorABGR; import net.caffeinemc.mods.sodium.api.util.ColorARGB; import net.caffeinemc.mods.sodium.client.model.color.ColorProvider; import net.caffeinemc.mods.sodium.client.model.color.ColorProviderRegistry; @@ -154,16 +155,12 @@ private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) { } private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material material) { - ModelQuadOrientation orientation = defaultLightMode == LightMode.FLAT ? ModelQuadOrientation.NORMAL : ModelQuadOrientation.orientByBrightness(brightnesses, quad); + ModelQuadOrientation orientation = this.defaultLightMode == LightMode.FLAT ? ModelQuadOrientation.NORMAL : ModelQuadOrientation.orientByBrightness(brightnesses, quad); ChunkVertexEncoder.Vertex[] vertices = this.vertices; Vector3f offset = this.posOffset; - var pass = material.pass; - var attemptDowngrade = true; - float uSum = 0.0f; float vSum = 0.0f; - int alphaSum = 0; for (int dstIndex = 0; dstIndex < 4; dstIndex++) { int srcIndex = orientation.getVertexIndex(dstIndex); @@ -174,10 +171,7 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material out.z = quad.z(srcIndex) + offset.z; // FRAPI uses ARGB color format; convert to ABGR. - var color = quad.color(srcIndex); - alphaSum += ColorARGB.unpackAlpha(color); - - out.color = ColorARGB.toABGR(color); + out.color = ColorARGB.toABGR(quad.color(srcIndex)); out.ao = brightnesses[srcIndex]; uSum += out.u = quad.u(srcIndex); @@ -187,35 +181,28 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material } var atlasSprite = SpriteFinderCache.forBlockAtlas().find(uSum / 4.0f, vSum / 4.0f); - - // don't do downgrade if some vertex is not fully opaque - if (pass.isTranslucent() && alphaSum < 4 * 255) { - attemptDowngrade = false; - } - - if (attemptDowngrade) { - attemptDowngrade = validateQuadUVs(atlasSprite); - } - - if (attemptDowngrade) { - pass = getDowngradedPass(atlasSprite, pass); - } - var materialBits = material.bits(); - ModelQuadFacing normalFace = quad.normalFace(); + // attempt render pass downgrade if possible + var pass = material.pass; + var downgradedPass = attemptPassDowngrade(quad, atlasSprite, pass); + if (downgradedPass != null) { + pass = downgradedPass; + } + + // collect all translucent quads into the translucency sorting system if enabled if (pass.isTranslucent() && this.collector != null) { this.collector.appendQuad(quad.getFaceNormal(), vertices, normalFace); } - ChunkModelBuilder builder = this.buffers.get(pass); - - if (attemptDowngrade && material == DefaultMaterials.TRANSLUCENT && pass == DefaultTerrainRenderPasses.CUTOUT) { + // if there was a downgrade from translucent to cutout, the material bits' alpha cutoff needs to be updated + if (downgradedPass != null && material == DefaultMaterials.TRANSLUCENT && pass == DefaultTerrainRenderPasses.CUTOUT) { // ONE_TENTH and HALF are functionally the same so it doesn't matter which one we take here materialBits = MaterialParameters.pack(AlphaCutoffParameter.ONE_TENTH, material.mipped); } + ChunkModelBuilder builder = this.buffers.get(pass); ChunkMeshBufferBuilder vertexBuffer = builder.getVertexBuffer(normalFace); vertexBuffer.push(vertices, materialBits); @@ -240,6 +227,30 @@ private boolean validateQuadUVs(TextureAtlasSprite atlasSprite) { return true; } + private TerrainRenderPass attemptPassDowngrade(MutableQuadViewImpl quad, TextureAtlasSprite sprite, TerrainRenderPass pass) { + boolean attemptDowngrade = true; + boolean hasNonOpaqueVertex = false; + + for (int i = 0; i < 4; i++) { + hasNonOpaqueVertex |= ColorABGR.unpackAlpha(this.vertices[i].color) != 0xFF; + } + + // don't do downgrade if some vertex is not fully opaque + if (pass.isTranslucent() && hasNonOpaqueVertex) { + attemptDowngrade = false; + } + + if (attemptDowngrade) { + attemptDowngrade = validateQuadUVs(sprite); + } + + if (attemptDowngrade) { + return getDowngradedPass(sprite, pass); + } + + return null; + } + private static TerrainRenderPass getDowngradedPass(TextureAtlasSprite sprite, TerrainRenderPass pass) { if (sprite.contents() instanceof SpriteContentsExtension contents) { if (pass == DefaultTerrainRenderPasses.TRANSLUCENT && !contents.sodium$hasTranslucentPixels()) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java index 35a3a9009b..4c5da4f84a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java @@ -12,6 +12,9 @@ import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.injection.At; +/** + * This mixin scans a {@link SpriteContents} for transparent and translucent pixels. This information is later used during mesh generation to reassign the render pass to either cutout if the sprite has no translucent pixels or solid if it doesn't even have any transparent pixels. + */ @Mixin(SpriteContents.class) public class SpriteContentsMixin implements SpriteContentsExtension { @Mutable @@ -25,15 +28,18 @@ public class SpriteContentsMixin implements SpriteContentsExtension { @Unique public boolean sodium$hasTranslucentPixels = false; + /* + * Uses a WrapOperation here since Inject doesn't work on 1.20.1 forge. + */ @WrapOperation(method = "", at = @At(value = "FIELD", target = "Lnet/minecraft/client/renderer/texture/SpriteContents;originalImage:Lcom/mojang/blaze3d/platform/NativeImage;", opcode = Opcodes.PUTFIELD)) private void sodium$beforeGenerateMipLevels(SpriteContents instance, NativeImage nativeImage, Operation original) { - sodium$scanSpriteContents(nativeImage); + scanSpriteContents(nativeImage); original.call(instance, nativeImage); } @Unique - private void sodium$scanSpriteContents(NativeImage nativeImage) { + private void scanSpriteContents(NativeImage nativeImage) { final long ppPixel = NativeImageHelper.getPointerRGBA(nativeImage); final int pixelCount = nativeImage.getHeight() * nativeImage.getWidth(); @@ -43,7 +49,7 @@ public class SpriteContentsMixin implements SpriteContentsExtension { int color = MemoryUtil.memGetInt(pPixel); int alpha = FastColor.ABGR32.alpha(color); - // track if this image has transparent or even translucent pixels + // 25 is used as the threshold since the alpha cutoff is 0.1 if (alpha <= 25) { // 0.1 * 255 this.sodium$hasTransparentPixels = true; } else if (alpha < 255) { From aff9e6aa7ea706db048412aac4081dff05b9ead6 Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 20 Aug 2024 19:21:18 +0200 Subject: [PATCH 10/12] cleanup color unpacking --- .../mixin/features/textures/scan/SpriteContentsMixin.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java index 4c5da4f84a..9f609ce4ae 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java @@ -3,10 +3,10 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.mojang.blaze3d.platform.NativeImage; +import net.caffeinemc.mods.sodium.api.util.ColorABGR; import net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline.SpriteContentsExtension; import net.caffeinemc.mods.sodium.client.util.NativeImageHelper; import net.minecraft.client.renderer.texture.SpriteContents; -import net.minecraft.util.FastColor; import org.lwjgl.system.MemoryUtil; import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.*; @@ -44,10 +44,8 @@ private void scanSpriteContents(NativeImage nativeImage) { final int pixelCount = nativeImage.getHeight() * nativeImage.getWidth(); for (int pixelIndex = 0; pixelIndex < pixelCount; pixelIndex++) { - long pPixel = ppPixel + (pixelIndex * 4L); - - int color = MemoryUtil.memGetInt(pPixel); - int alpha = FastColor.ABGR32.alpha(color); + int color = MemoryUtil.memGetInt(ppPixel + (pixelIndex * 4L)); + int alpha = ColorABGR.unpackAlpha(color); // 25 is used as the threshold since the alpha cutoff is 0.1 if (alpha <= 25) { // 0.1 * 255 From b1af71db39b028b2cc16783408e8e255050b1606 Mon Sep 17 00:00:00 2001 From: douira Date: Wed, 21 Aug 2024 01:40:45 +0200 Subject: [PATCH 11/12] add comments --- .../mixin/features/textures/scan/SpriteContentsMixin.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java index 9f609ce4ae..db106e65e4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/SpriteContentsMixin.java @@ -14,6 +14,8 @@ /** * This mixin scans a {@link SpriteContents} for transparent and translucent pixels. This information is later used during mesh generation to reassign the render pass to either cutout if the sprite has no translucent pixels or solid if it doesn't even have any transparent pixels. + * + * @author douira */ @Mixin(SpriteContents.class) public class SpriteContentsMixin implements SpriteContentsExtension { @@ -55,6 +57,8 @@ private void scanSpriteContents(NativeImage nativeImage) { } } + // the image contains transparency also if there are translucent pixels, + // since translucent pixels prevent a downgrade to the opaque render pass just as transparent pixels do this.sodium$hasTransparentPixels |= this.sodium$hasTranslucentPixels; } From 969558a37fadf2e876c8664d2d31f94f0ac7ab7f Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 25 Aug 2024 03:30:01 +0200 Subject: [PATCH 12/12] fix merge --- .../client/render/chunk/compile/pipeline/BlockRenderer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java index 2510b6e956..fee294d176 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockRenderer.java @@ -154,7 +154,7 @@ private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) { } } - private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material material, ChunkModelBuilder modelBuilder) { + private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material material) { // TODO: Find a way to reimplement quad reorientation ModelQuadOrientation orientation = ModelQuadOrientation.NORMAL; ChunkVertexEncoder.Vertex[] vertices = this.vertices;