From 0ac714c19ec787f0c206445051f5e309ff63ee74 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sun, 27 Oct 2024 16:49:23 -0500 Subject: [PATCH 001/215] Bump version to 0.6.0-beta.4 --- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 9d9844cf3b..fb66a7083a 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -11,7 +11,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = null // https://semver.org/ - var MOD_VERSION: String = "0.6.0-beta.2" + var MOD_VERSION: String = "0.6.0-beta.4" fun createVersionString(project: Project): String { val builder = StringBuilder() From 7b77a83d8a47ca9e550c7ebbd62cb616e42119b5 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 27 Oct 2024 22:46:38 +0100 Subject: [PATCH 002/215] Ensure tooltips are constrained to the screen (#2845) --- .../mods/sodium/client/gui/SodiumOptionsGUI.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java index d5ad5d5768..aba63b7b0b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java @@ -295,18 +295,21 @@ private void renderOptionTooltip(GuiGraphics graphics, ControlElement element int textPadding = 3; int boxPadding = 3; - int boxWidth = 200; - int boxY = dim.y(); int boxX = dim.getLimitX() + boxPadding; + int boxWidth = Math.min(200, this.width - boxX - boxPadding); + Option option = element.getOption(); - List tooltip = new ArrayList<>(this.font.split(option.getTooltip(), boxWidth - (textPadding * 2))); + var splitWidth = boxWidth - (textPadding * 2); + List tooltip = new ArrayList<>(this.font.split(option.getTooltip(),splitWidth)); OptionImpact impact = option.getImpact(); if (impact != null) { - tooltip.add(Language.getInstance().getVisualOrder(Component.translatable("sodium.options.performance_impact_string", impact.getLocalizedName()).withStyle(ChatFormatting.GRAY))); + var impactText = Component.translatable("sodium.options.performance_impact_string", + impact.getLocalizedName()); + tooltip.addAll(this.font.split(impactText.withStyle(ChatFormatting.GRAY), splitWidth)); } int boxHeight = (tooltip.size() * 12) + boxPadding; From 7ebd3dc040b8c2917ee3908b7337a16d12536a90 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Wed, 30 Oct 2024 03:27:54 -0500 Subject: [PATCH 003/215] Update authors and contributors list --- fabric/src/main/resources/fabric.mod.json | 45 ++++++++++--------- .../resources/META-INF/neoforge.mods.toml | 4 +- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 9b7fdda2c2..b2f72d02bb 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -6,7 +6,7 @@ "description" : "Sodium is a powerful rendering engine for Minecraft which greatly improves frame rates and micro-stutter, while fixing many graphical issues", "authors" : [ { - "name" : "@jellysquid3", + "name" : "JellySquid (jellysquid3)", "contact" : { "email" : "jellysquid@pm.me", "homepage" : "https://jellysquid.me" @@ -14,27 +14,28 @@ } ], "contributors" : [ - "@IMS212", - "@bytzo", - "@PepperCode1", - "@FlashyReese", - "@altrisi", - "@Grayray75", - "@Madis0", - "@Johni0702", - "@comp500", - "@coderbot16", - "@Moulberry", - "@MCRcortex", - "@Altirix", - "@embeddedt", - "@pajicadvance", - "@Kroppeb", - "@douira", - "@burgerindividual", - "@TwistedZero", - "@Leo40Git", - "@haykam821" + "IMS212", + "bytzo", + "PepperCode1", + "FlashyReese", + "altrisi", + "Grayray75", + "Madis0", + "Johni0702", + "comp500", + "coderbot16", + "Moulberry", + "MCRcortex", + "Altirix", + "embeddedt", + "pajicadvance", + "Kroppeb", + "douira", + "burgerindividual", + "TwistedZero", + "Leo40Git", + "haykam821", + "muzikbike" ], "contact" : { "homepage" : "https://github.com/CaffeineMC/sodium", diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/main/resources/META-INF/neoforge.mods.toml index e12ce22ee8..5e1a6b3f2e 100644 --- a/neoforge/src/main/resources/META-INF/neoforge.mods.toml +++ b/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -10,9 +10,9 @@ displayName = "Sodium" logoFile = "sodium-icon.png" #optional -authors = "JellySquid, IMS212" +authors = "JellySquid (jellysquid3), IMS212" -credits = "@bytzo, @PepperCode1, @FlashyReese, @altrisi, @Grayray75, @Madis0, @Johni0702, @comp500, @coderbot16, @Moulberry, @MCRcortex, @Altirix, @embeddedt, @pajicadvance, @Kroppeb, @douira, @burgerindividual, @TwistedZero, @Leo40Git, @haykam821" +credits = "bytzo, PepperCode1, FlashyReese, altrisi, Grayray75, Madis0, Johni0702, comp500, coderbot16, Moulberry, MCRcortex, Altirix, embeddedt, pajicadvance, Kroppeb, douira, burgerindividual, TwistedZero, Leo40Git, haykam821, muzikbike" description = ''' Sodium is a powerful rendering engine for Minecraft which improves frame rates and reduces lag spikes. From de814a0b57fbe812b51a3683229b0cb7826a50ce Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sat, 2 Nov 2024 23:00:06 -0500 Subject: [PATCH 004/215] Avoid static memory allocation in EntityRenderer Just allocate on the stack, since it's a small amount of memory (<1 KiB) and avoids needing complex finalizers. --- .../render/immediate/model/EntityRenderer.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/EntityRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/EntityRenderer.java index 0c618b8204..fd7743dac3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/EntityRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/EntityRenderer.java @@ -10,7 +10,6 @@ import org.joml.Vector2f; import org.joml.Vector3f; import org.lwjgl.system.MemoryStack; -import org.lwjgl.system.MemoryUtil; import static net.caffeinemc.mods.sodium.client.render.immediate.model.ModelCuboid.*; @@ -31,7 +30,7 @@ public class EntityRenderer { private static final Matrix3f lastMatrix = new Matrix3f(); - private static final long SCRATCH_BUFFER = MemoryUtil.nmemAlignedAlloc(64, NUM_CUBE_FACES * NUM_FACE_VERTICES * EntityVertex.STRIDE); + private static final int VERTEX_BUFFER_BYTES = NUM_CUBE_FACES * NUM_FACE_VERTICES * EntityVertex.STRIDE; private static final Vector3f[] CUBE_CORNERS = new Vector3f[NUM_CUBE_VERTICES]; private static final int[][] CUBE_VERTICES = new int[NUM_CUBE_FACES][]; @@ -74,24 +73,26 @@ public class EntityRenderer { public static void renderCuboid(PoseStack.Pose matrices, VertexBufferWriter writer, ModelCuboid cuboid, int light, int overlay, int color) { prepareNormalsIfChanged(matrices); - prepareVertices(matrices, cuboid); - var vertexCount = emitQuads(cuboid, color, overlay, light); - try (MemoryStack stack = MemoryStack.stackPush()) { - writer.push(stack, SCRATCH_BUFFER, vertexCount, EntityVertex.FORMAT); + final var vertexBuffer = stack.nmalloc(16, VERTEX_BUFFER_BYTES); + final var vertexCount = emitQuads(vertexBuffer, cuboid, color, overlay, light); + + if (vertexCount > 0) { + writer.push(stack, vertexBuffer, vertexCount, EntityVertex.FORMAT); + } } } - private static int emitQuads(ModelCuboid cuboid, int color, int overlay, int light) { + private static int emitQuads(final long buffer, ModelCuboid cuboid, int color, int overlay, int light) { final var positions = cuboid.mirror ? VERTEX_POSITIONS_MIRRORED : VERTEX_POSITIONS; final var textures = cuboid.mirror ? VERTEX_TEXTURES_MIRRORED : VERTEX_TEXTURES; final var normals = cuboid.mirror ? CUBE_NORMALS_MIRRORED : CUBE_NORMALS; var vertexCount = 0; - long ptr = SCRATCH_BUFFER; + long ptr = buffer; for (int quadIndex = 0; quadIndex < NUM_CUBE_FACES; quadIndex++) { if (!cuboid.shouldDrawFace(quadIndex)) { From 76026e10f587ce032d4577c61166f20d855634ce Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 3 Nov 2024 02:03:55 -0600 Subject: [PATCH 005/215] Fix y-offset calculation for back face culling in cloud rendering (#2864) --- .../mods/sodium/client/render/immediate/CloudRenderer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java index e0e6cf6088..b6f116100f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java @@ -76,7 +76,8 @@ public void render(Camera camera, // -1 if below clouds, +1 if above clouds var cloudType = Minecraft.getInstance().options.getCloudsType(); - int orientation = cloudType == CloudStatus.FANCY ? (int) Math.signum(pos.y() - cloudHeight) : 0; + var relativeCloudY = cloudHeight - (float) pos.y() + 0.33F; + int orientation = cloudType == CloudStatus.FANCY ? (int) Math.signum(-relativeCloudY) : 0; var parameters = new CloudGeometryParameters(centerCellX, centerCellZ, cloudDistance, orientation, cloudType); CloudGeometry geometry = this.cachedGeometry; @@ -98,7 +99,7 @@ public void render(Camera camera, var poseEntry = poseStack.last(); Matrix4f modelViewMatrix = poseEntry.pose(); - modelViewMatrix.translate(-translateX, cloudHeight - (float) pos.y() + 0.33F, -translateZ); + modelViewMatrix.translate(-translateX, relativeCloudY, -translateZ); final var prevShaderFogShape = RenderSystem.getShaderFogShape(); final var prevShaderFogEnd = RenderSystem.getShaderFogEnd(); From 02e6927a9b1dfb02a880f57e4bbb20d84c69066c Mon Sep 17 00:00:00 2001 From: JellySquid Date: Mon, 4 Nov 2024 18:48:13 -0600 Subject: [PATCH 006/215] Improve color mixing functions The existing functions did not implement rounding correctly (often leading to off-by-one errors). Additionally, the improved variants are both slightly faster and easier to understand. --- .../mods/sodium/api/util/ColorABGR.java | 21 ++++- .../mods/sodium/api/util/ColorMixer.java | 94 +++++++------------ .../render/immediate/CloudRenderer.java | 28 +++--- 3 files changed, 68 insertions(+), 75 deletions(-) diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java index a7e86c52c9..89d08c332c 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java @@ -9,11 +9,17 @@ * | Alpha | Blue | Green | Red | */ public class ColorABGR implements ColorU8 { - private static final int RED_COMPONENT_OFFSET = 0; + private static final int RED_COMPONENT_OFFSET = 0; private static final int GREEN_COMPONENT_OFFSET = 8; - private static final int BLUE_COMPONENT_OFFSET = 16; + private static final int BLUE_COMPONENT_OFFSET = 16; private static final int ALPHA_COMPONENT_OFFSET = 24; + private static final int RED_COMPONENT_MASK = COMPONENT_MASK << RED_COMPONENT_OFFSET; + private static final int GREEN_COMPONENT_MASK = COMPONENT_MASK << GREEN_COMPONENT_OFFSET; + private static final int BLUE_COMPONENT_MASK = COMPONENT_MASK << BLUE_COMPONENT_OFFSET; + private static final int ALPHA_COMPONENT_MASK = COMPONENT_MASK << ALPHA_COMPONENT_OFFSET; + + /** * Packs the specified color components into ABGR format. The alpha component is fully opaque. * @param r The red component of the color @@ -98,4 +104,15 @@ public static int unpackBlue(int color) { public static int unpackAlpha(int color) { return (color >> ALPHA_COMPONENT_OFFSET) & COMPONENT_MASK; } + + /** + * Darkens the RGB components of the color by multiplying them with the provided factor. The alpha component is + * not modified. + * + * @param color The packed 32-bit ABGR color to be multiplied + * @param factor The darkening factor (in the range of 0..255) to multiply with + */ + public static int darken(int color, int factor) { + return (ColorMixer.mul(color, factor) & ~ALPHA_COMPONENT_MASK) | (color & ALPHA_COMPONENT_MASK); + } } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java index a1934c8b44..6e53854185 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java @@ -1,77 +1,53 @@ package net.caffeinemc.mods.sodium.api.util; +/** + * A collection of optimized color mixing functions which directly operate on packed color values. These functions are + * agnostic to the ordering of color channels, and the output value will always use the same channel ordering as + * the input values. + */ public class ColorMixer { - private static final int CHANNEL_MASK = 0x00FF00FF; - /** - * Mixes a 32-bit color (with packed 8-bit components) into another using the given ratio. This is equivalent to - * (color1 * ratio) + (color2 * (1.0 - ratio)) but uses bitwise trickery for maximum performance. - *

- * The order of the channels within the packed color does not matter, and the output color will always - * have the same ordering as the input colors. + *

Linearly interpolate between the {@param start} and {@param end} points, represented as packed unsigned 8-bit + * values within a 32-bit integer. The result is computed as

(x * a) * (y * (255 - a))
.

* - * @param aColor The color to mix towards - * @param bColor The color to mix away from - * @param ratio The percentage (in 0.0..1.0 range) to mix the first color into the second color - * @return The mixed color in packed 32-bit format - */ - public static int mix(int aColor, int bColor, float ratio) { - int aRatio = (int) (256 * ratio); // int(ratio) - int bRatio = 256 - aRatio; // int(1.0 - ratio) - - // Mask off and shift two components from each color into a packed vector of 16-bit components, where the - // high 8 bits are all zeroes. - int a1 = (aColor >> 0) & CHANNEL_MASK; - int b1 = (bColor >> 0) & CHANNEL_MASK; - int a2 = (aColor >> 8) & CHANNEL_MASK; - int b2 = (bColor >> 8) & CHANNEL_MASK; - - // Multiply the packed 16-bit components against each mix factor, and add the components of each color - // to produce the mixed result. This will never overflow since both 16-bit integers are in 0..255 range. - - // Then, shift the high 8 bits of each packed 16-bit component into the low 8 bits, and mask off the high bits of - // each 16-bit component to produce a vector of packed 8-bit components, where every other component is empty. - int c1 = (((a1 * aRatio) + (b1 * bRatio)) >> 8) & CHANNEL_MASK; - int c2 = (((a2 * aRatio) + (b2 * bRatio)) >> 8) & CHANNEL_MASK; + *

The results are undefined if {@param a} is not within the interval [0, 255].

- // Join the color components into the original order - return ((c1 << 0) | (c2 << 8)); + * @param color0 The start of the range to interpolate + * @param color1 The end of the range to interpolate + * @param weight The weight value used to interpolate between color values (in 0..255 range) + * @return The color that was interpolated between the start and end points + */ + public static int mix(int color0, int color1, int weight) { + // Overflow is not possible, so adding the values is fine. + return mul(color0, weight) + mul(color1, ColorU8.COMPONENT_MASK - weight); } /** - * Multiplies the 32-bit colors (with packed 8-bit components) together. - *

- * The order of the channels within the packed color does not matter, and the output color will always - * have the same ordering as the input colors. + *

This function is identical to {@link ColorMixer#mix(int, int, int)}, but {@param a} is a normalized + * floating-point value within the interval of [0.0, 1.0].

+ * + *

The results are undefined if {@param a} is not within the interval [0.0, 1.0].

* - * @param a The first color to multiply - * @param b The second color to multiply - * @return The multiplied color in packed 32-bit format + * @param color0 The start of the range to interpolate + * @param color1 The end of the range to interpolate + * @param weight The weight value used to interpolate between color values (in 0.0..1.0 range) + * @return The color that was interpolated between the start and end points */ - public static int mul(int a, int b) { - // Take each 8-bit component pair, multiply them together to create intermediate 16-bit integers, - // and then shift the high half of each 16-bit integer into 8-bit integers. - int c0 = (((a >> 0) & 0xFF) * ((b >> 0) & 0xFF)) >> 8; - int c1 = (((a >> 8) & 0xFF) * ((b >> 8) & 0xFF)) >> 8; - int c2 = (((a >> 16) & 0xFF) * ((b >> 16) & 0xFF)) >> 8; - int c3 = (((a >> 24) & 0xFF) * ((b >> 24) & 0xFF)) >> 8; - - // Pack the components - return (c0 << 0) | (c1 << 8) | (c2 << 16) | (c3 << 24); + public static int mix(int color0, int color1, float weight) { + return mix(color0, color1, ColorU8.normalizedFloatToByte(weight)); } /** - * Multiplies the 32-bit colors with one component + *

Multiplies each 8-bit component of the packed 32-bit color.

+ * + * @param color The packed color values + * @param factor The multiplication factor (in 0..255 range) + * @return The result of the multiplication */ - public static int mulSingle(int a, int b) { - // Take each 8-bit component pair, multiply them together to create intermediate 16-bit integers, - // and then shift the high half of each 16-bit integer into 8-bit integers. - int c0 = (((a) & 0xFF) * b) >> 8; - int c1 = (((a >> 8) & 0xFF) * b) >> 8; - int c2 = (((a >> 16) & 0xFF) * b) >> 8; - int c3 = (((a >> 24) & 0xFF) * b) >> 8; + public static int mul(int color, int factor) { + final long result = (((((color & 0x00FF00FFL) * factor) + 0x00FF00FFL) >>> 8) & 0x00FF00FFL) | + (((((color & 0xFF00FF00L) * factor) + 0xFF00FF00L) >>> 8) & 0xFF00FF00L); - // Pack the components - return (c0 << 0) | (c1 << 8) | (c2 << 16) | (c3 << 24); + return (int) result; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java index b6f116100f..3020bb09d0 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java @@ -4,11 +4,11 @@ import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.*; +import net.caffeinemc.mods.sodium.api.util.ColorU8; import net.caffeinemc.mods.sodium.api.vertex.format.common.ColorVertex; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; import net.caffeinemc.mods.sodium.api.util.ColorABGR; import net.caffeinemc.mods.sodium.api.util.ColorARGB; -import net.caffeinemc.mods.sodium.api.util.ColorMixer; import net.minecraft.client.Camera; import net.minecraft.client.CloudStatus; import net.minecraft.client.Minecraft; @@ -350,7 +350,7 @@ private static void emitCellGeometry2D(VertexBufferWriter writer, int faces, int long ptr = buffer; int count = 0; - int mixedColor = ColorMixer.mul(color, CloudFace.POS_Y.getColor()); + int mixedColor = ColorABGR.darken(color, CloudFace.POS_Y.getShade()); ptr = writeVertex(ptr, x + 12.0f, 0.0f, z + 12.0f, mixedColor); ptr = writeVertex(ptr, x + 0.0f, 0.0f, z + 12.0f, mixedColor); @@ -376,7 +376,7 @@ private static void emitCellGeometry3D(VertexBufferWriter writer, int visibleFac } final var vertices = VERTICES[face.ordinal()]; - final int color = ColorMixer.mul(baseColor, face.getColor()); + final int color = ColorABGR.darken(baseColor, face.getShade()); for (int vertexIndex = 0; vertexIndex < 4; vertexIndex++) { Vector3f vertex = vertices[interior ? 3 - vertexIndex : vertexIndex]; @@ -459,24 +459,24 @@ private static int getCloudRenderDistance() { } private enum CloudFace { - NEG_Y(ColorABGR.pack(0.7F, 0.7F, 0.7F, 1.0f)), - POS_Y(ColorABGR.pack(1.0f, 1.0f, 1.0f, 1.0f)), - NEG_X(ColorABGR.pack(0.9F, 0.9F, 0.9F, 1.0f)), - POS_X(ColorABGR.pack(0.9F, 0.9F, 0.9F, 1.0f)), - NEG_Z(ColorABGR.pack(0.8F, 0.8F, 0.8F, 1.0f)), - POS_Z(ColorABGR.pack(0.8F, 0.8F, 0.8F, 1.0f)); + NEG_Y(0.7F), + POS_Y(1.0f), + NEG_X(0.9F), + POS_X(0.9F), + NEG_Z(0.8F), + POS_Z(0.8F); public static final CloudFace[] VALUES = CloudFace.values(); public static final int COUNT = VALUES.length; - private final int color; + private final int shade; - CloudFace(int color) { - this.color = color; + CloudFace(float shade) { + this.shade = ColorU8.normalizedFloatToByte(shade); } - public int getColor() { - return this.color; + public int getShade() { + return this.shade; } } From cf2742f81aa298c03da4cf5e6004ab1986b2e796 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Mon, 4 Nov 2024 19:01:18 -0600 Subject: [PATCH 007/215] Fixup documentation in ColorMixer --- .../mods/sodium/api/util/ColorMixer.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java index 6e53854185..75c16c7dd0 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java @@ -8,33 +8,33 @@ public class ColorMixer { /** *

Linearly interpolate between the {@param start} and {@param end} points, represented as packed unsigned 8-bit - * values within a 32-bit integer. The result is computed as

(x * a) * (y * (255 - a))
.

+ * values within a 32-bit integer. The result is computed as
(start * weight) + (end * (255 - weight))
.

* - *

The results are undefined if {@param a} is not within the interval [0, 255].

+ *

The results are undefined if {@param weight} is not within the interval [0, 255].

- * @param color0 The start of the range to interpolate - * @param color1 The end of the range to interpolate + * @param start The start of the range to interpolate + * @param end The end of the range to interpolate * @param weight The weight value used to interpolate between color values (in 0..255 range) * @return The color that was interpolated between the start and end points */ - public static int mix(int color0, int color1, int weight) { + public static int mix(int start, int end, int weight) { // Overflow is not possible, so adding the values is fine. - return mul(color0, weight) + mul(color1, ColorU8.COMPONENT_MASK - weight); + return mul(start, weight) + mul(end, ColorU8.COMPONENT_MASK - weight); } /** - *

This function is identical to {@link ColorMixer#mix(int, int, int)}, but {@param a} is a normalized + *

This function is identical to {@link ColorMixer#mix(int, int, int)}, but {@param weight} is a normalized * floating-point value within the interval of [0.0, 1.0].

* - *

The results are undefined if {@param a} is not within the interval [0.0, 1.0].

+ *

The results are undefined if {@param weight} is not within the interval [0.0, 1.0].

* - * @param color0 The start of the range to interpolate - * @param color1 The end of the range to interpolate + * @param start The start of the range to interpolate + * @param end The end of the range to interpolate * @param weight The weight value used to interpolate between color values (in 0.0..1.0 range) * @return The color that was interpolated between the start and end points */ - public static int mix(int color0, int color1, float weight) { - return mix(color0, color1, ColorU8.normalizedFloatToByte(weight)); + public static int mix(int start, int end, float weight) { + return mix(start, end, ColorU8.normalizedFloatToByte(weight)); } /** From ab01859d5c969bf46da1b3be550972446f29c670 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Mon, 4 Nov 2024 21:25:03 -0600 Subject: [PATCH 008/215] Unify color mixing/swizzling utilities The Fabric integration code was re-implementing a lot of the utilities that already exist in Sodium unnecessarily. Also, improve the documentation so that ABGR and RGBA are not used interchangeably. --- .../mods/sodium/api/util/ColorABGR.java | 45 ++++++++++-- .../mods/sodium/api/util/ColorARGB.java | 55 +++++++++++++-- .../mods/sodium/api/util/ColorMixer.java | 32 ++++++++- .../chunk/compile/pipeline/BlockRenderer.java | 4 +- .../format/impl/CompactChunkVertex.java | 4 +- .../render/frapi/helper/ColorHelper.java | 70 +++---------------- .../frapi/render/ItemRenderContext.java | 3 +- .../render/NonTerrainBlockRenderContext.java | 7 +- .../render/immediate/CloudRenderer.java | 4 +- .../immediate/model/BakedModelEncoder.java | 4 +- 10 files changed, 143 insertions(+), 85 deletions(-) diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java index 89d08c332c..6869755ae0 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java @@ -1,5 +1,7 @@ package net.caffeinemc.mods.sodium.api.util; +import java.nio.ByteOrder; + /** * Provides some utilities for packing and unpacking color components from packed integer colors in ABGR format, which * is used by OpenGL for color vectors. @@ -19,7 +21,6 @@ public class ColorABGR implements ColorU8 { private static final int BLUE_COMPONENT_MASK = COMPONENT_MASK << BLUE_COMPONENT_OFFSET; private static final int ALPHA_COMPONENT_MASK = COMPONENT_MASK << ALPHA_COMPONENT_OFFSET; - /** * Packs the specified color components into ABGR format. The alpha component is fully opaque. * @param r The red component of the color @@ -106,13 +107,49 @@ public static int unpackAlpha(int color) { } /** - * Darkens the RGB components of the color by multiplying them with the provided factor. The alpha component is - * not modified. + * Multiplies the RGB components of the color with the provided factor. The alpha component is not modified. * * @param color The packed 32-bit ABGR color to be multiplied * @param factor The darkening factor (in the range of 0..255) to multiply with */ - public static int darken(int color, int factor) { + public static int mulRGB(int color, int factor) { return (ColorMixer.mul(color, factor) & ~ALPHA_COMPONENT_MASK) | (color & ALPHA_COMPONENT_MASK); } + + /** + * See {@link #mulRGB(int, int)}. This function is identical, but it accepts a float in [0.0, 1.0] instead, which + * is then mapped to [0, 255]. + * + * @param color The packed 32-bit ABGR color to be multiplied + * @param factor The darkening factor (in the range of 0.0..1.0) to multiply with + */ + public static int mulRGB(int color, float factor) { + return mulRGB(color, ColorU8.normalizedFloatToByte(factor)); + } + + private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; + + /** + * Shuffles the ordering of the ABGR color so that it can then be written out to memory in the platform's native + * byte ordering. This should be used when writing the packed color to memory (in native-order) as a 32-bit word. + */ + public static int fromNativeByteOrder(int color) { + if (BIG_ENDIAN) { + return Integer.reverseBytes(color); + } else { + return color; + } + } + + /** + * Shuffles the ordering of the ABGR color from the platform's native byte ordering. This should be used when reading + * the packed color from memory (in native-order) as a 32-bit word. + */ + public static int toNativeByteOrder(int color) { + if (BIG_ENDIAN) { + return Integer.reverseBytes(color); + } else { + return color; + } + } } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorARGB.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorARGB.java index f93880555e..2d7899111f 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorARGB.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorARGB.java @@ -15,6 +15,11 @@ public class ColorARGB implements ColorU8 { private static final int GREEN_COMPONENT_OFFSET = 8; private static final int BLUE_COMPONENT_OFFSET = 0; + private static final int RED_COMPONENT_MASK = COMPONENT_MASK << RED_COMPONENT_OFFSET; + private static final int GREEN_COMPONENT_MASK = COMPONENT_MASK << GREEN_COMPONENT_OFFSET; + private static final int BLUE_COMPONENT_MASK = COMPONENT_MASK << BLUE_COMPONENT_OFFSET; + private static final int ALPHA_COMPONENT_MASK = COMPONENT_MASK << ALPHA_COMPONENT_OFFSET; + /** * Packs the specified color components into big-endian format for consumption by OpenGL. * @param r The red component of the color @@ -73,23 +78,40 @@ public static int unpackBlue(int color) { } /** - * Re-packs the ARGB color into an ABGR color with the specified alpha component. + * Swizzles from ARGB format into ABGR format, replacing the alpha component with {@param alpha}. */ - public static int toABGR(int color, float alpha) { - return Integer.reverseBytes(color << 8 | ColorU8.normalizedFloatToByte(alpha)); + public static int toABGR(int color, int alpha) { + // shl(ARGB, 8) -> RGB0 + // or(RGB0, 000A) -> RGBA + return Integer.reverseBytes(color << 8 | alpha); } /** - * Re-packs the ARGB color into a aBGR color with the specified alpha component. + * Swizzles from ARGB format into ABGR format, replacing the alpha component with {@param alpha}. The alpha + * component is mapped from [0.0, 1.0] to [0, 255]. */ - public static int toABGR(int color, int alpha) { - return Integer.reverseBytes(color << 8 | alpha); + public static int toABGR(int color, float alpha) { + return toABGR(color, ColorU8.normalizedFloatToByte(alpha)); } + /** + * Swizzles from ARGB format into ABGR format. + */ public static int toABGR(int color) { + // rotateLeft(ARGB, 8) -> RGBA + // reverseBytes(RGBA) -> ABGR return Integer.reverseBytes(Integer.rotateLeft(color, 8)); } + /** + * Swizzles from ABGR format into ARGB format. + */ + public static int fromABGR(int color) { + // reverseBytes(ABGR) -> RGBA + // rotateRight(RGBA, 8) -> ARGB + return Integer.rotateRight(Integer.reverseBytes(color), 8); + } + /** * Packs the specified color components into ARGB format. * @param rgb The red/green/blue component of the color @@ -98,4 +120,25 @@ public static int toABGR(int color) { public static int withAlpha(int rgb, int alpha) { return (alpha << ALPHA_COMPONENT_OFFSET) | (rgb & ~(COMPONENT_MASK << ALPHA_COMPONENT_OFFSET)); } + + /** + * Multiplies the RGB components of the color with the provided factor. The alpha component is not modified. + * + * @param color The packed 32-bit ABGR color to be multiplied + * @param factor The darkening factor (in the range of 0..255) to multiply with + */ + public static int mulRGB(int color, int factor) { + return (ColorMixer.mul(color, factor) & ~ALPHA_COMPONENT_MASK) | (color & ALPHA_COMPONENT_MASK); + } + + /** + * See {@link #mulRGB(int, int)}. This function is identical, but it accepts a float in [0.0, 1.0] instead, which + * is then mapped to [0, 255]. + * + * @param color The packed 32-bit ABGR color to be multiplied + * @param factor The darkening factor (in the range of 0.0..1.0) to multiply with + */ + public static int mulRGB(int color, float factor) { + return mulRGB(color, ColorU8.normalizedFloatToByte(factor)); + } } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java index 75c16c7dd0..e4ca7a0164 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java @@ -38,7 +38,27 @@ public static int mix(int start, int end, float weight) { } /** - *

Multiplies each 8-bit component of the packed 32-bit color.

+ *

Multiplies the packed 8-bit values component-wise to produce 16-bit intermediaries, and then round to the + * nearest 8-bit representation (similar to floating-point.)

+ * + * @param color0 The first color to multiply + * @param color1 The second color to multiply + * @return The product of the two colors + */ + public static int mulComponentWise(int color0, int color1) { + int comp0 = ((((color0 >>> 0) & 0xFF) * ((color1 >>> 0) & 0xFF)) + 0xFF) >>> 8; + int comp1 = ((((color0 >>> 8) & 0xFF) * ((color1 >>> 8) & 0xFF)) + 0xFF) >>> 8; + int comp2 = ((((color0 >>> 16) & 0xFF) * ((color1 >>> 16) & 0xFF)) + 0xFF) >>> 8; + int comp3 = ((((color0 >>> 24) & 0xFF) * ((color1 >>> 24) & 0xFF)) + 0xFF) >>> 8; + + return (comp0 << 0) | (comp1 << 8) | (comp2 << 16) | (comp3 << 24); + } + + /** + *

Multiplies each 8-bit component against the factor to produce 16-bit intermediaries, and then round to the + * nearest 8-bit representation (similar to floating-point.)

+ * + *

The results are undefined if {@param factor} is not within the interval [0, 255].

* * @param color The packed color values * @param factor The multiplication factor (in 0..255 range) @@ -50,4 +70,14 @@ public static int mul(int color, int factor) { return (int) result; } + + /** + * See {@link #mul(int, int)}, which this function is identical to, except that it takes a floating point value in + * the interval of [0.0, 1.0] and maps it to [0, 255]. + * + *

The results are undefined if {@param factor} is not within the interval [0.0, 1.0].

+ */ + public static int mul(int color, float factor) { + return mul(color, ColorU8.normalizedFloatToByte(factor)); + } } 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 8b0eac2e04..7f8e8ae031 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 @@ -2,6 +2,7 @@ import net.caffeinemc.mods.sodium.api.util.ColorABGR; import net.caffeinemc.mods.sodium.api.util.ColorARGB; +import net.caffeinemc.mods.sodium.api.util.ColorMixer; import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds; import net.caffeinemc.mods.sodium.client.model.color.ColorProvider; import net.caffeinemc.mods.sodium.client.model.color.ColorProviderRegistry; @@ -20,7 +21,6 @@ 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; -import net.caffeinemc.mods.sodium.client.render.frapi.helper.ColorHelper; import net.caffeinemc.mods.sodium.client.render.frapi.mesh.MutableQuadViewImpl; import net.caffeinemc.mods.sodium.client.render.frapi.render.AbstractBlockRenderContext; import net.caffeinemc.mods.sodium.client.render.texture.SpriteFinderCache; @@ -149,7 +149,7 @@ private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) { colorProvider.getColors(this.slice, this.pos, this.scratchPos, this.state, quad, vertexColors); for (int i = 0; i < 4; i++) { - quad.color(i, ColorHelper.multiplyColor(vertexColors[i], quad.color(i))); + quad.color(i, ColorMixer.mulComponentWise(vertexColors[i], quad.color(i))); } } } 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 754f09946b..44c73a8719 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 @@ -1,10 +1,10 @@ package net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.impl; +import net.caffeinemc.mods.sodium.api.util.ColorARGB; import net.caffeinemc.mods.sodium.client.gl.attribute.GlVertexFormat; import net.caffeinemc.mods.sodium.client.render.chunk.shader.ChunkShaderBindingPoints; import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexEncoder; import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexType; -import net.caffeinemc.mods.sodium.client.render.frapi.helper.ColorHelper; import net.minecraft.util.Mth; import org.lwjgl.system.MemoryUtil; @@ -58,7 +58,7 @@ public ChunkVertexEncoder getEncoder() { MemoryUtil.memPutInt(ptr + 0L, packPositionHi(x, y, z)); MemoryUtil.memPutInt(ptr + 4L, packPositionLo(x, y, z)); - MemoryUtil.memPutInt(ptr + 8L, ColorHelper.multiplyRGB(vertex.color, vertex.ao)); + MemoryUtil.memPutInt(ptr + 8L, ColorARGB.mulRGB(vertex.color, vertex.ao)); MemoryUtil.memPutInt(ptr + 12L, packTexture(u, v)); MemoryUtil.memPutInt(ptr + 16L, packLightAndData(light, materialBits, section)); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/ColorHelper.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/ColorHelper.java index 9961263598..2acf626078 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/ColorHelper.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/helper/ColorHelper.java @@ -16,7 +16,8 @@ package net.caffeinemc.mods.sodium.client.render.frapi.helper; -import java.nio.ByteOrder; +import net.caffeinemc.mods.sodium.api.util.ColorABGR; +import net.caffeinemc.mods.sodium.api.util.ColorARGB; /** * Static routines of general utility for renderer implementations. @@ -24,44 +25,9 @@ * designed to be usable without the default renderer. */ public abstract class ColorHelper { - private ColorHelper() { } - - private static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; - - /** Component-wise multiply. Components need to be in same order in both inputs! */ - public static int multiplyColor(int color1, int color2) { - if (color1 == -1) { - return color2; - } else if (color2 == -1) { - return color1; - } - - final int alpha = ((color1 >>> 24) & 0xFF) * ((color2 >>> 24) & 0xFF) / 0xFF; - final int red = ((color1 >>> 16) & 0xFF) * ((color2 >>> 16) & 0xFF) / 0xFF; - final int green = ((color1 >>> 8) & 0xFF) * ((color2 >>> 8) & 0xFF) / 0xFF; - final int blue = (color1 & 0xFF) * (color2 & 0xFF) / 0xFF; - - return (alpha << 24) | (red << 16) | (green << 8) | blue; - } - - /** Multiplies three lowest components by shade. High byte (usually alpha) unchanged. */ - public static int multiplyRGB(int color, float shade) { - final int alpha = ((color >>> 24) & 0xFF); - final int red = (int) (((color >>> 16) & 0xFF) * shade); - final int green = (int) (((color >>> 8) & 0xFF) * shade); - final int blue = (int) ((color & 0xFF) * shade); - - return (alpha << 24) | (red << 16) | (green << 8) | blue; - } - - /** - * Component-wise max. - */ public static int maxBrightness(int b0, int b1) { - if (b0 == 0) return b1; - if (b1 == 0) return b0; - - return Math.max(b0 & 0xFFFF, b1 & 0xFFFF) | Math.max(b0 & 0xFFFF0000, b1 & 0xFFFF0000); + return Math.max(b0 & 0x0000FFFF, b1 & 0x0000FFFF) | + Math.max(b0 & 0xFFFF0000, b1 & 0xFFFF0000); } /* @@ -81,36 +47,16 @@ Vanilla color format (big endian): RGBA (0xRRGGBBAA) */ /** - * Converts from ARGB color to ABGR color if little endian or RGBA color if big endian. + * Converts from ARGB color to ABGR color. The result will be in the platform's native byte order. */ public static int toVanillaColor(int color) { - if (color == -1) { - return -1; - } - - if (BIG_ENDIAN) { - // ARGB to RGBA - return ((color & 0x00FFFFFF) << 8) | ((color & 0xFF000000) >>> 24); - } else { - // ARGB to ABGR - return (color & 0xFF00FF00) | ((color & 0x00FF0000) >>> 16) | ((color & 0x000000FF) << 16); - } + return ColorABGR.toNativeByteOrder(ColorARGB.toABGR(color)); } /** - * Converts to ARGB color from ABGR color if little endian or RGBA color if big endian. + * Converts from ABGR color to ARGB color. The input should be in the platform's native byte order. */ public static int fromVanillaColor(int color) { - if (color == -1) { - return -1; - } - - if (BIG_ENDIAN) { - // RGBA to ARGB - return ((color & 0xFFFFFF00) >>> 8) | ((color & 0x000000FF) << 24); - } else { - // ABGR to ARGB - return (color & 0xFF00FF00) | ((color & 0x00FF0000) >>> 16) | ((color & 0x000000FF) << 16); - } + return ColorARGB.fromABGR(ColorABGR.fromNativeByteOrder(color)); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java index 62dfae8cad..fc52217c50 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java @@ -19,6 +19,7 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.math.MatrixUtil; +import net.caffeinemc.mods.sodium.api.util.ColorMixer; import net.caffeinemc.mods.sodium.client.render.frapi.helper.ColorHelper; import net.caffeinemc.mods.sodium.client.render.frapi.mesh.EncodingFormat; import net.caffeinemc.mods.sodium.client.render.frapi.mesh.MutableQuadViewImpl; @@ -202,7 +203,7 @@ private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) { final int itemColor = colorMap.getColor(itemStack, colorIndex); for (int i = 0; i < 4; i++) { - quad.color(i, ColorHelper.multiplyColor(itemColor, quad.color(i))); + quad.color(i, ColorMixer.mulComponentWise(itemColor, quad.color(i))); } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java index 130d240b2e..a0220a8776 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java @@ -18,10 +18,11 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; +import net.caffeinemc.mods.sodium.api.util.ColorARGB; +import net.caffeinemc.mods.sodium.api.util.ColorMixer; import net.caffeinemc.mods.sodium.client.model.light.LightMode; import net.caffeinemc.mods.sodium.client.model.light.LightPipelineProvider; import net.caffeinemc.mods.sodium.client.model.light.data.SingleBlockLightDataCache; -import net.caffeinemc.mods.sodium.client.render.frapi.helper.ColorHelper; import net.caffeinemc.mods.sodium.client.render.frapi.mesh.MutableQuadViewImpl; import net.caffeinemc.mods.sodium.client.render.texture.SpriteFinderCache; import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil; @@ -109,7 +110,7 @@ private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) { final int blockColor = 0xFF000000 | this.colorMap.getColor(this.state, this.level, this.pos, colorIndex); for (int i = 0; i < 4; i++) { - quad.color(i, ColorHelper.multiplyColor(blockColor, quad.color(i))); + quad.color(i, ColorMixer.mulComponentWise(blockColor, quad.color(i))); } } } @@ -121,7 +122,7 @@ protected void shadeQuad(MutableQuadViewImpl quad, LightMode lightMode, boolean float[] brightnesses = this.quadLightData.br; for (int i = 0; i < 4; i++) { - quad.color(i, ColorHelper.multiplyRGB(quad.color(i), brightnesses[i])); + quad.color(i, ColorARGB.mulRGB(quad.color(i), brightnesses[i])); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java index 3020bb09d0..7a384104da 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java @@ -350,7 +350,7 @@ private static void emitCellGeometry2D(VertexBufferWriter writer, int faces, int long ptr = buffer; int count = 0; - int mixedColor = ColorABGR.darken(color, CloudFace.POS_Y.getShade()); + int mixedColor = ColorABGR.mulRGB(color, CloudFace.POS_Y.getShade()); ptr = writeVertex(ptr, x + 12.0f, 0.0f, z + 12.0f, mixedColor); ptr = writeVertex(ptr, x + 0.0f, 0.0f, z + 12.0f, mixedColor); @@ -376,7 +376,7 @@ private static void emitCellGeometry3D(VertexBufferWriter writer, int visibleFac } final var vertices = VERTICES[face.ordinal()]; - final int color = ColorABGR.darken(baseColor, face.getShade()); + final int color = ColorABGR.mulRGB(baseColor, face.getShade()); for (int vertexIndex = 0; vertexIndex < 4; vertexIndex++) { Vector3f vertex = vertices[interior ? 3 - vertexIndex : vertexIndex]; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/BakedModelEncoder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/BakedModelEncoder.java index 3aadf5ca67..145b71d1e3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/BakedModelEncoder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/BakedModelEncoder.java @@ -1,13 +1,13 @@ package net.caffeinemc.mods.sodium.client.render.immediate.model; import com.mojang.blaze3d.vertex.PoseStack; +import net.caffeinemc.mods.sodium.api.util.ColorMixer; import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadView; import net.caffeinemc.mods.sodium.api.math.MatrixHelper; import net.caffeinemc.mods.sodium.api.util.ColorABGR; import net.caffeinemc.mods.sodium.api.util.ColorU8; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; import net.caffeinemc.mods.sodium.api.vertex.format.common.EntityVertex; -import net.caffeinemc.mods.sodium.client.render.frapi.helper.ColorHelper; import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; import org.joml.Matrix3f; import org.joml.Matrix4f; @@ -43,7 +43,7 @@ public static void writeQuadVertices(VertexBufferWriter writer, PoseStack.Pose m int newColor = color; if (colorize) { - newColor = ColorHelper.multiplyColor(newColor, quad.getColor(i)); + newColor = ColorMixer.mulComponentWise(newColor, quad.getColor(i)); } // The packed transformed normal vector From ab80f6189d6b6e31ea844874a94ba9c0d3d1ffda Mon Sep 17 00:00:00 2001 From: JellySquid Date: Tue, 5 Nov 2024 00:13:34 -0600 Subject: [PATCH 009/215] Add optimized function for bi-linear interpolation This reduces the number of ALU ops significantly and creates a common utility function in the project. --- .../mods/sodium/api/util/ColorMixer.java | 39 +++++++++ .../quad/blender/BlendedColorProvider.java | 85 ++++++++----------- 2 files changed, 73 insertions(+), 51 deletions(-) diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java index e4ca7a0164..0fcd616dc8 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java @@ -1,5 +1,7 @@ package net.caffeinemc.mods.sodium.api.util; +import org.jetbrains.annotations.ApiStatus; + /** * A collection of optimized color mixing functions which directly operate on packed color values. These functions are * agnostic to the ordering of color channels, and the output value will always use the same channel ordering as @@ -37,6 +39,43 @@ public static int mix(int start, int end, float weight) { return mix(start, end, ColorU8.normalizedFloatToByte(weight)); } + /** + *

Performs bi-linear interpolation on a 2x2 matrix of color values to derive the point (x, y). This is more + * efficient than chaining {@link #mul(int, float)} calls.

+ * + *

The results are undefined if {@param x} and {@param y} are not within the interval [0.0, 1.0].

+ * + * @param m00 The packed color value for (0, 0) + * @param m01 The packed color value for (0, 1) + * @param m10 The packed color value for (1, 0) + * @param m11 The packed color value for (1, 1) + * @param x The amount to interpolate between x=0 and x=1 + * @param y The amount to interpolate between y=0 and y=1 + * @return The interpolated color value + */ + @ApiStatus.Experimental + public static int mix2d(int m00, int m01, int m10, int m11, float x, float y) { + // The weights for each row and column in the matrix + int x1 = ColorU8.normalizedFloatToByte(x), x0 = 255 - x1; + int y1 = ColorU8.normalizedFloatToByte(y), y0 = 255 - y1; + + // Blend across the X-axis + // (M00 * X0) + (M10 * X1) + long row0a = ((((m00 & 0x00FF00FFL) * x0) + (((m10 & 0x00FF00FFL) * x1)) + 0x00FF00FFL) >>> 8) & 0x00FF00FFL; + long row0b = ((((m00 & 0xFF00FF00L) * x0) + (((m10 & 0xFF00FF00L) * x1)) + 0xFF00FF00L) >>> 8) & 0xFF00FF00L; + + // (M10 * X0) + (M11 * X1) + long row1a = ((((m01 & 0x00FF00FFL) * x0) + (((m11 & 0x00FF00FFL) * x1)) + 0x00FF00FFL) >>> 8) & 0x00FF00FFL; + long row1b = ((((m01 & 0xFF00FF00L) * x0) + (((m11 & 0xFF00FF00L) * x1)) + 0xFF00FF00L) >>> 8) & 0xFF00FF00L; + + // Blend across the Y-axis + // (ROW0 * Y0) + (ROW1 * Y1) + long result = ((((row0a * y0) + ((row1a * y1)) + 0x00FF00FFL) >>> 8) & 0x00FF00FFL) | + ((((row0b * y0) + ((row1b * y1)) + 0xFF00FF00L) >>> 8) & 0xFF00FF00L); + + return (int) result; + } + /** *

Multiplies the packed 8-bit values component-wise to produce 16-bit intermediaries, and then round to the * nearest 8-bit representation (similar to floating-point.)

diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/blender/BlendedColorProvider.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/blender/BlendedColorProvider.java index b1d53d164a..24014df9b9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/blender/BlendedColorProvider.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/blender/BlendedColorProvider.java @@ -3,7 +3,6 @@ import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadView; import net.caffeinemc.mods.sodium.client.model.color.ColorProvider; import net.caffeinemc.mods.sodium.client.world.LevelSlice; -import net.caffeinemc.mods.sodium.api.util.ColorARGB; import net.caffeinemc.mods.sodium.api.util.ColorMixer; import net.minecraft.core.BlockPos; import net.minecraft.util.Mth; @@ -17,56 +16,40 @@ public void getColors(LevelSlice slice, BlockPos pos, BlockPos.MutableBlockPos s } private int getVertexColor(LevelSlice slice, BlockPos pos, BlockPos.MutableBlockPos scratchPos, ModelQuadView quad, T state, int vertexIndex) { - // Offset the position by -0.5f to align smooth blending with flat blending. - final float posX = quad.getX(vertexIndex) - 0.5f; - final float posY = quad.getY(vertexIndex) - 0.5f; - final float posZ = quad.getZ(vertexIndex) - 0.5f; - - // Floor the positions here to always get the largest integer below the input - // as negative values by default round toward zero when casting to an integer. - // Which would cause negative ratios to be calculated in the interpolation later on. - final int posIntX = Mth.floor(posX); - final int posIntY = Mth.floor(posY); - final int posIntZ = Mth.floor(posZ); - - // Integer component of position vector - final int blockIntX = pos.getX() + posIntX; - final int blockIntY = pos.getY() + posIntY; - final int blockIntZ = pos.getZ() + posIntZ; - - // Retrieve the color values for each neighboring block - final int c00 = this.getColor(slice, state, scratchPos.set(blockIntX + 0, blockIntY, blockIntZ + 0)); - final int c01 = this.getColor(slice, state, scratchPos.set(blockIntX + 0, blockIntY, blockIntZ + 1)); - final int c10 = this.getColor(slice, state, scratchPos.set(blockIntX + 1, blockIntY, blockIntZ + 0)); - final int c11 = this.getColor(slice, state, scratchPos.set(blockIntX + 1, blockIntY, blockIntZ + 1)); - - // Linear interpolation across the Z-axis - int z0; - - if (c00 != c01) { - z0 = ColorMixer.mix(c00, c01, posZ - posIntZ); - } else { - z0 = c00; - } - - int z1; - - if (c10 != c11) { - z1 = ColorMixer.mix(c10, c11, posZ - posIntZ); - } else { - z1 = c10; - } - - // Linear interpolation across the X-axis - int x0; - - if (z0 != z1) { - x0 = ColorMixer.mix(z0, z1, posX - posIntX); - } else { - x0 = z0; - } - - return x0; + // The vertex position + // We add a half-texel offset since we are sampling points within a color texture + final float x = quad.getX(vertexIndex) - 0.5f; + final float y = quad.getY(vertexIndex) - 0.5f; + final float z = quad.getZ(vertexIndex) - 0.5f; + + // Integer component of vertex position + final int intX = Mth.floor(x); + final int intY = Mth.floor(y); + final int intZ = Mth.floor(z); + + // Fractional component of vertex position + final float fracX = x - intX; + final float fracY = y - intY; + final float fracZ = z - intZ; + + // Block coordinates (in world space) which the vertex is located within + // This is calculated after converting from floating point to avoid precision loss with large coordinates + final int blockX = pos.getX() + intX; + final int blockY = pos.getY() + intY; + final int blockZ = pos.getZ() + intZ; + + // Retrieve the color values for each neighboring value + // This creates a 2x2 matrix which is then sampled during interpolation + final int m00 = this.getColor(slice, state, scratchPos.set(blockX + 0, blockY, blockZ + 0)); + final int m01 = this.getColor(slice, state, scratchPos.set(blockX + 0, blockY, blockZ + 1)); + final int m10 = this.getColor(slice, state, scratchPos.set(blockX + 1, blockY, blockZ + 0)); + final int m11 = this.getColor(slice, state, scratchPos.set(blockX + 1, blockY, blockZ + 1)); + + // Perform interpolation across the X-axis, and then Y-axis + // y0 = (m00 * (1.0 - x)) + (m10 * x) + // y1 = (m01 * (1.0 - x)) + (m11 * x) + // result = (y0 * (1.0 - y)) + (y1 * y) + return ColorMixer.mix2d(m00, m01, m10, m11, fracX, fracZ); } protected abstract int getColor(LevelSlice slice, T state, BlockPos pos); From 419156b130e1f5ef598de728d89427d6520bdc74 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Tue, 5 Nov 2024 15:37:47 -0600 Subject: [PATCH 010/215] Reduce time complexity for box blurs Measuring the time spent per box blur in biome blending, the following results were observed. Radius Before After % Improvement 7 blocks 9100ns 3700ns 59% 3 blocks 5400ns 3200ns 41% 1 blocks 3700ns 2600ns 29% --- .../sodium/client/util/color/BoxBlur.java | 91 +++++++++---------- .../client/world/biome/LevelColorCache.java | 42 +++++---- 2 files changed, 68 insertions(+), 65 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/color/BoxBlur.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/color/BoxBlur.java index f3b2d70640..e0ac2586fe 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/color/BoxBlur.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/color/BoxBlur.java @@ -4,67 +4,63 @@ import net.minecraft.util.Mth; public class BoxBlur { - - public static void blur(ColorBuffer buf, ColorBuffer tmp, int radius) { - if (buf.width != tmp.width || buf.height != tmp.height) { - throw new IllegalArgumentException("Color buffers must have same dimensions"); - } - - if (isHomogenous(buf.data)) { + public static void blur(int[] src, int[] tmp, int width, int height, int radius) { + if (isHomogenous(src)) { return; } - blurImpl(buf.data, tmp.data, buf.width, buf.height, radius); // X-axis - blurImpl(tmp.data, buf.data, buf.width, buf.height, radius); // Y-axis + blurImpl(src, tmp, radius, width - radius, width, 0, height, height, radius); // X-axis + blurImpl(tmp, src, radius, width - radius, width, radius, height - radius, height, radius); // Y-axis } - private static void blurImpl(int[] src, int[] dst, int width, int height, int radius) { - int multiplier = getAveragingMultiplier((radius * 2) + 1); - - for (int y = 0; y < height; y++) { - int srcRowOffset = ColorBuffer.getIndex(0, y, width); - - int red, green, blue; - - { - int color = src[srcRowOffset]; - red = ColorARGB.unpackRed(color); - green = ColorARGB.unpackGreen(color); - blue = ColorARGB.unpackBlue(color); + private static void blurImpl(int[] src, int[] dst, int x0, int x1, int width, int y0, int y1, int height, int radius) { + int windowSize = (radius * 2) + 1; + int multiplier = getAveragingMultiplier(windowSize); + + for (int y = y0; y < y1; y++) { + int accR = 0; + int accG = 0; + int accB = 0; + + int windowPivotIndex = ColorBuffer.getIndex(x0, y, width); + int windowTailIndex = windowPivotIndex - radius; + int windowHeadIndex = windowPivotIndex + radius; + + // Initialize window + for (int x = -radius; x <= radius; x++) { + var color = src[windowPivotIndex + x]; + accR += ColorARGB.unpackRed(color); + accG += ColorARGB.unpackGreen(color); + accB += ColorARGB.unpackBlue(color); } - // Extend the window backwards by repeating the colors at the edge N times - red += red * radius; - green += green * radius; - blue += blue * radius; - - // Extend the window forwards by sampling ahead N times - for (int x = 1; x <= radius; x++) { - var color = src[srcRowOffset + x]; - red += ColorARGB.unpackRed(color); - green += ColorARGB.unpackGreen(color); - blue += ColorARGB.unpackBlue(color); - } + // Scan forwards + int x = x0; - for (int x = 0; x < width; x++) { + while (true) { // The x and y coordinates are transposed to flip the output image - dst[ColorBuffer.getIndex(y, x, width)] = averageRGB(red, green, blue, multiplier); + // noinspection SuspiciousNameCombination + dst[ColorBuffer.getIndex(y, x, width)] = averageRGB(accR, accG, accB, multiplier); + x++; + + if (x >= x1) { + break; + } { // Remove the color values that are behind the window - var color = src[srcRowOffset + Math.max(0, x - radius)]; - - red -= ColorARGB.unpackRed(color); - green -= ColorARGB.unpackGreen(color); - blue -= ColorARGB.unpackBlue(color); + var color = src[windowTailIndex++]; + accR -= ColorARGB.unpackRed(color); + accG -= ColorARGB.unpackGreen(color); + accB -= ColorARGB.unpackBlue(color); } { // Add the color values that are ahead of the window - var color = src[srcRowOffset + Math.min(width - 1, x + radius + 1)]; - red += ColorARGB.unpackRed(color); - green += ColorARGB.unpackGreen(color); - blue += ColorARGB.unpackBlue(color); + var color = src[++windowHeadIndex]; + accR += ColorARGB.unpackRed(color); + accG += ColorARGB.unpackGreen(color); + accB += ColorARGB.unpackBlue(color); } } } @@ -108,7 +104,7 @@ private static boolean isHomogenous(int[] array) { } public static class ColorBuffer { - protected final int[] data; + public final int[] data; protected final int width, height; public ColorBuffer(int width, int height) { @@ -121,13 +117,12 @@ public void set(int x, int y, int color) { this.data[getIndex(x, y, this.width)] = color; } - public int get(int x, int y) { return this.data[getIndex(x, y, this.width)]; } public static int getIndex(int x, int y, int width) { - return (y * width) + x; + return x + (y * width); } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java index 80561d0fc3..370635f67e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java @@ -4,7 +4,6 @@ import net.caffeinemc.mods.sodium.client.util.color.BoxBlur; import net.caffeinemc.mods.sodium.client.util.color.BoxBlur.ColorBuffer; import net.caffeinemc.mods.sodium.client.world.cloned.ChunkRenderContext; -import net.minecraft.client.renderer.BiomeColors; import net.minecraft.util.Mth; import net.minecraft.world.level.ColorResolver; import net.minecraft.world.level.biome.Biome; @@ -39,35 +38,36 @@ public LevelColorCache(LevelBiomeSlice biomeData, int blendRadius) { } public void update(ChunkRenderContext context) { - this.minBlockX = (context.getOrigin().minBlockX() - NEIGHBOR_BLOCK_RADIUS) - this.blendRadius; + this.minBlockX = (context.getOrigin().minBlockX() - NEIGHBOR_BLOCK_RADIUS); this.minBlockY = (context.getOrigin().minBlockY() - NEIGHBOR_BLOCK_RADIUS); - this.minBlockZ = (context.getOrigin().minBlockZ() - NEIGHBOR_BLOCK_RADIUS) - this.blendRadius; + this.minBlockZ = (context.getOrigin().minBlockZ() - NEIGHBOR_BLOCK_RADIUS); - this.maxBlockX = (context.getOrigin().maxBlockX() + NEIGHBOR_BLOCK_RADIUS) + this.blendRadius; + this.maxBlockX = (context.getOrigin().maxBlockX() + NEIGHBOR_BLOCK_RADIUS); this.maxBlockY = (context.getOrigin().maxBlockY() + NEIGHBOR_BLOCK_RADIUS); - this.maxBlockZ = (context.getOrigin().maxBlockZ() + NEIGHBOR_BLOCK_RADIUS) + this.blendRadius; + this.maxBlockZ = (context.getOrigin().maxBlockZ() + NEIGHBOR_BLOCK_RADIUS); this.populateStamp++; } public int getColor(ColorResolver resolver, int blockX, int blockY, int blockZ) { - var relBlockX = Mth.clamp(blockX, this.minBlockX, this.maxBlockX) - this.minBlockX; - var relBlockY = Mth.clamp(blockY, this.minBlockY, this.maxBlockY) - this.minBlockY; - var relBlockZ = Mth.clamp(blockZ, this.minBlockZ, this.maxBlockZ) - this.minBlockZ; + // Clamp inputs + blockX = Mth.clamp(blockX, this.minBlockX, this.maxBlockX) - this.minBlockX; + blockY = Mth.clamp(blockY, this.minBlockY, this.maxBlockY) - this.minBlockY; + blockZ = Mth.clamp(blockZ, this.minBlockZ, this.maxBlockZ) - this.minBlockZ; if (!this.slices.containsKey(resolver)) { this.initializeSlices(resolver); } - var slice = this.slices.get(resolver)[relBlockY]; + var slice = this.slices.get(resolver)[blockY]; if (slice.lastPopulateStamp < this.populateStamp) { - this.updateColorBuffers(relBlockY, resolver, slice); + this.updateColorBuffers(blockY, resolver, slice); } var buffer = slice.getBuffer(); - return buffer.get(relBlockX, relBlockZ); + return buffer.get(blockX + this.blendRadius, blockZ + this.blendRadius); } private void initializeSlices(ColorResolver resolver) { @@ -83,19 +83,27 @@ private void initializeSlices(ColorResolver resolver) { private void updateColorBuffers(int relY, ColorResolver resolver, Slice slice) { int blockY = this.minBlockY + relY; - for (int blockZ = this.minBlockZ; blockZ <= this.maxBlockZ; blockZ++) { - for (int blockX = this.minBlockX; blockX <= this.maxBlockX; blockX++) { + int minBlockZ = this.minBlockZ - this.blendRadius; + int minBlockX = this.minBlockX - this.blendRadius; + + int maxBlockZ = this.maxBlockZ + this.blendRadius; + int maxBlockX = this.maxBlockX + this.blendRadius; + + ColorBuffer buffer = slice.buffer; + + for (int blockZ = minBlockZ; blockZ <= maxBlockZ; blockZ++) { + for (int blockX = minBlockX; blockX <= maxBlockX; blockX++) { Biome biome = this.biomeData.getBiome(blockX, blockY, blockZ).value(); - int relBlockX = blockX - this.minBlockX; - int relBlockZ = blockZ - this.minBlockZ; + int relBlockX = blockX - minBlockX; + int relBlockZ = blockZ - minBlockZ; - slice.buffer.set(relBlockX, relBlockZ, resolver.getColor(biome, blockX, blockZ)); + buffer.set(relBlockX, relBlockZ, resolver.getColor(biome, blockX, blockZ)); } } if (this.blendRadius > 0) { - BoxBlur.blur(slice.buffer, this.tempColorBuffer, this.blendRadius); + BoxBlur.blur(buffer.data, this.tempColorBuffer.data, this.sizeXZ, this.sizeXZ, this.blendRadius); } slice.lastPopulateStamp = this.populateStamp; From 30766570ce51ec37c70c87db1985e95ddce2385f Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sat, 16 Nov 2024 19:10:12 -0600 Subject: [PATCH 011/215] Bump version to 0.6.0-final --- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index fb66a7083a..6a589c2812 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -11,7 +11,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = null // https://semver.org/ - var MOD_VERSION: String = "0.6.0-beta.4" + var MOD_VERSION: String = "0.6.0" fun createVersionString(project: Project): String { val builder = StringBuilder() From b22b9393de301b1c3e1d72d1d72dea213e77f143 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sun, 17 Nov 2024 13:32:41 -0600 Subject: [PATCH 012/215] Performance improvements for cloud rendering Cherry-pick of a51076c2bd79, with changes dropped where not applicable for Minecraft 1.21.1. --- .../render/immediate/CloudRenderer.java | 661 +++++++++++------- .../world/clouds/LevelRendererMixin.java | 2 +- 2 files changed, 398 insertions(+), 265 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java index 7a384104da..9357f9d24e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java @@ -4,11 +4,11 @@ import com.mojang.blaze3d.platform.NativeImage; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.*; -import net.caffeinemc.mods.sodium.api.util.ColorU8; -import net.caffeinemc.mods.sodium.api.vertex.format.common.ColorVertex; -import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; import net.caffeinemc.mods.sodium.api.util.ColorABGR; import net.caffeinemc.mods.sodium.api.util.ColorARGB; +import net.caffeinemc.mods.sodium.api.util.ColorU8; +import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; +import net.caffeinemc.mods.sodium.api.vertex.format.common.ColorVertex; import net.minecraft.client.Camera; import net.minecraft.client.CloudStatus; import net.minecraft.client.Minecraft; @@ -16,32 +16,50 @@ import net.minecraft.client.renderer.FogRenderer; import net.minecraft.client.renderer.ShaderInstance; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.resources.Resource; -import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.ResourceProvider; import net.minecraft.util.Mth; import net.minecraft.world.phys.Vec3; +import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.joml.Matrix4f; -import org.joml.Vector3f; import org.lwjgl.opengl.GL32C; import org.lwjgl.system.MemoryStack; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.io.InputStream; import java.util.Objects; public class CloudRenderer { - private static final ResourceLocation CLOUDS_TEXTURE_ID = ResourceLocation.withDefaultNamespace("textures/environment/clouds.png"); + private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-CloudRenderer"); + + private static final ResourceLocation CLOUDS_TEXTURE_ID = + ResourceLocation.withDefaultNamespace("textures/environment/clouds.png"); - private CloudTextureData textureData; - private ShaderInstance shaderProgram; + private static final float CLOUD_HEIGHT = 4.0f; // The height of the cloud cells + private static final float CLOUD_WIDTH = 12.0f; // The width/length of cloud cells - private @Nullable CloudRenderer.CloudGeometry cachedGeometry; + // Bitmasks for each cloud face + private static final int FACE_MASK_NEG_Y = 1 << 0; + private static final int FACE_MASK_POS_Y = 1 << 1; + private static final int FACE_MASK_NEG_X = 1 << 2; + private static final int FACE_MASK_POS_X = 1 << 3; + private static final int FACE_MASK_NEG_Z = 1 << 4; + private static final int FACE_MASK_POS_Z = 1 << 5; + + // The brightness of each fac + // The final color of each vertex is: vec4((texel.rgb * brightness), texel.a * 0.8) + private static final int BRIGHTNESS_POS_Y = ColorU8.normalizedFloatToByte(1.0F); // used for +Y + private static final int BRIGHTNESS_NEG_Y = ColorU8.normalizedFloatToByte(0.7F); // used for -Y + private static final int BRIGHTNESS_X_AXIS = ColorU8.normalizedFloatToByte(0.9F); // used for -X and +X + private static final int BRIGHTNESS_Z_AXIS = ColorU8.normalizedFloatToByte(0.8F); // used for -Z and +Z + + private @Nullable ShaderInstance shaderProgram; + private @Nullable CloudTextureData textureData; + private @Nullable CloudGeometry builtGeometry; public CloudRenderer(ResourceProvider resourceProvider) { - this.reloadTextures(resourceProvider); + this.reload(resourceProvider); } public void render(Camera camera, @@ -51,61 +69,78 @@ public void render(Camera camera, float ticks, float tickDelta) { - float cloudHeight = level.effects().getCloudHeight(); + float height = level.effects() + .getCloudHeight() + 0.33f; // arithmetic against NaN always produces NaN // Vanilla uses NaN height as a way to disable cloud rendering - if (Float.isNaN(cloudHeight)) { + if (Float.isNaN(height)) { return; } - // Skip rendering clouds if texture is completely blank - if (this.textureData.isBlank) { + // Skip rendering clouds if the texture data or shader program isn't available + // This can happen if the texture failed to load, or if the texture is completely empty + if (this.textureData == null || this.shaderProgram == null) { return; } - Vec3 pos = camera.getPosition(); + Vec3 cameraPos = camera.getPosition(); + int renderDistance = getCloudRenderDistance(); + var renderMode = Minecraft.getInstance().options.getCloudsType(); double cloudTime = (ticks + tickDelta) * 0.03F; - double cloudCenterX = (pos.x() + cloudTime); - double cloudCenterZ = (pos.z()) + 0.33D; - int cloudDistance = getCloudRenderDistance(); + // Translation of the clouds texture in world-space + float worldX = (float) (cameraPos.x() + cloudTime); + float worldZ = (float) (cameraPos.z() + 0.33D); - int centerCellX = (int) (Math.floor(cloudCenterX / 12.0)); - int centerCellZ = (int) (Math.floor(cloudCenterZ / 12.0)); + // The coordinates of the cloud cell which the camera is within + int cellX = Mth.floor(worldX / CLOUD_WIDTH); + int cellZ = Mth.floor(worldZ / CLOUD_WIDTH); - // -1 if below clouds, +1 if above clouds - var cloudType = Minecraft.getInstance().options.getCloudsType(); - var relativeCloudY = cloudHeight - (float) pos.y() + 0.33F; - int orientation = cloudType == CloudStatus.FANCY ? (int) Math.signum(-relativeCloudY) : 0; - var parameters = new CloudGeometryParameters(centerCellX, centerCellZ, cloudDistance, orientation, cloudType); + // The orientation of the camera relative to the clouds + // This is used to cull back-facing geometry + ViewOrientation orientation; - CloudGeometry geometry = this.cachedGeometry; + if (renderMode == CloudStatus.FANCY) { + orientation = ViewOrientation.getOrientation(cameraPos, height, height + CLOUD_HEIGHT); + } else { + // When fast clouds are used, there is no orientation of faces, since culling is disabled. + // To avoid unnecessary rebuilds, simply mark a null (undefined) orientation. + orientation = null; + } + var parameters = new CloudGeometryParameters(cellX, cellZ, renderDistance, orientation, renderMode); + + CloudGeometry geometry = this.builtGeometry; + + // Re-generate the cached cloud geometry if necessary if (geometry == null || !Objects.equals(geometry.params(), parameters)) { - this.cachedGeometry = (geometry = rebuildGeometry(geometry, parameters, this.textureData)); + this.builtGeometry = (geometry = rebuildGeometry(geometry, parameters, this.textureData)); } VertexBuffer vertexBuffer = geometry.vertexBuffer(); + + // The vertex buffer can be empty when there are no clouds to render if (vertexBuffer == null) { return; } - final float translateX = (float) (cloudCenterX - (centerCellX * 12)); - final float translateZ = (float) (cloudCenterZ - (centerCellZ * 12)); + // Apply world->view transform + final float viewPosX = (worldX - (cellX * CLOUD_WIDTH)); + final float viewPosY = (float) cameraPos.y() - height; + final float viewPosZ = (worldZ - (cellZ * CLOUD_WIDTH)); poseStack.pushPose(); - var poseEntry = poseStack.last(); Matrix4f modelViewMatrix = poseEntry.pose(); - modelViewMatrix.translate(-translateX, relativeCloudY, -translateZ); + modelViewMatrix.translate(-viewPosX, -viewPosY, -viewPosZ); final var prevShaderFogShape = RenderSystem.getShaderFogShape(); final var prevShaderFogEnd = RenderSystem.getShaderFogEnd(); final var prevShaderFogStart = RenderSystem.getShaderFogStart(); - FogRenderer.setupFog(camera, FogRenderer.FogMode.FOG_TERRAIN, cloudDistance * 8, shouldUseWorldFog(level, pos), tickDelta); + FogRenderer.setupFog(camera, FogRenderer.FogMode.FOG_TERRAIN, renderDistance * 8, shouldUseWorldFog(level, cameraPos), tickDelta); boolean fastClouds = geometry.params().renderMode() == CloudStatus.FAST; boolean fabulous = Minecraft.useShaderTransparency(); @@ -161,25 +196,25 @@ public void render(Camera camera, var writer = VertexBufferWriter.of(bufferBuilder); - var originCellX = parameters.originX(); - var originCellZ = parameters.originZ(); - - var orientation = parameters.orientation(); + final var radius = parameters.radius(); + final var orientation = parameters.orientation(); + final var flat = parameters.renderMode() == CloudStatus.FAST; - var radius = parameters.radius(); - var useFastGraphics = parameters.renderMode() == CloudStatus.FAST; + final var slice = textureData.slice(parameters.originX(), parameters.originZ(), radius); - addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, 0, 0, orientation, useFastGraphics); + // Iterate from the center coordinates (0, 0) outwards + // Since the geometry will be in sorted order, this avoids needing a depth pre-pass + addCellGeometryToBuffer(writer, slice, 0, 0, orientation, flat); for (int layer = 1; layer <= radius; layer++) { for (int z = -layer; z < layer; z++) { int x = Math.abs(z) - layer; - addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics); + addCellGeometryToBuffer(writer, slice, x, z, orientation, flat); } for (int z = layer; z > -layer; z--) { int x = layer - Math.abs(z); - addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics); + addCellGeometryToBuffer(writer, slice, x, z, orientation, flat); } } @@ -188,22 +223,22 @@ public void render(Camera camera, for (int z = -radius; z <= -l; z++) { int x = -z - layer; - addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics); + addCellGeometryToBuffer(writer, slice, x, z, orientation, flat); } for (int z = l; z <= radius; z++) { int x = z - layer; - addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics); + addCellGeometryToBuffer(writer, slice, x, z, orientation, flat); } for (int z = radius; z >= l; z--) { int x = layer - z; - addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics); + addCellGeometryToBuffer(writer, slice, x, z, orientation, flat); } for (int z = -l; z >= -radius; z--) { int x = layer + z; - addCellGeometryToBuffer(writer, textureData, originCellX, originCellZ, x, z, orientation, useFastGraphics); + addCellGeometryToBuffer(writer, slice, x, z, orientation, flat); } } @@ -228,172 +263,235 @@ public void render(Camera camera, } private static void addCellGeometryToBuffer(VertexBufferWriter writer, - CloudTextureData textureData, - int originX, - int originZ, - int offsetX, - int offsetZ, - int orientation, - boolean useFastGraphics) { - int cellX = originX + offsetX; - int cellZ = originZ + offsetZ; - - int cellIndex = textureData.getCellIndexWrapping(cellX, cellZ); - int cellFaces = textureData.getCellFaces(cellIndex) & getVisibleFaces(offsetX, offsetZ, orientation); - - if (cellFaces == 0) { + CloudTextureData.Slice textureData, + int x, + int z, + @Nullable CloudRenderer.ViewOrientation orientation, + boolean flat) + { + int index = textureData.getCellIndex(x, z); + int faces = textureData.getCellFaces(index) & getVisibleFaces(x, z, orientation); + + if (faces == 0) { return; } - int cellColor = textureData.getCellColor(cellIndex); + int color = textureData.getCellColor(index); - if (ColorABGR.unpackAlpha(cellColor) == 0) { + if (isTransparent(color)) { return; } - float x = offsetX * 12; - float z = offsetZ * 12; - - if (useFastGraphics) { - emitCellGeometry2D(writer, cellFaces, cellColor, x, z); + if (flat) { + emitCellGeometryFlat(writer, color, x, z); } else { - emitCellGeometry3D(writer, cellFaces, cellColor, x, z, false); - - int distance = Math.abs(offsetX) + Math.abs(offsetZ); + emitCellGeometryExterior(writer, faces, color, x, z); - if (distance <= 1) { - emitCellGeometry3D(writer, CloudFaceSet.all(), cellColor, x, z, true); + if (taxicabDistance(x, z) <= 1) { + emitCellGeometryInterior(writer, color, x, z); } } } - private static int getVisibleFaces(int x, int z, int orientation) { - int faces = CloudFaceSet.all(); + private static int getVisibleFaces(int x, int z, ViewOrientation orientation) { + int faces = 0; - if (x > 0) { - faces = CloudFaceSet.remove(faces, CloudFace.POS_X); + if (x <= 0) { + faces |= FACE_MASK_POS_X; } - if (z > 0) { - faces = CloudFaceSet.remove(faces, CloudFace.POS_Z); + if (z <= 0) { + faces |= FACE_MASK_POS_Z; } - if (x < 0) { - faces = CloudFaceSet.remove(faces, CloudFace.NEG_X); + if (x >= 0) { + faces |= FACE_MASK_NEG_X; } - if (z < 0) { - faces = CloudFaceSet.remove(faces, CloudFace.NEG_Z); + if (z >= 0) { + faces |= FACE_MASK_NEG_Z; } - if (orientation < 0) { - faces = CloudFaceSet.remove(faces, CloudFace.POS_Y); + if (orientation != ViewOrientation.BELOW_CLOUDS) { + faces |= FACE_MASK_POS_Y; } - if (orientation > 0) { - faces = CloudFaceSet.remove(faces, CloudFace.NEG_Y); + if (orientation != ViewOrientation.ABOVE_CLOUDS) { + faces |= FACE_MASK_NEG_Y; } return faces; } - private static final Vector3f[][] VERTICES = new Vector3f[CloudFace.COUNT][]; - - static { - VERTICES[CloudFace.NEG_Y.ordinal()] = new Vector3f[] { - new Vector3f(12.0f, 0.0f, 12.0f), - new Vector3f( 0.0f, 0.0f, 12.0f), - new Vector3f( 0.0f, 0.0f, 0.0f), - new Vector3f(12.0f, 0.0f, 0.0f) - }; - - VERTICES[CloudFace.POS_Y.ordinal()] = new Vector3f[] { - new Vector3f( 0.0f, 4.0f, 12.0f), - new Vector3f(12.0f, 4.0f, 12.0f), - new Vector3f(12.0f, 4.0f, 0.0f), - new Vector3f( 0.0f, 4.0f, 0.0f) - }; - - VERTICES[CloudFace.NEG_X.ordinal()] = new Vector3f[] { - new Vector3f( 0.0f, 0.0f, 12.0f), - new Vector3f( 0.0f, 4.0f, 12.0f), - new Vector3f( 0.0f, 4.0f, 0.0f), - new Vector3f( 0.0f, 0.0f, 0.0f) - }; - - VERTICES[CloudFace.POS_X.ordinal()] = new Vector3f[] { - new Vector3f(12.0f, 4.0f, 12.0f), - new Vector3f(12.0f, 0.0f, 12.0f), - new Vector3f(12.0f, 0.0f, 0.0f), - new Vector3f(12.0f, 4.0f, 0.0f) - }; - - VERTICES[CloudFace.NEG_Z.ordinal()] = new Vector3f[] { - new Vector3f(12.0f, 4.0f, 0.0f), - new Vector3f(12.0f, 0.0f, 0.0f), - new Vector3f( 0.0f, 0.0f, 0.0f), - new Vector3f( 0.0f, 4.0f, 0.0f) - }; - - VERTICES[CloudFace.POS_Z.ordinal()] = new Vector3f[] { - new Vector3f(12.0f, 0.0f, 12.0f), - new Vector3f(12.0f, 4.0f, 12.0f), - new Vector3f( 0.0f, 4.0f, 12.0f), - new Vector3f( 0.0f, 0.0f, 12.0f) - }; - } - - private static void emitCellGeometry2D(VertexBufferWriter writer, int faces, int color, float x, float z) { + private static void emitCellGeometryFlat(VertexBufferWriter writer, int texel, int x, int z) { try (MemoryStack stack = MemoryStack.stackPush()) { - final long buffer = stack.nmalloc(4 * ColorVertex.STRIDE); - - long ptr = buffer; - int count = 0; + final long vertexBuffer = stack.nmalloc(4 * ColorVertex.STRIDE); + long ptr = vertexBuffer; - int mixedColor = ColorABGR.mulRGB(color, CloudFace.POS_Y.getShade()); + final float x0 = (x * CLOUD_WIDTH); + final float x1 = x0 + CLOUD_WIDTH; + final float z0 = (z * CLOUD_WIDTH); + final float z1 = z0 + CLOUD_WIDTH; - ptr = writeVertex(ptr, x + 12.0f, 0.0f, z + 12.0f, mixedColor); - ptr = writeVertex(ptr, x + 0.0f, 0.0f, z + 12.0f, mixedColor); - ptr = writeVertex(ptr, x + 0.0f, 0.0f, z + 0.0f, mixedColor); - ptr = writeVertex(ptr, x + 12.0f, 0.0f, z + 0.0f, mixedColor); - - count += 4; + { + final int color = ColorABGR.mulRGB(texel, BRIGHTNESS_POS_Y); + ptr = writeVertex(ptr, x1, 0.0f, z1, color); + ptr = writeVertex(ptr, x0, 0.0f, z1, color); + ptr = writeVertex(ptr, x0, 0.0f, z0, color); + ptr = writeVertex(ptr, x1, 0.0f, z0, color); + } - writer.push(stack, buffer, count, ColorVertex.FORMAT); + writer.push(stack, vertexBuffer, 4, ColorVertex.FORMAT); } } - private static void emitCellGeometry3D(VertexBufferWriter writer, int visibleFaces, int baseColor, float posX, float posZ, boolean interior) { + private static void emitCellGeometryExterior(VertexBufferWriter writer, int cellFaces, int cellColor, int cellX, int cellZ) { try (MemoryStack stack = MemoryStack.stackPush()) { - final long buffer = stack.nmalloc(6 * 4 * ColorVertex.STRIDE); + final long vertexBuffer = stack.nmalloc(6 * 4 * ColorVertex.STRIDE); + int vertexCount = 0; + + long ptr = vertexBuffer; + + final float x0 = cellX * CLOUD_WIDTH; + final float y0 = 0.0f; + final float z0 = cellZ * CLOUD_WIDTH; + + final float x1 = x0 + CLOUD_WIDTH; + final float y1 = y0 + CLOUD_HEIGHT; + final float z1 = z0 + CLOUD_WIDTH; + + // -Y + if ((cellFaces & FACE_MASK_NEG_Y) != 0) { + final int vertexColor = ColorABGR.mulRGB(cellColor, BRIGHTNESS_NEG_Y); + ptr = writeVertex(ptr, x1, y0, z1, vertexColor); + ptr = writeVertex(ptr, x0, y0, z1, vertexColor); + ptr = writeVertex(ptr, x0, y0, z0, vertexColor); + ptr = writeVertex(ptr, x1, y0, z0, vertexColor); + vertexCount += 4; + } - long ptr = buffer; - int count = 0; + // +Y + if ((cellFaces & FACE_MASK_POS_Y) != 0) { + final int vertexColor = ColorABGR.mulRGB(cellColor, BRIGHTNESS_POS_Y); + ptr = writeVertex(ptr, x0, y1, z1, vertexColor); + ptr = writeVertex(ptr, x1, y1, z1, vertexColor); + ptr = writeVertex(ptr, x1, y1, z0, vertexColor); + ptr = writeVertex(ptr, x0, y1, z0, vertexColor); + vertexCount += 4; + } - for (var face : CloudFace.VALUES) { - if (!CloudFaceSet.contains(visibleFaces, face)) { - continue; + if ((cellFaces & (FACE_MASK_NEG_X | FACE_MASK_POS_X)) != 0) { + final int vertexColor = ColorABGR.mulRGB(cellColor, BRIGHTNESS_X_AXIS); + + // -X + if ((cellFaces & FACE_MASK_NEG_X) != 0) { + ptr = writeVertex(ptr, x0, y0, z1, vertexColor); + ptr = writeVertex(ptr, x0, y1, z1, vertexColor); + ptr = writeVertex(ptr, x0, y1, z0, vertexColor); + ptr = writeVertex(ptr, x0, y0, z0, vertexColor); + vertexCount += 4; } - final var vertices = VERTICES[face.ordinal()]; - final int color = ColorABGR.mulRGB(baseColor, face.getShade()); + // +X + if ((cellFaces & FACE_MASK_POS_X) != 0) { + ptr = writeVertex(ptr, x1, y1, z1, vertexColor); + ptr = writeVertex(ptr, x1, y0, z1, vertexColor); + ptr = writeVertex(ptr, x1, y0, z0, vertexColor); + ptr = writeVertex(ptr, x1, y1, z0, vertexColor); + vertexCount += 4; + } + } - for (int vertexIndex = 0; vertexIndex < 4; vertexIndex++) { - Vector3f vertex = vertices[interior ? 3 - vertexIndex : vertexIndex]; + if ((cellFaces & (FACE_MASK_NEG_Z | FACE_MASK_POS_Z)) != 0) { + final int vertexColor = ColorABGR.mulRGB(cellColor, BRIGHTNESS_Z_AXIS); - final float x = vertex.x + posX; - final float y = vertex.y; - final float z = vertex.z + posZ; + // -Z + if ((cellFaces & FACE_MASK_NEG_Z) != 0) { + ptr = writeVertex(ptr, x1, y1, z0, vertexColor); + ptr = writeVertex(ptr, x1, y0, z0, vertexColor); + ptr = writeVertex(ptr, x0, y0, z0, vertexColor); + ptr = writeVertex(ptr, x0, y1, z0, vertexColor); + vertexCount += 4; + } - ptr = writeVertex(ptr, x, y, z, color); + // +Z + if ((cellFaces & FACE_MASK_POS_Z) != 0) { + ptr = writeVertex(ptr, x1, y0, z1, vertexColor); + ptr = writeVertex(ptr, x1, y1, z1, vertexColor); + ptr = writeVertex(ptr, x0, y1, z1, vertexColor); + ptr = writeVertex(ptr, x0, y0, z1, vertexColor); + vertexCount += 4; } + } + + writer.push(stack, vertexBuffer, vertexCount, ColorVertex.FORMAT); + } + } + + private static void emitCellGeometryInterior(VertexBufferWriter writer, int baseColor, int cellX, int cellZ) { + try (MemoryStack stack = MemoryStack.stackPush()) { + final long vertexBuffer = stack.nmalloc(6 * 4 * ColorVertex.STRIDE); + long ptr = vertexBuffer; + + final float x0 = cellX * CLOUD_WIDTH; + final float y0 = 0.0f; + final float z0 = cellZ * CLOUD_WIDTH; - count += 4; + final float x1 = x0 + CLOUD_WIDTH; + final float y1 = y0 + CLOUD_HEIGHT; + final float z1 = z0 + CLOUD_WIDTH; + + { + // -Y + final int vertexColor = ColorABGR.mulRGB(baseColor, BRIGHTNESS_NEG_Y); + ptr = writeVertex(ptr, x1, y0, z0, vertexColor); + ptr = writeVertex(ptr, x0, y0, z0, vertexColor); + ptr = writeVertex(ptr, x0, y0, z1, vertexColor); + ptr = writeVertex(ptr, x1, y0, z1, vertexColor); } - if (count > 0) { - writer.push(stack, buffer, count, ColorVertex.FORMAT); + { + // +Y + final int vertexColor = ColorABGR.mulRGB(baseColor, BRIGHTNESS_POS_Y); + ptr = writeVertex(ptr, x0, y1, z0, vertexColor); + ptr = writeVertex(ptr, x1, y1, z0, vertexColor); + ptr = writeVertex(ptr, x1, y1, z1, vertexColor); + ptr = writeVertex(ptr, x0, y1, z1, vertexColor); } + + { + final int vertexColor = ColorABGR.mulRGB(baseColor, BRIGHTNESS_X_AXIS); + + // -X + ptr = writeVertex(ptr, x0, y0, z0, vertexColor); + ptr = writeVertex(ptr, x0, y1, z0, vertexColor); + ptr = writeVertex(ptr, x0, y1, z1, vertexColor); + ptr = writeVertex(ptr, x0, y0, z1, vertexColor); + + // +X + ptr = writeVertex(ptr, x1, y1, z0, vertexColor); + ptr = writeVertex(ptr, x1, y0, z0, vertexColor); + ptr = writeVertex(ptr, x1, y0, z1, vertexColor); + ptr = writeVertex(ptr, x1, y1, z1, vertexColor); + } + + { + final int vertexColor = ColorABGR.mulRGB(baseColor, BRIGHTNESS_Z_AXIS); + + // -Z + ptr = writeVertex(ptr, x0, y1, z0, vertexColor); + ptr = writeVertex(ptr, x0, y0, z0, vertexColor); + ptr = writeVertex(ptr, x1, y0, z0, vertexColor); + ptr = writeVertex(ptr, x1, y1, z0, vertexColor); + + // +Z + ptr = writeVertex(ptr, x0, y0, z1, vertexColor); + ptr = writeVertex(ptr, x0, y1, z1, vertexColor); + ptr = writeVertex(ptr, x1, y1, z1, vertexColor); + ptr = writeVertex(ptr, x1, y0, z1, vertexColor); + } + + writer.push(stack, vertexBuffer, 6 * 4, ColorVertex.FORMAT); } } @@ -409,16 +507,10 @@ private static void uploadToVertexBuffer(VertexBuffer vertexBuffer, MeshData bui VertexBuffer.unbind(); } - public void reloadTextures(ResourceProvider resourceProvider) { + public void reload(ResourceProvider resourceProvider) { this.destroy(); - - this.textureData = loadTextureData(); - - try { - this.shaderProgram = new ShaderInstance(resourceProvider, "clouds", DefaultVertexFormat.POSITION_COLOR); - } catch (IOException e) { - throw new RuntimeException(e); - } + this.textureData = loadTextureData(resourceProvider); + this.shaderProgram = loadShaderProgram(resourceProvider); } public void destroy() { @@ -427,25 +519,38 @@ public void destroy() { this.shaderProgram = null; } - if (this.cachedGeometry != null) { - var vertexBuffer = this.cachedGeometry.vertexBuffer(); + if (this.builtGeometry != null) { + var vertexBuffer = this.builtGeometry.vertexBuffer(); vertexBuffer.close(); - this.cachedGeometry = null; + this.builtGeometry = null; + } + } + + private static @Nullable ShaderInstance loadShaderProgram(ResourceProvider resourceProvider) { + try { + return new ShaderInstance(resourceProvider, "clouds", DefaultVertexFormat.POSITION_COLOR); + } catch (Throwable t) { + LOGGER.error("Failed to compile shader program for cloud rendering. The rendering of clouds in the skybox will be disabled. " + + "This may be caused by an incompatible resource pack.", t); } + + return null; } - private static CloudTextureData loadTextureData() { - ResourceManager resourceManager = Minecraft.getInstance().getResourceManager(); - Resource resource = resourceManager.getResource(CLOUDS_TEXTURE_ID) - .orElseThrow(); + private static @Nullable CloudTextureData loadTextureData(ResourceProvider resourceProvider) { + var resource = resourceProvider.getResource(CloudRenderer.CLOUDS_TEXTURE_ID) + .orElseThrow(); // always provided by default resource pack - try (InputStream inputStream = resource.open()){ - try (NativeImage nativeImage = NativeImage.read(inputStream)) { - return new CloudTextureData(nativeImage); - } - } catch (IOException ex) { - throw new RuntimeException("Failed to load texture data", ex); + try (var inputStream = resource.open(); + var nativeImage = NativeImage.read(inputStream)) + { + return CloudTextureData.load(nativeImage); + } + catch (Throwable t) { + LOGGER.error("Failed to load texture '{}'. The rendering of clouds in the skybox will be disabled. " + + "This may be caused by an incompatible resource pack.", CloudRenderer.CLOUDS_TEXTURE_ID, t); + return null; } } @@ -458,96 +563,96 @@ private static int getCloudRenderDistance() { return Math.max(32, (Minecraft.getInstance().options.getEffectiveRenderDistance() * 2) + 9); } - private enum CloudFace { - NEG_Y(0.7F), - POS_Y(1.0f), - NEG_X(0.9F), - POS_X(0.9F), - NEG_Z(0.8F), - POS_Z(0.8F); + private static boolean isTransparent(int argb) { + return ColorARGB.unpackAlpha(argb) < 10; + } - public static final CloudFace[] VALUES = CloudFace.values(); - public static final int COUNT = VALUES.length; + private static int taxicabDistance(int x, int z) { + return Math.abs(x) + Math.abs(z); + } - private final int shade; + private static class CloudTextureData { + private final byte[] faces; + private final int[] colors; - CloudFace(float shade) { - this.shade = ColorU8.normalizedFloatToByte(shade); - } + private final int width, height; - public int getShade() { - return this.shade; - } - } + private CloudTextureData(int width, int height) { + this.faces = new byte[width * height]; + this.colors = new int[width * height]; - private static class CloudFaceSet { - public static int empty() { - return 0; + this.width = width; + this.height = height; } - public static boolean contains(int set, CloudFace face) { - return (set & (1 << face.ordinal())) != 0; - } + public Slice slice(int originX, int originY, int radius) { + final var src = this; + final var dst = new CloudTextureData.Slice(radius); - public static int add(int set, CloudFace face) { - return set | (1 << face.ordinal()); - } + for (int dstY = 0; dstY < dst.height; dstY++) { + int srcX = Math.floorMod(originX - radius, this.width); + int srcY = Math.floorMod(originY - radius + dstY, this.height); - public static int remove(int set, CloudFace face) { - return set & ~(1 << face.ordinal()); - } + int dstX = 0; - public static int all() { - return (1 << CloudFace.COUNT) - 1; - } - } + while (dstX < dst.width) { + final int length = Math.min(src.width - srcX, dst.width - dstX); - private static boolean isTransparentCell(int color) { - return ColorARGB.unpackAlpha(color) <= 1; - } + final int srcPos = getCellIndex(srcX, srcY, src.width); + final int dstPos = getCellIndex(dstX, dstY, dst.width); - private static class CloudTextureData { - private final byte[] faces; - private final int[] colors; - private boolean isBlank; + System.arraycopy(this.faces, srcPos, dst.faces, dstPos, length); + System.arraycopy(this.colors, srcPos, dst.colors, dstPos, length); - private final int width, height; + srcX = 0; + dstX += length; + } + } - public CloudTextureData(NativeImage texture) { - int width = texture.getWidth(); - int height = texture.getHeight(); + return dst; + } - this.faces = new byte[width * height]; - this.colors = new int[width * height]; - this.isBlank = true; + public static @Nullable CloudTextureData load(NativeImage image) { + final int width = image.getWidth(); + final int height = image.getHeight(); - this.width = width; - this.height = height; + var data = new CloudTextureData(width, height); - this.loadTextureData(texture, width, height); + if (!data.loadTextureData(image, width, height)) { + return null; // The texture is empty, so it isn't necessary to render it + } + + return data; } - private void loadTextureData(NativeImage texture, int width, int height) { + private boolean loadTextureData(NativeImage texture, int width, int height) { + Validate.isTrue(this.width == width); + Validate.isTrue(this.height == height); + + boolean containsData = false; + for (int x = 0; x < width; x++) { for (int z = 0; z < height; z++) { - int index = this.getCellIndex(x, z); int color = texture.getPixelRGBA(x, z); + if (isTransparent(color)) { + continue; + } + + int index = getCellIndex(x, z, width); this.colors[index] = color; + this.faces[index] = (byte) getOpenFaces(texture, color, x, z); - if (!isTransparentCell(color)) { - this.faces[index] = (byte) getOpenFaces(texture, color, x, z); - this.isBlank = false; - } + containsData = true; } } + + return containsData; } private static int getOpenFaces(NativeImage image, int color, int x, int z) { // Since the cloud texture is only 2D, nothing can hide the top or bottom faces - int faces = CloudFaceSet.empty(); - faces = CloudFaceSet.add(faces, CloudFace.NEG_Y); - faces = CloudFaceSet.add(faces, CloudFace.POS_Y); + int faces = FACE_MASK_NEG_Y | FACE_MASK_POS_Y; // Generate faces where the neighbor cell is a different color // Do not generate duplicate faces between two cells @@ -556,7 +661,7 @@ private static int getOpenFaces(NativeImage image, int color, int x, int z) { int neighbor = getNeighborTexel(image, x - 1, z); if (color != neighbor) { - faces = CloudFaceSet.add(faces, CloudFace.NEG_X); + faces |= FACE_MASK_NEG_X; } } @@ -565,7 +670,7 @@ private static int getOpenFaces(NativeImage image, int color, int x, int z) { int neighbor = getNeighborTexel(image, x + 1, z); if (color != neighbor) { - faces = CloudFaceSet.add(faces, CloudFace.POS_X); + faces |= FACE_MASK_POS_X; } } @@ -574,7 +679,7 @@ private static int getOpenFaces(NativeImage image, int color, int x, int z) { int neighbor = getNeighborTexel(image, x, z - 1); if (color != neighbor) { - faces = CloudFaceSet.add(faces, CloudFace.NEG_Z); + faces |= FACE_MASK_NEG_Z; } } @@ -583,7 +688,7 @@ private static int getOpenFaces(NativeImage image, int color, int x, int z) { int neighbor = getNeighborTexel(image, x, z + 1); if (color != neighbor) { - faces = CloudFaceSet.add(faces, CloudFace.POS_Z); + faces |= FACE_MASK_POS_Z; } } @@ -609,23 +714,35 @@ private static int wrapTexelCoord(int coord, int min, int max) { return coord; } - public int getCellFaces(int index) { - return this.faces[index]; + private static int getCellIndex(int x, int z, int pitch) { + return (z * pitch) + x; } - public int getCellColor(int index) { - return this.colors[index]; - } + public static class Slice { + private final int width, height; + private final int radius; + private final byte[] faces; + private final int[] colors; - private int getCellIndexWrapping(int x, int z) { - return this.getCellIndex( - Math.floorMod(x, this.width), - Math.floorMod(z, this.height) - ); - } + public Slice(int radius) { + this.width = 1 + (radius * 2); + this.height = 1 + (radius * 2); + this.radius = radius; + this.faces = new byte[this.width * this.height]; + this.colors = new int[this.width * this.height]; + } - private int getCellIndex(int x, int z) { - return (x * this.width) + z; + public int getCellIndex(int x, int z) { + return CloudTextureData.getCellIndex(x + this.radius, z + this.radius, this.width); + } + + public int getCellFaces(int index) { + return Byte.toUnsignedInt(this.faces[index]); + } + + public int getCellColor(int index) { + return this.colors[index]; + } } } @@ -633,7 +750,23 @@ public record CloudGeometry(VertexBuffer vertexBuffer, CloudGeometryParameters p } - public record CloudGeometryParameters(int originX, int originZ, int radius, int orientation, CloudStatus renderMode) { + public record CloudGeometryParameters(int originX, int originZ, int radius, @Nullable ViewOrientation orientation, CloudStatus renderMode) { + + } + private enum ViewOrientation { + BELOW_CLOUDS, // Top faces should *not* be rendered + INSIDE_CLOUDS, // All faces *must* be rendered + ABOVE_CLOUDS; // Bottom faces should *not* be rendered + + public static @NotNull ViewOrientation getOrientation(Vec3 camera, float minY, float maxY) { + if (camera.y() <= minY + 0.125f /* epsilon */) { + return ViewOrientation.BELOW_CLOUDS; + } else if (camera.y() >= maxY - 0.125f /* epsilon */) { + return ViewOrientation.ABOVE_CLOUDS; + } else { + return ViewOrientation.INSIDE_CLOUDS; + } + } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/world/clouds/LevelRendererMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/world/clouds/LevelRendererMixin.java index a5047b7122..a3c589a5b3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/world/clouds/LevelRendererMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/world/clouds/LevelRendererMixin.java @@ -56,7 +56,7 @@ public void renderClouds(PoseStack poseStack, Matrix4f matrix4f, Matrix4f projec @Inject(method = "onResourceManagerReload(Lnet/minecraft/server/packs/resources/ResourceManager;)V", at = @At("RETURN")) private void onReload(ResourceManager manager, CallbackInfo ci) { if (this.cloudRenderer != null) { - this.cloudRenderer.reloadTextures(manager); + this.cloudRenderer.reload(manager); } } From b0da33b1c99f381559943efcc7584e019b69686d Mon Sep 17 00:00:00 2001 From: JellySquid Date: Mon, 18 Nov 2024 22:20:34 -0600 Subject: [PATCH 013/215] Use alternative workaround for NVIDIA drivers The NVIDIA driver enables a driver feature called "Threaded Optimizations" when it finds Minecraft, which causes severe performance issues and sometimes even crashes. Newer versions of the driver seem to use a slightly different heuristic which our workaround doesn't address. So, instead, use an alternative method that enables GL_DEBUG_OUTPUT_SYNCHRONOUS. This seems to reliably disable the functionality *even if* the user has configured it otherwise in their driver settings. Additionally, on Windows, we now always indicate to the driver that Minecraft is running, so that users with hybrid graphics don't see regressed performance. --- common/build.gradle.kts | 3 +- .../mods/sodium/client/gl/GlContextInfo.java | 19 --- .../mods/sodium/mixin/core/WindowMixin.java | 9 +- .../context_creation/WindowMixin.java | 43 ++--- .../compatibility/checks/PreLaunchChecks.java | 108 +++--------- .../environment/GlContextInfo.java | 19 +++ .../compatibility/environment/OsUtils.java | 27 ++- .../probe/GraphicsAdapterVendor.java | 46 +++-- .../workarounds/Workarounds.java | 22 +-- .../workarounds/intel/IntelWorkarounds.java | 40 +++++ .../workarounds/nvidia/NvidiaWorkarounds.java | 160 +++++++++++++++--- .../client/platform/windows/api/Gdi32.java | 18 +- .../platform/windows/api/d3dkmt/D3DKMT.java | 19 ++- .../sodium/service/SodiumWorkarounds.java | 7 +- 14 files changed, 326 insertions(+), 214 deletions(-) delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gl/GlContextInfo.java create mode 100644 common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GlContextInfo.java create mode 100644 common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java diff --git a/common/build.gradle.kts b/common/build.gradle.kts index cec4b9fdeb..c56c417140 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -69,9 +69,8 @@ dependencies { // We need to be careful during pre-launch that we don't touch any Minecraft classes, since other mods // will not yet have an opportunity to apply transformations. - configurationPreLaunch("org.apache.commons:commons-lang3:3.14.0") - configurationPreLaunch("commons-io:commons-io:2.15.1") configurationPreLaunch("org.lwjgl:lwjgl:3.3.3") + configurationPreLaunch("org.lwjgl:lwjgl-opengl:3.3.3") configurationPreLaunch("net.java.dev.jna:jna:5.14.0") configurationPreLaunch("net.java.dev.jna:jna-platform:5.14.0") configurationPreLaunch("org.slf4j:slf4j-api:2.0.9") diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/GlContextInfo.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/GlContextInfo.java deleted file mode 100644 index eef8034dbc..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/GlContextInfo.java +++ /dev/null @@ -1,19 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gl; - -import org.jetbrains.annotations.Nullable; -import org.lwjgl.opengl.GL11C; - -public record GlContextInfo(String vendor, String renderer, String version) { - @Nullable - public static GlContextInfo create() { - String vendor = GL11C.glGetString(GL11C.GL_VENDOR); - String renderer = GL11C.glGetString(GL11C.GL_RENDERER); - String version = GL11C.glGetString(GL11C.GL_VERSION); - - if (vendor == null || renderer == null || version == null) { - return null; - } - - return new GlContextInfo(vendor, renderer, version); - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/WindowMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/WindowMixin.java index fc8592bddf..78792f3971 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/WindowMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/WindowMixin.java @@ -22,9 +22,12 @@ public class WindowMixin implements NativeWindowHandle { @WrapOperation(method = "", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwCreateWindow(IILjava/lang/CharSequence;JJ)J"), require = 0) public long setAdditionalWindowHints(int titleEncoded, int width, CharSequence height, long title, long monitor, Operation original) { - if (!PlatformRuntimeInformation.getInstance().platformHasEarlyLoadingScreen() && SodiumClientMod.options().performance.useNoErrorGLContext && - !Workarounds.isWorkaroundEnabled(Workarounds.Reference.NO_ERROR_CONTEXT_UNSUPPORTED)) { - GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_NO_ERROR, GLFW.GLFW_TRUE); + if (!PlatformRuntimeInformation.getInstance().platformHasEarlyLoadingScreen()) { + if (SodiumClientMod.options().performance.useNoErrorGLContext) { + if (!Workarounds.isWorkaroundEnabled(Workarounds.Reference.NO_ERROR_CONTEXT_UNSUPPORTED)) { + GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_NO_ERROR, GLFW.GLFW_TRUE); + } + } } return original.call(titleEncoded, width, height, title, monitor); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java index 6f78390690..5b50b53a80 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java @@ -6,11 +6,10 @@ import com.mojang.blaze3d.platform.ScreenManager; import com.mojang.blaze3d.platform.Window; import com.mojang.blaze3d.platform.WindowEventHandler; -import net.caffeinemc.mods.sodium.client.compatibility.checks.PostLaunchChecks; import net.caffeinemc.mods.sodium.client.compatibility.checks.ModuleScanner; -import net.caffeinemc.mods.sodium.client.gl.GlContextInfo; -import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds; +import net.caffeinemc.mods.sodium.client.compatibility.checks.PostLaunchChecks; import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; +import net.caffeinemc.mods.sodium.client.compatibility.environment.GlContextInfo; import net.caffeinemc.mods.sodium.client.platform.NativeWindowHandle; import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; import net.minecraft.Util; @@ -38,59 +37,46 @@ public class WindowMixin { @Final private static Logger LOGGER; - @Shadow - @Final - private long window; - @Unique private long wglPrevContext = MemoryUtil.NULL; @Redirect(method = "", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwCreateWindow(IILjava/lang/CharSequence;JJ)J"), expect = 0, require = 0) private long wrapGlfwCreateWindow(int width, int height, CharSequence title, long monitor, long share) { - final boolean applyNvidiaWorkarounds = Workarounds.isWorkaroundEnabled(Workarounds.Reference.NVIDIA_THREADED_OPTIMIZATIONS); - - if (applyNvidiaWorkarounds) { - NvidiaWorkarounds.install(); - } + NvidiaWorkarounds.applyEnvironmentChanges(); try { return GLFW.glfwCreateWindow(width, height, title, monitor, share); } finally { - if (applyNvidiaWorkarounds) { - NvidiaWorkarounds.uninstall(); - } + NvidiaWorkarounds.undoEnvironmentChanges(); } } @SuppressWarnings("all") @WrapOperation(method = "", at = @At(value = "INVOKE", target = "Lnet/neoforged/fml/loading/ImmediateWindowHandler;setupMinecraftWindow(Ljava/util/function/IntSupplier;Ljava/util/function/IntSupplier;Ljava/util/function/Supplier;Ljava/util/function/LongSupplier;)J"), expect = 0, require = 0) private long wrapGlfwCreateWindowForge(final IntSupplier width, final IntSupplier height, final Supplier title, final LongSupplier monitor, Operation op) { - final boolean applyNvidiaWorkarounds = Workarounds.isWorkaroundEnabled(Workarounds.Reference.NVIDIA_THREADED_OPTIMIZATIONS); + boolean applyWorkaroundsLate = !PlatformRuntimeInformation.getInstance() + .platformHasEarlyLoadingScreen(); - if (applyNvidiaWorkarounds && !PlatformRuntimeInformation.getInstance().platformHasEarlyLoadingScreen()) { - NvidiaWorkarounds.install(); + if (applyWorkaroundsLate) { + NvidiaWorkarounds.applyEnvironmentChanges(); } try { return op.call(width, height, title, monitor); } finally { - if (applyNvidiaWorkarounds) { - NvidiaWorkarounds.uninstall(); + if (applyWorkaroundsLate) { + NvidiaWorkarounds.undoEnvironmentChanges(); } } } + @Inject(method = "", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL;createCapabilities()Lorg/lwjgl/opengl/GLCapabilities;", shift = At.Shift.AFTER)) private void postContextReady(WindowEventHandler eventHandler, ScreenManager monitorTracker, DisplayData settings, String videoMode, String title, CallbackInfo ci) { GlContextInfo driver = GlContextInfo.create(); - - if (driver == null) { - LOGGER.warn("Could not retrieve identifying strings for OpenGL implementation"); - } else { - LOGGER.info("OpenGL Vendor: {}", driver.vendor()); - LOGGER.info("OpenGL Renderer: {}", driver.renderer()); - LOGGER.info("OpenGL Version: {}", driver.version()); - } + LOGGER.info("OpenGL Vendor: {}", driver.vendor()); + LOGGER.info("OpenGL Renderer: {}", driver.renderer()); + LOGGER.info("OpenGL Version: {}", driver.version()); // Capture the current WGL context so that we can detect it being replaced later. if (Util.getPlatform() == Util.OS.WINDOWS) { @@ -99,6 +85,7 @@ private void postContextReady(WindowEventHandler eventHandler, ScreenManager mon this.wglPrevContext = MemoryUtil.NULL; } + NvidiaWorkarounds.applyContextChanges(driver); PostLaunchChecks.onContextInitialized(); ModuleScanner.checkModules((NativeWindowHandle) this); } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java index cd41ca489c..170f329ff4 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java @@ -1,19 +1,13 @@ package net.caffeinemc.mods.sodium.client.compatibility.checks; -import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; -import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe; -import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterVendor; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.intel.IntelWorkarounds; import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaDriverVersion; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; import net.caffeinemc.mods.sodium.client.platform.MessageBox; -import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; -import net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt.D3DKMT; -import org.jetbrains.annotations.Nullable; import org.lwjgl.Version; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Arrays; - /** * Performs OpenGL driver validation before the game creates an OpenGL context. This runs during the earliest possible * opportunity at game startup, and uses a custom hardware prober to search for problematic drivers. @@ -21,19 +15,23 @@ public class PreLaunchChecks { private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-EarlyDriverScanner"); - // This string should be determined at compile time, so it can be checked against the runtime version. - private static final String REQUIRED_LWJGL_VERSION = Version.VERSION_MAJOR + "." + Version.VERSION_MINOR + "." + Version.VERSION_REVISION; + // These version constants are inlined at compile time. + private static final String REQUIRED_LWJGL_VERSION = + Version.VERSION_MAJOR + "." + Version.VERSION_MINOR + "." + Version.VERSION_REVISION; - private static final String normalMessage = "You must change the LWJGL version in your launcher to continue. This is usually controlled by the settings for a profile or instance in your launcher."; + private static final String normalMessage = "You must change the LWJGL version in your launcher to continue. " + + "This is usually controlled by the settings for a profile or instance in your launcher."; - private static final String prismMessage = "It appears you are using Prism Launcher to start the game. You can likely fix this problem by opening your instance settings and navigating to the Version section in the sidebar."; + private static final String prismMessage = "It appears you are using Prism Launcher to start the game. You can " + + "likely fix this problem by opening your instance settings and navigating to the Version section in the " + + "sidebar."; public static void beforeLWJGLInit() { if (BugChecks.ISSUE_2561) { - if (!Version.getVersion().startsWith(REQUIRED_LWJGL_VERSION)) { + if (!isUsingKnownCompatibleLwjglVersion()) { String message = normalMessage; - if (System.getProperty("minecraft.launcher.brand", "unknown").equalsIgnoreCase("PrismLauncher")) { + if (isUsingPrismLauncher()) { message = prismMessage; } @@ -46,19 +44,20 @@ public static void beforeLWJGLInit() { Required version: ###REQUIRED_VERSION### """ + message) - .replace("###CURRENT_VERSION###", org.lwjgl.Version.getVersion()) + .replace("###CURRENT_VERSION###", Version.getVersion()) .replace("###REQUIRED_VERSION###", REQUIRED_LWJGL_VERSION), "https://github.com/CaffeineMC/sodium/wiki/LWJGL-Compatibility"); - } } } public static void onGameInit() { if (BugChecks.ISSUE_899) { - var installedVersion = findIntelDriverMatchingBug899(); + var installedVersion = IntelWorkarounds.findIntelDriverMatchingBug899(); if (installedVersion != null) { + var installedVersionString = installedVersion.toString(); + showCriticalErrorAndClose("Sodium Renderer - Unsupported Driver", """ The game failed to start because the currently installed Intel Graphics Driver is not \ @@ -68,15 +67,18 @@ public static void onGameInit() { Required version: 10.18.10.5161 (or newer) You must update your graphics card driver in order to continue.""" - .replace("###CURRENT_DRIVER###", installedVersion.toString()), + .replace("###CURRENT_DRIVER###", installedVersionString), "https://github.com/CaffeineMC/sodium/wiki/Driver-Compatibility#windows-intel-gen7"); } } if (BugChecks.ISSUE_1486) { - var installedVersion = findNvidiaDriverMatchingBug1486(); + var installedVersion = NvidiaWorkarounds.findNvidiaDriverMatchingBug1486(); if (installedVersion != null) { + var installedVersionString = NvidiaDriverVersion.parse(installedVersion) + .toString(); + showCriticalErrorAndClose("Sodium Renderer - Unsupported Driver", """ The game failed to start because the currently installed NVIDIA Graphics Driver is not \ @@ -86,7 +88,7 @@ public static void onGameInit() { Required version: 536.23 (or newer) You must update your graphics card driver in order to continue.""" - .replace("###CURRENT_DRIVER###", NvidiaDriverVersion.parse(installedVersion).toString()), + .replace("###CURRENT_DRIVER###", installedVersionString), "https://github.com/CaffeineMC/sodium/wiki/Driver-Compatibility#nvidia-gpus"); } @@ -107,67 +109,13 @@ private static void showCriticalErrorAndClose(String title, String message, Stri System.exit(1 /* failure code */); } - // https://github.com/CaffeineMC/sodium/issues/899 - private static @Nullable WindowsFileVersion findIntelDriverMatchingBug899() { - if (OsUtils.getOs() != OsUtils.OperatingSystem.WIN) { - return null; - } - - for (var adapter : GraphicsAdapterProbe.getAdapters()) { - if (adapter instanceof D3DKMT.WDDMAdapterInfo wddmAdapterInfo) { - @Nullable var driverName = wddmAdapterInfo.getOpenGlIcdName(); - - if (driverName == null) { - continue; - } - - var driverVersion = wddmAdapterInfo.openglIcdVersion(); - - // Intel OpenGL ICD for Generation 7 GPUs - if (driverName.matches("ig7icd(32|64)")) { - // https://www.intel.com/content/www/us/en/support/articles/000005654/graphics.html - // Anything which matches the 15.33 driver scheme (WDDM x.y.10.w) should be checked - // Drivers before build 5161 are assumed to have bugs with synchronization primitives - if (driverVersion.z() == 10 && driverVersion.w() < 5161) { - return driverVersion; - } - } - } - } - - return null; + private static boolean isUsingKnownCompatibleLwjglVersion() { + return Version.getVersion() + .startsWith(REQUIRED_LWJGL_VERSION); } - - // https://github.com/CaffeineMC/sodium/issues/1486 - // The way which NVIDIA tries to detect the Minecraft process could not be circumvented until fairly recently - // So we require that an up-to-date graphics driver is installed so that our workarounds can disable the Threaded - // Optimizations driver hack. - private static @Nullable WindowsFileVersion findNvidiaDriverMatchingBug1486() { - // The Linux driver has two separate branches which have overlapping version numbers, despite also having - // different feature sets. As a result, we can't reliably determine which Linux drivers are broken... - if (OsUtils.getOs() != OsUtils.OperatingSystem.WIN) { - return null; - } - - for (var adapter : GraphicsAdapterProbe.getAdapters()) { - if (adapter.vendor() != GraphicsAdapterVendor.NVIDIA) { - continue; - } - - if (adapter instanceof D3DKMT.WDDMAdapterInfo wddmAdapterInfo) { - var driverVersion = wddmAdapterInfo.openglIcdVersion(); - - if (driverVersion.z() == 15) { // Only match 5XX.XX drivers - // Broken in x.y.15.2647 (526.47) - // Fixed in x.y.15.3623 (536.23) - if (driverVersion.w() >= 2647 && driverVersion.w() < 3623) { - return driverVersion; - } - } - } - } - - return null; + private static boolean isUsingPrismLauncher() { + return System.getProperty("minecraft.launcher.brand", "unknown") + .equalsIgnoreCase("PrismLauncher"); } } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GlContextInfo.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GlContextInfo.java new file mode 100644 index 0000000000..7e8f484385 --- /dev/null +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GlContextInfo.java @@ -0,0 +1,19 @@ +package net.caffeinemc.mods.sodium.client.compatibility.environment; + +import org.jetbrains.annotations.Nullable; +import org.lwjgl.opengl.GL11C; + +import java.util.Objects; + +public record GlContextInfo(String vendor, String renderer, String version) { + public static GlContextInfo create() { + String vendor = Objects.requireNonNull(GL11C.glGetString(GL11C.GL_VENDOR), + "GL_VENDOR is NULL"); + String renderer = Objects.requireNonNull(GL11C.glGetString(GL11C.GL_RENDERER), + "GL_RENDERER is NULL"); + String version = Objects.requireNonNull(GL11C.glGetString(GL11C.GL_VERSION), + "GL_VERSION is NULL"); + + return new GlContextInfo(vendor, renderer, version); + } +} diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/OsUtils.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/OsUtils.java index 4f39eb9ce0..c232c0faa9 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/OsUtils.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/OsUtils.java @@ -1,21 +1,32 @@ package net.caffeinemc.mods.sodium.client.compatibility.environment; -import org.apache.commons.lang3.SystemUtils; +import java.util.Locale; public class OsUtils { + private static final OperatingSystem OS = determineOs(); - public static OperatingSystem getOs() { - if (SystemUtils.IS_OS_WINDOWS) { - return OperatingSystem.WIN; - } else if (SystemUtils.IS_OS_MAC) { - return OperatingSystem.MAC; - } else if (SystemUtils.IS_OS_LINUX) { - return OperatingSystem.LINUX; + public static OperatingSystem determineOs() { + var name = System.getProperty("os.name"); + + if (name != null) { + var normalized = name.toLowerCase(Locale.ROOT); + + if (normalized.startsWith("windows")) { + return OperatingSystem.WIN; + } else if (normalized.startsWith("mac")) { + return OperatingSystem.MAC; + } else if (normalized.startsWith("linux")) { + return OperatingSystem.LINUX; + } } return OperatingSystem.UNKNOWN; } + public static OperatingSystem getOs() { + return OS; + } + public enum OperatingSystem { WIN, MAC, diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java index 0026148242..661f817ac9 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java @@ -2,12 +2,32 @@ import org.jetbrains.annotations.NotNull; +import java.util.regex.Pattern; + public enum GraphicsAdapterVendor { NVIDIA, AMD, INTEL, UNKNOWN; + // Intel Gen 4, 5, 6 - ig4icd + // Intel Gen 7 - ig7icd + // Intel Gen 7.5 - ig75icd + // Intel Gen 8 - ig8icd + // Intel Gen 9, 9.5 - ig9icd + // Intel Gen 11 - ig11icd + // Intel Gen 12 - ig12icd (UHD Graphics, with early drivers) + // igxelpicd (Xe-LP; integrated) + // igxehpicd (Xe-HP; dedicated) + private static final Pattern INTEL_ICD_PATTERN = + Pattern.compile("ig(4|7|75|8|9|11|12|xelp|xehp)icd(32|64)\\.dll", Pattern.CASE_INSENSITIVE); + + private static final Pattern NVIDIA_ICD_PATTERN = + Pattern.compile("nvoglv(32|64)\\.dll", Pattern.CASE_INSENSITIVE); + + private static final Pattern AMD_ICD_PATTERN = + Pattern.compile("(atiglpxx|atig6pxx)\\.dll", Pattern.CASE_INSENSITIVE); + @NotNull static GraphicsAdapterVendor fromPciVendorId(String vendor) { if (vendor.contains("0x1002")) { @@ -22,27 +42,19 @@ static GraphicsAdapterVendor fromPciVendorId(String vendor) { } public static GraphicsAdapterVendor fromIcdName(String name) { - // Intel Gen 4, 5, 6 - ig4icd - // Intel Gen 7 - ig7icd - // Intel Gen 7.5 - ig75icd - // Intel Gen 8 - ig8icd - // Intel Gen 9, 9.5 - ig9icd - // Intel Gen 11 - ig11icd - // Intel Gen 12 - ig12icd (UHD Graphics, with early drivers) - // igxelpicd (Xe-LP; integrated) - // igxehpicd (Xe-HP; dedicated) - if (name.matches("ig(4|7|75|8|9|11|12|xelp|xehp)icd(32|64)")) { + if (matchesPattern(INTEL_ICD_PATTERN, name)) { return INTEL; - } - - if (name.matches("nvoglv(32|64)")) { + } else if (matchesPattern(NVIDIA_ICD_PATTERN, name)) { return NVIDIA; - } - - if (name.matches("atiglpxx|atig6pxx")) { + } else if (matchesPattern(AMD_ICD_PATTERN, name)) { return AMD; + } else { + return UNKNOWN; } + } - return UNKNOWN; + private static boolean matchesPattern(Pattern pattern, String name) { + return pattern.matcher(name) + .matches(); } } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java index 1e6afc0cad..98e286d03d 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java @@ -1,15 +1,17 @@ package net.caffeinemc.mods.sodium.client.compatibility.workarounds; import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; -import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterInfo; import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe; -import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterVendor; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; import net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt.D3DKMT; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -37,10 +39,8 @@ private static Set findNecessaryWorkarounds() { var workarounds = EnumSet.noneOf(Reference.class); var operatingSystem = OsUtils.getOs(); - var graphicsAdapters = GraphicsAdapterProbe.getAdapters(); - - if (isUsingNvidiaGraphicsCard(operatingSystem, graphicsAdapters)) { - workarounds.add(Reference.NVIDIA_THREADED_OPTIMIZATIONS); + if (NvidiaWorkarounds.isUsingNvidiaGraphicsCard()) { + workarounds.add(Reference.NVIDIA_THREADED_OPTIMIZATIONS_BROKEN); } if (isUsingIntelGen8OrOlder()) { @@ -84,12 +84,6 @@ private static boolean isUsingIntelGen8OrOlder() { return false; } - private static boolean isUsingNvidiaGraphicsCard(OsUtils.OperatingSystem operatingSystem, Collection adapters) { - - return (operatingSystem == OsUtils.OperatingSystem.WIN || operatingSystem == OsUtils.OperatingSystem.LINUX) && - adapters.stream().anyMatch(adapter -> adapter.vendor() == GraphicsAdapterVendor.NVIDIA); - } - public static boolean isWorkaroundEnabled(Reference id) { return ACTIVE_WORKAROUNDS.get() .contains(id); @@ -101,7 +95,7 @@ public enum Reference { * performance issues and crashes. * GitHub Issue */ - NVIDIA_THREADED_OPTIMIZATIONS, + NVIDIA_THREADED_OPTIMIZATIONS_BROKEN, /** * Requesting a No Error Context causes a crash at startup when using a Wayland session. diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java new file mode 100644 index 0000000000..97ab3a1aae --- /dev/null +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java @@ -0,0 +1,40 @@ +package net.caffeinemc.mods.sodium.client.compatibility.workarounds.intel; + +import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; +import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe; +import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; +import net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt.D3DKMT; +import org.jetbrains.annotations.Nullable; + +public class IntelWorkarounds { + // https://github.com/CaffeineMC/sodium/issues/899 + public static @Nullable WindowsFileVersion findIntelDriverMatchingBug899() { + if (OsUtils.getOs() != OsUtils.OperatingSystem.WIN) { + return null; + } + + for (var adapter : GraphicsAdapterProbe.getAdapters()) { + if (adapter instanceof D3DKMT.WDDMAdapterInfo wddmAdapterInfo) { + @Nullable var driverName = wddmAdapterInfo.getOpenGlIcdName(); + + if (driverName == null) { + continue; + } + + var driverVersion = wddmAdapterInfo.openglIcdVersion(); + + // Intel OpenGL ICD for Generation 7 GPUs + if (driverName.matches("ig7icd(32|64)")) { + // https://www.intel.com/content/www/us/en/support/articles/000005654/graphics.html + // Anything which matches the 15.33 driver scheme (WDDM x.y.10.w) should be checked + // Drivers before build 5161 are assumed to have bugs with synchronization primitives + if (driverVersion.z() == 10 && driverVersion.w() < 5161) { + return driverVersion; + } + } + } + } + + return null; + } +} diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java index 5f54ec5174..0caa32458f 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java @@ -1,49 +1,157 @@ package net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia; +import net.caffeinemc.mods.sodium.client.compatibility.environment.GlContextInfo; import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; +import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils.OperatingSystem; +import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe; +import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterVendor; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds; import net.caffeinemc.mods.sodium.client.platform.unix.Libc; -import net.caffeinemc.mods.sodium.client.platform.windows.api.Kernel32; import net.caffeinemc.mods.sodium.client.platform.windows.WindowsCommandLine; +import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; +import net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt.D3DKMT; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.opengl.GL; +import org.lwjgl.opengl.GL32C; +import org.lwjgl.opengl.KHRDebug; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Locale; + public class NvidiaWorkarounds { private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-NvidiaWorkarounds"); - public static void install() { - LOGGER.warn("Applying workaround: Prevent the NVIDIA OpenGL driver from using broken optimizations (NVIDIA_THREADED_OPTIMIZATIONS)"); + public static boolean isUsingNvidiaGraphicsCard() { + return GraphicsAdapterProbe.getAdapters() + .stream() + .anyMatch(adapter -> adapter.vendor() == GraphicsAdapterVendor.NVIDIA); + } - try { - switch (OsUtils.getOs()) { - case WIN -> { - // The NVIDIA drivers rely on parsing the command line arguments to detect Minecraft. If we destroy those, - // then it shouldn't be able to detect us anymore. - WindowsCommandLine.setCommandLine("net.caffeinemc.sodium"); - - // Ensures that Minecraft will run on the dedicated GPU, since the drivers can no longer detect it - Kernel32.setEnvironmentVariable("SHIM_MCCOMPAT", "0x800000001"); - } - case LINUX -> { - // Unlike Windows, we don't need to hide ourselves from the driver. We can just request that - // it not use threaded optimizations instead. - Libc.setEnvironmentVariable("__GL_THREADED_OPTIMIZATIONS", "0"); + // https://github.com/CaffeineMC/sodium/issues/1486 + // The way which NVIDIA tries to detect the Minecraft process could not be circumvented until fairly recently + // So we require that an up-to-date graphics driver is installed so that our workarounds can disable the Threaded + // Optimizations driver hack. + public static @Nullable WindowsFileVersion findNvidiaDriverMatchingBug1486() { + // The Linux driver has two separate branches which have overlapping version numbers, despite also having + // different feature sets. As a result, we can't reliably determine which Linux drivers are broken... + if (OsUtils.getOs() != OperatingSystem.WIN) { + return null; + } + + for (var adapter : GraphicsAdapterProbe.getAdapters()) { + if (adapter.vendor() != GraphicsAdapterVendor.NVIDIA) { + continue; + } + + if (adapter instanceof D3DKMT.WDDMAdapterInfo wddmAdapterInfo) { + var driverVersion = wddmAdapterInfo.openglIcdVersion(); + + if (driverVersion.z() == 15) { // Only match 5XX.XX drivers + // Broken in x.y.15.2647 (526.47) + // Fixed in x.y.15.3623 (536.23) + if (driverVersion.w() >= 2647 && driverVersion.w() < 3623) { + return driverVersion; + } } } + } + + return null; + } + + public static void applyEnvironmentChanges() { + // We can't know if the OpenGL context will actually be initialized using the NVIDIA ICD, but we need to + // modify the process environment *now* otherwise the driver will initialize with bad settings. For non-NVIDIA + // drivers, these workarounds are not likely to cause issues. + if (!isUsingNvidiaGraphicsCard()) { + return; + } + + LOGGER.info("Modifying process environment to apply workarounds for the NVIDIA graphics driver..."); + + try { + if (OsUtils.getOs() == OperatingSystem.WIN) { + applyEnvironmentChanges$Windows(); + } else if (OsUtils.getOs() == OperatingSystem.LINUX) { + applyEnvironmentChanges$Linux(); + } } catch (Throwable t) { - LOGGER.error("Failure while applying workarounds", t); + LOGGER.error("Failed to modify the process environment", t); + logWarning(); + } + } + + + private static void applyEnvironmentChanges$Windows() { + // The NVIDIA drivers rely on parsing the command line arguments to detect Minecraft. We need to + // make sure that it detects the game so that *some* important optimizations are applied. Later, + // we will try to enable GL_DEBUG_OUTPUT_SYNCHRONOUS so that "Threaded Optimizations" cannot + // be enabled. + WindowsCommandLine.setCommandLine("net.caffeinemc.sodium / net.minecraft.client.main.Main /"); + } + + private static void applyEnvironmentChanges$Linux() { + // Unlike Windows, we can just request that it not use threaded optimizations instead. + Libc.setEnvironmentVariable("__GL_THREADED_OPTIMIZATIONS", "0"); + } - LOGGER.error("READ ME! The workarounds for the NVIDIA Graphics Driver did not apply correctly!"); - LOGGER.error("READ ME! You are very likely going to run into unexplained crashes and severe performance issues!"); - LOGGER.error("READ ME! Please see this issue for more information: https://github.com/CaffeineMC/sodium/issues/1816"); + public static void undoEnvironmentChanges() { + if (OsUtils.getOs() == OperatingSystem.WIN) { + undoEnvironmentChanges$Windows(); } } - public static void uninstall() { - switch (OsUtils.getOs()) { - case WIN -> { - WindowsCommandLine.resetCommandLine(); + private static void undoEnvironmentChanges$Windows() { + WindowsCommandLine.resetCommandLine(); + } + + public static void applyContextChanges(GlContextInfo driver) { + var normalizedVendorName = driver.vendor() + .toLowerCase(Locale.ROOT); + + // The context may not have been initialized with the NVIDIA ICD, even if we think there is an NVIDIA + // graphics adapter in use. Because enabling these workarounds have the potential to severely hurt performance + // on other drivers, make sure we exit now. + if (!normalizedVendorName.startsWith("nvidia")) { + return; + } + + LOGGER.info("Modifying OpenGL context to apply workarounds for the NVIDIA graphics driver..."); + + if (Workarounds.isWorkaroundEnabled(Workarounds.Reference.NVIDIA_THREADED_OPTIMIZATIONS_BROKEN)) { + if (OsUtils.getOs() == OperatingSystem.WIN) { + applyContextChanges$Windows(); } - case LINUX -> { } } } + + private static void applyContextChanges$Windows() { + // On Windows, the NVIDIA drivers do not have any environment variable to control whether + // "Threaded Optimizations" are enabled. But we can enable the "GL_DEBUG_OUTPUT_SYNCHRONOUS" option to + // achieve the same effect. + var capabilities = GL.getCapabilities(); + + if (capabilities.GL_KHR_debug) { + LOGGER.info("Enabling GL_DEBUG_OUTPUT_SYNCHRONOUS to force the NVIDIA driver to disable threaded" + + "command submission"); + GL32C.glEnable(KHRDebug.GL_DEBUG_OUTPUT_SYNCHRONOUS); + } else { + LOGGER.error("GL_KHR_debug does not appear to be supported, unable to disable threaded " + + "command submission!"); + logWarning(); + } + } + + private static void logWarning() { + LOGGER.error("READ ME!"); + LOGGER.error("READ ME! The workarounds for the NVIDIA Graphics Driver did not apply correctly!"); + LOGGER.error("READ ME! You are very likely going to run into unexplained crashes and severe performance issues."); + LOGGER.error("READ ME! More information about what went wrong can be found above this message."); + LOGGER.error("READ ME!"); + LOGGER.error("READ ME! Please help us understand why this problem occurred by opening a bug report on our issue tracker:"); + LOGGER.error("READ ME! https://github.com/CaffeineMC/sodium/issues"); + LOGGER.error("READ ME!"); + + } } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Gdi32.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Gdi32.java index 3341963a38..680c071807 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Gdi32.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Gdi32.java @@ -1,6 +1,5 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api; -import org.apache.commons.lang3.Validate; import org.lwjgl.system.JNI; import org.lwjgl.system.SharedLibrary; @@ -21,19 +20,24 @@ public static boolean isD3DKMTSupported() { // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/d3dkmthk/nf-d3dkmthk-d3dkmtqueryadapterinfo public static int /* NTSTATUS */ nd3dKmtQueryAdapterInfo(long ptr /* D3DKMT_QUERYADAPTERINFO */) { - Validate.isTrue(PFN_D3DKMTQueryAdapterInfo != NULL); - return JNI.callPI(ptr, PFN_D3DKMTQueryAdapterInfo); + return JNI.callPI(ptr, checkPfn(PFN_D3DKMTQueryAdapterInfo)); } // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/d3dkmthk/nf-d3dkmthk-d3dkmtenumadapters2 public static int /* NTSTATUS */ nD3DKMTEnumAdapters(long ptr /* D3DKMT_ENUMADAPTERS */) { - Validate.isTrue(PFN_D3DKMTEnumAdapters != NULL); - return JNI.callPI(ptr, PFN_D3DKMTEnumAdapters); + return JNI.callPI(ptr, checkPfn(PFN_D3DKMTEnumAdapters)); } // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/d3dkmthk/nf-d3dkmthk-d3dkmtcloseadapter public static int /* NTSTATUS */ nD3DKMTCloseAdapter(long ptr /* D3DKMT_CLOSEADAPTER */) { - Validate.isTrue(PFN_D3DKMTCloseAdapter != NULL); - return JNI.callPI(ptr, PFN_D3DKMTCloseAdapter); + return JNI.callPI(ptr, checkPfn(PFN_D3DKMTCloseAdapter)); + } + + private static long checkPfn(long pfn) { + if (pfn == NULL) { + throw new NullPointerException("Function pointer not available"); + } + + return pfn; } } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java index 102fe183cf..6881b10ea7 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java @@ -5,7 +5,6 @@ import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; import net.caffeinemc.mods.sodium.client.platform.windows.api.Gdi32; import net.caffeinemc.mods.sodium.client.platform.windows.api.version.Version; -import org.apache.commons.io.FilenameUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.lwjgl.system.MemoryStack; @@ -13,7 +12,7 @@ import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; -import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @@ -201,8 +200,18 @@ public String toString() { } } - // Returns (null) if input is (null). - private static String getOpenGlIcdName(String path) { - return FilenameUtils.removeExtension(FilenameUtils.getName(path)); + private static String getOpenGlIcdName(@Nullable String filePath) { + if (filePath == null) { + return null; + } + + var fileName = Paths.get(filePath) + .getFileName(); + + if (fileName == null) { + return null; + } + + return fileName.toString(); } } diff --git a/neoforge/src/service/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java b/neoforge/src/service/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java index 1d6876b8e2..8dab82a896 100644 --- a/neoforge/src/service/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java +++ b/neoforge/src/service/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java @@ -18,11 +18,8 @@ public void bootstrap(String[] arguments) { GraphicsAdapterProbe.findAdapters(); PreLaunchChecks.onGameInit(); Workarounds.init(); - final boolean applyNvidiaWorkarounds = Workarounds.isWorkaroundEnabled(Workarounds.Reference.NVIDIA_THREADED_OPTIMIZATIONS); - if (applyNvidiaWorkarounds) { - System.out.println("[Sodium] Applying NVIDIA workarounds earlier on Forge."); - NvidiaWorkarounds.install(); - } + // Context creation happens earlier on NeoForge, so we need to apply this now + NvidiaWorkarounds.applyEnvironmentChanges(); } } From 46efd91759bc97b7e0ae944ab94d08607ad49f3c Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 24 Nov 2024 23:05:43 +0100 Subject: [PATCH 014/215] Sort render lists for regions and sections after traversal (#2780) Render sections and regions are sorted after the graph traversal is performed. This decouples their ordering from the graph, which isn't entirely correct for draw call sorting. Fixes #2266 --- .../client/render/chunk/RenderSection.java | 6 +- .../render/chunk/RenderSectionManager.java | 2 +- .../render/chunk/lists/ChunkRenderList.java | 40 ++++++++++++- .../render/chunk/lists/SortedRenderLists.java | 42 ------------- .../chunk/lists/VisibleChunkCollector.java | 60 +++++++++++++++---- .../chunk/occlusion/OcclusionCuller.java | 11 ++-- .../render/chunk/region/RenderRegion.java | 24 ++++++-- 7 files changed, 113 insertions(+), 72 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java index 0103850155..3ffa67f27e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java @@ -69,9 +69,9 @@ public RenderSection(RenderRegion region, int chunkX, int chunkY, int chunkZ) { this.chunkY = chunkY; this.chunkZ = chunkZ; - int rX = this.getChunkX() & (RenderRegion.REGION_WIDTH - 1); - int rY = this.getChunkY() & (RenderRegion.REGION_HEIGHT - 1); - int rZ = this.getChunkZ() & (RenderRegion.REGION_LENGTH - 1); + int rX = this.getChunkX() & RenderRegion.REGION_WIDTH_M; + int rY = this.getChunkY() & RenderRegion.REGION_HEIGHT_M; + int rZ = this.getChunkZ() & RenderRegion.REGION_LENGTH_M; this.sectionIndex = LocalSectionIndex.pack(rX, rY, rZ); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index 21aae7a7e1..7bd65792ef 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -146,7 +146,7 @@ private void createTerrainRenderList(Camera camera, Viewport viewport, int frame this.occlusionCuller.findVisible(visitor, viewport, searchDistance, useOcclusionCulling, frame); - this.renderLists = visitor.createRenderLists(); + this.renderLists = visitor.createRenderLists(viewport); this.taskLists = visitor.getRebuildLists(); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java index 75e6757df8..6212573773 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java @@ -1,11 +1,14 @@ package net.caffeinemc.mods.sodium.client.render.chunk.lists; +import net.caffeinemc.mods.sodium.client.render.chunk.LocalSectionIndex; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionFlags; +import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; +import net.caffeinemc.mods.sodium.client.render.viewport.CameraTransform; +import net.caffeinemc.mods.sodium.client.util.iterator.ByteArrayIterator; import net.caffeinemc.mods.sodium.client.util.iterator.ByteIterator; import net.caffeinemc.mods.sodium.client.util.iterator.ReversibleByteArrayIterator; -import net.caffeinemc.mods.sodium.client.util.iterator.ByteArrayIterator; -import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; +import net.minecraft.util.Mth; import org.jetbrains.annotations.Nullable; public class ChunkRenderList { @@ -37,6 +40,39 @@ public void reset(int frame) { this.lastVisibleFrame = frame; } + // clamping the relative camera position to the region bounds means there can only be very few different distances + private static final int SORTING_HISTOGRAM_SIZE = RenderRegion.REGION_WIDTH + RenderRegion.REGION_HEIGHT + RenderRegion.REGION_LENGTH - 2; + + public void sortSections(CameraTransform transform, int[] sortItems) { + var cameraX = Mth.clamp((transform.intX >> 4) - this.region.getChunkX(), 0, RenderRegion.REGION_WIDTH - 1); + var cameraY = Mth.clamp((transform.intY >> 4) - this.region.getChunkY(), 0, RenderRegion.REGION_HEIGHT - 1); + var cameraZ = Mth.clamp((transform.intZ >> 4) - this.region.getChunkZ(), 0, RenderRegion.REGION_LENGTH - 1); + + int[] histogram = new int[SORTING_HISTOGRAM_SIZE]; + + for (int i = 0; i < this.sectionsWithGeometryCount; i++) { + var index = this.sectionsWithGeometry[i] & 0xFF; // makes sure the byte -> int conversion is unsigned + var x = Math.abs(LocalSectionIndex.unpackX(index) - cameraX); + var y = Math.abs(LocalSectionIndex.unpackY(index) - cameraY); + var z = Math.abs(LocalSectionIndex.unpackZ(index) - cameraZ); + + var distance = x + y + z; + histogram[distance]++; + sortItems[i] = distance << 8 | index; + } + + // prefix sum to calculate indexes + for (int i = 1; i < SORTING_HISTOGRAM_SIZE; i++) { + histogram[i] += histogram[i - 1]; + } + + for (int i = 0; i < this.sectionsWithGeometryCount; i++) { + var item = sortItems[i]; + var distance = item >>> 8; + this.sectionsWithGeometry[--histogram[distance]] = (byte) item; + } + } + public void add(RenderSection render) { if (this.size >= RenderRegion.REGION_SIZE) { throw new ArrayIndexOutOfBoundsException("Render list is full"); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortedRenderLists.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortedRenderLists.java index 1837e0ae9d..080530bf11 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortedRenderLists.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortedRenderLists.java @@ -1,9 +1,7 @@ package net.caffeinemc.mods.sodium.client.render.chunk.lists; import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.util.iterator.ReversibleObjectArrayIterator; -import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; /** * Stores one render list of sections per region, sorted by the order in which @@ -27,44 +25,4 @@ public ReversibleObjectArrayIterator iterator(boolean reverse) public static SortedRenderLists empty() { return EMPTY; } - - public static class Builder { - private final ObjectArrayList lists = new ObjectArrayList<>(); - private final int frame; - - public Builder(int frame) { - this.frame = frame; - } - - public void add(RenderSection section) { - RenderRegion region = section.getRegion(); - ChunkRenderList list = region.getRenderList(); - - // Even if a section does not have render objects, we must ensure the render list is initialized and put - // into the sorted queue of lists, so that we maintain the correct order of draw calls. - if (list.getLastVisibleFrame() != this.frame) { - list.reset(this.frame); - - this.lists.add(list); - } - - // Only add the section to the render list if it actually contains render objects - if (section.getFlags() != 0) { - list.add(section); - } - } - - public SortedRenderLists build() { - var filtered = new ObjectArrayList(this.lists.size()); - - // Filter any empty render lists - for (var list : this.lists) { - if (list.size() > 0) { - filtered.add(list); - } - } - - return new SortedRenderLists(filtered); - } - } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java index 5ed657795f..1eda00204c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java @@ -1,10 +1,12 @@ package net.caffeinemc.mods.sodium.client.render.chunk.lists; +import it.unimi.dsi.fastutil.ints.IntArrays; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.caffeinemc.mods.sodium.client.render.chunk.ChunkUpdateType; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.OcclusionCuller; import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; +import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; import java.util.*; @@ -30,22 +32,22 @@ public VisibleChunkCollector(int frame) { } @Override - public void visit(RenderSection section, boolean visible) { - RenderRegion region = section.getRegion(); - ChunkRenderList renderList = region.getRenderList(); + public void visit(RenderSection section) { + // only process section (and associated render list) if it has content that needs rendering + if (section.getFlags() != 0) { + RenderRegion region = section.getRegion(); + ChunkRenderList renderList = region.getRenderList(); - // Even if a section does not have render objects, we must ensure the render list is initialized and put - // into the sorted queue of lists, so that we maintain the correct order of draw calls. - if (renderList.getLastVisibleFrame() != this.frame) { - renderList.reset(this.frame); + if (renderList.getLastVisibleFrame() != this.frame) { + renderList.reset(this.frame); - this.sortedRenderLists.add(renderList); - } + this.sortedRenderLists.add(renderList); + } - if (visible && section.getFlags() != 0) { renderList.add(section); } + // always add to rebuild lists though, because it might just not be built yet this.addToRebuildLists(section); } @@ -61,8 +63,42 @@ private void addToRebuildLists(RenderSection section) { } } - public SortedRenderLists createRenderLists() { - return new SortedRenderLists(this.sortedRenderLists); + private static int[] sortItems = new int[RenderRegion.REGION_SIZE]; + + public SortedRenderLists createRenderLists(Viewport viewport) { + // sort the regions by distance to fix rare region ordering bugs + var transform = viewport.getTransform(); + var cameraX = transform.intX >> (4 + RenderRegion.REGION_WIDTH_SH); + var cameraY = transform.intY >> (4 + RenderRegion.REGION_HEIGHT_SH); + var cameraZ = transform.intZ >> (4 + RenderRegion.REGION_LENGTH_SH); + var size = this.sortedRenderLists.size(); + + if (sortItems.length < size) { + sortItems = new int[size]; + } + + for (var i = 0; i < size; i++) { + var region = this.sortedRenderLists.get(i).getRegion(); + var x = Math.abs(region.getX() - cameraX); + var y = Math.abs(region.getY() - cameraY); + var z = Math.abs(region.getZ() - cameraZ); + sortItems[i] = (x + y + z) << 16 | i; + } + + IntArrays.unstableSort(sortItems, 0, size); + + var sorted = new ObjectArrayList(size); + for (var i = 0; i < size; i++) { + var key = sortItems[i]; + var renderList = this.sortedRenderLists.get(key & 0xFFFF); + sorted.add(renderList); + } + + for (var list : sorted) { + list.sortSections(transform, sortItems); + } + + return new SortedRenderLists(sorted); } public Map> getRebuildLists() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java index 95288386bb..e9714a21e1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java @@ -50,13 +50,12 @@ private static void processQueue(Visitor visitor, RenderSection section; while ((section = readQueue.dequeue()) != null) { - boolean visible = isSectionVisible(section, viewport, searchDistance); - visitor.visit(section, visible); - - if (!visible) { + if (!isSectionVisible(section, viewport, searchDistance)) { continue; } + visitor.visit(section); + int connections; { @@ -249,7 +248,7 @@ private void initWithinWorld(Visitor visitor, WriteQueue queue, V section.setLastVisibleFrame(frame); section.setIncomingDirections(GraphDirectionSet.NONE); - visitor.visit(section, true); + visitor.visit(section); int outgoing; @@ -335,6 +334,6 @@ private RenderSection getRenderSection(int x, int y, int z) { } public interface Visitor { - void visit(RenderSection section, boolean visible); + void visit(RenderSection section); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java index df6df676d4..29ca64b226 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java @@ -24,13 +24,13 @@ public class RenderRegion { public static final int REGION_HEIGHT = 4; public static final int REGION_LENGTH = 8; - private static final int REGION_WIDTH_M = RenderRegion.REGION_WIDTH - 1; - private static final int REGION_HEIGHT_M = RenderRegion.REGION_HEIGHT - 1; - private static final int REGION_LENGTH_M = RenderRegion.REGION_LENGTH - 1; + public static final int REGION_WIDTH_M = RenderRegion.REGION_WIDTH - 1; + public static final int REGION_HEIGHT_M = RenderRegion.REGION_HEIGHT - 1; + public static final int REGION_LENGTH_M = RenderRegion.REGION_LENGTH - 1; - protected static final int REGION_WIDTH_SH = Integer.bitCount(REGION_WIDTH_M); - protected static final int REGION_HEIGHT_SH = Integer.bitCount(REGION_HEIGHT_M); - protected static final int REGION_LENGTH_SH = Integer.bitCount(REGION_LENGTH_M); + public static final int REGION_WIDTH_SH = Integer.bitCount(REGION_WIDTH_M); + public static final int REGION_HEIGHT_SH = Integer.bitCount(REGION_HEIGHT_M); + public static final int REGION_LENGTH_SH = Integer.bitCount(REGION_LENGTH_M); public static final int REGION_SIZE = REGION_WIDTH * REGION_HEIGHT * REGION_LENGTH; @@ -64,6 +64,18 @@ public static long key(int x, int y, int z) { return SectionPos.asLong(x, y, z); } + public int getX() { + return this.x; + } + + public int getY() { + return this.y; + } + + public int getZ() { + return this.z; + } + public int getChunkX() { return this.x << REGION_WIDTH_SH; } From f6fae721165014201ed81f3f4cfc20c65a363555 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sat, 30 Nov 2024 20:36:31 -0600 Subject: [PATCH 015/215] Fix rounding error in ColorMixer#mix Rounding of the values now happens after the 16-bit intermediaries are added together. This affected some animated textures, causing them to exhibit flickering behavior. --- .../mods/sodium/api/util/ColorMixer.java | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java index 0fcd616dc8..220eae17ca 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorMixer.java @@ -10,18 +10,33 @@ public class ColorMixer { /** *

Linearly interpolate between the {@param start} and {@param end} points, represented as packed unsigned 8-bit - * values within a 32-bit integer. The result is computed as

(start * weight) + (end * (255 - weight))
.

+ * values within a 32-bit integer. The result is computed as
(start * weight) + (end * (255 - weight))
+ * using fixed-point arithmetic and round-to-nearest behavior.

* *

The results are undefined if {@param weight} is not within the interval [0, 255].

- - * @param start The start of the range to interpolate - * @param end The end of the range to interpolate + * + *

If {@param start} and {@param end} are the same value, the result of this function will always be that value, + * regardless of {@param weight}.

+ * + * @param start The value at the start of the range to interpolate + * @param end The value at the end of the range to interpolate * @param weight The weight value used to interpolate between color values (in 0..255 range) * @return The color that was interpolated between the start and end points */ public static int mix(int start, int end, int weight) { - // Overflow is not possible, so adding the values is fine. - return mul(start, weight) + mul(end, ColorU8.COMPONENT_MASK - weight); + // De-interleave the 8-bit component lanes into high and low halves for each point. + // Multiply the start point by alpha, and the end point by 1-alpha, to produce Q8.8 fixed-point intermediates. + // Add the Q8.8 fixed-point intermediaries together to obtain the mixed values. + final long hi = ((start & 0x00FF00FFL) * weight) + ((end & 0x00FF00FFL) * (ColorU8.COMPONENT_MASK - weight)); + final long lo = ((start & 0xFF00FF00L) * weight) + ((end & 0xFF00FF00L) * (ColorU8.COMPONENT_MASK - weight)); + + // Round the fixed-point values to the nearest integer, and interleave the high and low halves to + // produce the final packed result. + final long result = + (((hi + 0x00FF00FFL) >>> 8) & 0x00FF00FFL) | + (((lo + 0xFF00FF00L) >>> 8) & 0xFF00FF00L); + + return (int) result; } /** @@ -104,10 +119,19 @@ public static int mulComponentWise(int color0, int color1) { * @return The result of the multiplication */ public static int mul(int color, int factor) { - final long result = (((((color & 0x00FF00FFL) * factor) + 0x00FF00FFL) >>> 8) & 0x00FF00FFL) | - (((((color & 0xFF00FF00L) * factor) + 0xFF00FF00L) >>> 8) & 0xFF00FF00L); + // De-interleave the 8-bit component lanes into high and low halves. + // Perform 8-bit multiplication to produce Q8.8 fixed-point intermediaries. + final long hi = (color & 0x00FF00FFL) * factor; + final long lo = (color & 0xFF00FF00L) * factor; + + // Round the Q8.8 fixed-point values to the nearest integer, and interleave the high and low halves to + // produce the packed result. + final long result = + (((hi + 0x00FF00FFL) >>> 8) & 0x00FF00FFL) | + (((lo + 0xFF00FF00L) >>> 8) & 0xFF00FF00L); return (int) result; + } /** From 15e149713a0da8d7ff916c2c85b7d2ccbd5a4a38 Mon Sep 17 00:00:00 2001 From: muzikbike <52297970+muzikbike@users.noreply.github.com> Date: Sun, 24 Nov 2024 16:32:22 +0000 Subject: [PATCH 016/215] Add additional optimized block models This covers the following additional blocks: - Cauldrons - Brewing Stands - Bells Co-authored-by: JellySquid --- .../minecraft/models/block/bell_floor.json | 43 ++++ .../minecraft/models/block/brewing_stand.json | 57 +++++ .../minecraft/models/block/cauldron.json | 214 +++++++++++++++++ .../models/block/template_cauldron_full.json | 221 ++++++++++++++++++ .../block/template_cauldron_level1.json | 221 ++++++++++++++++++ .../block/template_cauldron_level2.json | 221 ++++++++++++++++++ 6 files changed, 977 insertions(+) create mode 100644 common/src/main/resources/assets/minecraft/models/block/bell_floor.json create mode 100644 common/src/main/resources/assets/minecraft/models/block/brewing_stand.json create mode 100644 common/src/main/resources/assets/minecraft/models/block/cauldron.json create mode 100644 common/src/main/resources/assets/minecraft/models/block/template_cauldron_full.json create mode 100644 common/src/main/resources/assets/minecraft/models/block/template_cauldron_level1.json create mode 100644 common/src/main/resources/assets/minecraft/models/block/template_cauldron_level2.json diff --git a/common/src/main/resources/assets/minecraft/models/block/bell_floor.json b/common/src/main/resources/assets/minecraft/models/block/bell_floor.json new file mode 100644 index 0000000000..3300cc2013 --- /dev/null +++ b/common/src/main/resources/assets/minecraft/models/block/bell_floor.json @@ -0,0 +1,43 @@ +{ + "textures": { + "bar": "block/dark_oak_planks", + "post": "block/stone", + "particle": "block/bell_bottom" + }, + "elements": [ + { + "from": [ 2, 13, 7 ], + "to": [ 14, 15, 9 ], + "faces": { + "north": { "uv": [ 2, 2, 14, 4 ], "texture": "#bar" }, + "south": { "uv": [ 2, 3, 14, 5 ], "texture": "#bar" }, + "up": { "uv": [ 2, 3, 14, 5 ], "texture": "#bar" }, + "down": { "uv": [ 2, 3, 14, 5 ], "texture": "#bar" } + } + }, + { + "from": [ 14, 0, 6 ], + "to": [ 16, 16, 10 ], + "faces": { + "north": { "uv": [ 0, 1, 2, 16 ], "texture": "#post" }, + "east": { "uv": [ 0, 1, 4, 16 ], "texture": "#post", "cullface": "east" }, + "south": { "uv": [ 0, 1, 2, 16 ], "texture": "#post" }, + "west": { "uv": [ 0, 1, 4, 16 ], "texture": "#post" }, + "up": { "uv": [ 0, 0, 2, 4 ], "texture": "#post", "cullface": "up" }, + "down": { "uv": [ 0, 0, 2, 4 ], "texture": "#post", "cullface": "down" } + } + }, + { + "from": [ 0, 0, 6 ], + "to": [ 2, 16, 10 ], + "faces": { + "north": { "uv": [ 0, 1, 2, 16 ], "texture": "#post" }, + "east": { "uv": [ 0, 1, 4, 16 ], "texture": "#post" }, + "south": { "uv": [ 0, 1, 2, 16 ], "texture": "#post" }, + "west": { "uv": [ 0, 1, 4, 16 ], "texture": "#post", "cullface": "west" }, + "up": { "uv": [ 0, 0, 2, 4 ], "texture": "#post", "cullface": "up" }, + "down": { "uv": [ 0, 0, 2, 4 ], "texture": "#post", "cullface": "down" } + } + } + ] +} diff --git a/common/src/main/resources/assets/minecraft/models/block/brewing_stand.json b/common/src/main/resources/assets/minecraft/models/block/brewing_stand.json new file mode 100644 index 0000000000..172c7704f3 --- /dev/null +++ b/common/src/main/resources/assets/minecraft/models/block/brewing_stand.json @@ -0,0 +1,57 @@ +{ + "textures": { + "base": "block/brewing_stand_base", + "particle": "block/brewing_stand", + "stand": "block/brewing_stand" + }, + "elements": [ + { + "from": [ 7, 0, 7 ], + "to": [ 9, 14, 9 ], + "faces": { + "north": { "uv": [ 7, 2, 9, 16 ], "texture": "#stand" }, + "east": { "uv": [ 7, 2, 9, 16 ], "texture": "#stand" }, + "south": { "uv": [ 7, 2, 9, 16 ], "texture": "#stand" }, + "west": { "uv": [ 7, 2, 9, 16 ], "texture": "#stand" }, + "up": { "uv": [ 7, 7, 9, 9 ], "texture": "#stand" }, + "down": { "uv": [ 7, 7, 9, 9 ], "texture": "#stand", "cullface": "down" } + } + }, + { + "from": [ 9, 0, 5 ], + "to": [ 15, 2, 11 ], + "faces": { + "north": { "uv": [ 9, 14, 15, 16 ], "texture": "#base" }, + "east": { "uv": [ 5, 14, 11, 16 ], "texture": "#base" }, + "south": { "uv": [ 9, 14, 15, 16 ], "texture": "#base" }, + "west": { "uv": [ 5, 14, 11, 16 ], "texture": "#base" }, + "up": { "uv": [ 9, 5, 15, 11 ], "texture": "#base" }, + "down": { "uv": [ 9, 5, 15, 11 ], "texture": "#base", "cullface": "down" } + } + }, + { + "from": [ 1, 0, 1 ], + "to": [ 7, 2, 7 ], + "faces": { + "north": { "uv": [ 1, 14, 7, 16 ], "texture": "#base" }, + "east": { "uv": [ 1, 14, 7, 16 ], "texture": "#base" }, + "south": { "uv": [ 1, 14, 7, 16 ], "texture": "#base" }, + "west": { "uv": [ 1, 14, 7, 16 ], "texture": "#base" }, + "up": { "uv": [ 1, 1, 7, 7 ], "texture": "#base" }, + "down": { "uv": [ 1, 1, 7, 7 ], "texture": "#base", "cullface": "down" } + } + }, + { + "from": [ 1, 0, 9 ], + "to": [ 7, 2, 15 ], + "faces": { + "north": { "uv": [ 1, 14, 7, 16 ], "texture": "#base" }, + "east": { "uv": [ 9, 14, 15, 16 ], "texture": "#base" }, + "south": { "uv": [ 1, 14, 7, 16 ], "texture": "#base" }, + "west": { "uv": [ 9, 14, 15, 16 ], "texture": "#base" }, + "up": { "uv": [ 1, 9, 7, 15 ], "texture": "#base" }, + "down": { "uv": [ 1, 9, 7, 15 ], "texture": "#base", "cullface": "down" } + } + } + ] +} diff --git a/common/src/main/resources/assets/minecraft/models/block/cauldron.json b/common/src/main/resources/assets/minecraft/models/block/cauldron.json new file mode 100644 index 0000000000..3b18fff1ef --- /dev/null +++ b/common/src/main/resources/assets/minecraft/models/block/cauldron.json @@ -0,0 +1,214 @@ +{ + "ambientocclusion": false, + "textures": { + "rim": "block/cauldron_top", + "particle": "block/cauldron_side", + "outside": "block/cauldron_side", + "inside": "block/cauldron_inner", + "feet": "block/cauldron_bottom" + }, + "elements": [ + { + "from": [ 0, 3, 0 ], + "to": [ 16, 16, 16 ], + "faces": { + "north": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "north" }, + "east": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "east" }, + "south": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "west" }, + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#inside" } + } + }, + { + "from": [ 0, 16, 0 ], + "to": [ 14, 16, 2 ], + "faces": { + "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 14, 16, 0 ], + "to": [ 16, 16, 14 ], + "faces": { + "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 2, 16, 14 ], + "to": [ 16, 16, 16 ], + "faces": { + "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 0, 16, 2 ], + "to": [ 2, 16, 16 ], + "faces": { + "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 2, 4, 2 ], + "to": [ 14, 16, 2 ], + "faces": { + "south": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 14, 4, 2 ], + "to": [ 14, 16, 14 ], + "faces": { + "west": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 4, 14 ], + "to": [ 14, 16, 14 ], + "faces": { + "north": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 4, 2 ], + "to": [ 2, 16, 14 ], + "faces": { + "east": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 4, 2 ], + "to": [ 14, 4, 14 ], + "faces": { + "up": { "uv": [ 2, 2, 14, 14 ], "texture": "#inside" } + } + }, + { + "from": [ 0, 0, 0 ], + "to": [ 4, 3, 4 ], + "faces": { + "north": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "north" }, + "west": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "west" } + } + }, + { + "from": [ 0, 0, 12 ], + "to": [ 4, 3, 16 ], + "faces": { + "south": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "south" }, + "west": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "west" } + } + }, + { + "from": [ 12, 0, 12 ], + "to": [ 16, 3, 16 ], + "faces": { + "east": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "east" }, + "south": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "south" } + } + }, + { + "from": [ 12, 0, 0 ], + "to": [ 16, 3, 4 ], + "faces": { + "north": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "north" }, + "east": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "east" } + } + }, + { + "from": [ 14, 0, 2 ], + "to": [ 16, 3, 4 ], + "faces": { + "south": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "west": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "down": { "uv": [ 14, 12, 16, 14 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 2, 0, 0 ], + "to": [ 4, 3, 2 ], + "faces": { + "east": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "south": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "down": { "uv": [ 2, 14, 4, 16 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 0, 0, 12 ], + "to": [ 2, 3, 14 ], + "faces": { + "north": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "east": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "down": { "uv": [ 0, 2, 2, 4 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 12, 0, 14 ], + "to": [ 14, 3, 16 ], + "faces": { + "north": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "west": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "down": { "uv": [ 12, 0, 14, 2 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 12, 0, 0 ], + "to": [ 16, 0, 2 ], + "faces": { + "down": { "uv": [ 12, 14, 16, 16 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 0, 0, 0 ], + "to": [ 2, 0, 4 ], + "faces": { + "down": { "uv": [ 0, 12, 2, 16 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 0, 0, 14 ], + "to": [ 4, 0, 16 ], + "faces": { + "down": { "uv": [ 0, 0, 4, 2 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 14, 0, 12 ], + "to": [ 16, 0, 16 ], + "faces": { + "down": { "uv": [ 14, 0, 16, 4 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 12, 0, 0 ], + "to": [ 14, 3, 2 ], + "faces": { + "south": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, + "west": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + } + }, + { + "from": [ 0, 0, 2 ], + "to": [ 2, 3, 4 ], + "faces": { + "east": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, + "south": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 0, 14 ], + "to": [ 4, 3, 16 ], + "faces": { + "north": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, + "east": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + } + }, + { + "from": [ 14, 0, 12 ], + "to": [ 16, 3, 14 ], + "faces": { + "north": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" }, + "west": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" } + } + } + ] +} diff --git a/common/src/main/resources/assets/minecraft/models/block/template_cauldron_full.json b/common/src/main/resources/assets/minecraft/models/block/template_cauldron_full.json new file mode 100644 index 0000000000..8b090ec92a --- /dev/null +++ b/common/src/main/resources/assets/minecraft/models/block/template_cauldron_full.json @@ -0,0 +1,221 @@ +{ + "ambientocclusion": false, + "textures": { + "rim": "block/cauldron_top", + "particle": "block/cauldron_side", + "outside": "block/cauldron_side", + "inside": "block/cauldron_inner", + "feet": "block/cauldron_bottom" + }, + "elements": [ + { + "from": [ 0, 3, 0 ], + "to": [ 16, 16, 16 ], + "faces": { + "north": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "north" }, + "east": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "east" }, + "south": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "west" }, + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#inside" } + } + }, + { + "from": [ 0, 16, 0 ], + "to": [ 14, 16, 2 ], + "faces": { + "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 14, 16, 0 ], + "to": [ 16, 16, 14 ], + "faces": { + "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 2, 16, 14 ], + "to": [ 16, 16, 16 ], + "faces": { + "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 0, 16, 2 ], + "to": [ 2, 16, 16 ], + "faces": { + "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 2, 4, 2 ], + "to": [ 14, 16, 2 ], + "faces": { + "south": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 14, 4, 2 ], + "to": [ 14, 16, 14 ], + "faces": { + "west": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 4, 14 ], + "to": [ 14, 16, 14 ], + "faces": { + "north": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 4, 2 ], + "to": [ 2, 16, 14 ], + "faces": { + "east": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 4, 2 ], + "to": [ 14, 4, 14 ], + "faces": { + "up": { "uv": [ 2, 2, 14, 14 ], "texture": "#inside" } + } + }, + { + "from": [ 0, 0, 0 ], + "to": [ 4, 3, 4 ], + "faces": { + "north": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "north" }, + "west": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "west" } + } + }, + { + "from": [ 0, 0, 12 ], + "to": [ 4, 3, 16 ], + "faces": { + "south": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "south" }, + "west": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "west" } + } + }, + { + "from": [ 12, 0, 12 ], + "to": [ 16, 3, 16 ], + "faces": { + "east": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "east" }, + "south": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "south" } + } + }, + { + "from": [ 12, 0, 0 ], + "to": [ 16, 3, 4 ], + "faces": { + "north": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "north" }, + "east": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "east" } + } + }, + { + "from": [ 14, 0, 2 ], + "to": [ 16, 3, 4 ], + "faces": { + "south": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "west": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "down": { "uv": [ 14, 12, 16, 14 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 2, 0, 0 ], + "to": [ 4, 3, 2 ], + "faces": { + "east": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "south": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "down": { "uv": [ 2, 14, 4, 16 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 0, 0, 12 ], + "to": [ 2, 3, 14 ], + "faces": { + "north": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "east": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "down": { "uv": [ 0, 2, 2, 4 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 12, 0, 14 ], + "to": [ 14, 3, 16 ], + "faces": { + "north": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "west": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "down": { "uv": [ 12, 0, 14, 2 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 12, 0, 0 ], + "to": [ 16, 0, 2 ], + "faces": { + "down": { "uv": [ 12, 14, 16, 16 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 0, 0, 0 ], + "to": [ 2, 0, 4 ], + "faces": { + "down": { "uv": [ 0, 12, 2, 16 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 0, 0, 14 ], + "to": [ 4, 0, 16 ], + "faces": { + "down": { "uv": [ 0, 0, 4, 2 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 14, 0, 12 ], + "to": [ 16, 0, 16 ], + "faces": { + "down": { "uv": [ 14, 0, 16, 4 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 12, 0, 0 ], + "to": [ 14, 3, 2 ], + "faces": { + "south": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, + "west": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + } + }, + { + "from": [ 0, 0, 2 ], + "to": [ 2, 3, 4 ], + "faces": { + "east": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, + "south": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 0, 14 ], + "to": [ 4, 3, 16 ], + "faces": { + "north": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, + "east": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + } + }, + { + "from": [ 14, 0, 12 ], + "to": [ 16, 3, 14 ], + "faces": { + "north": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" }, + "west": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 15, 2 ], + "to": [ 14, 15, 14 ], + "faces": { + "up": { "uv": [ 2, 2, 14, 14 ], "texture": "#content", "tintindex": 0 } + } + } + ] +} diff --git a/common/src/main/resources/assets/minecraft/models/block/template_cauldron_level1.json b/common/src/main/resources/assets/minecraft/models/block/template_cauldron_level1.json new file mode 100644 index 0000000000..afea17757e --- /dev/null +++ b/common/src/main/resources/assets/minecraft/models/block/template_cauldron_level1.json @@ -0,0 +1,221 @@ +{ + "ambientocclusion": false, + "textures": { + "rim": "block/cauldron_top", + "particle": "block/cauldron_side", + "outside": "block/cauldron_side", + "inside": "block/cauldron_inner", + "feet": "block/cauldron_bottom" + }, + "elements": [ + { + "from": [ 0, 3, 0 ], + "to": [ 16, 16, 16 ], + "faces": { + "north": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "north" }, + "east": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "east" }, + "south": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "west" }, + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#inside" } + } + }, + { + "from": [ 0, 16, 0 ], + "to": [ 14, 16, 2 ], + "faces": { + "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 14, 16, 0 ], + "to": [ 16, 16, 14 ], + "faces": { + "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 2, 16, 14 ], + "to": [ 16, 16, 16 ], + "faces": { + "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 0, 16, 2 ], + "to": [ 2, 16, 16 ], + "faces": { + "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 2, 4, 2 ], + "to": [ 14, 16, 2 ], + "faces": { + "south": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 14, 4, 2 ], + "to": [ 14, 16, 14 ], + "faces": { + "west": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 4, 14 ], + "to": [ 14, 16, 14 ], + "faces": { + "north": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 4, 2 ], + "to": [ 2, 16, 14 ], + "faces": { + "east": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 4, 2 ], + "to": [ 14, 4, 14 ], + "faces": { + "up": { "uv": [ 2, 2, 14, 14 ], "texture": "#inside" } + } + }, + { + "from": [ 0, 0, 0 ], + "to": [ 4, 3, 4 ], + "faces": { + "north": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "north" }, + "west": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "west" } + } + }, + { + "from": [ 0, 0, 12 ], + "to": [ 4, 3, 16 ], + "faces": { + "south": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "south" }, + "west": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "west" } + } + }, + { + "from": [ 12, 0, 12 ], + "to": [ 16, 3, 16 ], + "faces": { + "east": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "east" }, + "south": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "south" } + } + }, + { + "from": [ 12, 0, 0 ], + "to": [ 16, 3, 4 ], + "faces": { + "north": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "north" }, + "east": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "east" } + } + }, + { + "from": [ 14, 0, 2 ], + "to": [ 16, 3, 4 ], + "faces": { + "south": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "west": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "down": { "uv": [ 14, 12, 16, 14 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 2, 0, 0 ], + "to": [ 4, 3, 2 ], + "faces": { + "east": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "south": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "down": { "uv": [ 2, 14, 4, 16 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 0, 0, 12 ], + "to": [ 2, 3, 14 ], + "faces": { + "north": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "east": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "down": { "uv": [ 0, 2, 2, 4 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 12, 0, 14 ], + "to": [ 14, 3, 16 ], + "faces": { + "north": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "west": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "down": { "uv": [ 12, 0, 14, 2 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 12, 0, 0 ], + "to": [ 16, 0, 2 ], + "faces": { + "down": { "uv": [ 12, 14, 16, 16 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 0, 0, 0 ], + "to": [ 2, 0, 4 ], + "faces": { + "down": { "uv": [ 0, 12, 2, 16 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 0, 0, 14 ], + "to": [ 4, 0, 16 ], + "faces": { + "down": { "uv": [ 0, 0, 4, 2 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 14, 0, 12 ], + "to": [ 16, 0, 16 ], + "faces": { + "down": { "uv": [ 14, 0, 16, 4 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 12, 0, 0 ], + "to": [ 14, 3, 2 ], + "faces": { + "south": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, + "west": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + } + }, + { + "from": [ 0, 0, 2 ], + "to": [ 2, 3, 4 ], + "faces": { + "east": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, + "south": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 0, 14 ], + "to": [ 4, 3, 16 ], + "faces": { + "north": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, + "east": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + } + }, + { + "from": [ 14, 0, 12 ], + "to": [ 16, 3, 14 ], + "faces": { + "north": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" }, + "west": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 9, 2 ], + "to": [ 14, 9, 14 ], + "faces": { + "up": { "uv": [ 2, 2, 14, 14 ], "texture": "#content", "tintindex": 0 } + } + } + ] +} diff --git a/common/src/main/resources/assets/minecraft/models/block/template_cauldron_level2.json b/common/src/main/resources/assets/minecraft/models/block/template_cauldron_level2.json new file mode 100644 index 0000000000..63ee8c7596 --- /dev/null +++ b/common/src/main/resources/assets/minecraft/models/block/template_cauldron_level2.json @@ -0,0 +1,221 @@ +{ + "ambientocclusion": false, + "textures": { + "rim": "block/cauldron_top", + "particle": "block/cauldron_side", + "outside": "block/cauldron_side", + "inside": "block/cauldron_inner", + "feet": "block/cauldron_bottom" + }, + "elements": [ + { + "from": [ 0, 3, 0 ], + "to": [ 16, 16, 16 ], + "faces": { + "north": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "north" }, + "east": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "east" }, + "south": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "west" }, + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#inside" } + } + }, + { + "from": [ 0, 16, 0 ], + "to": [ 14, 16, 2 ], + "faces": { + "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 14, 16, 0 ], + "to": [ 16, 16, 14 ], + "faces": { + "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 2, 16, 14 ], + "to": [ 16, 16, 16 ], + "faces": { + "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 0, 16, 2 ], + "to": [ 2, 16, 16 ], + "faces": { + "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#rim", "cullface": "up" } + } + }, + { + "from": [ 2, 4, 2 ], + "to": [ 14, 16, 2 ], + "faces": { + "south": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 14, 4, 2 ], + "to": [ 14, 16, 14 ], + "faces": { + "west": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 4, 14 ], + "to": [ 14, 16, 14 ], + "faces": { + "north": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 4, 2 ], + "to": [ 2, 16, 14 ], + "faces": { + "east": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 4, 2 ], + "to": [ 14, 4, 14 ], + "faces": { + "up": { "uv": [ 2, 2, 14, 14 ], "texture": "#inside" } + } + }, + { + "from": [ 0, 0, 0 ], + "to": [ 4, 3, 4 ], + "faces": { + "north": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "north" }, + "west": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "west" } + } + }, + { + "from": [ 0, 0, 12 ], + "to": [ 4, 3, 16 ], + "faces": { + "south": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "south" }, + "west": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "west" } + } + }, + { + "from": [ 12, 0, 12 ], + "to": [ 16, 3, 16 ], + "faces": { + "east": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "east" }, + "south": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "south" } + } + }, + { + "from": [ 12, 0, 0 ], + "to": [ 16, 3, 4 ], + "faces": { + "north": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "north" }, + "east": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "east" } + } + }, + { + "from": [ 14, 0, 2 ], + "to": [ 16, 3, 4 ], + "faces": { + "south": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "west": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "down": { "uv": [ 14, 12, 16, 14 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 2, 0, 0 ], + "to": [ 4, 3, 2 ], + "faces": { + "east": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "south": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "down": { "uv": [ 2, 14, 4, 16 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 0, 0, 12 ], + "to": [ 2, 3, 14 ], + "faces": { + "north": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "east": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "down": { "uv": [ 0, 2, 2, 4 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 12, 0, 14 ], + "to": [ 14, 3, 16 ], + "faces": { + "north": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, + "west": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, + "down": { "uv": [ 12, 0, 14, 2 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 12, 0, 0 ], + "to": [ 16, 0, 2 ], + "faces": { + "down": { "uv": [ 12, 14, 16, 16 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 0, 0, 0 ], + "to": [ 2, 0, 4 ], + "faces": { + "down": { "uv": [ 0, 12, 2, 16 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 0, 0, 14 ], + "to": [ 4, 0, 16 ], + "faces": { + "down": { "uv": [ 0, 0, 4, 2 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 14, 0, 12 ], + "to": [ 16, 0, 16 ], + "faces": { + "down": { "uv": [ 14, 0, 16, 4 ], "texture": "#feet", "cullface": "down" } + } + }, + { + "from": [ 12, 0, 0 ], + "to": [ 14, 3, 2 ], + "faces": { + "south": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, + "west": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + } + }, + { + "from": [ 0, 0, 2 ], + "to": [ 2, 3, 4 ], + "faces": { + "east": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, + "south": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 0, 14 ], + "to": [ 4, 3, 16 ], + "faces": { + "north": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, + "east": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + } + }, + { + "from": [ 14, 0, 12 ], + "to": [ 16, 3, 14 ], + "faces": { + "north": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" }, + "west": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" } + } + }, + { + "from": [ 2, 12, 2 ], + "to": [ 14, 12, 14 ], + "faces": { + "up": { "uv": [ 2, 2, 14, 14 ], "texture": "#content", "tintindex": 0 } + } + } + ] +} From 783f8d847db97dc4122395dcbfd487b56e01f2f3 Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 2 Dec 2024 02:05:50 +0100 Subject: [PATCH 017/215] Avoid marking the section graph as dirty if state didn't change (#2886) Avoids rebuilding the render lists and doing a graph search more often than necessary by checking if the section actually changed in a way that's relevant to the graph search. For worlds that update their blocks frequently (every tick or every redstone tick) this avoids half the graph searches. Some graph searches are still necessary to schedule rebuild tasks, but when the task results come back, this doesn't do another graph search unless the section's visibility data or build state changed in a way that needs the render list to be updated. --- .../client/render/chunk/RenderSection.java | 24 +++++++++++++++---- .../render/chunk/RenderSectionManager.java | 16 +++++++------ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java index 3ffa67f27e..9aa933474c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java @@ -139,30 +139,44 @@ public void delete() { this.disposed = true; } - public void setInfo(@Nullable BuiltSectionInfo info) { + public boolean setInfo(@Nullable BuiltSectionInfo info) { if (info != null) { - this.setRenderState(info); + return this.setRenderState(info); } else { - this.clearRenderState(); + return this.clearRenderState(); } } - private void setRenderState(@NotNull BuiltSectionInfo info) { + private boolean setRenderState(@NotNull BuiltSectionInfo info) { + var prevBuilt = this.built; + var prevFlags = this.flags; + var prevVisibilityData = this.visibilityData; + this.built = true; this.flags = info.flags; this.visibilityData = info.visibilityData; + this.globalBlockEntities = info.globalBlockEntities; this.culledBlockEntities = info.culledBlockEntities; this.animatedSprites = info.animatedSprites; + + // the section is marked as having received graph-relevant changes if it's build state, flags, or connectedness has changed. + // the entities and sprites don't need to be checked since whether they exist is encoded in the flags. + return !prevBuilt || prevFlags != this.flags || prevVisibilityData != this.visibilityData; } - private void clearRenderState() { + private boolean clearRenderState() { + var wasBuilt = this.built; + this.built = false; this.flags = RenderSectionFlags.NONE; this.visibilityData = VisibilityEncoding.NULL; this.globalBlockEntities = null; this.culledBlockEntities = null; this.animatedSprites = null; + + // changes to data if it moves from built to not built don't matter, so only build state changes matter + return wasBuilt; } /** diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index 7bd65792ef..d6564985b9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -209,6 +209,7 @@ public void onSectionAdded(int x, int y, int z) { this.connectNeighborNodes(renderSection); + // force update to schedule build task this.needsGraphUpdate = true; } @@ -235,6 +236,7 @@ public void onSectionRemoved(int x, int y, int z) { section.delete(); + // force update to remove section from render lists this.needsGraphUpdate = true; } @@ -301,7 +303,7 @@ public void uploadChunks() { // (sort results never change the graph) // generally there's no sort results without a camera movement, which would also trigger // a graph update, but it can sometimes happen because of async task execution - this.needsGraphUpdate = this.needsGraphUpdate || this.processChunkBuildResults(results); + this.needsGraphUpdate |= this.processChunkBuildResults(results); for (var result : results) { result.destroy(); @@ -317,8 +319,7 @@ private boolean processChunkBuildResults(ArrayList results) { for (var result : filtered) { TranslucentData oldData = result.render.getTranslucentData(); if (result instanceof ChunkBuildOutput chunkBuildOutput) { - this.updateSectionInfo(result.render, chunkBuildOutput.info); - touchedSectionInfo = true; + touchedSectionInfo |= this.updateSectionInfo(result.render, chunkBuildOutput.info); if (chunkBuildOutput.translucentData != null) { this.sortTriggering.integrateTranslucentData(oldData, chunkBuildOutput.translucentData, this.cameraPosition, this::scheduleSort); @@ -346,13 +347,13 @@ private boolean processChunkBuildResults(ArrayList results) { return touchedSectionInfo; } - private void updateSectionInfo(RenderSection render, BuiltSectionInfo info) { - render.setInfo(info); + private boolean updateSectionInfo(RenderSection render, BuiltSectionInfo info) { + var infoChanged = render.setInfo(info); if (info == null || ArrayUtils.isEmpty(info.globalBlockEntities)) { - this.sectionsWithGlobalEntities.remove(render); + return this.sectionsWithGlobalEntities.remove(render) || infoChanged; } else { - this.sectionsWithGlobalEntities.add(render); + return this.sectionsWithGlobalEntities.add(render) || infoChanged; } } @@ -609,6 +610,7 @@ public void scheduleRebuild(int x, int y, int z, boolean important) { if (pendingUpdate != null) { section.setPendingUpdate(pendingUpdate); + // force update to schedule rebuild task on this section this.needsGraphUpdate = true; } } From 62e3cf3fd1ce3d1163f540cdcfddb9c39844e9be Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 2 Dec 2024 17:01:01 +0100 Subject: [PATCH 018/215] Use larger bounding box for nearby sections in frustum check (#2879) This fixes some problems where very large block entities in nearby sections may be incorrectly culled. But it does not comprehensively fix the problem for all other sections, since that would require visiting the 27-neighborhood of every section, which is too slow. --- .../chunk/occlusion/OcclusionCuller.java | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java index e9714a21e1..e2cd453b2c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java @@ -37,6 +37,8 @@ public void findVisible(Visitor visitor, while (queues.flip()) { processQueue(visitor, viewport, searchDistance, useOcclusionCulling, frame, queues.read(), queues.write()); } + + this.addNearbySections(visitor, viewport, searchDistance, frame); } private static void processQueue(Visitor visitor, @@ -208,13 +210,54 @@ private static int nearestToZero(int min, int max) { // The bounding box of a chunk section must be large enough to contain all possible geometry within it. Block models // can extend outside a block volume by +/- 1.0 blocks on all axis. Additionally, we make use of a small epsilon // to deal with floating point imprecision during a frustum check (see GH#2132). - private static final float CHUNK_SECTION_SIZE = 8.0f /* chunk bounds */ + 1.0f /* maximum model extent */ + 0.125f /* epsilon */; + private static final float CHUNK_SECTION_RADIUS = 8.0f /* chunk bounds */; + private static final float CHUNK_SECTION_SIZE = CHUNK_SECTION_RADIUS + 1.0f /* maximum model extent */ + 0.125f /* epsilon */; public static boolean isWithinFrustum(Viewport viewport, RenderSection section) { return viewport.isBoxVisible(section.getCenterX(), section.getCenterY(), section.getCenterZ(), CHUNK_SECTION_SIZE, CHUNK_SECTION_SIZE, CHUNK_SECTION_SIZE); } + // this bigger chunk section size is only used for frustum-testing nearby sections with large models + private static final float CHUNK_SECTION_SIZE_NEARBY = CHUNK_SECTION_RADIUS + 2.0f /* bigger model extent */ + 0.125f /* epsilon */; + + public static boolean isWithinNearbySectionFrustum(Viewport viewport, RenderSection section) { + return viewport.isBoxVisible(section.getCenterX(), section.getCenterY(), section.getCenterZ(), + CHUNK_SECTION_SIZE_NEARBY, CHUNK_SECTION_SIZE_NEARBY, CHUNK_SECTION_SIZE_NEARBY); + } + + // This method visits sections near the origin that are not in the path of the graph traversal + // but have bounding boxes that may intersect with the frustum. It does this additional check + // for all neighboring, even diagonally neighboring, sections around the origin to render them + // if their extended bounding box is visible, and they may render large models that extend + // outside the 16x16x16 base volume of the section. + private void addNearbySections(Visitor visitor, Viewport viewport, float searchDistance, int frame) { + var origin = viewport.getChunkCoord(); + var originX = origin.getX(); + var originY = origin.getY(); + var originZ = origin.getZ(); + + for (var dx = -1; dx <= 1; dx++) { + for (var dy = -1; dy <= 1; dy++) { + for (var dz = -1; dz <= 1; dz++) { + if (dx == 0 && dy == 0 && dz == 0) { + continue; + } + + var section = this.getRenderSection(originX + dx, originY + dy, originZ + dz); + + // additionally render not yet visited but visible sections + if (section != null && section.getLastVisibleFrame() != frame && isWithinNearbySectionFrustum(viewport, section)) { + // reset state on first visit, but don't enqueue + section.setLastVisibleFrame(frame); + + visitor.visit(section); + } + } + } + } + } + private void init(Visitor visitor, WriteQueue queue, Viewport viewport, From 0ca071eb45d700e7102cb44b7e26f1964bf3d139 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Mon, 2 Dec 2024 14:16:16 -0600 Subject: [PATCH 019/215] Bump version to 0.6.1 --- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 6a589c2812..139d96d7bf 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -11,7 +11,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = null // https://semver.org/ - var MOD_VERSION: String = "0.6.0" + var MOD_VERSION: String = "0.6.1" fun createVersionString(project: Project): String { val builder = StringBuilder() From e6ccbe8d500f3b4bb8902d8dffe7f45252ff9300 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Mon, 2 Dec 2024 14:25:41 -0600 Subject: [PATCH 020/215] Bump dependency versions --- buildSrc/src/main/kotlin/BuildConfig.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 139d96d7bf..b675e25483 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -2,9 +2,9 @@ import org.gradle.api.Project object BuildConfig { val MINECRAFT_VERSION: String = "1.21.1" - val NEOFORGE_VERSION: String = "21.1.46" - val FABRIC_LOADER_VERSION: String = "0.16.4" - val FABRIC_API_VERSION: String = "0.103.0+1.21.1" + val NEOFORGE_VERSION: String = "21.1.83" + val FABRIC_LOADER_VERSION: String = "0.16.9" + val FABRIC_API_VERSION: String = "0.110.0+1.21.1" // This value can be set to null to disable Parchment. // TODO: Re-add Parchment From 3cd3016cf64716a491ba4645974b0069e0b8e71c Mon Sep 17 00:00:00 2001 From: JellySquid Date: Mon, 2 Dec 2024 22:54:35 -0600 Subject: [PATCH 021/215] Update mod manifest --- fabric/src/main/resources/fabric.mod.json | 2 +- .../src/main/resources/META-INF/neoforge.mods.toml | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index b2f72d02bb..30f2fbc748 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -72,7 +72,7 @@ ], "depends" : { "minecraft" : ["1.21", "1.21.1"], - "fabricloader" : ">=0.12.0", + "fabricloader" : ">=0.16.0", "fabric-block-view-api-v2" : "*", "fabric-renderer-api-v1" : "*", "fabric-rendering-data-attachment-v1" : "*", diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/main/resources/META-INF/neoforge.mods.toml index 5e1a6b3f2e..3d2869ec81 100644 --- a/neoforge/src/main/resources/META-INF/neoforge.mods.toml +++ b/neoforge/src/main/resources/META-INF/neoforge.mods.toml @@ -1,5 +1,5 @@ modLoader = "javafml" -loaderVersion = "*" +loaderVersion = "[4,)" license = "Polyform-Shield-1.0.0" [[mods]] @@ -26,7 +26,14 @@ provides = ["indium"] [[dependencies.sodium]] modId = "minecraft" type = "required" -versionRange = "[1.21,1.21.1]" +versionRange = "1.21.1" +ordering = "NONE" +side = "CLIENT" + +[[dependencies.sodium]] +modId = "neoforge" +type = "required" +versionRange = "[21.1.82,)" ordering = "NONE" side = "CLIENT" From 4a125ecc0241462f69c7000d1130f2b68ce0e8d3 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Tue, 3 Dec 2024 00:27:21 -0600 Subject: [PATCH 022/215] Update compatible mods listing --- fabric/src/main/resources/fabric.mod.json | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 30f2fbc748..b80dfa1aa9 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -83,23 +83,26 @@ "embeddium": "*", "optifabric" : "*", "canvas" : "*", + "vulkanmod": "*", "sodium-blendingregistry" : "*", "ocrenderfix_sodium" : "*", "betterfpsdist" : "<=1.21-4.5", - "bobby" : "<=5.2.3", - "chunksfadein" : "<=1.0.1-1.21", + "bobby" : "<5.2.4", + "chunksfadein" : "<2.0.2-1.21.2", "cull-less-leaves" : "<=1.3.0", "cullleaves" : "<=3.4.0-fabric", "custom_hud" : "<3.4.2", "farsight" : "<=1.21-4.3", - "iceberg" : "<1.2.4", - "iris" : "<=1.7.3", + "iceberg" : "<1.2.7", + "iris" : "<1.8.1", "movingelevators" : "<=1.4.7", "notenoughcrashes" : "<4.4.8", - "noxesium" : "<2.1.4", - "reeses-sodium-options" : "<=1.7.3", - "sodium-extra" : "<=0.5.7", - "sspb" : "<=3.3.1" + "noxesium" : "<2.3.3", + "reeses-sodium-options" : "<1.8.0", + "sodium-extra" : "<0.6.0", + "sspb" : "<4.0.0", + "moreculling": "<1.0.0", + "simply-no-shading": "<7.6.2" }, "provides" : [ "indium" From 63ebaef39a49f58d15903833c4207f2039b30b8d Mon Sep 17 00:00:00 2001 From: IMS212 Date: Mon, 25 Nov 2024 11:01:38 -0600 Subject: [PATCH 023/215] Add support for new NeoForge fluid overlay API --- .../compile/pipeline/DefaultFluidRenderer.java | 16 ++++++++-------- .../client/services/PlatformBlockAccess.java | 9 +++++++++ .../sodium/fabric/block/FabricBlockAccess.java | 5 +++++ .../neoforge/block/NeoForgeBlockAccess.java | 5 +++++ 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java index b49cde9421..3ab477de79 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java @@ -65,7 +65,7 @@ public DefaultFluidRenderer(LightPipelineProvider lighters) { this.lighters = lighters; } - private boolean isFluidOccluded(BlockAndTintGetter world, int x, int y, int z, Direction dir, BlockState blockState, Fluid fluid) { + private boolean isFluidOccluded(BlockAndTintGetter world, int x, int y, int z, Direction dir, BlockState blockState, FluidState fluid) { //Test own block state first, this prevents waterlogged blocks from having hidden internal geometry // which can result in z-fighting var pos = this.scratchPos.set(x, y, z); @@ -76,7 +76,7 @@ private boolean isFluidOccluded(BlockAndTintGetter world, int x, int y, int z, D //Test neighboring block state var adjPos = this.scratchPos.set(x + dir.getStepX(), y + dir.getStepY(), z + dir.getStepZ()); BlockState adjBlockState = world.getBlockState(adjPos); - if (adjBlockState.getFluidState().getType().isSame(fluid)) { + if (PlatformBlockAccess.getInstance().shouldOccludeFluid(dir.getOpposite(), adjBlockState, fluid)) { return true; } return adjBlockState.canOcclude() && dir != Direction.UP && adjBlockState.isFaceSturdy(world, adjPos, dir.getOpposite(), SupportType.FULL); @@ -109,13 +109,13 @@ public void render(LevelSlice level, BlockState blockState, FluidState fluidStat Fluid fluid = fluidState.getType(); - boolean sfUp = this.isFluidOccluded(level, posX, posY, posZ, Direction.UP, blockState, fluid); - boolean sfDown = this.isFluidOccluded(level, posX, posY, posZ, Direction.DOWN, blockState, fluid) || + boolean sfUp = this.isFluidOccluded(level, posX, posY, posZ, Direction.UP, blockState, fluidState); + boolean sfDown = this.isFluidOccluded(level, posX, posY, posZ, Direction.DOWN, blockState, fluidState) || !this.isSideExposed(level, posX, posY, posZ, Direction.DOWN, 0.8888889F); - boolean sfNorth = this.isFluidOccluded(level, posX, posY, posZ, Direction.NORTH, blockState, fluid); - boolean sfSouth = this.isFluidOccluded(level, posX, posY, posZ, Direction.SOUTH, blockState, fluid); - boolean sfWest = this.isFluidOccluded(level, posX, posY, posZ, Direction.WEST, blockState, fluid); - boolean sfEast = this.isFluidOccluded(level, posX, posY, posZ, Direction.EAST, blockState, fluid); + boolean sfNorth = this.isFluidOccluded(level, posX, posY, posZ, Direction.NORTH, blockState, fluidState); + boolean sfSouth = this.isFluidOccluded(level, posX, posY, posZ, Direction.SOUTH, blockState, fluidState); + boolean sfWest = this.isFluidOccluded(level, posX, posY, posZ, Direction.WEST, blockState, fluidState); + boolean sfEast = this.isFluidOccluded(level, posX, posY, posZ, Direction.EAST, blockState, fluidState); if (sfUp && sfDown && sfEast && sfWest && sfNorth && sfSouth) { return; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformBlockAccess.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformBlockAccess.java index 7679b20dc3..a124dcabb7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformBlockAccess.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformBlockAccess.java @@ -85,4 +85,13 @@ static PlatformBlockAccess getInstance() { * @return Whether this block entity should activate the outline shader. */ boolean shouldBlockEntityGlow(BlockEntity blockEntity, LocalPlayer player); + + /** + * Determines if a fluid adjacent to the block on the given side should not be rendered. + * + * @param adjDirection the face of this block that the fluid is adjacent to + * @param fluid the fluid that is touching that face + * @return if this block should cause the fluid's face to not render + */ + boolean shouldOccludeFluid(Direction adjDirection, BlockState adjBlockState, FluidState fluid); } diff --git a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/block/FabricBlockAccess.java b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/block/FabricBlockAccess.java index 29069822a4..0fd7c13935 100644 --- a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/block/FabricBlockAccess.java +++ b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/block/FabricBlockAccess.java @@ -90,4 +90,9 @@ public AmbientOcclusionMode usesAmbientOcclusion(BakedModel model, BlockState st public boolean shouldBlockEntityGlow(BlockEntity blockEntity, LocalPlayer player) { return false; } + + @Override + public boolean shouldOccludeFluid(Direction adjDirection, BlockState adjBlockState, FluidState fluid) { + return adjBlockState.getFluidState().getType().isSame(fluid.getType()); + } } diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/block/NeoForgeBlockAccess.java b/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/block/NeoForgeBlockAccess.java index 978c2e8fe2..3712c8217e 100644 --- a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/block/NeoForgeBlockAccess.java +++ b/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/block/NeoForgeBlockAccess.java @@ -58,4 +58,9 @@ public AmbientOcclusionMode usesAmbientOcclusion(BakedModel model, BlockState st public boolean shouldBlockEntityGlow(BlockEntity blockEntity, LocalPlayer player) { return blockEntity.hasCustomOutlineRendering(player); } + + @Override + public boolean shouldOccludeFluid(Direction adjDirection, BlockState adjBlockState, FluidState fluid) { + return adjBlockState.shouldHideAdjacentFluidFace(adjDirection, fluid); + } } From af9b55326d3f87333d1b7632c440ddb5449d510e Mon Sep 17 00:00:00 2001 From: JellySquid Date: Tue, 3 Dec 2024 15:15:14 -0600 Subject: [PATCH 024/215] Bump version to 0.6.2 --- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index b675e25483..ae978f82aa 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -11,7 +11,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = null // https://semver.org/ - var MOD_VERSION: String = "0.6.1" + var MOD_VERSION: String = "0.6.2" fun createVersionString(project: Project): String { val builder = StringBuilder() From 3f5d853f271452d30c5b87dd1f78f4c1c2c30520 Mon Sep 17 00:00:00 2001 From: douira Date: Wed, 4 Dec 2024 10:49:59 -0600 Subject: [PATCH 025/215] Fix hidden surface elimination in fluid rendering for waterlogged blocks (#2907) Co-authored-by: JellySquid --- .../compile/pipeline/BlockOcclusionCache.java | 92 ++++++++++++++++++- .../pipeline/DefaultFluidRenderer.java | 53 +++++------ 2 files changed, 112 insertions(+), 33 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockOcclusionCache.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockOcclusionCache.java index 294b2b96e4..0627f02a9c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockOcclusionCache.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/BlockOcclusionCache.java @@ -8,6 +8,7 @@ import net.minecraft.core.Direction; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; import net.minecraft.world.phys.shapes.BooleanOp; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; @@ -45,7 +46,8 @@ public boolean shouldDrawSide(BlockState selfState, BlockGetter view, BlockPos s // Blocks can define special behavior to control whether faces are rendered. // This is mostly used by transparent blocks (Leaves, Glass, etc.) to not render interior faces between blocks // of the same type. - if (selfState.skipRendering(otherState, facing) || PlatformBlockAccess.getInstance().shouldSkipRender(view, selfState, otherState, selfPos, otherPos, facing)) { + if (selfState.skipRendering(otherState, facing) || + PlatformBlockAccess.getInstance().shouldSkipRender(view, selfState, otherState, selfPos, otherPos, facing)) { return false; } @@ -79,6 +81,86 @@ public boolean shouldDrawSide(BlockState selfState, BlockGetter view, BlockPos s return this.lookup(selfShape, otherShape); } + /** + * Checks if a face of a fluid block should be rendered. It takes into account both occluding fluid face against + * its own waterlogged block state and the neighboring block state. This is an approximation that doesn't check + * voxel for shapes between the fluid and the neighboring block since that is handled by the fluid renderer + * separately and more accurately using actual fluid heights. It only uses voxel shape comparison for checking + * self-occlusion with the waterlogged block state. + * + * @param selfBlockState The state of the block in the level + * @param view The block view for this render context + * @param selfPos The position of the fluid + * @param facing The facing direction of the side to check + * @param fluid The fluid state + * @param fluidShape The non-empty shape of the fluid + * @return True if the fluid side facing {@param dir} is not occluded, otherwise false + */ + public boolean shouldDrawFullBlockFluidSide( + BlockState selfBlockState, + BlockGetter view, + BlockPos selfPos, + Direction facing, + FluidState fluid, + VoxelShape fluidShape) + { + var fluidShapeIsBlock = fluidShape == Shapes.block(); + + // only perform self-occlusion if the own block state can't occlude + if (selfBlockState.canOcclude()) { + var selfShape = selfBlockState.getFaceOcclusionShape(view, selfPos, facing); + + // only a non-empty self-shape can occlude anything + if (!selfShape.isEmpty()) { + // a full self-shape occludes everything + if (selfShape == Shapes.block() && fluidShapeIsBlock) { + return false; + } + + // perform occlusion of the fluid by the block it's contained in + if (!this.lookup(fluidShape, selfShape)) { + return false; + } + } + } + + // perform occlusion against the neighboring block + BlockPos.MutableBlockPos otherPos = this.cachedPositionObject; + otherPos.set(selfPos.getX() + facing.getStepX(), selfPos.getY() + facing.getStepY(), selfPos.getZ() + facing.getStepZ()); + BlockState otherState = view.getBlockState(otherPos); + + // don't render anything if the other blocks is the same fluid + if (otherState.getFluidState() == fluid) { + return false; + } + + // check for special fluid occlusion behavior + if (PlatformBlockAccess.getInstance().shouldOccludeFluid(facing.getOpposite(), otherState, fluid)) { + return false; + } + + // the up direction doesn't do occlusion with other block shapes + if (facing == Direction.UP) { + return true; + } + + // only occlude against blocks that can potentially occlude in the first place + if (!otherState.canOcclude()) { + return true; + } + + var otherShape = otherState.getFaceOcclusionShape(view, otherPos, facing.getOpposite()); + + // If the other block has an empty cull shape, then it cannot hide any geometry + if (otherShape.isEmpty()) { + return true; + } + + // If both blocks use a full-cube cull shape, then they will always hide the faces between each other. + // No voxel shape comparison is done after this point because it's redundant with the later more accurate check. + return otherShape != Shapes.block() || !fluidShapeIsBlock; + } + private boolean lookup(VoxelShape self, VoxelShape other) { ShapeComparison comparison = this.cachedComparisonObject; comparison.self = self; @@ -106,6 +188,14 @@ private boolean calculate(ShapeComparison comparison) { return result; } + private static boolean isFullShape(VoxelShape selfShape) { + return selfShape == Shapes.block(); + } + + private static boolean isEmptyShape(VoxelShape voxelShape) { + return voxelShape == Shapes.empty() || voxelShape.isEmpty(); + } + private static final class ShapeComparison { private VoxelShape self, other; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java index 3ab477de79..6b8ddea15b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java @@ -4,7 +4,6 @@ import net.caffeinemc.mods.sodium.api.util.ColorARGB; import net.caffeinemc.mods.sodium.api.util.NormI8; import net.caffeinemc.mods.sodium.client.model.color.ColorProvider; -import net.caffeinemc.mods.sodium.client.model.color.ColorProviderRegistry; import net.caffeinemc.mods.sodium.client.model.light.LightMode; import net.caffeinemc.mods.sodium.client.model.light.LightPipeline; import net.caffeinemc.mods.sodium.client.model.light.LightPipelineProvider; @@ -28,8 +27,6 @@ import net.minecraft.tags.FluidTags; import net.minecraft.util.Mth; import net.minecraft.world.level.BlockAndTintGetter; -import net.minecraft.world.level.block.LiquidBlock; -import net.minecraft.world.level.block.SupportType; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.Fluid; import net.minecraft.world.level.material.FluidState; @@ -49,6 +46,8 @@ public class DefaultFluidRenderer { private final MutableFloat scratchHeight = new MutableFloat(0); private final MutableInt scratchSamples = new MutableInt(); + private final BlockOcclusionCache occlusionCache = new BlockOcclusionCache(); + private final ModelQuadViewMutable quad = new ModelQuad(); private final LightPipelineProvider lighters; @@ -65,21 +64,10 @@ public DefaultFluidRenderer(LightPipelineProvider lighters) { this.lighters = lighters; } - private boolean isFluidOccluded(BlockAndTintGetter world, int x, int y, int z, Direction dir, BlockState blockState, FluidState fluid) { - //Test own block state first, this prevents waterlogged blocks from having hidden internal geometry - // which can result in z-fighting - var pos = this.scratchPos.set(x, y, z); - if (blockState.canOcclude() && blockState.isFaceSturdy(world, pos, dir, SupportType.FULL)) { - return true; - } - - //Test neighboring block state - var adjPos = this.scratchPos.set(x + dir.getStepX(), y + dir.getStepY(), z + dir.getStepZ()); - BlockState adjBlockState = world.getBlockState(adjPos); - if (PlatformBlockAccess.getInstance().shouldOccludeFluid(dir.getOpposite(), adjBlockState, fluid)) { - return true; - } - return adjBlockState.canOcclude() && dir != Direction.UP && adjBlockState.isFaceSturdy(world, adjPos, dir.getOpposite(), SupportType.FULL); + private boolean isFullBlockFluidOccluded(BlockAndTintGetter world, BlockPos pos, Direction dir, BlockState blockState, FluidState fluid) { + // check if this face of the fluid, assuming a full-block cull shape, is occluded by the block it's in or a neighboring block. + // it doesn't do a voxel shape comparison with the neighboring blocks since that is already done by isSideExposed + return !this.occlusionCache.shouldDrawFullBlockFluidSide(blockState, world, pos, dir, fluid, Shapes.block()); } private boolean isSideExposed(BlockAndTintGetter world, int x, int y, int z, Direction dir, float height) { @@ -109,15 +97,16 @@ public void render(LevelSlice level, BlockState blockState, FluidState fluidStat Fluid fluid = fluidState.getType(); - boolean sfUp = this.isFluidOccluded(level, posX, posY, posZ, Direction.UP, blockState, fluidState); - boolean sfDown = this.isFluidOccluded(level, posX, posY, posZ, Direction.DOWN, blockState, fluidState) || + boolean cullUp = this.isFullBlockFluidOccluded(level, blockPos, Direction.UP, blockState, fluidState); + boolean cullDown = this.isFullBlockFluidOccluded(level, blockPos, Direction.DOWN, blockState, fluidState) || !this.isSideExposed(level, posX, posY, posZ, Direction.DOWN, 0.8888889F); - boolean sfNorth = this.isFluidOccluded(level, posX, posY, posZ, Direction.NORTH, blockState, fluidState); - boolean sfSouth = this.isFluidOccluded(level, posX, posY, posZ, Direction.SOUTH, blockState, fluidState); - boolean sfWest = this.isFluidOccluded(level, posX, posY, posZ, Direction.WEST, blockState, fluidState); - boolean sfEast = this.isFluidOccluded(level, posX, posY, posZ, Direction.EAST, blockState, fluidState); + boolean cullNorth = this.isFullBlockFluidOccluded(level, blockPos, Direction.NORTH, blockState, fluidState); + boolean cullSouth = this.isFullBlockFluidOccluded(level, blockPos, Direction.SOUTH, blockState, fluidState); + boolean cullWest = this.isFullBlockFluidOccluded(level, blockPos, Direction.WEST, blockState, fluidState); + boolean cullEast = this.isFullBlockFluidOccluded(level, blockPos, Direction.EAST, blockState, fluidState); - if (sfUp && sfDown && sfEast && sfWest && sfNorth && sfSouth) { + // stop rendering if all faces of the fluid are occluded + if (cullUp && cullDown && cullEast && cullWest && cullNorth && cullSouth) { return; } @@ -149,7 +138,7 @@ public void render(LevelSlice level, BlockState blockState, FluidState fluidStat .move(Direction.NORTH) .move(Direction.EAST)); } - float yOffset = sfDown ? 0.0F : EPSILON; + float yOffset = cullDown ? 0.0F : EPSILON; final ModelQuadViewMutable quad = this.quad; @@ -158,7 +147,7 @@ public void render(LevelSlice level, BlockState blockState, FluidState fluidStat quad.setFlags(0); - if (!sfUp && this.isSideExposed(level, posX, posY, posZ, Direction.UP, Math.min(Math.min(northWestHeight, southWestHeight), Math.min(southEastHeight, northEastHeight)))) { + if (!cullUp && this.isSideExposed(level, posX, posY, posZ, Direction.UP, Math.min(Math.min(northWestHeight, southWestHeight), Math.min(southEastHeight, northEastHeight)))) { northWestHeight -= EPSILON; southWestHeight -= EPSILON; southEastHeight -= EPSILON; @@ -243,7 +232,7 @@ && isAlignedEquals(southEastHeight, southWestHeight) } } - if (!sfDown) { + if (!cullDown) { TextureAtlasSprite sprite = sprites[0]; float minU = sprite.getU0(); @@ -273,7 +262,7 @@ && isAlignedEquals(southEastHeight, southWestHeight) switch (dir) { case NORTH -> { - if (sfNorth) { + if (cullNorth) { continue; } c1 = northWestHeight; @@ -284,7 +273,7 @@ && isAlignedEquals(southEastHeight, southWestHeight) z2 = z1; } case SOUTH -> { - if (sfSouth) { + if (cullSouth) { continue; } c1 = southEastHeight; @@ -295,7 +284,7 @@ && isAlignedEquals(southEastHeight, southWestHeight) z2 = z1; } case WEST -> { - if (sfWest) { + if (cullWest) { continue; } c1 = southWestHeight; @@ -306,7 +295,7 @@ && isAlignedEquals(southEastHeight, southWestHeight) z2 = 0.0f; } case EAST -> { - if (sfEast) { + if (cullEast) { continue; } c1 = northEastHeight; From 57e1ccb7227d23ddde59e82634b4d31ee6e25b28 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Wed, 4 Dec 2024 14:12:04 -0600 Subject: [PATCH 026/215] Fix detection for specific Intel OpenGL ICDs The OpenGL ICD name now includes the file extension, which the regex expressions were not matching. --- .../workarounds/Workarounds.java | 25 ++----------------- .../workarounds/intel/IntelWorkarounds.java | 21 +++++++++++++++- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java index 98e286d03d..9a07886829 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java @@ -1,10 +1,8 @@ package net.caffeinemc.mods.sodium.client.compatibility.workarounds; import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; -import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.intel.IntelWorkarounds; import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; -import net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt.D3DKMT; -import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,7 +41,7 @@ private static Set findNecessaryWorkarounds() { workarounds.add(Reference.NVIDIA_THREADED_OPTIMIZATIONS_BROKEN); } - if (isUsingIntelGen8OrOlder()) { + if (IntelWorkarounds.isUsingIntelGen8OrOlder()) { workarounds.add(Reference.INTEL_FRAMEBUFFER_BLIT_CRASH_WHEN_UNFOCUSED); workarounds.add(Reference.INTEL_DEPTH_BUFFER_COMPARISON_UNRELIABLE); } @@ -65,25 +63,6 @@ private static Set findNecessaryWorkarounds() { return Collections.unmodifiableSet(workarounds); } - private static boolean isUsingIntelGen8OrOlder() { - if (OsUtils.getOs() != OsUtils.OperatingSystem.WIN) { - return false; - } - - for (var adapter : GraphicsAdapterProbe.getAdapters()) { - if (adapter instanceof D3DKMT.WDDMAdapterInfo wddmAdapterInfo) { - @Nullable var driverName = wddmAdapterInfo.getOpenGlIcdName(); - - // Intel OpenGL ICD for legacy GPUs - if (driverName != null && driverName.matches("ig(7|75|8)icd(32|64)")) { - return true; - } - } - } - - return false; - } - public static boolean isWorkaroundEnabled(Reference id) { return ACTIVE_WORKAROUNDS.get() .contains(id); diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java index 97ab3a1aae..390502d152 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java @@ -24,7 +24,7 @@ public class IntelWorkarounds { var driverVersion = wddmAdapterInfo.openglIcdVersion(); // Intel OpenGL ICD for Generation 7 GPUs - if (driverName.matches("ig7icd(32|64)")) { + if (driverName.matches("ig7icd(32|64).dll")) { // https://www.intel.com/content/www/us/en/support/articles/000005654/graphics.html // Anything which matches the 15.33 driver scheme (WDDM x.y.10.w) should be checked // Drivers before build 5161 are assumed to have bugs with synchronization primitives @@ -37,4 +37,23 @@ public class IntelWorkarounds { return null; } + + public static boolean isUsingIntelGen8OrOlder() { + if (OsUtils.getOs() != OsUtils.OperatingSystem.WIN) { + return false; + } + + for (var adapter : GraphicsAdapterProbe.getAdapters()) { + if (adapter instanceof D3DKMT.WDDMAdapterInfo wddmAdapterInfo) { + @Nullable var driverName = wddmAdapterInfo.getOpenGlIcdName(); + + // Intel OpenGL ICD for legacy GPUs + if (driverName != null && driverName.matches("ig(7|75|8)icd(32|64)\\.dll")) { + return true; + } + } + } + + return false; + } } From 58e837ce7fff27334006a8568852ecc39c022adb Mon Sep 17 00:00:00 2001 From: JellySquid Date: Wed, 4 Dec 2024 15:02:20 -0600 Subject: [PATCH 027/215] Avoid showing the incompatible driver error in some cases For systems with hybrid graphics, it may be the case that an incompatible graphics driver is installed, but that it isn't used for the OpenGL context. We can avoid showing errors in this situation by checking the vendor string of the context immediately after creation. This is not the most robust check, but in practice, a single system should not have multiple graphics drivers installed from the same vendor, so checking the string should be relatively safe. --- .../context_creation/WindowMixin.java | 11 +- .../checks/GraphicsDriverChecks.java | 63 +++++++++ .../checks/PostLaunchChecks.java | 8 +- .../compatibility/checks/PreLaunchChecks.java | 125 +++++------------- .../probe/GraphicsAdapterVendor.java | 16 +++ .../workarounds/Workarounds.java | 2 +- .../workarounds/nvidia/NvidiaWorkarounds.java | 13 +- .../client/platform/PlatformHelper.java | 29 ++++ .../mods/sodium/fabric/SodiumPreLaunch.java | 3 +- .../sodium/service/SodiumWorkarounds.java | 3 +- 10 files changed, 163 insertions(+), 110 deletions(-) create mode 100644 common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/GraphicsDriverChecks.java create mode 100644 common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java index 5b50b53a80..f15795c221 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java @@ -73,10 +73,10 @@ private long wrapGlfwCreateWindowForge(final IntSupplier width, final IntSupplie @Inject(method = "", at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL;createCapabilities()Lorg/lwjgl/opengl/GLCapabilities;", shift = At.Shift.AFTER)) private void postContextReady(WindowEventHandler eventHandler, ScreenManager monitorTracker, DisplayData settings, String videoMode, String title, CallbackInfo ci) { - GlContextInfo driver = GlContextInfo.create(); - LOGGER.info("OpenGL Vendor: {}", driver.vendor()); - LOGGER.info("OpenGL Renderer: {}", driver.renderer()); - LOGGER.info("OpenGL Version: {}", driver.version()); + GlContextInfo context = GlContextInfo.create(); + LOGGER.info("OpenGL Vendor: {}", context.vendor()); + LOGGER.info("OpenGL Renderer: {}", context.renderer()); + LOGGER.info("OpenGL Version: {}", context.version()); // Capture the current WGL context so that we can detect it being replaced later. if (Util.getPlatform() == Util.OS.WINDOWS) { @@ -85,8 +85,7 @@ private void postContextReady(WindowEventHandler eventHandler, ScreenManager mon this.wglPrevContext = MemoryUtil.NULL; } - NvidiaWorkarounds.applyContextChanges(driver); - PostLaunchChecks.onContextInitialized(); + PostLaunchChecks.onContextInitialized((NativeWindowHandle) this, context); ModuleScanner.checkModules((NativeWindowHandle) this); } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/GraphicsDriverChecks.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/GraphicsDriverChecks.java new file mode 100644 index 0000000000..5d410dadac --- /dev/null +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/GraphicsDriverChecks.java @@ -0,0 +1,63 @@ +package net.caffeinemc.mods.sodium.client.compatibility.checks; + +import net.caffeinemc.mods.sodium.client.compatibility.environment.GlContextInfo; +import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterVendor; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.intel.IntelWorkarounds; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaDriverVersion; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; +import net.caffeinemc.mods.sodium.client.platform.NativeWindowHandle; +import net.caffeinemc.mods.sodium.client.platform.PlatformHelper; + +class GraphicsDriverChecks { + static void postContextInit(NativeWindowHandle window, GlContextInfo context) { + var vendor = GraphicsAdapterVendor.fromContext(context); + + if (vendor == GraphicsAdapterVendor.UNKNOWN) { + return; + } + + if (vendor == GraphicsAdapterVendor.INTEL && BugChecks.ISSUE_899) { + var installedVersion = IntelWorkarounds.findIntelDriverMatchingBug899(); + + if (installedVersion != null) { + var installedVersionString = installedVersion.toString(); + + PlatformHelper.showCriticalErrorAndClose(window, + "Sodium Renderer - Unsupported Driver", + """ + The game failed to start because the currently installed Intel Graphics Driver is not \ + compatible. + + Installed version: ###CURRENT_DRIVER### + Required version: 10.18.10.5161 (or newer) + + You must update your graphics card driver in order to continue.""" + .replace("###CURRENT_DRIVER###", installedVersionString), + "https://github.com/CaffeineMC/sodium/wiki/Driver-Compatibility#windows-intel-gen7"); + } + } + + if (vendor == GraphicsAdapterVendor.NVIDIA && BugChecks.ISSUE_1486) { + var installedVersion = NvidiaWorkarounds.findNvidiaDriverMatchingBug1486(); + + if (installedVersion != null) { + var installedVersionString = NvidiaDriverVersion.parse(installedVersion) + .toString(); + + PlatformHelper.showCriticalErrorAndClose(window, + "Sodium Renderer - Unsupported Driver", + """ + The game failed to start because the currently installed NVIDIA Graphics Driver is not \ + compatible. + + Installed version: ###CURRENT_DRIVER### + Required version: 536.23 (or newer) + + You must update your graphics card driver in order to continue.""" + .replace("###CURRENT_DRIVER###", installedVersionString), + "https://github.com/CaffeineMC/sodium/wiki/Driver-Compatibility#nvidia-gpus"); + + } + } + } +} diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PostLaunchChecks.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PostLaunchChecks.java index 85f5d92488..907b877afe 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PostLaunchChecks.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PostLaunchChecks.java @@ -1,7 +1,10 @@ package net.caffeinemc.mods.sodium.client.compatibility.checks; +import net.caffeinemc.mods.sodium.client.compatibility.environment.GlContextInfo; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; import net.caffeinemc.mods.sodium.client.console.Console; import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; +import net.caffeinemc.mods.sodium.client.platform.NativeWindowHandle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,7 +15,10 @@ public class PostLaunchChecks { private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-PostlaunchChecks"); - public static void onContextInitialized() { + public static void onContextInitialized(NativeWindowHandle window, GlContextInfo context) { + GraphicsDriverChecks.postContextInit(window, context); + NvidiaWorkarounds.applyContextChanges(context); + // FIXME: This can be determined earlier, but we can't access the GUI classes in pre-launch if (isUsingPojavLauncher()) { Console.instance().logMessage(MessageLevel.SEVERE, "sodium.console.pojav_launcher", true, 30.0); diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java index 170f329ff4..70cb73e27e 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java @@ -1,112 +1,55 @@ package net.caffeinemc.mods.sodium.client.compatibility.checks; -import net.caffeinemc.mods.sodium.client.compatibility.workarounds.intel.IntelWorkarounds; -import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaDriverVersion; -import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; -import net.caffeinemc.mods.sodium.client.platform.MessageBox; +import net.caffeinemc.mods.sodium.client.platform.PlatformHelper; import org.lwjgl.Version; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Performs OpenGL driver validation before the game creates an OpenGL context. This runs during the earliest possible * opportunity at game startup, and uses a custom hardware prober to search for problematic drivers. */ public class PreLaunchChecks { - private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-EarlyDriverScanner"); - // These version constants are inlined at compile time. private static final String REQUIRED_LWJGL_VERSION = Version.VERSION_MAJOR + "." + Version.VERSION_MINOR + "." + Version.VERSION_REVISION; - private static final String normalMessage = "You must change the LWJGL version in your launcher to continue. " + - "This is usually controlled by the settings for a profile or instance in your launcher."; - - private static final String prismMessage = "It appears you are using Prism Launcher to start the game. You can " + - "likely fix this problem by opening your instance settings and navigating to the Version section in the " + - "sidebar."; - - public static void beforeLWJGLInit() { + public static void checkEnvironment() { if (BugChecks.ISSUE_2561) { - if (!isUsingKnownCompatibleLwjglVersion()) { - String message = normalMessage; - - if (isUsingPrismLauncher()) { - message = prismMessage; - } - - showCriticalErrorAndClose("Sodium Renderer - Unsupported LWJGL", - (""" - The game failed to start because the currently active LWJGL version is not \ - compatible. - - Installed version: ###CURRENT_VERSION### - Required version: ###REQUIRED_VERSION### - - """ + message) - .replace("###CURRENT_VERSION###", Version.getVersion()) - .replace("###REQUIRED_VERSION###", REQUIRED_LWJGL_VERSION), - "https://github.com/CaffeineMC/sodium/wiki/LWJGL-Compatibility"); - } + checkLwjglRuntimeVersion(); } } - public static void onGameInit() { - if (BugChecks.ISSUE_899) { - var installedVersion = IntelWorkarounds.findIntelDriverMatchingBug899(); - - if (installedVersion != null) { - var installedVersionString = installedVersion.toString(); - - showCriticalErrorAndClose("Sodium Renderer - Unsupported Driver", - """ - The game failed to start because the currently installed Intel Graphics Driver is not \ - compatible. - - Installed version: ###CURRENT_DRIVER### - Required version: 10.18.10.5161 (or newer) - - You must update your graphics card driver in order to continue.""" - .replace("###CURRENT_DRIVER###", installedVersionString), - "https://github.com/CaffeineMC/sodium/wiki/Driver-Compatibility#windows-intel-gen7"); - } + private static void checkLwjglRuntimeVersion() { + if (isUsingKnownCompatibleLwjglVersion()) { + return; } - if (BugChecks.ISSUE_1486) { - var installedVersion = NvidiaWorkarounds.findNvidiaDriverMatchingBug1486(); - - if (installedVersion != null) { - var installedVersionString = NvidiaDriverVersion.parse(installedVersion) - .toString(); - - showCriticalErrorAndClose("Sodium Renderer - Unsupported Driver", - """ - The game failed to start because the currently installed NVIDIA Graphics Driver is not \ - compatible. - - Installed version: ###CURRENT_DRIVER### - Required version: 536.23 (or newer) - - You must update your graphics card driver in order to continue.""" - .replace("###CURRENT_DRIVER###", installedVersionString), - "https://github.com/CaffeineMC/sodium/wiki/Driver-Compatibility#nvidia-gpus"); - - } + String advice; + + if (isUsingPrismLauncher()) { + advice = """ + It appears you are using Prism Launcher to start the game. You can \ + likely fix this problem by opening your instance settings and navigating to the Version\ + section in the sidebar."""; + } else { + advice = """ + You must change the LWJGL version in your launcher to continue. \ + This is usually controlled by the settings for a profile or instance in your launcher."""; } - } - private static void showCriticalErrorAndClose(String title, String message, String url) { - // Always print the information to the log file first, just in case we can't show the message box. - LOGGER.error(""" - ###ERROR_DESCRIPTION### - - For more information, please see: ###HELP_URL###""" - .replace("###ERROR_DESCRIPTION###", message) - .replace("###HELP_URL###", url == null ? "" : url)); - - // Try to show a graphical message box (if the platform supports it) and shut down the game. - MessageBox.showMessageBox(null, MessageBox.IconType.ERROR, title, message, url); - System.exit(1 /* failure code */); + String message = """ + The game failed to start because the currently active LWJGL version is not \ + compatible. + + Installed version: ###CURRENT_VERSION### + Required version: ###REQUIRED_VERSION### + + ###ADVICE_STRING###""" + .replace("###CURRENT_VERSION###", Version.getVersion()) + .replace("###REQUIRED_VERSION###", REQUIRED_LWJGL_VERSION) + .replace("###ADVICE_STRING###", advice); + + PlatformHelper.showCriticalErrorAndClose(null, "Sodium Renderer - Unsupported LWJGL", message, + "https://github.com/CaffeineMC/sodium/wiki/LWJGL-Compatibility"); } private static boolean isUsingKnownCompatibleLwjglVersion() { @@ -115,7 +58,11 @@ private static boolean isUsingKnownCompatibleLwjglVersion() { } private static boolean isUsingPrismLauncher() { - return System.getProperty("minecraft.launcher.brand", "unknown") + return getLauncherBrand() .equalsIgnoreCase("PrismLauncher"); } + + private static String getLauncherBrand() { + return System.getProperty("minecraft.launcher.brand", "unknown"); + } } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java index 661f817ac9..57ddb79400 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java @@ -1,7 +1,9 @@ package net.caffeinemc.mods.sodium.client.compatibility.environment.probe; +import net.caffeinemc.mods.sodium.client.compatibility.environment.GlContextInfo; import org.jetbrains.annotations.NotNull; +import java.util.Locale; import java.util.regex.Pattern; public enum GraphicsAdapterVendor { @@ -41,6 +43,7 @@ static GraphicsAdapterVendor fromPciVendorId(String vendor) { return UNKNOWN; } + @NotNull public static GraphicsAdapterVendor fromIcdName(String name) { if (matchesPattern(INTEL_ICD_PATTERN, name)) { return INTEL; @@ -53,6 +56,19 @@ public static GraphicsAdapterVendor fromIcdName(String name) { } } + @NotNull + public static GraphicsAdapterVendor fromContext(GlContextInfo context) { + var vendor = context.vendor(); + + return switch (vendor) { + case "NVIDIA Corporation" -> NVIDIA; + case "Intel", "Intel Open Source Technology Center" -> INTEL; + case "AMD", "ATI Technologies Inc." -> AMD; + default -> UNKNOWN; + }; + + } + private static boolean matchesPattern(Pattern pattern, String name) { return pattern.matcher(name) .matches(); diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java index 9a07886829..659d98b6b3 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java @@ -37,7 +37,7 @@ private static Set findNecessaryWorkarounds() { var workarounds = EnumSet.noneOf(Reference.class); var operatingSystem = OsUtils.getOs(); - if (NvidiaWorkarounds.isUsingNvidiaGraphicsCard()) { + if (NvidiaWorkarounds.isNvidiaGraphicsCardPresent()) { workarounds.add(Reference.NVIDIA_THREADED_OPTIMIZATIONS_BROKEN); } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java index 0caa32458f..d7c1be4da6 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java @@ -17,12 +17,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Locale; - public class NvidiaWorkarounds { private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-NvidiaWorkarounds"); - public static boolean isUsingNvidiaGraphicsCard() { + public static boolean isNvidiaGraphicsCardPresent() { return GraphicsAdapterProbe.getAdapters() .stream() .anyMatch(adapter -> adapter.vendor() == GraphicsAdapterVendor.NVIDIA); @@ -64,7 +62,7 @@ public static void applyEnvironmentChanges() { // We can't know if the OpenGL context will actually be initialized using the NVIDIA ICD, but we need to // modify the process environment *now* otherwise the driver will initialize with bad settings. For non-NVIDIA // drivers, these workarounds are not likely to cause issues. - if (!isUsingNvidiaGraphicsCard()) { + if (!isNvidiaGraphicsCardPresent()) { return; } @@ -106,14 +104,11 @@ public static void undoEnvironmentChanges() { WindowsCommandLine.resetCommandLine(); } - public static void applyContextChanges(GlContextInfo driver) { - var normalizedVendorName = driver.vendor() - .toLowerCase(Locale.ROOT); - + public static void applyContextChanges(GlContextInfo context) { // The context may not have been initialized with the NVIDIA ICD, even if we think there is an NVIDIA // graphics adapter in use. Because enabling these workarounds have the potential to severely hurt performance // on other drivers, make sure we exit now. - if (!normalizedVendorName.startsWith("nvidia")) { + if (GraphicsAdapterVendor.fromContext(context) != GraphicsAdapterVendor.NVIDIA) { return; } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java new file mode 100644 index 0000000000..dfc1f2b77f --- /dev/null +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java @@ -0,0 +1,29 @@ +package net.caffeinemc.mods.sodium.client.platform; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PlatformHelper { + private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-EarlyDriverScanner"); + + public static void showCriticalErrorAndClose( + @Nullable NativeWindowHandle window, + @NotNull String messageTitle, + @NotNull String messageBody, + @NotNull String helpUrl) + { + // Always print the information to the log file first, just in case we can't show the message box. + LOGGER.error(""" + ###ERROR_DESCRIPTION### + + For more information, please see: ###HELP_URL###""" + .replace("###ERROR_DESCRIPTION###", messageBody) + .replace("###HELP_URL###", helpUrl)); + + // Try to show a graphical message box (if the platform supports it) and shut down the game. + MessageBox.showMessageBox(window, MessageBox.IconType.ERROR, messageTitle, messageBody, helpUrl); + System.exit(1 /* failure code */); + } +} diff --git a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/SodiumPreLaunch.java b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/SodiumPreLaunch.java index 167edadf8b..473c5a6e8e 100644 --- a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/SodiumPreLaunch.java +++ b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/SodiumPreLaunch.java @@ -8,9 +8,8 @@ public class SodiumPreLaunch implements PreLaunchEntrypoint { @Override public void onPreLaunch() { - PreLaunchChecks.beforeLWJGLInit(); + PreLaunchChecks.checkEnvironment(); GraphicsAdapterProbe.findAdapters(); - PreLaunchChecks.onGameInit(); Workarounds.init(); } } diff --git a/neoforge/src/service/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java b/neoforge/src/service/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java index 8dab82a896..eb106c0ad0 100644 --- a/neoforge/src/service/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java +++ b/neoforge/src/service/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java @@ -14,9 +14,8 @@ public String name() { @Override public void bootstrap(String[] arguments) { - PreLaunchChecks.beforeLWJGLInit(); + PreLaunchChecks.checkEnvironment(); GraphicsAdapterProbe.findAdapters(); - PreLaunchChecks.onGameInit(); Workarounds.init(); // Context creation happens earlier on NeoForge, so we need to apply this now From 1bd4f062902d40684c967dad56d07c7a6700180e Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sat, 7 Dec 2024 00:47:20 -0600 Subject: [PATCH 028/215] Bump version --- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index ae978f82aa..7fb32a3250 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -11,7 +11,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = null // https://semver.org/ - var MOD_VERSION: String = "0.6.2" + var MOD_VERSION: String = "0.6.3" fun createVersionString(project: Project): String { val builder = StringBuilder() From b804224423cc99ecc27394628b1f359578181e1f Mon Sep 17 00:00:00 2001 From: JellySquid Date: Tue, 10 Dec 2024 13:34:20 -0600 Subject: [PATCH 029/215] Backport cloud rendering tear-down fixes The vertex buffer may be null if there is no geometry within the view distance, but the tear-down function will not check before trying to delete the buffer. Additionally, make sure that we de-allocate the vertex buffer when there is no geometry visible, to avoid leaking buffer handles. --- .../render/immediate/CloudRenderer.java | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java index 9357f9d24e..9a01b32127 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java @@ -242,19 +242,24 @@ public void render(Camera camera, } } - MeshData builtBuffer = bufferBuilder.build(); + @Nullable MeshData meshData = bufferBuilder.build(); + @Nullable VertexBuffer vertexBuffer = null; - VertexBuffer vertexBuffer = null; + if (existingGeometry != null) { + vertexBuffer = existingGeometry.vertexBuffer(); + } - if (builtBuffer != null) { - if (existingGeometry != null) { - vertexBuffer = existingGeometry.vertexBuffer(); - } + if (meshData != null) { if (vertexBuffer == null) { vertexBuffer = new VertexBuffer(VertexBuffer.Usage.DYNAMIC); } - uploadToVertexBuffer(vertexBuffer, builtBuffer); + uploadToVertexBuffer(vertexBuffer, meshData); + } else { + if (vertexBuffer != null) { + vertexBuffer.close(); + vertexBuffer = null; + } } Tesselator.getInstance().clear(); @@ -521,7 +526,10 @@ public void destroy() { if (this.builtGeometry != null) { var vertexBuffer = this.builtGeometry.vertexBuffer(); - vertexBuffer.close(); + + if (vertexBuffer != null) { + vertexBuffer.close(); + } this.builtGeometry = null; } @@ -746,7 +754,7 @@ public int getCellColor(int index) { } } - public record CloudGeometry(VertexBuffer vertexBuffer, CloudGeometryParameters params) { + public record CloudGeometry(@Nullable VertexBuffer vertexBuffer, CloudGeometryParameters params) { } From 617422f6ec26b080f5fbc4abdf207687383ea809 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Thu, 12 Dec 2024 15:52:55 -0600 Subject: [PATCH 030/215] Update README.md --- README.md | 83 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index e80d00e483..e8af77f8ca 100644 --- a/README.md +++ b/README.md @@ -5,25 +5,53 @@ Sodium is a powerful rendering engine and optimization mod for the Minecraft client which improves frame rates and reduces micro-stutter, while fixing many graphical issues in Minecraft. -### 📥 Installation +**This mod is the result of thousands of hours of development, and is made possible thanks to players like you.** If you +would like to show a token of your appreciation for my work, and help support the development of Sodium in the process, +then consider [buying me a coffee](https://caffeinemc.net/donate). -The latest version of Sodium can be downloaded from our official [Modrinth](https://modrinth.com/mod/sodium) and -[CurseForge](https://www.curseforge.com/minecraft/mc-mods/sodium) pages. + + +--- + +### 📥 Downloads + +#### Stable builds + +The latest stable release of Sodium can be downloaded from our official [Modrinth](https://modrinth.com/mod/sodium) and +[CurseForge](https://www.curseforge.com/minecraft/mc-mods/sodium) pages. + +#### Nightly builds (for developers) + +We also provide bleeding-edge builds ("nightlies") which are useful for testing the very latest changes before they're +packaged into a release. These builds are only provided for other mod developers and users with expert skills, and do +not come with any support or warranty. It is often the case they have issues and lack compatibility with other mods. + +The latest nightly build for each current branch of development can be downloaded below. + +- Minecraft 1.21.4 (latest): [Download nightly](https://nightly.link/CaffeineMC/sodium/workflows/build-commit/dev/sodium-artifacts-dev.zip) or [View all builds](https://github.com/CaffeineMC/sodium/actions/workflows/build-commit.yml?query=branch%3Adev) +- Minecraft 1.21.3: [Download nightly](https://nightly.link/CaffeineMC/sodium/workflows/build-commit/1.21.3%2Fstable/sodium-artifacts-1.21.3-stable.zip) or [View all builds](https://github.com/CaffeineMC/sodium/actions/workflows/build-commit.yml?query=branch%3A1.21.3%2Fstable) +- Minecraft 1.21.1: [Download nightly](https://nightly.link/CaffeineMC/sodium/workflows/build-commit/1.21.1%2Fstable/sodium-artifacts-1.21.1-stable.zip) or [View all builds](https://github.com/CaffeineMC/sodium/actions/workflows/build-commit.yml?query=branch%3A1.21.1%2Fstable) + +### 🖥️ Installation Since the release of Sodium 0.6.0, both the _Fabric_ and _NeoForge_ mod loaders are supported. We generally recommend that new users prefer to use the _Fabric_ mod loader, since it is more lightweight and stable (for the time being.) For more information about downloading and installing the mod, please refer to our [Installation Guide](https://github.com/CaffeineMC/sodium/wiki/Installation). -### 🐛 Reporting Issues +### 🙇 Getting Help -You can report bugs and crashes by opening an issue on our [issue tracker](https://github.com/CaffeineMC/sodium/issues). -Before opening a new issue, use the search tool to make sure that your issue has not already been reported and ensure -that you have completely filled out the issue template. Issues that are duplicates or do not contain the necessary -information to triage and debug may be closed. +For technical support (including help with mod installation problems and game crashes), please use our +[official Discord server](https://caffeinemc.net/discord). + +### 📬 Reporting Issues + +If you do not need technical support and would like to report an issue (bug, crash, etc.) or otherwise request changes +(for mod compatibility, new features, etc.), then we encourage you to open an issue on the +[project issue tracker](https://github.com/CaffeineMC/sodium/issues). Please note that while the issue tracker is open to feature requests, development is primarily focused on -improving hardware compatibility, performance, and finishing any unimplemented features necessary for parity with +improving compatibility, performance, and finishing any unimplemented features necessary for parity with the vanilla renderer. ### 💬 Join the Community @@ -36,15 +64,16 @@ We have an [official Discord community](https://caffeinemc.net/discord) for all ## ✅ Hardware Compatibility -We only provide support for graphics cards which have up-to-date drivers for OpenGL 4.6. Most graphics cards which have -been released since year 2010 are supported, such as the... +We only provide official support for graphics cards which have up-to-date drivers that are compatible with OpenGL 4.5 +or newer. Most graphics cards released in the past 12 years will meet these requirements, including the following: - AMD Radeon HD 7000 Series (GCN 1) or newer - NVIDIA GeForce 400 Series (Fermi) or newer - Intel HD Graphics 500 Series (Skylake) or newer -In some cases, older graphics cards may also work (so long as they have up-to-date drivers which have support for -OpenGL 3.3), but they are not officially supported, and may not be compatible with future versions of Sodium. +Nearly all graphics cards that are already compatible with Minecraft (which requires OpenGL 3.3) should also work +with Sodium. But our team cannot ensure compatibility or provide support for older graphics cards, and they may +not work with future versions of Sodium. #### OpenGL Compatibility Layers @@ -52,29 +81,23 @@ Devices which need to use OpenGL translation layers (such as GL4ES, ANGLE, etc) not work with Sodium. These translation layers do not implement required functionality and they suffer from underlying driver bugs which cannot be worked around. -## 🛠️ Developer Guide - -### Building from sources +## 🛠️ Building from sources -Sodium uses a typical Gradle project structure and can be compiled by simply running the default `build` task. The build -artifacts (typical mod binaries, and their sources) can be found in the `build/libs` directory. +Sodium uses the [Gradle build tool](https://gradle.org/) and can be built with the `gradle build` command. The build +artifacts (production binaries and their source bundles) can be found in the `build/mods` directory. -#### Requirements +The [Gradle wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:using_wrapper) is provided for ease of use and will automatically download and install the +appropriate version of Gradle for the project build. To use the Gradle wrapper, substitute `gradle` in build commands +with `./gradlew.bat` (Windows) or `./gradlew` (macOS and Linux). -We recommend using a package manager (such as [SDKMAN](https://sdkman.io/)) to manage toolchain dependencies and keep -them up to date. For many Linux distributions, these dependencies will be standard packages in your software -repositories. +### Build Requirements - OpenJDK 21 - - We recommend using the [Eclipse Temurin](https://adoptium.net/) distribution, as it's known to be high quality - and to work without issues. -- Gradle 8.6.x (optional) - - The [Gradle wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html#sec:using_wrapper) is provided - in this repository can be used instead of installing a suitable version of Gradle yourself. However, if you are - building many projects, you may prefer to install it yourself through a suitable package manager as to save disk - space and to avoid many different Gradle daemons sitting around in memory. + - We recommend using the [Eclipse Temurin](https://adoptium.net/) distribution as it's regularly tested by our developers and known + to be of high quality. +- Gradle 8.10.x - Typically, newer versions of Gradle will work without issues, but the build script is only tested against the - version specified by the wrapper script. + version used by the [wrapper script](/gradle/wrapper/gradle-wrapper.properties). ## 📜 License From a26e1b55b6550837bc3cc0bac2a2d1f2e78c7dbc Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sun, 15 Dec 2024 12:49:00 -0600 Subject: [PATCH 031/215] Update textures for Leaves variants * Added "Pale Oak Leaves" for Minecraft 1.21.4. * Reduced the file size of all block textures. --- .../minecraft/textures/block/acacia_leaves.png | Bin 474 -> 246 bytes .../minecraft/textures/block/azalea_leaves.png | Bin 481 -> 250 bytes .../minecraft/textures/block/birch_leaves.png | Bin 450 -> 228 bytes .../minecraft/textures/block/cherry_leaves.png | Bin 677 -> 337 bytes .../textures/block/dark_oak_leaves.png | Bin 462 -> 237 bytes .../textures/block/flowering_azalea_leaves.png | Bin 550 -> 286 bytes .../minecraft/textures/block/jungle_leaves.png | Bin 520 -> 296 bytes .../textures/block/mangrove_leaves.png | Bin 457 -> 268 bytes .../minecraft/textures/block/oak_leaves.png | Bin 467 -> 243 bytes .../textures/block/pale_oak_leaves.png | Bin 0 -> 266 bytes .../minecraft/textures/block/spruce_leaves.png | Bin 440 -> 230 bytes .../minecraft/textures/block/acacia_leaves.png | Bin 788 -> 508 bytes .../minecraft/textures/block/birch_leaves.png | Bin 788 -> 508 bytes .../textures/block/dark_oak_leaves.png | Bin 788 -> 508 bytes .../minecraft/textures/block/jungle_leaves.png | Bin 4752 -> 374 bytes .../minecraft/textures/block/oak_leaves.png | Bin 788 -> 508 bytes .../minecraft/textures/block/spruce_leaves.png | Bin 501 -> 262 bytes 17 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 common/src/main/resources/assets/minecraft/textures/block/pale_oak_leaves.png diff --git a/common/src/main/resources/assets/minecraft/textures/block/acacia_leaves.png b/common/src/main/resources/assets/minecraft/textures/block/acacia_leaves.png index f0a470c38cf4472793a9a5adf7833df33991036c..55c7deb58b4ba9653abf7a79064883e487de2ee6 100644 GIT binary patch delta 218 zcmV<0044w01NH%sB!9h0L_t(|+G6NvZ~yP_>&v)x>(>833}z$a+^j4>p=5xy`#IaEzw2@`ovI2HFVCS5B0-=Qpl*E0LiS^69oVW`?2#Y`8I~X}xz|}1c zUJtR5)C7|m*Y*Z^8f$< delta 448 zcmV;x0YCos0ontQB!2;OQb$4nuFf3k00004XF*Lt006O%3;baP00009a7bBm000id z000id0mpBsWB>pGZ%IT!R5(vkSqOp24KlbG4>6N|rC%zN57OOLzkdI!9^=b;J$&r2n1Zel|~g+RqPlB>^r#<7Ztr@NxCg8Q-zMcxy9+isJJX^ZxN3gPOq5o9(OtGxtq zMzQj~%_0A|jDNh%Kx!fbVj_JV4uN7sAIIZAFLm9}Gf=-0Lj=n}?f^Ul6E|sKa-voJ zU)nP;M)uIs^Dq(7#8rkp{X75hLU#BBx~7!s=nrB>c|J8OYr9LcSQdL80z0(C56 z8xbIJ|031`q$c+&>z)8~=AzpdNlY7Sh-k79DAF)FlR5kEMeG(t^o&aQFO#!gM1MYV q4AWZh9}oq8$_V;AAdb+as=_b%SV^CDk_@x}00004o`?@#r2Q~Bms_~HTcn~X3~kwucx2B9#^7-CJI`U^kkdJOuc*H zoK7O%VeJ4JJU7u3m_dg*hft&Ip1AZ~wCtX%5qgMga7ZWg7h#voj(B=BI*F(acFEl2 Y18(!7WktJ1H2?qr07*qoM6N<$f;C@lPXGV_ delta 455 zcmV;&0XY8p0pSCXB!2;OQb$4nuFf3k00004XF*Lt006O%3;baP00009a7bBm000id z000id0mpBsWB>pGc1c7*R5(v}j+Wcg z1z2fK6^+GV@o-ngcjMeY249J@(`$~%OMDo3e*7tTD4xV-Z!Vs_OmTgqpF z0HT1Lvb)tTQVPzySEI{uL3;bstARPZj6=3(g?SDp+ x8^Q@E#E1aeh#;2eO(g;r3fjqCfljYE`U4&s4|Yt{LMZ?M002ovPDHLkV1l>-!|DJ4 diff --git a/common/src/main/resources/assets/minecraft/textures/block/birch_leaves.png b/common/src/main/resources/assets/minecraft/textures/block/birch_leaves.png index a1cbce02739f54bd606d76c5a20cbd9d6966f702..24ca726fbd41cc5a08ca5ee0cf00049b66aac722 100644 GIT binary patch delta 200 zcmV;(05|`_1LOgaB!8<(L_t(|+G6NvZ~yP);KT^R1-S+P0d0|iiN;_ILfwCY-X!I= z`$^$}JOm18{A`Sk;o!l|2k`topNK(=kH2{vqYZ=}gXcDlV|P|P&hg&=_tUn?8IW-b z9K(Dp^5eSC)fmHf&w|XX=E< z=)M&#c6P=wSB+YAiNN0KHqE;9jH4)gf!O`;D2;z+%)uwybc{Cu0000}TnXyo(##&p*%hOtQ}N+xByphJQ+igJJpj)D7|xFtl*| z{b6q!JQcWDF0Ao}ah&f#=C)mX40qUq*iQ^RcqpXA3D6Rdwfzwx*pk2g>g z?A80KWD?kD3V-Hb0AF~V0Eab5WK( z0;VZwtXS&1icS0JDHYcW diff --git a/common/src/main/resources/assets/minecraft/textures/block/cherry_leaves.png b/common/src/main/resources/assets/minecraft/textures/block/cherry_leaves.png index 0ec0eb21f66e6016eb4d2b33ff46bf3ea08940e6..d904d41544d265f6ee4a3fe06003568ae26102c1 100644 GIT binary patch delta 311 zcmV-70m%NP1{Z1G!mlSC{NFND_dkpc6Mw(_?EmjO?|?C~ z+Gn*}|AT0-0U*xTTkipVk}(QGF%U%eC{`ZH+S*p|3icNEmU;;fAaAAmJ6-(lJJEtZ| zVMRa+DDqexB_}vRvW}+HSyu}59n>HJYXWD>&FmNAzYGjYC+!UD;j>ir_NhiLg8_$q zI^n%XIi&+FVbfmNcOBGsB^ckFo?0DtGw>uy*nAysS?L{aa{}Q2&?@9HV4VhfN;nSsPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi z!TEJMFi2V7X7D1n+k#;BElrfutG5^WF}f9C@$J$k$;WDsKbmYZ<^nUPf9xQ z-u>SF?)mO{=f3B4x1IH@h>fk=gWKU_w!UkZ&-(p~9-n;UfsUXwTFKISuisIQ{n_zp zD_LG{O2p%vg2k=ttaD4hHD5awkb_7Zxc^u+pykA2@~&uJb~lyt%<|1xB)T zO|_lw-K>$FT=m3CqDyRcB5TvbUsZ_0g+9rH=jU(wkcpRC`b3`WO4zTVlwx_!N|7o4 zpHE({XMePi9vIdJ9RTE?&tBWIoy``ijVVWit$X+DQ-1aEfyjK?T0|Oa2m-RFK71CL z$TM(cz(^tICo<-91B~*>qXs*dY_p2?xH=P2mwq>5*-T1xSMvMx5dr>EpBqK|%CSgD oCSo8UGFrg2oOEjbm}~YMawbS|g<^^`00000Ne4wvM6N<$f*X}DsQ>@~ diff --git a/common/src/main/resources/assets/minecraft/textures/block/dark_oak_leaves.png b/common/src/main/resources/assets/minecraft/textures/block/dark_oak_leaves.png index d249b77554cb548785f10ce07d5c9efef4830876..69813be169a2f25ddfdacd08d12907232bf210bd 100644 GIT binary patch delta 209 zcmV;?051Q|1MLBjB!9F?L_t(|+F~fkE%*<@P7Y3t+jnjU(=%qy02E3t7!U+O49&e) zpS@S`h)3_Dn5oL-XKsh*?$763?j3dk$UxX*Z-624d-%|*omI6uleL4oRoLx7m<4Ve zb{mcT;(`^xcEr5;35)>j=wCl^c01zcLU9q40OP`{E)^Hq>`IV0tDUV#oY8IDI^>W9 z(DuNr?Im4Yu<4coyvhU{|KoP!SYUV#AOHFz_uaALmO0qANg)3MGV3ZTaFQ?W00000 LNkvXXu0mjfF`s8P delta 436 zcmV;l0Zabv0nP)EB!2;OQb$4nuFf3k00004XF*Lt006O%3;baP00009a7bBm000id z000id0mpBsWB>pGV@X6oR5(v{k}(beQ4ogTzMcz6P%3Z$g?1bDCJrIdD4amy0-Qji zmdLgW2SA}j;sW%GZ<1dovL@NQw=@6z^Uusn#-s5oeC%*M#DB?jQp{)bm<_W6-+HyS zd!yM1``teD`n^D$7+8F<)oxibPJ-P|w>Y29;eNXZk|7~htJgTpNWNaKMi>PE%m*2y zBtRlns8Dr`f9xv2d}zB3S*fK*_r zDn*?6CJ$^1QmY7vQwji-y#-aqxHE^y-~g#N?B81;2Dwg1EV*nB>Q!$6qXmdDaFE&% z0qIG5H2@&t^uuIE7&u#_K)?H!8Yv?IEEtMf3qBc%>@8MmNG3_LHB2R23tAV36BE$O eE^8|rX2lyg(^ZE8sSkw!0000Dq?;#c!KKy79e zBtQRO-}mipGyGcYrR5(vvlfO$sQ547Tdsq@dhLj>HG)NE>QCLG$RJat?*iw7e z5dH=J4GnHBmCzitNI`RmP!TN+Q9@`T6+~F#>wQigx%h?Wy?^JPdwzVs_vox`jd}N_ zcegLv(~2az4Twj(Od#Ae4|kr}>KDVuNA=51ra~?5x%}v1?djX_5!{KXRIhw9>7dvo zWp!qkM?B3ao`2|t#f|<}vmTJMmB)P(i4B_C^NY#etV&ld8RwP;BzSn#k@kVzG96W_ zKN6g|T98#CRe!jk71={WaanA%wV-l-;@p&P3u4o`Gd7)Y#PXpuNPx3bI94dJDlKPs zZ``AT2Fa-pXxU=gck~5-1Y{Zq(pq+CyIm2^HafMzPXQN>Do#9{mdaXzs>+|sIwz_T zK9@s8$wiJ$rCyYafK{skIAil_&|uFeo0Hg)$p<&h^?!Upvf3|#|LpM1y*Pb!HMtFE zCpYQg#87^8MIby$hBnZnLy7^;p(~jQgtu}88Z3JF!MWPHXhdQ_Ou6*=_X^B74kl6e z_ApUdHR^=?8wr3PLg`9&ptE5Q8ASlyi<5Q{PesCE4;6rp-4#ftLakrYkre(cUw|P1 P0000}I zy)vK(d5YlzwD~x1a;_tY2!%i1PwV`>FZq2nIoA=zKrk>}R9$2|Ql(l#L^1GJr_2tJ zST_^|ZMv%ahu{JN;^8amP!D@+P_+e>>gT5x52dLl(UlK%%FTPcE% T#an{_0000$4g delta 494 zcmVpGok>JNR5(vvk}*pIK@f!}OKe3&P;6qlQ}h%eWk4b-Vr3Js7yJb& zY-}tnQdn78SPH3yaQYJj(P)v*1VI#nrm>NDns1UhHuqp}_J4Ng&Age}OliH8kv_uZ zFrJ;8k(=n&ZEbJH?{6OxECo`#s=3NhC2qIclAF)rD$#YM7JpaXFUKrB@c_B|`6b-> z%W#Bh=+&}ppo!+K$g06|DX zL#_0Ys2n0Q7(3H{Uy*^n0Bib4mcsOL?II7bS&F7{D zrwnzpASMlhWwj>_NYZ#}=u?K1yaOMQed#P~9H0!pk;#P*t8@6PU@G=jxHGkmDE!zc kEj!kNnDwVJm`2t818JEa&V$%T0000007*qoM6N<$g1q(H*Z=?k diff --git a/common/src/main/resources/assets/minecraft/textures/block/mangrove_leaves.png b/common/src/main/resources/assets/minecraft/textures/block/mangrove_leaves.png index 8891092c057c0bab27a8720fcecc770d8b72c657..1fe0289ad99948fab6ceb58e471ded1c29b15810 100644 GIT binary patch delta 240 zcmV(>9qMn;Sv4CTXdM|=B!K$~=c!axiH zQPlG;^h!BvnZPSkSwJSr`bU#ZEt>qC6TP*>IHdXWJog+0-2macaRBhKiQ<@gG0Jt?E!AK2YkQFrSl^9ur(d=ER#%MbNcxjPAwQB-^D@VYt4Jtt2 zG8i|2DB##PYBbIb0I?>$fc^8xt+btcT7cKLj;gy%ll}p?0W=f@0NAwCZ_2r$0LdxH q4pGUP(kjR5(vNGT$UtqH036yyrw6yX$0 z8;N=glp+#Rs8NcDVmZXZK9j#Pqx>R{J>&mvkN4tXKL2@KE`RN5wMy2bD2fhW_}K5Q zX&Nib(rzXbYulDP_Pk#ExhKiFo6S1x>t^G=NFzuVuE*m)0{%o?jYdQAWuL$~Rxt^H ze+ne#WPtcfUHgBLaF2L8mw;M?e6@Hxow^Uw=iy*)+if~`NDSrgesEDpbAUMYQ}vQa z{5l@};C;9A*MFcypbF%J`^CcTpbWTxngAinaq&=sxN>|3r^AkN0MEo=1ytBZmMykN9MWMA2xucU*t)LWvdREOT8T693wgQyx6n~V zRaG4%d{1M>lpB!9X|L_t(|+G5B^$@mY#P7Y3tJNN7a(=%qy02EFKC=|mW3_x?~ z&yK&3zyk@wfY2_Q8kN0+xj(<>9ZU{R?(ji^2PY^tr*Go9&ly6M2#C1q>X2PQ^{#@0 z-EN4T5{SD!?r=d6dz~W?>-j|Qf~-5g=98;;xV9q4cU0QRI8eOgYqhxs1ucNRr3fw; zXRo+kWqRVBu-CaAL6B`#!Zl3XoY@slH`SIL_Yn8~kALKBD|@PNZKNOJ=nsgR4#H~c R(ait=002ovPDHLkV1g>RXHEbB delta 441 zcmV;q0Y?7w0n-DJB!2;OQb$4nuFf3k00004XF*Lt006O%3;baP00009a7bBm000id z000id0mpBsWB>pGXh}ptR5(v{@5NGEoqKp#TtJE*3+e)N!==*j(vkMKDJRrkc%CJ$y_rDiJkKO`%j-g^88STIK*V1uS5E|-r zVkD%J5E(E@eJIt~g;6tA#6DxZ1B*yJQsWIlOs!3s-!eKVx4ad=$o5B4x?pe_kU^5L jP}zJwr1DtJqUrwtmmW}rrdQN!00000NkvXXu0mjfg!#XD diff --git a/common/src/main/resources/assets/minecraft/textures/block/pale_oak_leaves.png b/common/src/main/resources/assets/minecraft/textures/block/pale_oak_leaves.png new file mode 100644 index 0000000000000000000000000000000000000000..7a0f81ba2c77c692168d664001943504a227cd0b GIT binary patch literal 266 zcmV+l0rmcgP)84wK^ycf46yBV199hl%y2+A+;)RI0L|X)2iqJGajIp$ Q!2kdN07*qoM6N<$f|37nvj6}9 literal 0 HcmV?d00001 diff --git a/common/src/main/resources/assets/minecraft/textures/block/spruce_leaves.png b/common/src/main/resources/assets/minecraft/textures/block/spruce_leaves.png index 8bd6cdba419c5009847907f14bedb3a24b91cf8d..d37a65140fd9616671ec078d0d6e7ba17246b42d 100644 GIT binary patch delta 202 zcmV;*05$)(1LgscB!8_*L_t(|+G6PH>iVyyrp5@O{r&v`m6Cx81R)Frw>EpQH?i4! zaxfr+`v1U@i^j~TKIC)GXNisp8HuR$`I@ zNJMWZmqKWjA1yI}Xnp0f#DI9QbV89^_hmLVcDvRc6*Vr zs%T$dWTg7@71A^e{CkC1#(Ub))<(9pGO-V#SR5(v{lCiCWKn#Zc=#er2H5*V-BBXR#f(Z~)AViH&2FL=4 zB6TV%c3=P|AmOu~c_-e*jn8M_{hxg&bzQ&XIR3%kZnw9^eShD-yRJKOc#Cb1nqZnH zDa&%Z-|s=1rh+HlP1?2%qA1#AH#{-9s;V~5VL_H<-a^gpcD-KZa=EB4ieekbQPy>p zd7iaKaafR~kR%lBq4az{CC_s)Z+S=%ItmR5Pt1kGFi4Ulx{u=H@zAx~j$Y6-O<+iP z`gdnSjJfS#2Y-R}&qaoIo{5i=wLZt|^%7IlirsuO!8nM}ZzzF_iaML$)Sz(>>kQ7Q zY@TOA3{e<{O2)pmsxA0Xxo}w)jjfo&MoLUhe}@*k$60=KEFbtK;M4ha9Cr9!$C~BTvo?% z92pD-YKKB0`I+qTc+`J99`bxX#pmy_T8$%XTmfdYnM|is+xBX;l0YD!KE|ro z>-LUzr_<3QhV7I}CA~l;B&AX*^#O!Rup^O(o{2<4&tkDCCKwFbxv-pQG^#dsu-R;E zPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi z!vFvd!vV){sAK>D0;x$vK~y+TJyXdq!%+~Pj#5&~fmm8WaFzZA4o)PJI!HK(YokuY z5=s37(l|&&k0NS|I5;>o5>66ly990RM2pf?EyMc;{gUQ4^MAd0^Je*K2?PS)Gcz+h zG&CfMM1t1W*Xiu+jOh=}&CL=Dg)%-+RaGVD=jS{yFd+Q&^rU2AVIjT0zw717%1Tkv z;PCK}N=r-C_xXG>Iyx$d1D2MS$hx|^s@eDU_CRn&MTK;Ab%+q z+S-~J(8a|C-GAQR^2o@De0+RRrZil}_vYq?5om&xFDNJ=REZz>R4S$X*Vh+4K0f{? z@Or({)6qGuhVJg}cx-Hp4V1sWzSgAb6%`dJ0hE=M30}9`?dqEW zY860rb+s%nFLPa8oy^V6F=kMgnN5?B|AHtCMG8I zk%oo_+1=e$+u?AC&1R#ltSsWTwl=D*tp!gV9Uau&-Ay$$HG)^~@9&lG>+2JoIi1c- zo8fSnb8>Qs&5G#w_*ltgGKmBF{QS%e5qNlbpo4=0y1KghpBZ^P9(jFzCEnlPr_Iex z4cyq+pnr5aO_!IKjPe#178s&IdseGeW8gnOKNBuPOxB&99h9n>Q5GsFE-n@%1I&!b zOkT#v$2A69h{a+wKR^HLyWMVl2~vj2V4DEOAYtQWtP+59T`m_*Pfx4h`uqEZPft(v z5EC~&sy#S3D3C2|zu%vcyU_yW<>jdjA*mAoiFU=sMeQHJ_vq+IZQx&20OmD3JS^DO z}@*k$60=KEFbtK;M4ha9Cr9!$C~BTvo?% z92pD-YKKB0`I+qTc+`J99`bxX#pmy_T8$%XTmfdYnM|is+xBX;l0YD!KE|ro z>-LUzr_<3QhV7I}CA~l;B&AX*^#O!Rup^O(o{2<4&tkDCCKwFbxv-pQG^#dsu-R;E zPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi z!vFvd!vV){sAK>D0;x$vK~y+TJyXdq!%+~Pj#5&~fmm8WaFzZA4o)PJI!HK(YokuY z5=s37(l|&&k0NS|I5;>o5>66ly990RM2pf?EyMc;{gUQ4^MAd0^Je*K2?PS)Gcz+h zG&CfMM1t1W*Xiu+jOh=}&CL=Dg)%-+RaGVD=jS{yFd+Q&^rU2AVIjT0zw717%1Tkv z;PCK}N=r-C_xXG>Iyx$d1D2MS$hx|^s@eDU_CRn&MTK;Ab%+q z+S-~J(8a|C-GAQR^2o@De0+RRrZil}_vYq?5om&xFDNJ=REZz>R4S$X*Vh+4K0f{? z@Or({)6qGuhVJg}cx-Hp4V1sWzSgAb6%`dJ0hE=M30}9`?dqEW zY860rb+s%nFLPa8oy^V6F=kMgnN5?B|AHtCMG8I zk%oo_+1=e$+u?AC&1R#ltSsWTwl=D*tp!gV9Uau&-Ay$$HG)^~@9&lG>+2JoIi1c- zo8fSnb8>Qs&5G#w_*ltgGKmBF{QS%e5qNlbpo4=0y1KghpBZ^P9(jFzCEnlPr_Iex z4cyq+pnr5aO_!IKjPe#178s&IdseGeW8gnOKNBuPOxB&99h9n>Q5GsFE-n@%1I&!b zOkT#v$2A69h{a+wKR^HLyWMVl2~vj2V4DEOAYtQWtP+59T`m_*Pfx4h`uqEZPft(v z5EC~&sy#S3D3C2|zu%vcyU_yW<>jdjA*mAoiFU=sMeQHJ_vq+IZQx&20OmD3JS^DO z}@*k$60=KEFbtK;M4ha9Cr9!$C~BTvo?% z92pD-YKKB0`I+qTc+`J99`bxX#pmy_T8$%XTmfdYnM|is+xBX;l0YD!KE|ro z>-LUzr_<3QhV7I}CA~l;B&AX*^#O!Rup^O(o{2<4&tkDCCKwFbxv-pQG^#dsu-R;E zPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi z!vFvd!vV){sAK>D0;x$vK~y+TJyXdq!%+~Pj#5&~fmm8WaFzZA4o)PJI!HK(YokuY z5=s37(l|&&k0NS|I5;>o5>66ly990RM2pf?EyMc;{gUQ4^MAd0^Je*K2?PS)Gcz+h zG&CfMM1t1W*Xiu+jOh=}&CL=Dg)%-+RaGVD=jS{yFd+Q&^rU2AVIjT0zw717%1Tkv z;PCK}N=r-C_xXG>Iyx$d1D2MS$hx|^s@eDU_CRn&MTK;Ab%+q z+S-~J(8a|C-GAQR^2o@De0+RRrZil}_vYq?5om&xFDNJ=REZz>R4S$X*Vh+4K0f{? z@Or({)6qGuhVJg}cx-Hp4V1sWzSgAb6%`dJ0hE=M30}9`?dqEW zY860rb+s%nFLPa8oy^V6F=kMgnN5?B|AHtCMG8I zk%oo_+1=e$+u?AC&1R#ltSsWTwl=D*tp!gV9Uau&-Ay$$HG)^~@9&lG>+2JoIi1c- zo8fSnb8>Qs&5G#w_*ltgGKmBF{QS%e5qNlbpo4=0y1KghpBZ^P9(jFzCEnlPr_Iex z4cyq+pnr5aO_!IKjPe#178s&IdseGeW8gnOKNBuPOxB&99h9n>Q5GsFE-n@%1I&!b zOkT#v$2A69h{a+wKR^HLyWMVl2~vj2V4DEOAYtQWtP+59T`m_*Pfx4h`uqEZPft(v z5EC~&sy#S3D3C2|zu%vcyU_yW<>jdjA*mAoiFU=sMeQHJ_vq+IZQx&20OmD3JS^DO z%(s+lli}$+1;otOPlA}@;vvIisSey%QDX|%YyI2 zx~~2&O;gXos{oe*AQZz4ZQFJkAV%ztBASovgaF10VA3I7*MIqU(=;|slY@f!@fbRd zXuzrf#KSOHRaML#Ub-9mdcSu89VTQ$felka4j@F7778svnmB4TwiGYKR4G8srfMMR5V ztgfJFQACX%T`hI}Ua=}&WvxoZp4DQPiYRqmYP;06mR7LroAABcbM|=7_CGS`<=uDR zy}x(g@7;Hk*^rttD=0855Q3l}O_Ev%`Y}uk7zw^3o^@lOyG5pJj2cZ86aWUmlI07r z7#q-7gJd7p2xtV*6@xYo$czos!dQOZz68jVdgX0Ep4Mx__y|}&P!Q;MgC+#>c+ejK zjat{U)6C5h!mBGQLzB5&)Pc> z5rdTW)0S^Qd~2zMgj7vJ0^9De87VUfLGIlvx2lpZ$A#A|UU-lL2ZxT$`8c)m>-~x0 z*%LOVM&1srMt5eu@Y9Bsj(=^|N2gm_t{U!qRJUNh|NQtdUC^?eWjcE6xb7X}Gp@Mp zUuhOXiM61+AHmyA7hrGo_-r z;gO-qi=thE>64UDE zJW8-RNVb?K<{@yRo5~e(VglJp2Vq2Y>Y2R|V5H(?(X<`q^Ia|%&n4p7945X%p-}J< zAzvtjfd%Z$v(lIwwmK&>5Iq=b(uq4LJ5AZFYz7lE*m7tUhXdx>eeqfBT5UhP)!EAe z;DhhR?0f+a;ae>H!5&UJF&BXJ2K2KYPJN!8D-fAxknhuHS>9z(iT>UN1oQ5KEde4v;k@HfF}Voxy7FFBu940)CTE$`3M+qBF5vW(hgaWw~5s6?CDaT>4h*ZFsLau-nxEvv6xJYD> z7zROUtWFxU;v@qFzmhZAHXgXyiE-WldtP_P+5EXL$10PyO;UeE*wiP1KP-exnaIQ><(e_9K!6M@l~ z8ly=7+FytDeRUWHWf|!SpMlfb2+ElEzpRgq^wiI!0%F7k1}TQYQUpO@u~aHy zQb7u10-+HR8|4a8Bp!9m^<{;xufJeX;NKfxC6nmQYptwV=U0Eb^3V<-c5G)k_ zCt>{F4fC0r(YIqI|GzjsiAJ> z*?Zwo8PJTfCoOP7Q1E4@v98(<6F})tYqW{}kNrb~q}*Y5rWrFOc zxYvs#{AAy)Jv(ygyTzHs8B6TwPJ?gCGC_0Aa<1CBDy~f@yq!?6+7z_!Wyl-CM`0yX z*F0dy7X=Hc+Ttm?F!LAjF|&eyEN_i*w>P(+>v-Se@w~gO^npF;bVi+ijPHKk9}dh2 z`06=&;hl!I-1ATC>im+q8P%blPuB-IB;mo&7S8l+{nNQ~`x>7ppk2FmDLd^E`W6Op--;izzN-s5fByWODIvy_6U#}~w_S60@FTjPo+&8QMZH~{UXZCvhC(;h z9g$TXn>dsEytcO9m7gC~4xOv1*{_eP_HFyjw0Z7}tm+$aKJ~vkodJJxx%0aRt{4BR z6Ww;#-@F+NUZqN|wwAc}6+X_(s_rho@#yY}_x=4pF^X1peC2OzUe)@2O+&}x$b-5K zZFKVM#Xqi)LrqOhpHCk>a?OM_PufbFp2R%s9F>th)^~Nl}@*k$60=KEFbtK;M4ha9Cr9!$C~BTvo?% z92pD-YKKB0`I+qTc+`J99`bxX#pmy_T8$%XTmfdYnM|is+xBX;l0YD!KE|ro z>-LUzr_<3QhV7I}CA~l;B&AX*^#O!Rup^O(o{2<4&tkDCCKwFbxv-pQG^#dsu-R;E zPx#1ZP1_K>z@;j|==^1poj532;bRa{vGi z!vFvd!vV){sAK>D0;x$vK~y+TJyXdq!%+~Pj#5&~fmm8WaFzZA4o)PJI!HK(YokuY z5=s37(l|&&k0NS|I5;>o5>66ly990RM2pf?EyMc;{gUQ4^MAd0^Je*K2?PS)Gcz+h zG&CfMM1t1W*Xiu+jOh=}&CL=Dg)%-+RaGVD=jS{yFd+Q&^rU2AVIjT0zw717%1Tkv z;PCK}N=r-C_xXG>Iyx$d1D2MS$hx|^s@eDU_CRn&MTK;Ab%+q z+S-~J(8a|C-GAQR^2o@De0+RRrZil}_vYq?5om&xFDNJ=REZz>R4S$X*Vh+4K0f{? z@Or({)6qGuhVJg}cx-Hp4V1sWzSgAb6%`dJ0hE=M30}9`?dqEW zY860rb+s%nFLPa8oy^V6F=kMgnN5?B|AHtCMG8I zk%oo_+1=e$+u?AC&1R#ltSsWTwl=D*tp!gV9Uau&-Ay$$HG)^~@9&lG>+2JoIi1c- zo8fSnb8>Qs&5G#w_*ltgGKmBF{QS%e5qNlbpo4=0y1KghpBZ^P9(jFzCEnlPr_Iex z4cyq+pnr5aO_!IKjPe#178s&IdseGeW8gnOKNBuPOxB&99h9n>Q5GsFE-n@%1I&!b zOkT#v$2A69h{a+wKR^HLyWMVl2~vj2V4DEOAYtQWtP+59T`m_*Pfx4h`uqEZPft(v z5EC~&sy#S3D3C2|zu%vcyU_yW<>jdjA*mAoiFU=sMeQHJ_vq+IZQx&20OmD3JS^DO zSB!A6GL_t(|+G0>sQ)8SpYu0}d?eFgoXp0U^G=@SL0B5Cth3?R$ zIzhMX;lT$I9`I_^V50rx@cX{vv6lHf&-4F=@caEfgu#gVlY3Lko_Jl?2cP2fx0{J+ zB7EQXN5y*F4aN^!$i1@~PP>T#cH9FgZRK_ld+$S@?$Sd3R$~TGXgqOa19l$Cw!3T8 zDB|J7LE3&FOAUuWSUTpGib+I4R5(vjg{eoWRP?6A0igcfF!&*C%jI&w zHUZ$T*UJ(j-)=VptMc`Fb#2>PF!Fl6wn8>H?E+#9vMLF{EX$m1mT4q>bzM6OPDX<7 z_q$bs6hQFlbbp%KR^sh;YdD)5BMQ|-*yxN(ybD3-8i@jj!(p&Bu2!qBRujwcScV^u z$3J?)M{V&iw5!+GIC>*0Hcc}9UvnBq(kPXGDjr5-GP2n-2l4w9LwAQ7|9`o Date: Sun, 15 Dec 2024 13:57:26 -0600 Subject: [PATCH 032/215] Clean up BitArray class --- .../mods/sodium/client/util/MathUtil.java | 14 ++ .../client/util/collections/BitArray.java | 152 +++--------------- 2 files changed, 35 insertions(+), 131 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java index 5a11ae1355..01d267e128 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java @@ -12,6 +12,20 @@ public static long toMib(long bytes) { return bytes / (1024L * 1024L); // 1 MiB = 1048576 (2^20) bytes } + /** + *

Rounds the integer {@param num} up to the next multiple of {@param alignment}. This multiple *MUST* be + * a power-of-two, or undefined behavior will occur.

+ * + * @param num The number to round up + * @param alignment The power-of-two multiple to round to + * @return The rounded number + */ + public static int align(int num, int alignment) { + int additive = alignment - 1; + int mask = ~additive; + return (num + additive) & mask; + } + /** * Converts a float to a comparable integer value. This is used to compare * floating point values by their int bits (for example packed in a long). diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/BitArray.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/BitArray.java index 2fb9d6252a..f018b18e6c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/BitArray.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/BitArray.java @@ -1,5 +1,7 @@ package net.caffeinemc.mods.sodium.client.util.collections; +import net.caffeinemc.mods.sodium.client.util.MathUtil; + import java.util.Arrays; /** @@ -10,30 +12,14 @@ public class BitArray { private static final int ADDRESS_BITS_PER_WORD = 6; private static final int BITS_PER_WORD = 1 << ADDRESS_BITS_PER_WORD; private static final int BIT_INDEX_MASK = BITS_PER_WORD - 1; - private static final long WORD_MASK = 0xFFFFFFFFFFFFFFFFL; + private static final long WORD_MASK = -1L; private final long[] words; - private final int count; - - /** - * Returns {@param num} aligned to the next multiple of {@param alignment}. - * - * Taken from https://github.com/CaffeineMC/sodium/blob/1.19.x/next/components/gfx-utils/src/main/java/net/caffeinemc/gfx/util/misc/MathUtil.java - * - * @param num The number that will be rounded if needed - * @param alignment The multiple that the output will be rounded to (must be a - * power-of-two) - * @return The aligned position, either equal to or greater than {@param num} - */ - private static int align(int num, int alignment) { - int additive = alignment - 1; - int mask = ~additive; - return (num + additive) & mask; - } + private final int capacity; - public BitArray(int count) { - this.words = new long[(align(count, BITS_PER_WORD) >> ADDRESS_BITS_PER_WORD)]; - this.count = count; + public BitArray(int capacity) { + this.words = new long[(MathUtil.align(capacity, BITS_PER_WORD) >> ADDRESS_BITS_PER_WORD)]; + this.capacity = capacity; } public boolean get(int index) { @@ -64,13 +50,14 @@ public void set(int startIdx, int endIdx) { long firstWordMask = WORD_MASK << startIdx; long lastWordMask = WORD_MASK >>> -endIdx; + if (startWordIndex == endWordIndex) { this.words[startWordIndex] |= (firstWordMask & lastWordMask); } else { this.words[startWordIndex] |= firstWordMask; for (int i = startWordIndex + 1; i < endWordIndex; i++) { - this.words[i] = 0xFFFFFFFFFFFFFFFFL; + this.words[i] = -1L; } this.words[endWordIndex] |= lastWordMask; @@ -86,128 +73,33 @@ public void unset(int startIdx, int endIdx) { long firstWordMask = ~(WORD_MASK << startIdx); long lastWordMask = ~(WORD_MASK >>> -endIdx); + if (startWordIndex == endWordIndex) { this.words[startWordIndex] &= (firstWordMask & lastWordMask); } else { this.words[startWordIndex] &= firstWordMask; for (int i = startWordIndex + 1; i < endWordIndex; i++) { - this.words[i] = 0x0000000000000000L; + this.words[i] = 0L; } this.words[endWordIndex] &= lastWordMask; } } - // FIXME - /* public boolean checkUnset(int startIdx, int endIdx) { - int startWordIndex = wordIndex(startIdx); - int endWordIndex = wordIndex(endIdx - 1); - - long firstWordMask = ~(WORD_MASK << startIdx); - long lastWordMask = ~(WORD_MASK >>> -endIdx); - if (startWordIndex == endWordIndex) { - return (this.words[startWordIndex] & firstWordMask & lastWordMask) == 0x0000000000000000L; - } else { - if ((this.words[startWordIndex] & firstWordMask) != 0x0000000000000000L) { - return false; - } - - for (int i = startWordIndex + 1; i < endWordIndex; i++) { - if (this.words[i] != 0x0000000000000000L) { - return false; - } - } - - return (this.words[endWordIndex] & lastWordMask) == 0x0000000000000000L; - } - }*/ - - public void copy(BitArray src, int startIdx, int endIdx) { - int startWordIndex = wordIndex(startIdx); - int endWordIndex = wordIndex(endIdx - 1); - - long firstWordMask = WORD_MASK << startIdx; - long lastWordMask = WORD_MASK >>> -endIdx; - if (startWordIndex == endWordIndex) { - long combinedMask = firstWordMask & lastWordMask; - long invCombinedMask = ~combinedMask; - this.words[startWordIndex] = (this.words[startWordIndex] & invCombinedMask) - | (src.words[startWordIndex] & combinedMask); - } else { - long invFirstWordMask = ~firstWordMask; - long invLastWordMask = ~lastWordMask; - - this.words[startWordIndex] = (this.words[startWordIndex] & invFirstWordMask) - | (src.words[startWordIndex] & firstWordMask); - - int length = endWordIndex - (startWordIndex + 1); - if (length > 0) { - System.arraycopy( - src.words, - startWordIndex + 1, - this.words, - startWordIndex + 1, - length); - } - - this.words[endWordIndex] = (this.words[endWordIndex] & invLastWordMask) - | (src.words[endWordIndex] & lastWordMask); - } - } - - public void copy(BitArray src, int index) { - int wordIndex = wordIndex(index); - long invBitMask = 1L << bitIndex(index); - long bitMask = ~invBitMask; - this.words[wordIndex] = (this.words[wordIndex] & bitMask) | (src.words[wordIndex] & invBitMask); - } - - public void and(BitArray src, int startIdx, int endIdx) { - int startWordIndex = wordIndex(startIdx); - int endWordIndex = wordIndex(endIdx - 1); - - long firstWordMask = WORD_MASK << startIdx; - long lastWordMask = WORD_MASK >>> -endIdx; - if (startWordIndex == endWordIndex) { - long combinedMask = firstWordMask & lastWordMask; - long invCombinedMask = ~combinedMask; - this.words[startWordIndex] &= (src.words[startWordIndex] | invCombinedMask); - } else { - long invFirstWordMask = ~firstWordMask; - long invLastWordMask = ~lastWordMask; - - this.words[startWordIndex] &= (src.words[startWordIndex] | invFirstWordMask); - - for (int i = startWordIndex + 1; i < endWordIndex; i++) { - this.words[i] &= src.words[i]; - } - - this.words[endWordIndex] &= (src.words[endWordIndex] | invLastWordMask); - } - } - - private static int wordIndex(int index) { - return index >> ADDRESS_BITS_PER_WORD; - } - - private static int bitIndex(int index) { - return index & BIT_INDEX_MASK; - } - public void fill(boolean value) { - Arrays.fill(this.words, value ? 0xFFFFFFFFFFFFFFFFL : 0x0000000000000000L); + Arrays.fill(this.words, value ? -1L : 0L); } - public void unset() { + public void unsetAll() { this.fill(false); } - public void set() { + public void setAll() { this.fill(true); } - public int count() { + public int countSetBits() { int sum = 0; for (long word : this.words) { @@ -218,7 +110,7 @@ public int count() { } public int capacity() { - return this.count; + return this.capacity; } public boolean getAndSet(int index) { @@ -262,13 +154,11 @@ public int nextSetBit(int fromIndex) { } } - public String toBitString() { - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < this.count; i++) { - sb.append(this.get(i) ? '1' : '0'); - } + private static int wordIndex(int index) { + return index >> ADDRESS_BITS_PER_WORD; + } - return sb.toString(); + private static int bitIndex(int index) { + return index & BIT_INDEX_MASK; } } From 2d2808402126c16dcf352b938a8d7f49c80e8be5 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sun, 15 Dec 2024 14:15:29 -0600 Subject: [PATCH 033/215] Use link.caffeinemc.net domain for some URLs --- .../net/caffeinemc/mods/sodium/desktop/LaunchWarn.java | 2 +- .../client/compatibility/checks/GraphicsDriverChecks.java | 8 ++++---- .../sodium/client/compatibility/checks/ModuleScanner.java | 8 ++++---- .../client/compatibility/checks/PreLaunchChecks.java | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/LaunchWarn.java b/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/LaunchWarn.java index 577c762e52..1a612a41aa 100644 --- a/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/LaunchWarn.java +++ b/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/LaunchWarn.java @@ -7,7 +7,7 @@ import java.io.IOException; public class LaunchWarn { - private static final String HELP_URL = "https://github.com/CaffeineMC/sodium/wiki/Installation"; + private static final String HELP_URL = "https://link.caffeinemc.net/guides/sodium/installation"; private static final String RICH_MESSAGE = "" + diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/GraphicsDriverChecks.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/GraphicsDriverChecks.java index 5d410dadac..d37572b0f4 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/GraphicsDriverChecks.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/GraphicsDriverChecks.java @@ -31,9 +31,9 @@ static void postContextInit(NativeWindowHandle window, GlContextInfo context) { Installed version: ###CURRENT_DRIVER### Required version: 10.18.10.5161 (or newer) - You must update your graphics card driver in order to continue.""" + Please click the 'Help' button to read more about how to fix this problem.""" .replace("###CURRENT_DRIVER###", installedVersionString), - "https://github.com/CaffeineMC/sodium/wiki/Driver-Compatibility#windows-intel-gen7"); + "https://link.caffeinemc.net/help/sodium/graphics-driver/windows/intel/gh-899"); } } @@ -53,9 +53,9 @@ static void postContextInit(NativeWindowHandle window, GlContextInfo context) { Installed version: ###CURRENT_DRIVER### Required version: 536.23 (or newer) - You must update your graphics card driver in order to continue.""" + Please click the 'Help' button to read more about how to fix this problem.""" .replace("###CURRENT_DRIVER###", installedVersionString), - "https://github.com/CaffeineMC/sodium/wiki/Driver-Compatibility#nvidia-gpus"); + "https://link.caffeinemc.net/help/sodium/graphics-driver/windows/nvidia/gh-1486"); } } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/ModuleScanner.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/ModuleScanner.java index 8107366ef7..8af1a8d70f 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/ModuleScanner.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/ModuleScanner.java @@ -105,10 +105,10 @@ You appear to be using an older version of RivaTuner Statistics Server (RTSS) wh You must either update to a newer version (7.3.4 and later) or close the RivaTuner Statistics Server application. For more information on how to solve this problem, click the 'Help' button.""", - "https://github.com/CaffeineMC/sodium/wiki/Known-Issues#rtss-incompatible"); + "https://link.caffeinemc.net/help/sodium/incompatible-software/rivatuner-statistics-server/gh-2048"); throw new RuntimeException("The installed version of RivaTuner Statistics Server (RTSS) is not compatible with Sodium, " + - "see here for more details: https://github.com/CaffeineMC/sodium/wiki/Known-Issues#rtss-incompatible"); + "see here for more details: https://link.caffeinemc.net/help/sodium/incompatible-software/rivatuner-statistics-server/gh-2048"); } } @@ -133,10 +133,10 @@ private static void checkASUSGpuTweakIII(NativeWindowHandle window) { b) Completely uninstall the ASUS GPU Tweak III application. For more information on how to solve this problem, click the 'Help' button.""", - "https://github.com/CaffeineMC/sodium/wiki/Known-Issues#asus-gtiii-incompatible"); + "https://link.caffeinemc.net/help/sodium/incompatible-software/asus-gtiii/gh-2637"); throw new RuntimeException("ASUS GPU Tweak III is not compatible with Minecraft, " + - "see here for more details: https://github.com/CaffeineMC/sodium/wiki/Known-Issues#asus-gtiii-incompatible"); + "see here for more details: https://link.caffeinemc.net/help/sodium/incompatible-software/asus-gtiii/gh-2637"); } private static @Nullable WindowsFileVersion findRTSSModuleVersion() { diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java index 70cb73e27e..5912c283d2 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java @@ -49,7 +49,7 @@ private static void checkLwjglRuntimeVersion() { .replace("###ADVICE_STRING###", advice); PlatformHelper.showCriticalErrorAndClose(null, "Sodium Renderer - Unsupported LWJGL", message, - "https://github.com/CaffeineMC/sodium/wiki/LWJGL-Compatibility"); + "https://link.caffeinemc.net/help/sodium/runtime-issue/lwjgl3/gh-2561"); } private static boolean isUsingKnownCompatibleLwjglVersion() { From 842cdd67ac9d3f4d6a36ae53ff86d0a320233c78 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sun, 15 Dec 2024 17:12:27 -0600 Subject: [PATCH 034/215] Use ShellExecuteW from message box callbacks This fixes a regression caused by 26f4263. The underlying problem is that accessing Java's AWT *after* LWJGL3 has initialized is not possible. Minecraft has a utility class which uses rundll32 internally, but we cannot access that due to classloader restrictions on NeoForge. That leaves us with having to implement the call ourselves, and simply using Shell32 directly (like we do for other Windows APIs) seems easiest. --- .../sodium/client/platform/MessageBox.java | 7 +-- .../client/platform/windows/api/Shell32.java | 57 +++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Shell32.java diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/MessageBox.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/MessageBox.java index bf0cfb47cb..fe80d7a501 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/MessageBox.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/MessageBox.java @@ -1,6 +1,7 @@ package net.caffeinemc.mods.sodium.client.platform; import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; +import net.caffeinemc.mods.sodium.client.platform.windows.api.Shell32; import net.caffeinemc.mods.sodium.client.platform.windows.api.User32; import net.caffeinemc.mods.sodium.client.platform.windows.api.msgbox.MsgBoxCallback; import net.caffeinemc.mods.sodium.client.platform.windows.api.msgbox.MsgBoxParamSw; @@ -58,11 +59,7 @@ public void showMessageBox(NativeWindowHandle window, if (helpUrl != null) { msgBoxCallback = MsgBoxCallback.create(lpHelpInfo -> { - try { - Desktop.getDesktop().browse(URI.create(helpUrl)); - } catch (IOException e) { - System.out.println("Failed to open! Giving up."); - } + Shell32.browseUrl(window, helpUrl); }); } else { msgBoxCallback = null; diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Shell32.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Shell32.java new file mode 100644 index 0000000000..5409b7fcea --- /dev/null +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Shell32.java @@ -0,0 +1,57 @@ +package net.caffeinemc.mods.sodium.client.platform.windows.api; + +import net.caffeinemc.mods.sodium.client.platform.NativeWindowHandle; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.system.JNI; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.SharedLibrary; + +import java.util.Objects; + +import static org.lwjgl.system.APIUtil.apiCreateLibrary; +import static org.lwjgl.system.APIUtil.apiGetFunctionAddressOptional; +import static org.lwjgl.system.MemoryUtil.NULL; + +public class Shell32 { + private static final SharedLibrary LIBRARY = apiCreateLibrary("shell32"); + + private static final long PFN_ShellExecuteW = apiGetFunctionAddressOptional(LIBRARY, "ShellExecuteW"); + + public static void browseUrl(@Nullable NativeWindowHandle window, String url) { + Objects.requireNonNull(url, "URL parameter must be non-null"); + + try (var stack = MemoryStack.stackPush()) { + stack.nUTF16("open", true); + var lpOperation = stack.getPointerAddress(); + + stack.nUTF16(url, true); + var lpFile = stack.getPointerAddress(); + + nShellExecuteW(window != null ? window.getWin32Handle() : NULL, + lpOperation, + lpFile, + NULL, + NULL, + 0x1 /* SW_NORMAL */); + } + } + + public static long nShellExecuteW( + /* HWND */ long hwnd, + /* LPCWSTR */ long lpOperation, + /* LPCWSTR */ long lpFile, + /* LPCWSTR */ long lpParameters, + /* LPCWSTR */ long lpDirectory, + /* INT */ int nShowCmd + ) { + return JNI.invokePPPPPP(hwnd, lpOperation, lpFile, lpParameters, lpDirectory, nShowCmd, checkPfn(PFN_ShellExecuteW)); + } + + private static long checkPfn(long pfn) { + if (pfn == NULL) { + throw new NullPointerException("Function pointer not available"); + } + + return pfn; + } +} From e03e3d7be328c0f38079ed292eeed36fdaae0f9a Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sun, 15 Dec 2024 19:09:22 -0600 Subject: [PATCH 035/215] Do not bake ambient lighting into cached per-face light data Fixes #2806 --- .../light/smooth/SmoothLightPipeline.java | 62 ++++++++++++++----- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java index 781f5780c2..1cbe1b1014 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java @@ -6,10 +6,10 @@ import net.caffeinemc.mods.sodium.client.model.light.data.QuadLightData; import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadView; import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFlags; +import net.caffeinemc.mods.sodium.client.util.DirectionUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.util.Mth; -import net.minecraft.world.level.material.FluidState; import org.joml.Vector3f; /** @@ -58,12 +58,23 @@ public class SmoothLightPipeline implements LightPipeline { */ private final float[] weights = new float[4]; + // Cached per-face brightness received from the dimension's ambient lighting. + // This data is static for a given world and dimension. + private final float[] ambientBrightnessShaded = new float[6]; + private final float[] ambientBrightnessUnshaded = new float[6]; + public SmoothLightPipeline(LightDataAccess cache) { this.lightCache = cache; for (int i = 0; i < this.cachedFaceData.length; i++) { this.cachedFaceData[i] = new AoFaceData(); } + + for (Direction direction : DirectionUtil.ALL_DIRECTIONS) { + var level = cache.getLevel(); + this.ambientBrightnessShaded[direction.ordinal()] = level.getShade(direction, true); + this.ambientBrightnessUnshaded[direction.ordinal()] = level.getShade(direction, false); + } } @Override @@ -102,6 +113,8 @@ public void calculate(ModelQuadView quad, BlockPos pos, QuadLightData out, Direc private void applyAlignedFullFace(AoNeighborInfo neighborInfo, BlockPos pos, Direction dir, QuadLightData out, boolean shade) { AoFaceData faceData = this.getCachedFaceData(pos, dir, true, shade); neighborInfo.mapCorners(faceData.lm, faceData.ao, out.lm, out.br); + + this.applyAmbientLighting(out.br, dir, shade); } /** @@ -119,6 +132,8 @@ private void applyAlignedPartialFace(AoNeighborInfo neighborInfo, ModelQuadView neighborInfo.calculateCornerWeights(cx, cy, cz, weights); this.applyAlignedPartialFaceVertex(pos, dir, weights, i, out, true, shade); } + + this.applyAmbientLighting(out.br, dir, shade); } /** @@ -150,6 +165,8 @@ private void applyParallelFace(AoNeighborInfo neighborInfo, ModelQuadView quad, this.applyInsetPartialFaceVertex(pos, dir, depth, 1.0f - depth, weights, i, out, shade); } } + + this.applyAmbientLighting(out.br, dir, shade); } /** @@ -178,6 +195,8 @@ private void applyNonParallelFace(AoNeighborInfo neighborInfo, ModelQuadView qua this.applyInsetPartialFaceVertex(pos, dir, depth, 1.0f - depth, weights, i, out, shade); } } + + this.applyAmbientLighting(out.br, dir, shade); } private void applyAlignedPartialFaceVertex(BlockPos pos, Direction dir, float[] w, int i, QuadLightData out, boolean offset, boolean shade) { @@ -257,7 +276,7 @@ private void applyIrregularFace(BlockPos blockPos, ModelQuadView quad, QuadLight final AoFaceData fd = gatherInsetFace(quad, blockPos, i, face, shade); AoNeighborInfo.get(face).calculateCornerWeights(quad.getX(i), quad.getY(i), quad.getZ(i), w); final float n = x * x; - final float a = fd.getBlendedShade(w); + final float a = fd.getBlendedShade(w) * this.getAmbientBrightness(face, shade); final float s = fd.getBlendedSkyLight(w); final float b = fd.getBlendedBlockLight(w); ao += n * a; @@ -275,7 +294,7 @@ private void applyIrregularFace(BlockPos blockPos, ModelQuadView quad, QuadLight final AoFaceData fd = gatherInsetFace(quad, blockPos, i, face, shade); AoNeighborInfo.get(face).calculateCornerWeights(quad.getX(i), quad.getY(i), quad.getZ(i), w); final float n = y * y; - final float a = fd.getBlendedShade(w); + final float a = fd.getBlendedShade(w) * this.getAmbientBrightness(face, shade); final float s = fd.getBlendedSkyLight(w); final float b = fd.getBlendedBlockLight(w); ao += n * a; @@ -293,7 +312,7 @@ private void applyIrregularFace(BlockPos blockPos, ModelQuadView quad, QuadLight final AoFaceData fd = gatherInsetFace(quad, blockPos, i, face, shade); AoNeighborInfo.get(face).calculateCornerWeights(quad.getX(i), quad.getY(i), quad.getZ(i), w); final float n = z * z; - final float a = fd.getBlendedShade(w); + final float a = fd.getBlendedShade(w) * this.getAmbientBrightness(face, shade); final float s = fd.getBlendedSkyLight(w); final float b = fd.getBlendedBlockLight(w); ao += n * a; @@ -309,29 +328,42 @@ private void applyIrregularFace(BlockPos blockPos, ModelQuadView quad, QuadLight } } - private void applySidedBrightness(AoFaceData out, Direction face, boolean shade) { - float brightness = this.lightCache.getLevel().getShade(face, shade); - float[] ao = out.ao; + /** + * Applies the "ambient" lighting from the dimension to a quad that is parallel with the block grid. + * @param brightness The array of brightnesses for each quad vertex + * @param face The facing of the quad + * @param shade Whether the quad should receive directional lighting + */ + private void applyAmbientLighting(final float[] brightness, Direction face, boolean shade) { + final float multiplier = this.getAmbientBrightness(face, shade); - for (int i = 0; i < ao.length; i++) { - ao[i] *= brightness; + for (int i = 0; i < brightness.length; i++) { + brightness[i] *= multiplier; } } + /** + * Returns the "ambient" brightness a block face receives in the world. + * @param face The block face + * @param shade Whether the block face is receiving directional light + */ + private float getAmbientBrightness(Direction face, boolean shade) { + return (shade ? this.ambientBrightnessShaded : this.ambientBrightnessUnshaded)[face.ordinal()]; + } + /** * Returns the cached data for a given facing or calculates it if it hasn't been cached. */ private AoFaceData getCachedFaceData(BlockPos pos, Direction face, boolean offset, boolean shade) { AoFaceData data = this.cachedFaceData[offset ? face.ordinal() : face.ordinal() + 6]; - if (!data.hasLightData()) { - data.initLightData(this.lightCache, pos, face, offset); - - this.applySidedBrightness(data, face, shade); - - data.unpackLightData(); + if (data.hasLightData()) { + return data; } + data.initLightData(this.lightCache, pos, face, offset); + data.unpackLightData(); + return data; } From 2ffcf5f13e916e883ad57b1b2ba12ef654ffb49c Mon Sep 17 00:00:00 2001 From: JellySquid Date: Tue, 17 Dec 2024 14:09:29 -0600 Subject: [PATCH 036/215] Switch to Parchment mappings 2024.11.17 --- buildSrc/src/main/kotlin/BuildConfig.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 7fb32a3250..3a585d8c1e 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -7,8 +7,7 @@ object BuildConfig { val FABRIC_API_VERSION: String = "0.110.0+1.21.1" // This value can be set to null to disable Parchment. - // TODO: Re-add Parchment - val PARCHMENT_VERSION: String? = null + val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ var MOD_VERSION: String = "0.6.3" From e0c7108d20909648e762a6a5f600bbfc9d1e6b41 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Tue, 17 Dec 2024 14:09:38 -0600 Subject: [PATCH 037/215] Bump version and dependencies --- buildSrc/src/main/kotlin/BuildConfig.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 3a585d8c1e..4cc7434b9f 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -2,7 +2,7 @@ import org.gradle.api.Project object BuildConfig { val MINECRAFT_VERSION: String = "1.21.1" - val NEOFORGE_VERSION: String = "21.1.83" + val NEOFORGE_VERSION: String = "21.1.90" val FABRIC_LOADER_VERSION: String = "0.16.9" val FABRIC_API_VERSION: String = "0.110.0+1.21.1" @@ -10,7 +10,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ - var MOD_VERSION: String = "0.6.3" + var MOD_VERSION: String = "0.6.4" fun createVersionString(project: Project): String { val builder = StringBuilder() From 05fa4de11025c1511ded0cda84cac853e01b89af Mon Sep 17 00:00:00 2001 From: JellySquid Date: Tue, 17 Dec 2024 15:37:50 -0600 Subject: [PATCH 038/215] Do not cache ambient brightness at initialization The world may not be assigned to the renderer at initialization, which is the case for non-terrain rendering (i.e. block entities.) Likely, there is no performance benefit to caching this data in the first place, so the easiest solution is to just remove the code. --- .../model/light/smooth/SmoothLightPipeline.java | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java index 1cbe1b1014..09f888520a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java @@ -6,7 +6,6 @@ import net.caffeinemc.mods.sodium.client.model.light.data.QuadLightData; import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadView; import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFlags; -import net.caffeinemc.mods.sodium.client.util.DirectionUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.util.Mth; @@ -58,23 +57,12 @@ public class SmoothLightPipeline implements LightPipeline { */ private final float[] weights = new float[4]; - // Cached per-face brightness received from the dimension's ambient lighting. - // This data is static for a given world and dimension. - private final float[] ambientBrightnessShaded = new float[6]; - private final float[] ambientBrightnessUnshaded = new float[6]; - public SmoothLightPipeline(LightDataAccess cache) { this.lightCache = cache; for (int i = 0; i < this.cachedFaceData.length; i++) { this.cachedFaceData[i] = new AoFaceData(); } - - for (Direction direction : DirectionUtil.ALL_DIRECTIONS) { - var level = cache.getLevel(); - this.ambientBrightnessShaded[direction.ordinal()] = level.getShade(direction, true); - this.ambientBrightnessUnshaded[direction.ordinal()] = level.getShade(direction, false); - } } @Override @@ -348,7 +336,8 @@ private void applyAmbientLighting(final float[] brightness, Direction face, bool * @param shade Whether the block face is receiving directional light */ private float getAmbientBrightness(Direction face, boolean shade) { - return (shade ? this.ambientBrightnessShaded : this.ambientBrightnessUnshaded)[face.ordinal()]; + return this.lightCache.getLevel() + .getShade(face, shade); } /** From 3c33071353ad65a77791923b92156c93fb372bd3 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Tue, 17 Dec 2024 15:40:06 -0600 Subject: [PATCH 039/215] Bump version to 0.6.5 --- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 4cc7434b9f..5b521b97db 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -10,7 +10,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ - var MOD_VERSION: String = "0.6.4" + var MOD_VERSION: String = "0.6.5" fun createVersionString(project: Project): String { val builder = StringBuilder() From 04a72b83a68c309f6f38908600ae023f9588c78b Mon Sep 17 00:00:00 2001 From: muzikbike <52297970+muzikbike@users.noreply.github.com> Date: Tue, 31 Dec 2024 18:48:50 +0000 Subject: [PATCH 040/215] Rename model texture references to match Vanilla (#2958) Custom models which extended these base models would not properly have their textures applied, as the texture references were accidentally changed. --- .../minecraft/models/block/cauldron.json | 94 +++++++++---------- .../minecraft/models/block/composter.json | 31 +++--- .../models/block/template_cauldron_full.json | 94 +++++++++---------- .../block/template_cauldron_level1.json | 94 +++++++++---------- .../block/template_cauldron_level2.json | 94 +++++++++---------- 5 files changed, 204 insertions(+), 203 deletions(-) diff --git a/common/src/main/resources/assets/minecraft/models/block/cauldron.json b/common/src/main/resources/assets/minecraft/models/block/cauldron.json index 3b18fff1ef..f1e0e005c2 100644 --- a/common/src/main/resources/assets/minecraft/models/block/cauldron.json +++ b/common/src/main/resources/assets/minecraft/models/block/cauldron.json @@ -1,21 +1,21 @@ { "ambientocclusion": false, "textures": { - "rim": "block/cauldron_top", + "top": "block/cauldron_top", "particle": "block/cauldron_side", - "outside": "block/cauldron_side", + "side": "block/cauldron_side", "inside": "block/cauldron_inner", - "feet": "block/cauldron_bottom" + "bottom": "block/cauldron_bottom" }, "elements": [ { "from": [ 0, 3, 0 ], "to": [ 16, 16, 16 ], "faces": { - "north": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "north" }, - "east": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "east" }, - "south": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "south" }, - "west": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "west" }, + "north": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "north" }, + "east": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "east" }, + "south": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "west" }, "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#inside" } } }, @@ -23,56 +23,56 @@ "from": [ 0, 16, 0 ], "to": [ 14, 16, 2 ], "faces": { - "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 14, 16, 0 ], "to": [ 16, 16, 14 ], "faces": { - "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 2, 16, 14 ], "to": [ 16, 16, 16 ], "faces": { - "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 0, 16, 2 ], "to": [ 2, 16, 16 ], "faces": { - "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 2, 4, 2 ], "to": [ 14, 16, 2 ], "faces": { - "south": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "south": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { "from": [ 14, 4, 2 ], "to": [ 14, 16, 14 ], "faces": { - "west": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "west": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { "from": [ 2, 4, 14 ], "to": [ 14, 16, 14 ], "faces": { - "north": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "north": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { "from": [ 2, 4, 2 ], "to": [ 2, 16, 14 ], "faces": { - "east": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "east": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { @@ -86,128 +86,128 @@ "from": [ 0, 0, 0 ], "to": [ 4, 3, 4 ], "faces": { - "north": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "north" }, - "west": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "west" } + "north": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "north" }, + "west": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "west" } } }, { "from": [ 0, 0, 12 ], "to": [ 4, 3, 16 ], "faces": { - "south": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "south" }, - "west": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "west" } + "south": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "west" } } }, { "from": [ 12, 0, 12 ], "to": [ 16, 3, 16 ], "faces": { - "east": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "east" }, - "south": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "south" } + "east": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "east" }, + "south": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "south" } } }, { "from": [ 12, 0, 0 ], "to": [ 16, 3, 4 ], "faces": { - "north": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "north" }, - "east": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "east" } + "north": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "north" }, + "east": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "east" } } }, { "from": [ 14, 0, 2 ], "to": [ 16, 3, 4 ], "faces": { - "south": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "west": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "down": { "uv": [ 14, 12, 16, 14 ], "texture": "#feet", "cullface": "down" } + "south": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "west": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "down": { "uv": [ 14, 12, 16, 14 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 2, 0, 0 ], "to": [ 4, 3, 2 ], "faces": { - "east": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "south": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "down": { "uv": [ 2, 14, 4, 16 ], "texture": "#feet", "cullface": "down" } + "east": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "south": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "down": { "uv": [ 2, 14, 4, 16 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 0, 0, 12 ], "to": [ 2, 3, 14 ], "faces": { - "north": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "east": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "down": { "uv": [ 0, 2, 2, 4 ], "texture": "#feet", "cullface": "down" } + "north": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "east": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "down": { "uv": [ 0, 2, 2, 4 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 12, 0, 14 ], "to": [ 14, 3, 16 ], "faces": { - "north": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "west": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "down": { "uv": [ 12, 0, 14, 2 ], "texture": "#feet", "cullface": "down" } + "north": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "west": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "down": { "uv": [ 12, 0, 14, 2 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 12, 0, 0 ], "to": [ 16, 0, 2 ], "faces": { - "down": { "uv": [ 12, 14, 16, 16 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 12, 14, 16, 16 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 0, 0, 0 ], "to": [ 2, 0, 4 ], "faces": { - "down": { "uv": [ 0, 12, 2, 16 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 0, 12, 2, 16 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 0, 0, 14 ], "to": [ 4, 0, 16 ], "faces": { - "down": { "uv": [ 0, 0, 4, 2 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 0, 0, 4, 2 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 14, 0, 12 ], "to": [ 16, 0, 16 ], "faces": { - "down": { "uv": [ 14, 0, 16, 4 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 14, 0, 16, 4 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 12, 0, 0 ], "to": [ 14, 3, 2 ], "faces": { - "south": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, - "west": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + "south": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" }, + "west": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" } } }, { "from": [ 0, 0, 2 ], "to": [ 2, 3, 4 ], "faces": { - "east": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, - "south": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + "east": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" }, + "south": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" } } }, { "from": [ 2, 0, 14 ], "to": [ 4, 3, 16 ], "faces": { - "north": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, - "east": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + "north": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" }, + "east": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" } } }, { "from": [ 14, 0, 12 ], "to": [ 16, 3, 14 ], "faces": { - "north": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" }, - "west": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" } + "north": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" }, + "west": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" } } } ] diff --git a/common/src/main/resources/assets/minecraft/models/block/composter.json b/common/src/main/resources/assets/minecraft/models/block/composter.json index 88af7aed12..0bccf7e940 100644 --- a/common/src/main/resources/assets/minecraft/models/block/composter.json +++ b/common/src/main/resources/assets/minecraft/models/block/composter.json @@ -1,9 +1,10 @@ { "parent": "minecraft:block/block", "textures": { - "rim": "block/composter_top", + "top": "block/composter_top", + "bottom": "block/composter_bottom", "particle": "block/composter_side", - "outside": "block/composter_side", + "side": "block/composter_side", "inside": "block/composter_bottom" }, "elements": [ @@ -11,67 +12,67 @@ "from": [ 0, 0, 0 ], "to": [ 16, 16, 16 ], "faces": { - "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#outside", "cullface": "north" }, - "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#outside", "cullface": "east" }, - "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#outside", "cullface": "south" }, - "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#outside", "cullface": "west" }, - "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#inside", "cullface": "down" } + "north": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "north" }, + "east": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "east" }, + "south": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 16 ], "texture": "#side", "cullface": "west" }, + "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 0, 16, 0 ], "to": [ 14, 16, 2 ], "faces": { - "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 14, 16, 0 ], "to": [ 16, 16, 14 ], "faces": { - "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 2, 16, 14 ], "to": [ 16, 16, 16 ], "faces": { - "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 0, 16, 2 ], "to": [ 2, 16, 16 ], "faces": { - "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 2, 2, 2 ], "to": [ 14, 16, 2 ], "faces": { - "south": { "uv": [ 2, 0, 14, 14 ], "texture": "#outside" } + "south": { "uv": [ 2, 0, 14, 14 ], "texture": "#side" } } }, { "from": [ 14, 2, 2 ], "to": [ 14, 16, 14 ], "faces": { - "west": { "uv": [ 2, 0, 14, 14 ], "texture": "#outside" } + "west": { "uv": [ 2, 0, 14, 14 ], "texture": "#side" } } }, { "from": [ 2, 2, 14 ], "to": [ 14, 16, 14 ], "faces": { - "north": { "uv": [ 2, 0, 14, 14 ], "texture": "#outside" } + "north": { "uv": [ 2, 0, 14, 14 ], "texture": "#side" } } }, { "from": [ 2, 2, 2 ], "to": [ 2, 16, 14 ], "faces": { - "east": { "uv": [ 2, 0, 14, 14 ], "texture": "#outside" } + "east": { "uv": [ 2, 0, 14, 14 ], "texture": "#side" } } }, { diff --git a/common/src/main/resources/assets/minecraft/models/block/template_cauldron_full.json b/common/src/main/resources/assets/minecraft/models/block/template_cauldron_full.json index 8b090ec92a..98f15eb9f9 100644 --- a/common/src/main/resources/assets/minecraft/models/block/template_cauldron_full.json +++ b/common/src/main/resources/assets/minecraft/models/block/template_cauldron_full.json @@ -1,21 +1,21 @@ { "ambientocclusion": false, "textures": { - "rim": "block/cauldron_top", + "top": "block/cauldron_top", "particle": "block/cauldron_side", - "outside": "block/cauldron_side", + "side": "block/cauldron_side", "inside": "block/cauldron_inner", - "feet": "block/cauldron_bottom" + "bottom": "block/cauldron_bottom" }, "elements": [ { "from": [ 0, 3, 0 ], "to": [ 16, 16, 16 ], "faces": { - "north": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "north" }, - "east": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "east" }, - "south": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "south" }, - "west": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "west" }, + "north": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "north" }, + "east": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "east" }, + "south": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "west" }, "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#inside" } } }, @@ -23,56 +23,56 @@ "from": [ 0, 16, 0 ], "to": [ 14, 16, 2 ], "faces": { - "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 14, 16, 0 ], "to": [ 16, 16, 14 ], "faces": { - "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 2, 16, 14 ], "to": [ 16, 16, 16 ], "faces": { - "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 0, 16, 2 ], "to": [ 2, 16, 16 ], "faces": { - "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 2, 4, 2 ], "to": [ 14, 16, 2 ], "faces": { - "south": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "south": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { "from": [ 14, 4, 2 ], "to": [ 14, 16, 14 ], "faces": { - "west": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "west": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { "from": [ 2, 4, 14 ], "to": [ 14, 16, 14 ], "faces": { - "north": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "north": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { "from": [ 2, 4, 2 ], "to": [ 2, 16, 14 ], "faces": { - "east": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "east": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { @@ -86,128 +86,128 @@ "from": [ 0, 0, 0 ], "to": [ 4, 3, 4 ], "faces": { - "north": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "north" }, - "west": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "west" } + "north": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "north" }, + "west": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "west" } } }, { "from": [ 0, 0, 12 ], "to": [ 4, 3, 16 ], "faces": { - "south": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "south" }, - "west": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "west" } + "south": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "west" } } }, { "from": [ 12, 0, 12 ], "to": [ 16, 3, 16 ], "faces": { - "east": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "east" }, - "south": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "south" } + "east": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "east" }, + "south": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "south" } } }, { "from": [ 12, 0, 0 ], "to": [ 16, 3, 4 ], "faces": { - "north": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "north" }, - "east": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "east" } + "north": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "north" }, + "east": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "east" } } }, { "from": [ 14, 0, 2 ], "to": [ 16, 3, 4 ], "faces": { - "south": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "west": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "down": { "uv": [ 14, 12, 16, 14 ], "texture": "#feet", "cullface": "down" } + "south": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "west": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "down": { "uv": [ 14, 12, 16, 14 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 2, 0, 0 ], "to": [ 4, 3, 2 ], "faces": { - "east": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "south": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "down": { "uv": [ 2, 14, 4, 16 ], "texture": "#feet", "cullface": "down" } + "east": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "south": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "down": { "uv": [ 2, 14, 4, 16 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 0, 0, 12 ], "to": [ 2, 3, 14 ], "faces": { - "north": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "east": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "down": { "uv": [ 0, 2, 2, 4 ], "texture": "#feet", "cullface": "down" } + "north": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "east": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "down": { "uv": [ 0, 2, 2, 4 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 12, 0, 14 ], "to": [ 14, 3, 16 ], "faces": { - "north": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "west": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "down": { "uv": [ 12, 0, 14, 2 ], "texture": "#feet", "cullface": "down" } + "north": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "west": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "down": { "uv": [ 12, 0, 14, 2 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 12, 0, 0 ], "to": [ 16, 0, 2 ], "faces": { - "down": { "uv": [ 12, 14, 16, 16 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 12, 14, 16, 16 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 0, 0, 0 ], "to": [ 2, 0, 4 ], "faces": { - "down": { "uv": [ 0, 12, 2, 16 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 0, 12, 2, 16 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 0, 0, 14 ], "to": [ 4, 0, 16 ], "faces": { - "down": { "uv": [ 0, 0, 4, 2 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 0, 0, 4, 2 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 14, 0, 12 ], "to": [ 16, 0, 16 ], "faces": { - "down": { "uv": [ 14, 0, 16, 4 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 14, 0, 16, 4 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 12, 0, 0 ], "to": [ 14, 3, 2 ], "faces": { - "south": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, - "west": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + "south": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" }, + "west": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" } } }, { "from": [ 0, 0, 2 ], "to": [ 2, 3, 4 ], "faces": { - "east": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, - "south": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + "east": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" }, + "south": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" } } }, { "from": [ 2, 0, 14 ], "to": [ 4, 3, 16 ], "faces": { - "north": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, - "east": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + "north": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" }, + "east": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" } } }, { "from": [ 14, 0, 12 ], "to": [ 16, 3, 14 ], "faces": { - "north": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" }, - "west": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" } + "north": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" }, + "west": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" } } }, { diff --git a/common/src/main/resources/assets/minecraft/models/block/template_cauldron_level1.json b/common/src/main/resources/assets/minecraft/models/block/template_cauldron_level1.json index afea17757e..c04f6ba3ae 100644 --- a/common/src/main/resources/assets/minecraft/models/block/template_cauldron_level1.json +++ b/common/src/main/resources/assets/minecraft/models/block/template_cauldron_level1.json @@ -1,21 +1,21 @@ { "ambientocclusion": false, "textures": { - "rim": "block/cauldron_top", + "top": "block/cauldron_top", "particle": "block/cauldron_side", - "outside": "block/cauldron_side", + "side": "block/cauldron_side", "inside": "block/cauldron_inner", - "feet": "block/cauldron_bottom" + "bottom": "block/cauldron_bottom" }, "elements": [ { "from": [ 0, 3, 0 ], "to": [ 16, 16, 16 ], "faces": { - "north": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "north" }, - "east": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "east" }, - "south": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "south" }, - "west": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "west" }, + "north": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "north" }, + "east": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "east" }, + "south": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "west" }, "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#inside" } } }, @@ -23,56 +23,56 @@ "from": [ 0, 16, 0 ], "to": [ 14, 16, 2 ], "faces": { - "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 14, 16, 0 ], "to": [ 16, 16, 14 ], "faces": { - "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 2, 16, 14 ], "to": [ 16, 16, 16 ], "faces": { - "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 0, 16, 2 ], "to": [ 2, 16, 16 ], "faces": { - "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 2, 4, 2 ], "to": [ 14, 16, 2 ], "faces": { - "south": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "south": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { "from": [ 14, 4, 2 ], "to": [ 14, 16, 14 ], "faces": { - "west": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "west": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { "from": [ 2, 4, 14 ], "to": [ 14, 16, 14 ], "faces": { - "north": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "north": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { "from": [ 2, 4, 2 ], "to": [ 2, 16, 14 ], "faces": { - "east": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "east": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { @@ -86,128 +86,128 @@ "from": [ 0, 0, 0 ], "to": [ 4, 3, 4 ], "faces": { - "north": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "north" }, - "west": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "west" } + "north": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "north" }, + "west": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "west" } } }, { "from": [ 0, 0, 12 ], "to": [ 4, 3, 16 ], "faces": { - "south": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "south" }, - "west": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "west" } + "south": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "west" } } }, { "from": [ 12, 0, 12 ], "to": [ 16, 3, 16 ], "faces": { - "east": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "east" }, - "south": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "south" } + "east": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "east" }, + "south": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "south" } } }, { "from": [ 12, 0, 0 ], "to": [ 16, 3, 4 ], "faces": { - "north": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "north" }, - "east": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "east" } + "north": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "north" }, + "east": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "east" } } }, { "from": [ 14, 0, 2 ], "to": [ 16, 3, 4 ], "faces": { - "south": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "west": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "down": { "uv": [ 14, 12, 16, 14 ], "texture": "#feet", "cullface": "down" } + "south": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "west": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "down": { "uv": [ 14, 12, 16, 14 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 2, 0, 0 ], "to": [ 4, 3, 2 ], "faces": { - "east": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "south": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "down": { "uv": [ 2, 14, 4, 16 ], "texture": "#feet", "cullface": "down" } + "east": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "south": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "down": { "uv": [ 2, 14, 4, 16 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 0, 0, 12 ], "to": [ 2, 3, 14 ], "faces": { - "north": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "east": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "down": { "uv": [ 0, 2, 2, 4 ], "texture": "#feet", "cullface": "down" } + "north": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "east": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "down": { "uv": [ 0, 2, 2, 4 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 12, 0, 14 ], "to": [ 14, 3, 16 ], "faces": { - "north": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "west": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "down": { "uv": [ 12, 0, 14, 2 ], "texture": "#feet", "cullface": "down" } + "north": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "west": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "down": { "uv": [ 12, 0, 14, 2 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 12, 0, 0 ], "to": [ 16, 0, 2 ], "faces": { - "down": { "uv": [ 12, 14, 16, 16 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 12, 14, 16, 16 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 0, 0, 0 ], "to": [ 2, 0, 4 ], "faces": { - "down": { "uv": [ 0, 12, 2, 16 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 0, 12, 2, 16 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 0, 0, 14 ], "to": [ 4, 0, 16 ], "faces": { - "down": { "uv": [ 0, 0, 4, 2 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 0, 0, 4, 2 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 14, 0, 12 ], "to": [ 16, 0, 16 ], "faces": { - "down": { "uv": [ 14, 0, 16, 4 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 14, 0, 16, 4 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 12, 0, 0 ], "to": [ 14, 3, 2 ], "faces": { - "south": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, - "west": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + "south": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" }, + "west": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" } } }, { "from": [ 0, 0, 2 ], "to": [ 2, 3, 4 ], "faces": { - "east": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, - "south": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + "east": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" }, + "south": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" } } }, { "from": [ 2, 0, 14 ], "to": [ 4, 3, 16 ], "faces": { - "north": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, - "east": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + "north": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" }, + "east": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" } } }, { "from": [ 14, 0, 12 ], "to": [ 16, 3, 14 ], "faces": { - "north": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" }, - "west": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" } + "north": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" }, + "west": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" } } }, { diff --git a/common/src/main/resources/assets/minecraft/models/block/template_cauldron_level2.json b/common/src/main/resources/assets/minecraft/models/block/template_cauldron_level2.json index 63ee8c7596..ef4fce285d 100644 --- a/common/src/main/resources/assets/minecraft/models/block/template_cauldron_level2.json +++ b/common/src/main/resources/assets/minecraft/models/block/template_cauldron_level2.json @@ -1,21 +1,21 @@ { "ambientocclusion": false, "textures": { - "rim": "block/cauldron_top", + "top": "block/cauldron_top", "particle": "block/cauldron_side", - "outside": "block/cauldron_side", + "side": "block/cauldron_side", "inside": "block/cauldron_inner", - "feet": "block/cauldron_bottom" + "bottom": "block/cauldron_bottom" }, "elements": [ { "from": [ 0, 3, 0 ], "to": [ 16, 16, 16 ], "faces": { - "north": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "north" }, - "east": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "east" }, - "south": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "south" }, - "west": { "uv": [ 0, 0, 16, 13 ], "texture": "#outside", "cullface": "west" }, + "north": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "north" }, + "east": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "east" }, + "south": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 13 ], "texture": "#side", "cullface": "west" }, "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#inside" } } }, @@ -23,56 +23,56 @@ "from": [ 0, 16, 0 ], "to": [ 14, 16, 2 ], "faces": { - "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 14, 16, 0 ], "to": [ 16, 16, 14 ], "faces": { - "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 2, 16, 14 ], "to": [ 16, 16, 16 ], "faces": { - "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 0, 16, 2 ], "to": [ 2, 16, 16 ], "faces": { - "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 2, 4, 2 ], "to": [ 14, 16, 2 ], "faces": { - "south": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "south": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { "from": [ 14, 4, 2 ], "to": [ 14, 16, 14 ], "faces": { - "west": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "west": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { "from": [ 2, 4, 14 ], "to": [ 14, 16, 14 ], "faces": { - "north": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "north": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { "from": [ 2, 4, 2 ], "to": [ 2, 16, 14 ], "faces": { - "east": { "uv": [ 2, 0, 14, 12 ], "texture": "#outside" } + "east": { "uv": [ 2, 0, 14, 12 ], "texture": "#side" } } }, { @@ -86,128 +86,128 @@ "from": [ 0, 0, 0 ], "to": [ 4, 3, 4 ], "faces": { - "north": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "north" }, - "west": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "west" } + "north": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "north" }, + "west": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "west" } } }, { "from": [ 0, 0, 12 ], "to": [ 4, 3, 16 ], "faces": { - "south": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "south" }, - "west": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "west" } + "south": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "west" } } }, { "from": [ 12, 0, 12 ], "to": [ 16, 3, 16 ], "faces": { - "east": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "east" }, - "south": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "south" } + "east": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "east" }, + "south": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "south" } } }, { "from": [ 12, 0, 0 ], "to": [ 16, 3, 4 ], "faces": { - "north": { "uv": [ 0, 13, 4, 16 ], "texture": "#outside", "cullface": "north" }, - "east": { "uv": [ 12, 13, 16, 16 ], "texture": "#outside", "cullface": "east" } + "north": { "uv": [ 0, 13, 4, 16 ], "texture": "#side", "cullface": "north" }, + "east": { "uv": [ 12, 13, 16, 16 ], "texture": "#side", "cullface": "east" } } }, { "from": [ 14, 0, 2 ], "to": [ 16, 3, 4 ], "faces": { - "south": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "west": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "down": { "uv": [ 14, 12, 16, 14 ], "texture": "#feet", "cullface": "down" } + "south": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "west": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "down": { "uv": [ 14, 12, 16, 14 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 2, 0, 0 ], "to": [ 4, 3, 2 ], "faces": { - "east": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "south": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "down": { "uv": [ 2, 14, 4, 16 ], "texture": "#feet", "cullface": "down" } + "east": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "south": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "down": { "uv": [ 2, 14, 4, 16 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 0, 0, 12 ], "to": [ 2, 3, 14 ], "faces": { - "north": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "east": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "down": { "uv": [ 0, 2, 2, 4 ], "texture": "#feet", "cullface": "down" } + "north": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "east": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "down": { "uv": [ 0, 2, 2, 4 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 12, 0, 14 ], "to": [ 14, 3, 16 ], "faces": { - "north": { "uv": [ 2, 13, 4, 16 ], "texture": "#outside" }, - "west": { "uv": [ 14, 13, 16, 16 ], "texture": "#outside" }, - "down": { "uv": [ 12, 0, 14, 2 ], "texture": "#feet", "cullface": "down" } + "north": { "uv": [ 2, 13, 4, 16 ], "texture": "#side" }, + "west": { "uv": [ 14, 13, 16, 16 ], "texture": "#side" }, + "down": { "uv": [ 12, 0, 14, 2 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 12, 0, 0 ], "to": [ 16, 0, 2 ], "faces": { - "down": { "uv": [ 12, 14, 16, 16 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 12, 14, 16, 16 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 0, 0, 0 ], "to": [ 2, 0, 4 ], "faces": { - "down": { "uv": [ 0, 12, 2, 16 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 0, 12, 2, 16 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 0, 0, 14 ], "to": [ 4, 0, 16 ], "faces": { - "down": { "uv": [ 0, 0, 4, 2 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 0, 0, 4, 2 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 14, 0, 12 ], "to": [ 16, 0, 16 ], "faces": { - "down": { "uv": [ 14, 0, 16, 4 ], "texture": "#feet", "cullface": "down" } + "down": { "uv": [ 14, 0, 16, 4 ], "texture": "#bottom", "cullface": "down" } } }, { "from": [ 12, 0, 0 ], "to": [ 14, 3, 2 ], "faces": { - "south": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, - "west": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + "south": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" }, + "west": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" } } }, { "from": [ 0, 0, 2 ], "to": [ 2, 3, 4 ], "faces": { - "east": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, - "south": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + "east": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" }, + "south": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" } } }, { "from": [ 2, 0, 14 ], "to": [ 4, 3, 16 ], "faces": { - "north": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" }, - "east": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" } + "north": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" }, + "east": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" } } }, { "from": [ 14, 0, 12 ], "to": [ 16, 3, 14 ], "faces": { - "north": { "uv": [ 0, 13, 2, 16 ], "texture": "#outside" }, - "west": { "uv": [ 12, 13, 14, 16 ], "texture": "#outside" } + "north": { "uv": [ 0, 13, 2, 16 ], "texture": "#side" }, + "west": { "uv": [ 12, 13, 14, 16 ], "texture": "#side" } } }, { From 6f07a9c566e5083e8e5c660622d3916629fec0b4 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sun, 29 Dec 2024 17:07:50 -0600 Subject: [PATCH 041/215] Do not apply optimizations to sprites with special tickers --- .../chunk/compile/pipeline/BlockRenderer.java | 19 ++++++++--- .../pipeline/TextureAtlasSpriteExtension.java | 5 +++ .../scan/TextureAtlasSpriteMixin.java | 33 +++++++++++++++++++ .../main/resources/sodium-common.mixins.json | 1 + 4 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/TextureAtlasSpriteExtension.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/TextureAtlasSpriteMixin.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 7f8e8ae031..66b941192c 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 @@ -256,14 +256,23 @@ private boolean validateQuadUVs(TextureAtlasSprite atlasSprite) { } 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 (sprite instanceof TextureAtlasSpriteExtension spriteExt) { + // Some mods may use a custom ticker which we cannot look into. To avoid problems with these mods, + // do not attempt to downgrade the render pass. + if (spriteExt.sodium$hasUnknownImageContents()) { + return pass; } - if (pass == DefaultTerrainRenderPasses.CUTOUT && !contents.sodium$hasTransparentPixels()) { - pass = DefaultTerrainRenderPasses.SOLID; + + if (sprite.contents() instanceof SpriteContentsExtension contentsExt) { + if (pass == DefaultTerrainRenderPasses.TRANSLUCENT && !contentsExt.sodium$hasTranslucentPixels()) { + pass = DefaultTerrainRenderPasses.CUTOUT; + } + if (pass == DefaultTerrainRenderPasses.CUTOUT && !contentsExt.sodium$hasTransparentPixels()) { + pass = DefaultTerrainRenderPasses.SOLID; + } } } + return pass; } } \ No newline at end of file diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/TextureAtlasSpriteExtension.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/TextureAtlasSpriteExtension.java new file mode 100644 index 0000000000..462de88a24 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/TextureAtlasSpriteExtension.java @@ -0,0 +1,5 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline; + +public interface TextureAtlasSpriteExtension { + boolean sodium$hasUnknownImageContents(); +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/TextureAtlasSpriteMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/TextureAtlasSpriteMixin.java new file mode 100644 index 0000000000..521c67bc60 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/scan/TextureAtlasSpriteMixin.java @@ -0,0 +1,33 @@ +package net.caffeinemc.mods.sodium.mixin.features.textures.scan; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline.TextureAtlasSpriteExtension; +import net.minecraft.client.renderer.texture.SpriteContents; +import net.minecraft.client.renderer.texture.SpriteTicker; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(TextureAtlasSprite.class) +public class TextureAtlasSpriteMixin implements TextureAtlasSpriteExtension { + @Unique + private boolean hasUnknownImageContents; + + @WrapOperation(method = "createTicker", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/texture/SpriteContents;createTicker()Lnet/minecraft/client/renderer/texture/SpriteTicker;")) + private SpriteTicker hookTickerInstantiation(SpriteContents instance, Operation original) { + var ticker = original.call(instance); + + if (ticker != null && !(ticker instanceof SpriteContents.Ticker)) { + this.hasUnknownImageContents = true; + } + + return ticker; + } + + @Override + public boolean sodium$hasUnknownImageContents() { + return this.hasUnknownImageContents; + } +} diff --git a/common/src/main/resources/sodium-common.mixins.json b/common/src/main/resources/sodium-common.mixins.json index a41962e84e..95a130bd95 100644 --- a/common/src/main/resources/sodium-common.mixins.json +++ b/common/src/main/resources/sodium-common.mixins.json @@ -87,6 +87,7 @@ "features.textures.mipmaps.MipmapGeneratorMixin", "features.textures.mipmaps.SpriteContentsMixin", "features.textures.scan.SpriteContentsMixin", + "features.textures.scan.TextureAtlasSpriteMixin", "workarounds.context_creation.WindowMixin", "workarounds.event_loop.RenderSystemMixin" ] From 8188df6fe02ea060cc649080e484741f3dd35f25 Mon Sep 17 00:00:00 2001 From: Apollo <102649729+Apollounknowndev@users.noreply.github.com> Date: Fri, 3 Jan 2025 23:23:47 -0500 Subject: [PATCH 042/215] Fixup model texture names for Hoppers (#2964) --- .../main/resources/assets/minecraft/models/block/hopper.json | 4 ++-- .../resources/assets/minecraft/models/block/hopper_side.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/main/resources/assets/minecraft/models/block/hopper.json b/common/src/main/resources/assets/minecraft/models/block/hopper.json index b1b655b3c9..89031c44a8 100644 --- a/common/src/main/resources/assets/minecraft/models/block/hopper.json +++ b/common/src/main/resources/assets/minecraft/models/block/hopper.json @@ -1,9 +1,9 @@ { "ambientocclusion": false, "textures": { - "rim": "block/hopper_top", + "top": "block/hopper_top", "particle": "block/hopper_outside", - "outside": "block/hopper_outside", + "side": "block/hopper_outside", "inside": "block/hopper_inside" }, "elements": [ diff --git a/common/src/main/resources/assets/minecraft/models/block/hopper_side.json b/common/src/main/resources/assets/minecraft/models/block/hopper_side.json index 10667bdac5..06de0dbb26 100644 --- a/common/src/main/resources/assets/minecraft/models/block/hopper_side.json +++ b/common/src/main/resources/assets/minecraft/models/block/hopper_side.json @@ -1,9 +1,9 @@ { "ambientocclusion": false, "textures": { - "rim": "block/hopper_top", + "top": "block/hopper_top", "particle": "block/hopper_outside", - "outside": "block/hopper_outside", + "side": "block/hopper_outside", "inside": "block/hopper_inside" }, "elements": [ From 145921c676aa6cedcaa2f981cd4d9aee190ed7c3 Mon Sep 17 00:00:00 2001 From: lowercasebtw <126462578+lowercasebtw@users.noreply.github.com> Date: Sat, 4 Jan 2025 15:53:14 -0600 Subject: [PATCH 043/215] Rewrite injections in ItemRendererMixin to be less invasive (#2968) --- .../render/vertex/VertexConsumerUtils.java | 4 +- .../render/model/item/ItemRendererMixin.java | 80 +++++++++---------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/vertex/VertexConsumerUtils.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/vertex/VertexConsumerUtils.java index 6118d3df41..978da79efd 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/vertex/VertexConsumerUtils.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/vertex/VertexConsumerUtils.java @@ -3,6 +3,8 @@ import com.mojang.blaze3d.vertex.VertexConsumer; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; +import javax.annotation.Nullable; + public class VertexConsumerUtils { /** * Attempt to convert a {@link VertexConsumer} into a {@link VertexBufferWriter}. If this fails, return null @@ -10,7 +12,7 @@ public class VertexConsumerUtils { * @param consumer the consumer to convert * @return a {@link VertexBufferWriter}, or null if the consumer does not support this */ - public static VertexBufferWriter convertOrLog(VertexConsumer consumer) { + public static @Nullable VertexBufferWriter convertOrLog(VertexConsumer consumer) { VertexBufferWriter writer = VertexBufferWriter.tryOf(consumer); if (writer == null) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/item/ItemRendererMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/item/ItemRendererMixin.java index a3b15c4252..8ea16da500 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/item/ItemRendererMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/item/ItemRendererMixin.java @@ -1,28 +1,29 @@ package net.caffeinemc.mods.sodium.mixin.features.render.model.item; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.caffeinemc.mods.sodium.api.util.ColorARGB; +import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; import net.caffeinemc.mods.sodium.client.model.quad.BakedQuadView; import net.caffeinemc.mods.sodium.client.render.immediate.model.BakedModelEncoder; import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil; import net.caffeinemc.mods.sodium.client.render.vertex.VertexConsumerUtils; -import net.caffeinemc.mods.sodium.client.model.color.interop.ItemColorsExtension; import net.caffeinemc.mods.sodium.client.util.DirectionUtil; -import net.caffeinemc.mods.sodium.api.util.ColorARGB; -import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; -import net.minecraft.client.color.item.ItemColor; import net.minecraft.client.color.item.ItemColors; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.entity.ItemRenderer; -import net.minecraft.client.resources.model.BakedModel; import net.minecraft.core.Direction; import net.minecraft.util.RandomSource; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.levelgen.SingleThreadedRandomSource; -import org.spongepowered.asm.mixin.*; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; + import java.util.List; @Mixin(ItemRenderer.class) @@ -35,48 +36,45 @@ public class ItemRendererMixin { private ItemColors itemColors; /** - * @reason Avoid allocations - * @author JellySquid + * @reason Avoid Allocations + * @return JellySquid */ - @Inject(method = "renderModelLists", at = @At("HEAD"), cancellable = true) - private void renderModelFast(BakedModel model, ItemStack itemStack, int light, int overlay, PoseStack matrixStack, VertexConsumer vertexConsumer, CallbackInfo ci) { + @WrapOperation(method = "renderModelLists", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/RandomSource;create()Lnet/minecraft/util/RandomSource;")) + private RandomSource renderModelFastRandom(Operation original) { + return this.random; + } + + /** + * @reason Avoid Allocations + * @return JellySquid + */ + @WrapOperation(method = "renderModelLists", at = @At(value = "INVOKE", target = "Lnet/minecraft/core/Direction;values()[Lnet/minecraft/core/Direction;")) + private Direction[] renderModelFastDirections(Operation original) { + return DirectionUtil.ALL_DIRECTIONS; + } + + /** + * @reason Avoid Allocations + * @return JellySquid + */ + @WrapOperation(method = "renderModelLists", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/ItemRenderer;renderQuadList(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;Ljava/util/List;Lnet/minecraft/world/item/ItemStack;II)V")) + private void renderModelFast(ItemRenderer itemRenderer, PoseStack poseStack, VertexConsumer vertexConsumer, List quads, ItemStack itemStack, int light, int overlay, Operation original) { var writer = VertexConsumerUtils.convertOrLog(vertexConsumer); if (writer == null) { + original.call(itemRenderer, poseStack, vertexConsumer, quads, itemStack, light, overlay); return; } - ci.cancel(); - - RandomSource random = this.random; - PoseStack.Pose matrices = matrixStack.last(); - - ItemColor colorProvider = null; - - if (!itemStack.isEmpty()) { - colorProvider = ((ItemColorsExtension) this.itemColors).sodium$getColorProvider(itemStack); - } - - for (Direction direction : DirectionUtil.ALL_DIRECTIONS) { - random.setSeed(42L); - List quads = model.getQuads(null, direction, random); - - if (!quads.isEmpty()) { - this.renderBakedItemQuads(matrices, writer, quads, itemStack, colorProvider, light, overlay); - } - } - - random.setSeed(42L); - List quads = model.getQuads(null, null, random); - + // TODO/NOTE: Should .last be a LocalRef? if (!quads.isEmpty()) { - this.renderBakedItemQuads(matrices, writer, quads, itemStack, colorProvider, light, overlay); + this.renderBakedItemQuads(poseStack.last(), writer, quads, itemStack, light, overlay); } } @Unique @SuppressWarnings("ForLoopReplaceableByForEach") - private void renderBakedItemQuads(PoseStack.Pose matrices, VertexBufferWriter writer, List quads, ItemStack itemStack, ItemColor colorProvider, int light, int overlay) { + private void renderBakedItemQuads(PoseStack.Pose matrices, VertexBufferWriter writer, List quads, ItemStack itemStack, int light, int overlay) { for (int i = 0; i < quads.size(); i++) { BakedQuad bakedQuad = quads.get(i); @@ -88,8 +86,8 @@ private void renderBakedItemQuads(PoseStack.Pose matrices, VertexBufferWriter wr int color = 0xFFFFFFFF; - if (colorProvider != null && quad.hasColor()) { - color = ColorARGB.toABGR((colorProvider.getColor(itemStack, quad.getColorIndex()))); + if (quad.hasColor()) { + color = ColorARGB.toABGR((this.itemColors.getColor(itemStack, quad.getColorIndex()))); } BakedModelEncoder.writeQuadVertices(writer, matrices, quad, color, light, overlay, BakedModelEncoder.shouldMultiplyAlpha()); From 42aea7e6ddbad27d36d64af4d44374679f9001e5 Mon Sep 17 00:00:00 2001 From: douira Date: Sat, 4 Jan 2025 22:53:37 +0100 Subject: [PATCH 044/215] Fix invalid texture name in Hopper model (#2966) --- .../assets/minecraft/models/block/hopper.json | 40 +++++++++---------- .../minecraft/models/block/hopper_side.json | 40 +++++++++---------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/common/src/main/resources/assets/minecraft/models/block/hopper.json b/common/src/main/resources/assets/minecraft/models/block/hopper.json index 89031c44a8..a28b8f787e 100644 --- a/common/src/main/resources/assets/minecraft/models/block/hopper.json +++ b/common/src/main/resources/assets/minecraft/models/block/hopper.json @@ -11,10 +11,10 @@ "from": [ 6, 0, 6 ], "to": [ 10, 4, 10 ], "faces": { - "north": { "uv": [ 6, 12, 10, 16 ], "texture": "#outside" }, - "east": { "uv": [ 6, 12, 10, 16 ], "texture": "#outside" }, - "south": { "uv": [ 6, 12, 10, 16 ], "texture": "#outside" }, - "west": { "uv": [ 6, 12, 10, 16 ], "texture": "#outside" }, + "north": { "uv": [ 6, 12, 10, 16 ], "texture": "#side" }, + "east": { "uv": [ 6, 12, 10, 16 ], "texture": "#side" }, + "south": { "uv": [ 6, 12, 10, 16 ], "texture": "#side" }, + "west": { "uv": [ 6, 12, 10, 16 ], "texture": "#side" }, "down": { "uv": [ 6, 6, 10, 10 ], "texture": "#inside", "cullface": "down" } } }, @@ -22,10 +22,10 @@ "from": [ 4, 4, 4 ], "to": [ 12, 10, 12 ], "faces": { - "north": { "uv": [ 4, 6, 12, 12 ], "texture": "#outside" }, - "east": { "uv": [ 4, 6, 12, 12 ], "texture": "#outside" }, - "south": { "uv": [ 4, 6, 12, 12 ], "texture": "#outside" }, - "west": { "uv": [ 4, 6, 12, 12 ], "texture": "#outside" }, + "north": { "uv": [ 4, 6, 12, 12 ], "texture": "#side" }, + "east": { "uv": [ 4, 6, 12, 12 ], "texture": "#side" }, + "south": { "uv": [ 4, 6, 12, 12 ], "texture": "#side" }, + "west": { "uv": [ 4, 6, 12, 12 ], "texture": "#side" }, "down": { "uv": [ 4, 4, 12, 12 ], "texture": "#inside" } } }, @@ -33,10 +33,10 @@ "from": [ 0, 10, 0 ], "to": [ 16, 16, 16 ], "faces": { - "north": { "uv": [ 0, 0, 16, 6 ], "texture": "#outside", "cullface": "north" }, - "east": { "uv": [ 0, 0, 16, 6 ], "texture": "#outside", "cullface": "east" }, - "south": { "uv": [ 0, 0, 16, 6 ], "texture": "#outside", "cullface": "south" }, - "west": { "uv": [ 0, 0, 16, 6 ], "texture": "#outside", "cullface": "west" }, + "north": { "uv": [ 0, 0, 16, 6 ], "texture": "#side", "cullface": "north" }, + "east": { "uv": [ 0, 0, 16, 6 ], "texture": "#side", "cullface": "east" }, + "south": { "uv": [ 0, 0, 16, 6 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 6 ], "texture": "#side", "cullface": "west" }, "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#inside" } } }, @@ -44,56 +44,56 @@ "from": [ 0, 16, 0 ], "to": [ 14, 16, 2 ], "faces": { - "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 14, 16, 0 ], "to": [ 16, 16, 14 ], "faces": { - "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 2, 16, 14 ], "to": [ 16, 16, 16 ], "faces": { - "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 0, 16, 2 ], "to": [ 2, 16, 16 ], "faces": { - "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 2, 11, 2 ], "to": [ 14, 16, 2 ], "faces": { - "south": { "uv": [ 2, 0, 14, 5 ], "texture": "#outside", "cullface": "up" } + "south": { "uv": [ 2, 0, 14, 5 ], "texture": "#side", "cullface": "up" } } }, { "from": [ 14, 11, 2 ], "to": [ 14, 16, 14 ], "faces": { - "west": { "uv": [ 2, 0, 14, 5 ], "texture": "#outside", "cullface": "up" } + "west": { "uv": [ 2, 0, 14, 5 ], "texture": "#side", "cullface": "up" } } }, { "from": [ 2, 11, 14 ], "to": [ 14, 16, 14 ], "faces": { - "north": { "uv": [ 2, 0, 14, 5 ], "texture": "#outside", "cullface": "up" } + "north": { "uv": [ 2, 0, 14, 5 ], "texture": "#side", "cullface": "up" } } }, { "from": [ 2, 11, 2 ], "to": [ 2, 16, 14 ], "faces": { - "east": { "uv": [ 2, 0, 14, 5 ], "texture": "#outside", "cullface": "up" } + "east": { "uv": [ 2, 0, 14, 5 ], "texture": "#side", "cullface": "up" } } }, { diff --git a/common/src/main/resources/assets/minecraft/models/block/hopper_side.json b/common/src/main/resources/assets/minecraft/models/block/hopper_side.json index 06de0dbb26..21973342ad 100644 --- a/common/src/main/resources/assets/minecraft/models/block/hopper_side.json +++ b/common/src/main/resources/assets/minecraft/models/block/hopper_side.json @@ -11,10 +11,10 @@ "from": [ 6, 4, 0 ], "to": [ 10, 8, 4 ], "faces": { - "north": { "uv": [ 6, 8, 10, 12 ], "texture": "#outside", "cullface": "north" }, - "east": { "uv": [ 12, 8, 16, 12 ], "texture": "#outside" }, - "west": { "uv": [ 0, 8, 4, 12 ], "texture": "#outside" }, - "up": { "uv": [ 6, 0, 10, 4 ], "texture": "#outside" }, + "north": { "uv": [ 6, 8, 10, 12 ], "texture": "#side", "cullface": "north" }, + "east": { "uv": [ 12, 8, 16, 12 ], "texture": "#side" }, + "west": { "uv": [ 0, 8, 4, 12 ], "texture": "#side" }, + "up": { "uv": [ 6, 0, 10, 4 ], "texture": "#side" }, "down": { "uv": [ 6, 12, 10, 16 ], "texture": "#inside" } } }, @@ -22,10 +22,10 @@ "from": [ 4, 4, 4 ], "to": [ 12, 10, 12 ], "faces": { - "north": { "uv": [ 4, 6, 12, 12 ], "texture": "#outside" }, - "east": { "uv": [ 4, 6, 12, 12 ], "texture": "#outside" }, - "south": { "uv": [ 4, 6, 12, 12 ], "texture": "#outside" }, - "west": { "uv": [ 4, 6, 12, 12 ], "texture": "#outside" }, + "north": { "uv": [ 4, 6, 12, 12 ], "texture": "#side" }, + "east": { "uv": [ 4, 6, 12, 12 ], "texture": "#side" }, + "south": { "uv": [ 4, 6, 12, 12 ], "texture": "#side" }, + "west": { "uv": [ 4, 6, 12, 12 ], "texture": "#side" }, "down": { "uv": [ 4, 4, 12, 12 ], "texture": "#inside" } } }, @@ -33,10 +33,10 @@ "from": [ 0, 10, 0 ], "to": [ 16, 16, 16 ], "faces": { - "north": { "uv": [ 0, 0, 16, 6 ], "texture": "#outside", "cullface": "north" }, - "east": { "uv": [ 0, 0, 16, 6 ], "texture": "#outside", "cullface": "east" }, - "south": { "uv": [ 0, 0, 16, 6 ], "texture": "#outside", "cullface": "south" }, - "west": { "uv": [ 0, 0, 16, 6 ], "texture": "#outside", "cullface": "west" }, + "north": { "uv": [ 0, 0, 16, 6 ], "texture": "#side", "cullface": "north" }, + "east": { "uv": [ 0, 0, 16, 6 ], "texture": "#side", "cullface": "east" }, + "south": { "uv": [ 0, 0, 16, 6 ], "texture": "#side", "cullface": "south" }, + "west": { "uv": [ 0, 0, 16, 6 ], "texture": "#side", "cullface": "west" }, "down": { "uv": [ 0, 0, 16, 16 ], "texture": "#inside" } } }, @@ -44,56 +44,56 @@ "from": [ 0, 16, 0 ], "to": [ 14, 16, 2 ], "faces": { - "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 0, 0, 14, 2 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 14, 16, 0 ], "to": [ 16, 16, 14 ], "faces": { - "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 14, 0, 16, 14 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 2, 16, 14 ], "to": [ 16, 16, 16 ], "faces": { - "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 2, 14, 16, 16 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 0, 16, 2 ], "to": [ 2, 16, 16 ], "faces": { - "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#rim", "cullface": "up" } + "up": { "uv": [ 0, 2, 2, 16 ], "texture": "#top", "cullface": "up" } } }, { "from": [ 2, 11, 2 ], "to": [ 14, 16, 2 ], "faces": { - "south": { "uv": [ 2, 0, 14, 5 ], "texture": "#outside", "cullface": "up" } + "south": { "uv": [ 2, 0, 14, 5 ], "texture": "#side", "cullface": "up" } } }, { "from": [ 14, 11, 2 ], "to": [ 14, 16, 14 ], "faces": { - "west": { "uv": [ 2, 0, 14, 5 ], "texture": "#outside", "cullface": "up" } + "west": { "uv": [ 2, 0, 14, 5 ], "texture": "#side", "cullface": "up" } } }, { "from": [ 2, 11, 14 ], "to": [ 14, 16, 14 ], "faces": { - "north": { "uv": [ 2, 0, 14, 5 ], "texture": "#outside", "cullface": "up" } + "north": { "uv": [ 2, 0, 14, 5 ], "texture": "#side", "cullface": "up" } } }, { "from": [ 2, 11, 2 ], "to": [ 2, 16, 14 ], "faces": { - "east": { "uv": [ 2, 0, 14, 5 ], "texture": "#outside", "cullface": "up" } + "east": { "uv": [ 2, 0, 14, 5 ], "texture": "#side", "cullface": "up" } } }, { From a93f1d29c7a338d01193b5d486da1dde3d493cd4 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Mon, 6 Jan 2025 12:10:25 -0600 Subject: [PATCH 045/215] Use correct pointer shifts on 32-bit platforms This fixes rendering glitches in translucent render passes when using a 32-bit runtime/operating system. --- .../sodium/client/render/chunk/DefaultChunkRenderer.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java index e273e61744..1d1165c705 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java @@ -25,6 +25,7 @@ import net.caffeinemc.mods.sodium.client.util.BitwiseMath; import net.caffeinemc.mods.sodium.client.util.UInt32; import org.lwjgl.system.MemoryUtil; +import org.lwjgl.system.Pointer; import java.util.Iterator; @@ -172,7 +173,7 @@ private static void addNonIndexedDrawCommands(MultiDrawBatch batch, long pMeshDa // Uint32 -> Int32 cast is always safe and should be optimized away MemoryUtil.memPutInt(pBaseVertex + (size << 2), (int) SectionRenderDataUnsafe.getVertexOffset(pMeshData, facing)); MemoryUtil.memPutInt(pElementCount + (size << 2), (int) SectionRenderDataUnsafe.getElementCount(pMeshData, facing)); - MemoryUtil.memPutAddress(pElementPointer + (size << 3), 0 /* using a shared index buffer */); + MemoryUtil.memPutAddress(pElementPointer + (size << Pointer.POINTER_SHIFT), 0 /* using a shared index buffer */); size += (mask >> facing) & 1; } @@ -204,7 +205,7 @@ private static void addIndexedDrawCommands(MultiDrawBatch batch, long pMeshData, // * 4 to convert to bytes (the index buffer contains integers) // the section render data storage for the indices stores the offset in indices (also called elements) - MemoryUtil.memPutAddress(pElementPointer + (size << 3), elementOffset << 2); + MemoryUtil.memPutAddress(pElementPointer + (size << Pointer.POINTER_SHIFT), elementOffset << 2); // adding the number of elements works because the index data has one index per element (which are the indices) elementOffset += elementCount; From ecf166b5ee368ac339ae8d84b2e559908ca6f4ab Mon Sep 17 00:00:00 2001 From: JellySquid Date: Fri, 17 Jan 2025 20:14:25 -0600 Subject: [PATCH 046/215] Try to handle exceptions during block entity iteration Other mods are mutating the block entity list from other threads and violating memory safety. This often causes crashes when we iterate over the list, and it's *very* difficult for the average user to understand why it's happening. To address the problem, this patch wraps the iterator, and wraps any uncaught exception so it can be handled by the caller. If known-problematic mods are installed, a more helpful error message will be shown to the user instead of the current cryptic message. --- .../client/util/iterator/WrappedIterator.java | 39 +++++++++++++++ .../world/cloned/ClonedChunkSection.java | 48 ++++++++++++++----- 2 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/util/iterator/WrappedIterator.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/iterator/WrappedIterator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/iterator/WrappedIterator.java new file mode 100644 index 0000000000..02543ffdfb --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/iterator/WrappedIterator.java @@ -0,0 +1,39 @@ +package net.caffeinemc.mods.sodium.client.util.iterator; + +import java.util.Iterator; + +public class WrappedIterator implements Iterator { + private final Iterator delegate; + + private WrappedIterator(Iterator delegate) { + this.delegate = delegate; + } + + public static WrappedIterator create(Iterable iterable) { + return new WrappedIterator<>(iterable.iterator()); + } + + @Override + public boolean hasNext() { + try { + return this.delegate.hasNext(); + } catch (Throwable t) { + throw new Exception("Iterator#hasNext() threw unhandled exception", t); + } + } + + @Override + public T next() { + try { + return this.delegate.next(); + } catch (Throwable t) { + throw new Exception("Iterator#next() threw unhandled exception", t); + } + } + + public static class Exception extends RuntimeException { + private Exception(String message, Throwable t) { + super(message, t); + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ClonedChunkSection.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ClonedChunkSection.java index 6390b26921..b1eff5852c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ClonedChunkSection.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ClonedChunkSection.java @@ -4,10 +4,10 @@ import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps; import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap; import net.caffeinemc.mods.sodium.client.services.*; -import net.caffeinemc.mods.sodium.client.world.PalettedContainerROExtension; +import net.caffeinemc.mods.sodium.client.util.iterator.WrappedIterator; import net.caffeinemc.mods.sodium.client.world.LevelSlice; +import net.caffeinemc.mods.sodium.client.world.PalettedContainerROExtension; import net.caffeinemc.mods.sodium.client.world.SodiumAuxiliaryLightManager; -import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.SectionPos; import net.minecraft.world.level.Level; @@ -17,18 +17,12 @@ 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.chunk.DataLayer; -import net.minecraft.world.level.chunk.LevelChunk; -import net.minecraft.world.level.chunk.LevelChunkSection; -import net.minecraft.world.level.chunk.PalettedContainer; -import net.minecraft.world.level.chunk.PalettedContainerRO; +import net.minecraft.world.level.chunk.*; import net.minecraft.world.level.levelgen.DebugLevelSource; import net.minecraft.world.level.levelgen.structure.BoundingBox; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Map; - public class ClonedChunkSection { private static final DataLayer DEFAULT_SKY_LIGHT_ARRAY = new DataLayer(15); private static final DataLayer DEFAULT_BLOCK_LIGHT_ARRAY = new DataLayer(0); @@ -67,7 +61,7 @@ public ClonedChunkSection(Level level, LevelChunk chunk, @Nullable LevelChunkSec } else { blockData = constructDebugWorldContainer(pos); } - blockEntityMap = copyBlockEntities(chunk, pos); + blockEntityMap = tryCopyBlockEntities(chunk, pos); if (blockEntityMap != null && PlatformBlockAccess.getInstance().platformHasBlockData()) { blockEntityRenderDataMap = copyBlockEntityRenderData(level, blockEntityMap); } @@ -150,6 +144,30 @@ private static DataLayer copyLightArray(Level level, LightLayer type, SectionPos return array; } + @Nullable + private static Int2ReferenceMap tryCopyBlockEntities(LevelChunk chunk, SectionPos chunkCoord) { + try { + // Some mods are violating memory safety, and the block entity iterator occasionally returns garbage results + // or otherwise throws exceptions because of this. To better diagnose these crashes, wrap the iterator + // so that we can handle any uncaught exceptions with the following special case. + return copyBlockEntities(chunk, chunkCoord); + } catch (WrappedIterator.Exception t) { + // Very infrequent check, only going to be called on game crash. Don't bother caching this. + if (PlatformRuntimeInformation.getInstance().isModInLoadingList("entityculling")) { + // The Entity Culling mod is known to mangle the block entity set, so try to attribute it directly + // if we know it's installed. Yes, this is accusatory, but we are tired of these cryptic crashes, + // and users need more information about how to resolve the problem themselves. This was the + // second-best option to outright preventing the launch of Sodium when Entity Culling is installed. + throw new RuntimeException("Failed to iterate block entities! This is *very likely* the fault of the " + + "Entity Culling mod, and cannot be fixed by Sodium. See here for more details: " + + "https://link.caffeinemc.net/help/sodium/mod-issue/entity-culling/gh-2985", t); + } else { + throw new RuntimeException("Failed to iterate block entities! This is *very likely* the fault of " + + "another misbehaving mod, not Sodium. Please check your mods list.", t); + } + } + } + @Nullable private static Int2ReferenceMap copyBlockEntities(LevelChunk chunk, SectionPos chunkCoord) { BoundingBox box = new BoundingBox(chunkCoord.minBlockX(), chunkCoord.minBlockY(), chunkCoord.minBlockZ(), @@ -157,10 +175,14 @@ private static Int2ReferenceMap copyBlockEntities(LevelChunk chunk, Int2ReferenceOpenHashMap blockEntities = null; + // Catch exceptions thrown by the iterator and handle them specially via the wrapped exception type + var it = WrappedIterator.create(chunk.getBlockEntities().entrySet()); + // Copy the block entities from the chunk into our cloned section - for (Map.Entry entry : chunk.getBlockEntities().entrySet()) { - BlockPos pos = entry.getKey(); - BlockEntity entity = entry.getValue(); + while (it.hasNext()) { + var entry = it.next(); + var pos = entry.getKey(); + var entity = entry.getValue(); if (box.isInside(pos)) { if (blockEntities == null) { From 02bcc3472279734abf20e15b95488f259ff8c1cd Mon Sep 17 00:00:00 2001 From: JellySquid Date: Fri, 17 Jan 2025 20:50:22 -0600 Subject: [PATCH 047/215] Bump version to 0.6.7-rc.1 --- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 5b521b97db..4dd1916587 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -10,7 +10,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ - var MOD_VERSION: String = "0.6.5" + var MOD_VERSION: String = "0.6.7-rc.1" fun createVersionString(project: Project): String { val builder = StringBuilder() From dc1d9138d753563efe8fb54aab63934b95754607 Mon Sep 17 00:00:00 2001 From: IMS212 Date: Thu, 23 Jan 2025 15:02:07 -0800 Subject: [PATCH 048/215] Workaround incorrect quad ordering caused by material downgrading This fixes observers appearing red on XyCraft. --- .../chunk/compile/pipeline/BlockRenderer.java | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 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 66b941192c..5d451302b7 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 @@ -43,6 +43,8 @@ import org.jetbrains.annotations.Nullable; import org.joml.Vector3f; +import java.util.Iterator; + public class BlockRenderer extends AbstractBlockRenderContext { private final ColorProviderRegistry colorProviderRegistry; private final int[] vertexColors = new int[4]; @@ -55,6 +57,7 @@ public class BlockRenderer extends AbstractBlockRenderContext { @Nullable private ColorProvider colorProvider; private TranslucentGeometryCollector collector; + private boolean allowDowngrade; public BlockRenderer(ColorProviderRegistry colorRegistry, LightPipelineProvider lighters) { this.colorProviderRegistry = colorRegistry; @@ -99,9 +102,20 @@ public void renderModel(BakedModel model, BlockState state, BlockPos pos, BlockP modelData = PlatformModelAccess.getInstance().getModelData(slice, model, state, pos, slice.getPlatformModelData(pos)); Iterable renderTypes = PlatformModelAccess.getInstance().getModelRenderTypes(level, model, state, pos, random, modelData); + this.allowDowngrade = true; + + Iterator it = renderTypes.iterator(); + var defaultType = ItemBlockRenderTypes.getChunkRenderType(state); + + while (it.hasNext()) { + this.type = it.next(); + + // TODO: This can be removed once we have a better solution for https://github.com/CaffeineMC/sodium/issues/2868 + // If the model contains any materials that are not the default, we can't allow the block to be downgraded. This avoids a potentially incorrect render order if there are overlapping quads. + if (it.hasNext() || this.type != defaultType) { + this.allowDowngrade = false; + } - for (RenderType type : renderTypes) { - this.type = type; ((FabricBakedModel) model).emitBlockQuads(this.level, state, pos, this.randomSupplier, this); } @@ -228,7 +242,7 @@ private boolean validateQuadUVs(TextureAtlasSprite atlasSprite) { } private @Nullable TerrainRenderPass attemptPassDowngrade(TextureAtlasSprite sprite, TerrainRenderPass pass) { - if (Workarounds.isWorkaroundEnabled(Workarounds.Reference.INTEL_DEPTH_BUFFER_COMPARISON_UNRELIABLE)) { + if (!allowDowngrade || Workarounds.isWorkaroundEnabled(Workarounds.Reference.INTEL_DEPTH_BUFFER_COMPARISON_UNRELIABLE)) { return null; } From eb9831f92f862f9dd1e47beb76d439f7981acda4 Mon Sep 17 00:00:00 2001 From: douira Date: Sat, 7 Dec 2024 21:04:23 +0100 Subject: [PATCH 049/215] Use correct coordinates for sorting chunk sections (#2924) Fix section and region sorting by using the correct section coordinate instead of the integer part of the camera transform, which is incorrect near the origin. Closes #2918 --- .../render/chunk/lists/ChunkRenderList.java | 10 +++++----- .../render/chunk/lists/VisibleChunkCollector.java | 15 +++++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java index 6212573773..e45638b8ad 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java @@ -4,10 +4,10 @@ import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionFlags; import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; -import net.caffeinemc.mods.sodium.client.render.viewport.CameraTransform; import net.caffeinemc.mods.sodium.client.util.iterator.ByteArrayIterator; import net.caffeinemc.mods.sodium.client.util.iterator.ByteIterator; import net.caffeinemc.mods.sodium.client.util.iterator.ReversibleByteArrayIterator; +import net.minecraft.core.SectionPos; import net.minecraft.util.Mth; import org.jetbrains.annotations.Nullable; @@ -43,10 +43,10 @@ public void reset(int frame) { // clamping the relative camera position to the region bounds means there can only be very few different distances private static final int SORTING_HISTOGRAM_SIZE = RenderRegion.REGION_WIDTH + RenderRegion.REGION_HEIGHT + RenderRegion.REGION_LENGTH - 2; - public void sortSections(CameraTransform transform, int[] sortItems) { - var cameraX = Mth.clamp((transform.intX >> 4) - this.region.getChunkX(), 0, RenderRegion.REGION_WIDTH - 1); - var cameraY = Mth.clamp((transform.intY >> 4) - this.region.getChunkY(), 0, RenderRegion.REGION_HEIGHT - 1); - var cameraZ = Mth.clamp((transform.intZ >> 4) - this.region.getChunkZ(), 0, RenderRegion.REGION_LENGTH - 1); + public void sortSections(SectionPos cameraPos, int[] sortItems) { + var cameraX = Mth.clamp(cameraPos.getX() - this.region.getChunkX(), 0, RenderRegion.REGION_WIDTH - 1); + var cameraY = Mth.clamp(cameraPos.getY() - this.region.getChunkY(), 0, RenderRegion.REGION_HEIGHT - 1); + var cameraZ = Mth.clamp(cameraPos.getZ() - this.region.getChunkZ(), 0, RenderRegion.REGION_LENGTH - 1); int[] histogram = new int[SORTING_HISTOGRAM_SIZE]; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java index 1eda00204c..89cf22cf78 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java @@ -8,7 +8,10 @@ import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; -import java.util.*; +import java.util.ArrayDeque; +import java.util.EnumMap; +import java.util.Map; +import java.util.Queue; /** * The visible chunk collector is passed to the occlusion graph search culler to @@ -67,10 +70,10 @@ private void addToRebuildLists(RenderSection section) { public SortedRenderLists createRenderLists(Viewport viewport) { // sort the regions by distance to fix rare region ordering bugs - var transform = viewport.getTransform(); - var cameraX = transform.intX >> (4 + RenderRegion.REGION_WIDTH_SH); - var cameraY = transform.intY >> (4 + RenderRegion.REGION_HEIGHT_SH); - var cameraZ = transform.intZ >> (4 + RenderRegion.REGION_LENGTH_SH); + var sectionPos = viewport.getChunkCoord(); + var cameraX = sectionPos.getX() >> RenderRegion.REGION_WIDTH_SH; + var cameraY = sectionPos.getY() >> RenderRegion.REGION_HEIGHT_SH; + var cameraZ = sectionPos.getZ() >> RenderRegion.REGION_LENGTH_SH; var size = this.sortedRenderLists.size(); if (sortItems.length < size) { @@ -95,7 +98,7 @@ public SortedRenderLists createRenderLists(Viewport viewport) { } for (var list : sorted) { - list.sortSections(transform, sortItems); + list.sortSections(sectionPos, sortItems); } return new SortedRenderLists(sorted); From 4689ab555178b9f95ff6ec462c608faed163d4f8 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Tue, 28 Jan 2025 00:17:11 -0600 Subject: [PATCH 050/215] Add Javadoc to BugChecks.java --- .../compatibility/checks/BugChecks.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/BugChecks.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/BugChecks.java index c4ef0b2a3e..e42457e2fb 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/BugChecks.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/BugChecks.java @@ -5,10 +5,58 @@ * failing checks will crash the game and prompt the user for intervention. */ class BugChecks { + /** + * Some older drivers for Intel Gen7 Graphics on Windows are defective and will never return from + * a call to
glClientWaitSync
. As there is no way to recover the main thread after the + * deadlock occurs, trying to work around this seems to be impossible. Updating the driver to version + * 15.33.53.5161 resolves the problem, and is the currently recommended solution. + * GitHub Issue + */ public static final boolean ISSUE_899 = configureCheck("issue899", true); + + /** + * Since version 526.47 of the NVIDIA Graphics Driver, the OpenGL user-mode driver will attempt to detect + * Minecraft with hard-coded logic in the driver, and enable broken optimizations (referred to as "Threaded + * Optimizations"). This will cause crashes during framebuffer initialization (and some draw calls) for unclear + * reasons, especially on systems with hybrid graphics. Furthermore, performance is often severely degraded + * due to bubbles in the NVIDIA command submission thread, especially when other mods query context state. + *

+ * Sodium can prevent the detection of Minecraft and the enablement of these unstable optimizations, but the logic + * is extensive and depends heavily on the exact operating system and graphics driver version used. Some older + * graphics driver versions have no workaround available presently. + * GitHub Issue + */ public static final boolean ISSUE_1486 = configureCheck("issue1486", true); + + /** + * Older versions of the RivaTuner Statistics Server will attempt to use legacy OpenGL functions in a Core + * profile, which causes either a crash or excessive log spam, depending on the OpenGL implementation. This is + * because RivaTuner relies on replacing the OpenGL context immediately after the application initializes it, + * but does so improperly when a No Error Context is used. This problem can't be avoided by simply configuring + * RivaTuner to not inject into Minecraft, as it *always* injects first to modify the context, and *then* disables + * itself. + * GitHub Issue + */ public static final boolean ISSUE_2048 = configureCheck("issue2048", true); + + /** + * LWJGL does not provide API stability guarantees for other libraries using it to create their own C bindings. + * Because of this, the game will crash due to breaking method/type signature changes very early at startup. We + * should not be using these "internal" classes in LWJGL, but the amount of work involved doing everything ourselves + * is astronomical. When Minecraft ships a version of OpenJDK with the Foreign Function & Memory API, we can replace + * our dependency on LWJGL and remove this check. + * GitHub Issue + */ public static final boolean ISSUE_2561 = configureCheck("issue2561", true); + + /** + * ASUS's GPU Tweak III does not correctly restore OpenGL context state after rendering its in-game overlay, + * which frequently causes graphical corruption, excessive log file spam, and crashes. These problems are + * actually reproducible without any mods installed, but it seems to be exacerbated with Sodium due to how different + * the rendering pipeline is. This problem can be avoided by configuring the application to not inject into + * Minecraft (or Java applications). + * GitHub Issue + */ public static final boolean ISSUE_2637 = configureCheck("issue2637", true); private static boolean configureCheck(String name, boolean defaultValue) { From 6b293a5657e84b6447937d6d8d3294002c13491f Mon Sep 17 00:00:00 2001 From: JellySquid Date: Tue, 4 Feb 2025 16:55:24 -0600 Subject: [PATCH 051/215] Add vendor detection for Intel Xe2 graphics Closes #3005 --- .../environment/probe/GraphicsAdapterVendor.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java index 57ddb79400..1db4d3df8a 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java @@ -18,11 +18,10 @@ public enum GraphicsAdapterVendor { // Intel Gen 8 - ig8icd // Intel Gen 9, 9.5 - ig9icd // Intel Gen 11 - ig11icd - // Intel Gen 12 - ig12icd (UHD Graphics, with early drivers) - // igxelpicd (Xe-LP; integrated) - // igxehpicd (Xe-HP; dedicated) + // Intel Xe - ig12icd (Xe-LP; early drivers) or igxelpicd (Xe-LP; later drivers), and igxehpicd (Xe-HP) + // Intel Xe2 - igxe2lpicd (Xe2-LP) and igxe2lpicd (Xe2-HP) private static final Pattern INTEL_ICD_PATTERN = - Pattern.compile("ig(4|7|75|8|9|11|12|xelp|xehp)icd(32|64)\\.dll", Pattern.CASE_INSENSITIVE); + Pattern.compile("ig(4|7|75|8|9|11|12|(xe(2)?(hp|lp)))icd(32|64)\\.dll", Pattern.CASE_INSENSITIVE); private static final Pattern NVIDIA_ICD_PATTERN = Pattern.compile("nvoglv(32|64)\\.dll", Pattern.CASE_INSENSITIVE); From 410a730ccb0db1ef7fe8f56da276a41b3506e725 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sat, 8 Feb 2025 17:32:21 -0600 Subject: [PATCH 052/215] Bump version to 0.6.7 --- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 4dd1916587..aab51ecdd8 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -10,7 +10,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ - var MOD_VERSION: String = "0.6.7-rc.1" + var MOD_VERSION: String = "0.6.7" fun createVersionString(project: Project): String { val builder = StringBuilder() From f9eee565c173ded8a0fd1cf0f105ab0bd5bc5eaf Mon Sep 17 00:00:00 2001 From: JellySquid Date: Thu, 13 Feb 2025 19:29:57 -0600 Subject: [PATCH 053/215] Move SpriteUtil to API package The existing private interface is kept intact as to not break any mods currently relying on it. --- .../mods/sodium/api/texture/SpriteUtil.java | 31 +++++++++++++++++++ .../render/chunk/RenderSectionManager.java | 6 ++-- .../buffers/BakedChunkModelBuilder.java | 3 +- .../compile/buffers/ChunkModelBuilder.java | 3 +- .../chunk/compile/pipeline/BlockRenderer.java | 4 ++- .../render/chunk/data/BuiltSectionInfo.java | 6 ++-- .../frapi/render/ItemRenderContext.java | 7 +++-- .../render/NonTerrainBlockRenderContext.java | 7 +++-- .../client/render/texture/SpriteUtil.java | 20 +++++++----- .../client/render/texture/SpriteUtilImpl.java | 23 ++++++++++++++ .../intrinsics/BufferBuilderMixin.java | 22 ++++++++----- .../render/model/item/ItemRendererMixin.java | 6 ++-- .../animations/tracking/GuiGraphicsMixin.java | 6 ++-- .../tracking/ModelBlockRendererMixin.java | 6 ++-- .../tracking/TextureAtlasMixin.java | 4 +-- .../tracking/TextureSheetParticleMixin.java | 6 ++-- .../model/block/ModelBlockRendererMixin.java | 6 ++-- .../model/block/ModelBlockRendererMixin.java | 6 ++-- 18 files changed, 128 insertions(+), 44 deletions(-) create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/texture/SpriteUtil.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/texture/SpriteUtilImpl.java diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/texture/SpriteUtil.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/texture/SpriteUtil.java new file mode 100644 index 0000000000..4f33c26a7f --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/texture/SpriteUtil.java @@ -0,0 +1,31 @@ +package net.caffeinemc.mods.sodium.api.texture; + +import net.caffeinemc.mods.sodium.api.internal.DependencyInjection; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * Utility functions for querying sprite information and updating per-frame information about sprite visibility. + */ +@ApiStatus.Experimental +public interface SpriteUtil { + SpriteUtil INSTANCE = DependencyInjection.load(SpriteUtil.class, + "net.caffeinemc.mods.sodium.client.render.texture.SpriteUtilImpl"); + + /** + * Marks the sprite as "active", meaning that it is visible during this frame and should have the animation + * state updated. Mods which perform their own rendering without the use of Minecraft's helpers will need to + * call this method once every frame, when their sprite is actively being used in rendering. + * @param sprite The sprite to mark as active + */ + void markSpriteActive(@NotNull TextureAtlasSprite sprite); + + /** + * Returns if the provided sprite has an animation. + * + * @param sprite The sprite to query an animation for + * @return {@code true} if the provided sprite has an animation, otherwise {@code false} + */ + boolean hasAnimation(@NotNull TextureAtlasSprite sprite); +} \ No newline at end of file diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index d6564985b9..b955b6b08a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -8,6 +8,7 @@ import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import it.unimi.dsi.fastutil.objects.ReferenceSet; import it.unimi.dsi.fastutil.objects.ReferenceSets; +import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.caffeinemc.mods.sodium.client.SodiumClientMod; import net.caffeinemc.mods.sodium.client.gl.device.CommandList; import net.caffeinemc.mods.sodium.client.gl.device.RenderDevice; @@ -15,8 +16,8 @@ import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildOutput; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkSortOutput; import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkBuilder; -import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkJobResult; import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkJobCollector; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkJobResult; import net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderMeshingTask; import net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderSortingTask; import net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderTask; @@ -37,7 +38,6 @@ import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.CameraMovement; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.SortTriggering; import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkMeshFormats; -import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil; import net.caffeinemc.mods.sodium.client.render.util.RenderAsserts; import net.caffeinemc.mods.sodium.client.render.viewport.CameraTransform; import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; @@ -276,7 +276,7 @@ public void tickVisibleRenders() { } for (TextureAtlasSprite sprite : sprites) { - SpriteUtil.markSpriteActive(sprite); + SpriteUtil.INSTANCE.markSpriteActive(sprite); } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/BakedChunkModelBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/BakedChunkModelBuilder.java index 36e5aad24d..3c1b798305 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/BakedChunkModelBuilder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/BakedChunkModelBuilder.java @@ -7,6 +7,7 @@ import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TranslucentGeometryCollector; import net.caffeinemc.mods.sodium.client.render.chunk.vertex.builder.ChunkMeshBufferBuilder; import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import org.jetbrains.annotations.NotNull; public class BakedChunkModelBuilder implements ChunkModelBuilder { private final ChunkMeshBufferBuilder[] vertexBuffers; @@ -24,7 +25,7 @@ public ChunkMeshBufferBuilder getVertexBuffer(ModelQuadFacing facing) { } @Override - public void addSprite(TextureAtlasSprite sprite) { + public void addSprite(@NotNull TextureAtlasSprite sprite) { this.renderData.addSprite(sprite); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/ChunkModelBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/ChunkModelBuilder.java index 637d1ce082..3b2f4e44b8 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/ChunkModelBuilder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/ChunkModelBuilder.java @@ -6,11 +6,12 @@ import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TranslucentGeometryCollector; import net.caffeinemc.mods.sodium.client.render.chunk.vertex.builder.ChunkMeshBufferBuilder; import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import org.jetbrains.annotations.NotNull; public interface ChunkModelBuilder { ChunkMeshBufferBuilder getVertexBuffer(ModelQuadFacing facing); - void addSprite(TextureAtlasSprite sprite); + void addSprite(@NotNull TextureAtlasSprite sprite); /** * This method should not be used unless absolutely necessary! It exists only for compatibility purposes. 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 5d451302b7..3ab498a662 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 @@ -220,7 +220,9 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material ChunkMeshBufferBuilder vertexBuffer = builder.getVertexBuffer(normalFace); vertexBuffer.push(vertices, materialBits); - builder.addSprite(atlasSprite); + if (atlasSprite != null) { + builder.addSprite(atlasSprite); + } } private boolean validateQuadUVs(TextureAtlasSprite atlasSprite) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionInfo.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionInfo.java index 25bf883a4d..744e06224d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionInfo.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionInfo.java @@ -1,10 +1,10 @@ package net.caffeinemc.mods.sodium.client.render.chunk.data; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionFlags; import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.VisibilityEncoding; import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass; -import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil; import net.minecraft.client.renderer.chunk.VisibilitySet; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.core.Direction; @@ -78,8 +78,8 @@ public void setOcclusionData(VisibilitySet data) { * before rendering as necessary. * @param sprite The sprite */ - public void addSprite(TextureAtlasSprite sprite) { - if (SpriteUtil.hasAnimation(sprite)) { + public void addSprite(@NotNull TextureAtlasSprite sprite) { + if (SpriteUtil.INSTANCE.hasAnimation(sprite)) { this.animatedSprites.add(sprite); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java index fc52217c50..f2fe41f95b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java @@ -19,12 +19,12 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.math.MatrixUtil; +import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.caffeinemc.mods.sodium.api.util.ColorMixer; import net.caffeinemc.mods.sodium.client.render.frapi.helper.ColorHelper; import net.caffeinemc.mods.sodium.client.render.frapi.mesh.EncodingFormat; import net.caffeinemc.mods.sodium.client.render.frapi.mesh.MutableQuadViewImpl; import net.caffeinemc.mods.sodium.client.render.texture.SpriteFinderCache; -import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil; import net.caffeinemc.mods.sodium.mixin.features.render.frapi.ItemRendererAccessor; import net.fabricmc.fabric.api.renderer.v1.material.BlendMode; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; @@ -224,7 +224,10 @@ private void shadeQuad(MutableQuadViewImpl quad, boolean emissive) { private void bufferQuad(MutableQuadViewImpl quad, VertexConsumer vertexConsumer) { QuadEncoder.writeQuadVertices(quad, vertexConsumer, overlay, matPosition, trustedNormals, matNormal); - SpriteUtil.markSpriteActive(quad.sprite(SpriteFinderCache.forBlockAtlas())); + var sprite = quad.sprite(SpriteFinderCache.forBlockAtlas()); + if (sprite != null) { + SpriteUtil.INSTANCE.markSpriteActive(sprite); + } } /** diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java index a0220a8776..3e9e938cce 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java @@ -18,6 +18,7 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; +import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.caffeinemc.mods.sodium.api.util.ColorARGB; import net.caffeinemc.mods.sodium.api.util.ColorMixer; import net.caffeinemc.mods.sodium.client.model.light.LightMode; @@ -25,7 +26,6 @@ import net.caffeinemc.mods.sodium.client.model.light.data.SingleBlockLightDataCache; import net.caffeinemc.mods.sodium.client.render.frapi.mesh.MutableQuadViewImpl; import net.caffeinemc.mods.sodium.client.render.texture.SpriteFinderCache; -import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil; import net.caffeinemc.mods.sodium.client.services.SodiumModelData; import net.fabricmc.fabric.api.renderer.v1.material.RenderMaterial; import net.fabricmc.fabric.api.renderer.v1.material.ShadeMode; @@ -128,6 +128,9 @@ protected void shadeQuad(MutableQuadViewImpl quad, LightMode lightMode, boolean private void bufferQuad(MutableQuadViewImpl quad) { QuadEncoder.writeQuadVertices(quad, vertexConsumer, overlay, matPosition, trustedNormals, matNormal); - SpriteUtil.markSpriteActive(quad.sprite(SpriteFinderCache.forBlockAtlas())); + var sprite = quad.sprite(SpriteFinderCache.forBlockAtlas()); + if (sprite != null) { + SpriteUtil.INSTANCE.markSpriteActive(sprite); + } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/texture/SpriteUtil.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/texture/SpriteUtil.java index 084acc18b5..09e0369b96 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/texture/SpriteUtil.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/texture/SpriteUtil.java @@ -3,18 +3,22 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite; import org.jetbrains.annotations.Nullable; +// Kept for mod compatibility, to be removed in next major release. +@Deprecated(forRemoval = true) public class SpriteUtil { + @Deprecated(forRemoval = true) public static void markSpriteActive(@Nullable TextureAtlasSprite sprite) { - if (sprite == null) { - // Can happen in some cases, for example if a mod passes a BakedQuad with a null sprite - // to a VertexConsumer that does not have a texture element. - return; + if (sprite != null) { + net.caffeinemc.mods.sodium.api.texture.SpriteUtil.INSTANCE.markSpriteActive(sprite); } - - ((SpriteContentsExtension) sprite.contents()).sodium$setActive(true); } - public static boolean hasAnimation(TextureAtlasSprite sprite) { - return ((SpriteContentsExtension) sprite.contents()).sodium$hasAnimation(); + @Deprecated(forRemoval = true) + public static boolean hasAnimation(@Nullable TextureAtlasSprite sprite) { + if (sprite != null) { + return net.caffeinemc.mods.sodium.api.texture.SpriteUtil.INSTANCE.hasAnimation(sprite); + } + + return false; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/texture/SpriteUtilImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/texture/SpriteUtilImpl.java new file mode 100644 index 0000000000..2c03d76e06 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/texture/SpriteUtilImpl.java @@ -0,0 +1,23 @@ +package net.caffeinemc.mods.sodium.client.render.texture; + +import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class SpriteUtilImpl implements SpriteUtil { + @Override + public void markSpriteActive(@NotNull TextureAtlasSprite sprite) { + Objects.requireNonNull(sprite); + + ((SpriteContentsExtension) sprite.contents()).sodium$setActive(true); + } + + @Override + public boolean hasAnimation(@NotNull TextureAtlasSprite sprite) { + Objects.requireNonNull(sprite); + + return ((SpriteContentsExtension) sprite.contents()).sodium$hasAnimation(); + } +} \ No newline at end of file diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/BufferBuilderMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/BufferBuilderMixin.java index a72f45f5bc..9232a508e5 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/BufferBuilderMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/BufferBuilderMixin.java @@ -3,11 +3,11 @@ import com.mojang.blaze3d.vertex.BufferBuilder; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; -import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadView; -import net.caffeinemc.mods.sodium.client.render.immediate.model.BakedModelEncoder; -import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil; +import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.caffeinemc.mods.sodium.api.util.ColorABGR; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; +import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadView; +import net.caffeinemc.mods.sodium.client.render.immediate.model.BakedModelEncoder; import net.minecraft.client.renderer.block.model.BakedQuad; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -25,7 +25,9 @@ public void putBulkData(PoseStack.Pose matrices, BakedQuad bakedQuad, float r, f if (!this.fastFormat) { VertexConsumer.super.putBulkData(matrices, bakedQuad, r, g, b, a, light, overlay); - SpriteUtil.markSpriteActive(bakedQuad.getSprite()); + if (bakedQuad.getSprite() != null) { + SpriteUtil.INSTANCE.markSpriteActive(bakedQuad.getSprite()); + } return; } @@ -41,7 +43,9 @@ public void putBulkData(PoseStack.Pose matrices, BakedQuad bakedQuad, float r, f int color = ColorABGR.pack(r, g, b, a); BakedModelEncoder.writeQuadVertices(writer, matrices, quad, color, light, overlay, false); - SpriteUtil.markSpriteActive(quad.getSprite()); + if (quad.getSprite() != null) { + SpriteUtil.INSTANCE.markSpriteActive(quad.getSprite()); + } } @Override @@ -49,7 +53,9 @@ public void putBulkData(PoseStack.Pose matrices, BakedQuad bakedQuad, float[] br if (!this.fastFormat) { VertexConsumer.super.putBulkData(matrices, bakedQuad, brightnessTable, r, g, b, a, light, overlay, colorize); - SpriteUtil.markSpriteActive(bakedQuad.getSprite()); + if (bakedQuad.getSprite() != null) { + SpriteUtil.INSTANCE.markSpriteActive(bakedQuad.getSprite()); + } return; } @@ -64,6 +70,8 @@ public void putBulkData(PoseStack.Pose matrices, BakedQuad bakedQuad, float[] br BakedModelEncoder.writeQuadVertices(writer, matrices, quad, r, g, b, a, brightnessTable, colorize, light, overlay); - SpriteUtil.markSpriteActive(quad.getSprite()); + if (quad.getSprite() != null) { + SpriteUtil.INSTANCE.markSpriteActive(quad.getSprite()); + } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/item/ItemRendererMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/item/ItemRendererMixin.java index 8ea16da500..fc3576589e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/item/ItemRendererMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/item/ItemRendererMixin.java @@ -4,11 +4,11 @@ import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; +import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.caffeinemc.mods.sodium.api.util.ColorARGB; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; import net.caffeinemc.mods.sodium.client.model.quad.BakedQuadView; import net.caffeinemc.mods.sodium.client.render.immediate.model.BakedModelEncoder; -import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil; import net.caffeinemc.mods.sodium.client.render.vertex.VertexConsumerUtils; import net.caffeinemc.mods.sodium.client.util.DirectionUtil; import net.minecraft.client.color.item.ItemColors; @@ -92,7 +92,9 @@ private void renderBakedItemQuads(PoseStack.Pose matrices, VertexBufferWriter wr BakedModelEncoder.writeQuadVertices(writer, matrices, quad, color, light, overlay, BakedModelEncoder.shouldMultiplyAlpha()); - SpriteUtil.markSpriteActive(quad.getSprite()); + if (quad.getSprite() != null) { + SpriteUtil.INSTANCE.markSpriteActive(quad.getSprite()); + } } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/GuiGraphicsMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/GuiGraphicsMixin.java index 49d71efb44..fed72a54e8 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/GuiGraphicsMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/GuiGraphicsMixin.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.mixin.features.textures.animations.tracking; -import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil; +import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import org.spongepowered.asm.mixin.Mixin; @@ -16,7 +16,7 @@ private void preDrawSprite(int x, int y, int z, TextureAtlasSprite sprite, CallbackInfo ci) { - SpriteUtil.markSpriteActive(sprite); + SpriteUtil.INSTANCE.markSpriteActive(sprite); } @Inject(method = "blit(IIIIILnet/minecraft/client/renderer/texture/TextureAtlasSprite;FFFF)V", at = @At("HEAD")) @@ -26,6 +26,6 @@ private void preDrawSprite(int x, int y, int z, float red, float green, float blue, float alpha, CallbackInfo ci) { - SpriteUtil.markSpriteActive(sprite); + SpriteUtil.INSTANCE.markSpriteActive(sprite); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/ModelBlockRendererMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/ModelBlockRendererMixin.java index 020ee3cb27..4444d4c71c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/ModelBlockRendererMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/ModelBlockRendererMixin.java @@ -2,7 +2,7 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; -import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil; +import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.minecraft.client.renderer.block.ModelBlockRenderer; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.core.BlockPos; @@ -22,6 +22,8 @@ public class ModelBlockRendererMixin { */ @Inject(method = "putQuadData", at = @At("HEAD")) private void preRenderQuad(BlockAndTintGetter level, BlockState state, BlockPos pos, VertexConsumer vertexConsumer, PoseStack.Pose matrices, BakedQuad quad, float brightness0, float brightness1, float brightness2, float brightness3, int light0, int light1, int light2, int light3, int overlay, CallbackInfo ci) { - SpriteUtil.markSpriteActive(quad.getSprite()); + if (quad.getSprite() != null) { + SpriteUtil.INSTANCE.markSpriteActive(quad.getSprite()); + } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/TextureAtlasMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/TextureAtlasMixin.java index d60038f2c4..f23ca8fe4f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/TextureAtlasMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/TextureAtlasMixin.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.mixin.features.textures.animations.tracking; -import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil; +import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.minecraft.client.renderer.texture.TextureAtlas; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import org.spongepowered.asm.mixin.Mixin; @@ -15,7 +15,7 @@ private void preReturnSprite(CallbackInfoReturnable cir) { TextureAtlasSprite sprite = cir.getReturnValue(); if (sprite != null) { - SpriteUtil.markSpriteActive(sprite); + SpriteUtil.INSTANCE.markSpriteActive(sprite); } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/TextureSheetParticleMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/TextureSheetParticleMixin.java index 6773925be4..a4f162ab15 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/TextureSheetParticleMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/TextureSheetParticleMixin.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.mixin.features.textures.animations.tracking; import com.mojang.blaze3d.vertex.VertexConsumer; -import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil; +import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.minecraft.client.Camera; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.particle.SingleQuadParticle; @@ -28,13 +28,13 @@ protected TextureSheetParticleMixin(ClientLevel level, double x, double y, doubl @Inject(method = "setSprite(Lnet/minecraft/client/renderer/texture/TextureAtlasSprite;)V", at = @At("RETURN")) private void afterSetSprite(TextureAtlasSprite sprite, CallbackInfo ci) { - this.shouldTickSprite = sprite != null && SpriteUtil.hasAnimation(sprite); + this.shouldTickSprite = sprite != null && SpriteUtil.INSTANCE.hasAnimation(sprite); } @Override public void render(VertexConsumer vertexConsumer, Camera camera, float tickDelta) { if (this.shouldTickSprite) { - SpriteUtil.markSpriteActive(this.sprite); + SpriteUtil.INSTANCE.markSpriteActive(this.sprite); } super.render(vertexConsumer, camera, tickDelta); diff --git a/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java b/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java index 6fbc0d2ecf..636c9974eb 100644 --- a/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java +++ b/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java @@ -2,11 +2,11 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; +import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.caffeinemc.mods.sodium.api.util.ColorABGR; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; import net.caffeinemc.mods.sodium.client.model.quad.BakedQuadView; import net.caffeinemc.mods.sodium.client.render.immediate.model.BakedModelEncoder; -import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil; import net.caffeinemc.mods.sodium.client.render.vertex.VertexConsumerUtils; import net.caffeinemc.mods.sodium.client.util.DirectionUtil; import net.minecraft.client.renderer.block.ModelBlockRenderer; @@ -46,7 +46,9 @@ private static void renderQuads(PoseStack.Pose matrices, VertexBufferWriter writ BakedModelEncoder.writeQuadVertices(writer, matrices, quad, color, light, overlay, false); - SpriteUtil.markSpriteActive(quad.getSprite()); + if (quad.getSprite() != null) { + SpriteUtil.INSTANCE.markSpriteActive(quad.getSprite()); + } } } diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java b/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java index c87264a58e..c93024b0fd 100644 --- a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java +++ b/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java @@ -2,11 +2,11 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.VertexConsumer; +import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.caffeinemc.mods.sodium.api.util.ColorABGR; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; import net.caffeinemc.mods.sodium.client.model.quad.BakedQuadView; import net.caffeinemc.mods.sodium.client.render.immediate.model.BakedModelEncoder; -import net.caffeinemc.mods.sodium.client.render.texture.SpriteUtil; import net.caffeinemc.mods.sodium.client.render.vertex.VertexConsumerUtils; import net.caffeinemc.mods.sodium.client.util.DirectionUtil; import net.minecraft.client.renderer.RenderType; @@ -48,7 +48,9 @@ private static void renderQuads(PoseStack.Pose matrices, VertexBufferWriter writ BakedModelEncoder.writeQuadVertices(writer, matrices, quad, color, light, overlay, false); - SpriteUtil.markSpriteActive(quad.getSprite()); + if (quad.getSprite() != null) { + SpriteUtil.INSTANCE.markSpriteActive(quad.getSprite()); + } } } From 9d702c0bdf8df56792ab87e7089a2765ffe2b8fd Mon Sep 17 00:00:00 2001 From: JellySquid Date: Fri, 14 Feb 2025 17:54:11 -0600 Subject: [PATCH 054/215] Fixup OpenGL ICD names for Intel Xe1 and Xe2 `igxe(2)?(lp|hp)gicd` should have been `igxe(2)?(lpg|hpg)` --- .../environment/probe/GraphicsAdapterVendor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java index 1db4d3df8a..b6b89443a8 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java @@ -18,10 +18,10 @@ public enum GraphicsAdapterVendor { // Intel Gen 8 - ig8icd // Intel Gen 9, 9.5 - ig9icd // Intel Gen 11 - ig11icd - // Intel Xe - ig12icd (Xe-LP; early drivers) or igxelpicd (Xe-LP; later drivers), and igxehpicd (Xe-HP) - // Intel Xe2 - igxe2lpicd (Xe2-LP) and igxe2lpicd (Xe2-HP) + // Intel Xe - ig12icd (Xe-LP; early drivers) or igxelpgicd (Xe-LP; later drivers), and igxehpgicd (Xe-HP) + // Intel Xe2 - igxe2lpgicd (Xe2-LP) and igxe2hpgicd (Xe2-HP) private static final Pattern INTEL_ICD_PATTERN = - Pattern.compile("ig(4|7|75|8|9|11|12|(xe(2)?(hp|lp)))icd(32|64)\\.dll", Pattern.CASE_INSENSITIVE); + Pattern.compile("ig(4|7|75|8|9|11|12|(xe(2)?(hpg|lpg)))icd(32|64)\\.dll", Pattern.CASE_INSENSITIVE); private static final Pattern NVIDIA_ICD_PATTERN = Pattern.compile("nvoglv(32|64)\\.dll", Pattern.CASE_INSENSITIVE); From adae07ef3bb3eb0e281be88f54e00d3ab275c95a Mon Sep 17 00:00:00 2001 From: JellySquid Date: Fri, 14 Feb 2025 18:18:41 -0600 Subject: [PATCH 055/215] Distinguish between LP/HP and LPG/HPG in Intel Xe detection --- .../environment/probe/GraphicsAdapterVendor.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java index b6b89443a8..436bee2ae7 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java @@ -18,10 +18,13 @@ public enum GraphicsAdapterVendor { // Intel Gen 8 - ig8icd // Intel Gen 9, 9.5 - ig9icd // Intel Gen 11 - ig11icd - // Intel Xe - ig12icd (Xe-LP; early drivers) or igxelpgicd (Xe-LP; later drivers), and igxehpgicd (Xe-HP) - // Intel Xe2 - igxe2lpgicd (Xe2-LP) and igxe2hpgicd (Xe2-HP) + // Intel Xe-LP - ig12icd (early drivers) or igxelpicd (later drivers) + // Intel Xe-HP - igxehpicd + // Intel Xe-HPG - igxehpgicd + // Intel Xe2-LPG - igxe2lpgicd + // Intel Xe2-HPG - igxe2hpgicd private static final Pattern INTEL_ICD_PATTERN = - Pattern.compile("ig(4|7|75|8|9|11|12|(xe(2)?(hpg|lpg)))icd(32|64)\\.dll", Pattern.CASE_INSENSITIVE); + Pattern.compile("ig(4|7|75|8|9|11|12|(xe2?(hpg?|lpg?)))icd(32|64)\\.dll", Pattern.CASE_INSENSITIVE); private static final Pattern NVIDIA_ICD_PATTERN = Pattern.compile("nvoglv(32|64)\\.dll", Pattern.CASE_INSENSITIVE); From f16b9cb051e863af314dcc63df1c1a20289e4aca Mon Sep 17 00:00:00 2001 From: JellySquid Date: Fri, 17 Jan 2025 18:43:27 -0600 Subject: [PATCH 056/215] Make texture sampling more robust in terrain rendering The epsilon used to prevent texture bleeding between sprites in the atlas is now dynamically computed according to hardware capabilities and atlas size. Using a dynamic value solves a problem where Sodium would compute an epsilon that is too small (less than one sub-texel) with very large atlases, and therefore introduce texture bleeding at the edges of blocks. By default, this patch assumes that all hardware is capable of 8 bits of sub-texel precision, which seems to hold true in the general case since nearly all hardware is also Direct3D-compatible. The only exception to this rule happens to be with Apple's OpenGL implementation, where the precision is instead limited to 4 bits (the minimum of which is required by OpenGL's spec.) This is also the reason why texture bleeding was observed to be a much bigger issue on macOS, since the fixed epsilon used before was simply too small when limited to 4 bits of sub-texel precision. Additionally, the rounding behavior of texture coordinate encoding is refined to avoid problems with block models that encode their own epsilon for other reasons, which has been observed with some mods. --- .../client/gl/device/GLRenderDevice.java | 14 +++++++++++ .../sodium/client/gl/device/RenderDevice.java | 2 ++ .../gl/shader/uniform/GlUniformFloat2v.java | 22 +++++++++++++++++ .../chunk/shader/DefaultShaderInterface.java | 24 +++++++++++++++++++ .../format/impl/CompactChunkVertex.java | 9 +++---- .../render/texture/TextureAtlasAccessor.java | 9 ++++--- .../shaders/blocks/block_layer_opaque.vsh | 3 ++- .../sodium/shaders/include/chunk_vertex.glsl | 10 ++++---- 8 files changed, 75 insertions(+), 18 deletions(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/uniform/GlUniformFloat2v.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java index 98af1f3ba3..95d3a9d8b7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java @@ -1,5 +1,6 @@ package net.caffeinemc.mods.sodium.client.gl.device; +import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; import net.caffeinemc.mods.sodium.client.gl.array.GlVertexArray; import net.caffeinemc.mods.sodium.client.gl.buffer.*; import net.caffeinemc.mods.sodium.client.gl.functions.DeviceFunctions; @@ -60,6 +61,19 @@ public DeviceFunctions getDeviceFunctions() { return this.functions; } + @Override + public int getSubTexelPrecisionBits() { + // OpenGL only specifies "at least" 4 bits of sub-texel precision for texture fetches. Thankfully, nearly every + // graphics card is Direct3D-compatible and capable of providing 8 bits of precision. The only exception to this + // rule seems to be when using OpenGL on macOS, where it appears to arbitrarily limit the precision to 4 bits + // *even if* the hardware is capable of better. + if (OsUtils.getOs() == OsUtils.OperatingSystem.MAC) { + return 4; + } + + return 8; + } + private void checkDeviceActive() { if (!this.isActive) { throw new IllegalStateException("Tried to access device from unmanaged context"); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/RenderDevice.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/RenderDevice.java index e87edf1cec..9787b829b6 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/RenderDevice.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/RenderDevice.java @@ -22,4 +22,6 @@ static void exitManagedCode() { GLCapabilities getCapabilities(); DeviceFunctions getDeviceFunctions(); + + int getSubTexelPrecisionBits(); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/uniform/GlUniformFloat2v.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/uniform/GlUniformFloat2v.java new file mode 100644 index 0000000000..428458f8cc --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/uniform/GlUniformFloat2v.java @@ -0,0 +1,22 @@ +package net.caffeinemc.mods.sodium.client.gl.shader.uniform; + +import org.lwjgl.opengl.GL30C; + +public class GlUniformFloat2v extends GlUniform { + public GlUniformFloat2v(int index) { + super(index); + } + + @Override + public void set(float[] value) { + if (value.length != 2) { + throw new IllegalArgumentException("value.length != 2"); + } + + GL30C.glUniform2fv(this.index, value); + } + + public void set(float x, float y) { + GL30C.glUniform2f(this.index, x, y); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/DefaultShaderInterface.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/DefaultShaderInterface.java index 1ce8947477..f039ca3a01 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/DefaultShaderInterface.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/DefaultShaderInterface.java @@ -1,10 +1,16 @@ package net.caffeinemc.mods.sodium.client.render.chunk.shader; import com.mojang.blaze3d.platform.GlStateManager; +import net.caffeinemc.mods.sodium.client.gl.device.GLRenderDevice; +import net.caffeinemc.mods.sodium.client.gl.shader.uniform.GlUniformFloat2v; import net.caffeinemc.mods.sodium.client.gl.shader.uniform.GlUniformFloat3v; import net.caffeinemc.mods.sodium.client.gl.shader.uniform.GlUniformInt; import net.caffeinemc.mods.sodium.client.gl.shader.uniform.GlUniformMatrix4f; +import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.impl.CompactChunkVertex; import net.caffeinemc.mods.sodium.client.util.TextureUtil; +import net.caffeinemc.mods.sodium.mixin.core.render.texture.TextureAtlasAccessor; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.TextureAtlas; import org.joml.Matrix4fc; import org.lwjgl.opengl.GL32C; @@ -20,6 +26,7 @@ public class DefaultShaderInterface implements ChunkShaderInterface { private final GlUniformMatrix4f uniformModelViewMatrix; private final GlUniformMatrix4f uniformProjectionMatrix; private final GlUniformFloat3v uniformRegionOffset; + private final GlUniformFloat2v uniformTexCoordShrink; // The fog shader component used by this program in order to setup the appropriate GL state private final ChunkShaderFogComponent fogShader; @@ -28,6 +35,7 @@ public DefaultShaderInterface(ShaderBindingContext context, ChunkShaderOptions o this.uniformModelViewMatrix = context.bindUniform("u_ModelViewMatrix", GlUniformMatrix4f::new); this.uniformProjectionMatrix = context.bindUniform("u_ProjectionMatrix", GlUniformMatrix4f::new); this.uniformRegionOffset = context.bindUniform("u_RegionOffset", GlUniformFloat3v::new); + this.uniformTexCoordShrink = context.bindUniform("u_TexCoordShrink", GlUniformFloat2v::new); this.uniformTextures = new EnumMap<>(ChunkShaderTextureSlot.class); this.uniformTextures.put(ChunkShaderTextureSlot.BLOCK, context.bindUniform("u_BlockTex", GlUniformInt::new)); @@ -38,9 +46,25 @@ public DefaultShaderInterface(ShaderBindingContext context, ChunkShaderOptions o @Override // the shader interface should not modify pipeline state public void setupState() { + // TODO: Bind to these textures directly rather than using fragile RenderSystem state this.bindTexture(ChunkShaderTextureSlot.BLOCK, TextureUtil.getBlockTextureId()); this.bindTexture(ChunkShaderTextureSlot.LIGHT, TextureUtil.getLightTextureId()); + var textureAtlas = (TextureAtlasAccessor) Minecraft.getInstance() + .getTextureManager() + .getTexture(TextureAtlas.LOCATION_BLOCKS); + + // There is a limited amount of sub-texel precision when using hardware texture sampling. The mapped texture + // area must be "shrunk" by at least one sub-texel to avoid bleed between textures in the atlas. And since we + // offset texture coordinates in the vertex format by one texel, we also need to undo that here. + double subTexelPrecision = (1 << GLRenderDevice.INSTANCE.getSubTexelPrecisionBits()); + double subTexelOffset = 1.0f / CompactChunkVertex.TEXTURE_MAX_VALUE; + + this.uniformTexCoordShrink.set( + (float) (subTexelOffset - (((1.0D / textureAtlas.getWidth()) / subTexelPrecision))), + (float) (subTexelOffset - (((1.0D / textureAtlas.getHeight()) / subTexelPrecision))) + ); + this.fogShader.setup(); } 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 44c73a8719..ab191b9934 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 @@ -18,8 +18,8 @@ public class CompactChunkVertex implements ChunkVertexType { .addElement(DefaultChunkMeshAttributes.LIGHT_MATERIAL_INDEX, ChunkShaderBindingPoints.ATTRIBUTE_LIGHT_MATERIAL_INDEX, 16) .build(); - private static final int POSITION_MAX_VALUE = 1 << 20; - private static final int TEXTURE_MAX_VALUE = 1 << 15; + public static final int POSITION_MAX_VALUE = 1 << 20; + public static final int TEXTURE_MAX_VALUE = 1 << 15; private static final float MODEL_ORIGIN = 8.0f; private static final float MODEL_RANGE = 32.0f; @@ -101,7 +101,7 @@ private static int encodeTexture(float center, float x) { // This makes it possible to use much smaller epsilons for avoiding texture bleed, since the epsilon is no // longer encoded into the vertex data (instead, we only store the sign.) int bias = (x < center) ? 1 : -1; - int quantized = floorInt(x * TEXTURE_MAX_VALUE) + bias; + int quantized = Math.round(x * TEXTURE_MAX_VALUE) + bias; return (quantized & 0x7FFF) | (sign(bias) << 15); } @@ -125,7 +125,4 @@ private static int sign(int x) { return (x >>> 31); } - private static int floorInt(float x) { - return (int) Math.floor(x); - } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/texture/TextureAtlasAccessor.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/texture/TextureAtlasAccessor.java index f95d1f8f59..7f9a6b060b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/texture/TextureAtlasAccessor.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/texture/TextureAtlasAccessor.java @@ -1,15 +1,14 @@ package net.caffeinemc.mods.sodium.mixin.core.render.texture; import net.minecraft.client.renderer.texture.TextureAtlas; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.resources.ResourceLocation; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; -import java.util.Map; - @Mixin(TextureAtlas.class) public interface TextureAtlasAccessor { @Accessor - Map getTexturesByName(); + int getWidth(); + + @Accessor + int getHeight(); } diff --git a/common/src/main/resources/assets/sodium/shaders/blocks/block_layer_opaque.vsh b/common/src/main/resources/assets/sodium/shaders/blocks/block_layer_opaque.vsh index e281de1eae..fcd0ddfc50 100644 --- a/common/src/main/resources/assets/sodium/shaders/blocks/block_layer_opaque.vsh +++ b/common/src/main/resources/assets/sodium/shaders/blocks/block_layer_opaque.vsh @@ -19,6 +19,7 @@ out float v_FragDistance; uniform int u_FogShape; uniform vec3 u_RegionOffset; +uniform vec2 u_TexCoordShrink; uniform sampler2D u_LightTex; // The light map texture sampler @@ -47,7 +48,7 @@ void main() { // Add the light color to the vertex color, and pass the texture coordinates to the fragment shader v_Color = _vert_color * texture(u_LightTex, _vert_tex_light_coord); - v_TexCoord = _vert_tex_diffuse_coord; + v_TexCoord = (_vert_tex_diffuse_coord_bias * u_TexCoordShrink) + _vert_tex_diffuse_coord; // FMA for precision v_MaterialMipBias = _material_mip_bias(_material_params); #ifdef USE_FRAGMENT_DISCARD diff --git a/common/src/main/resources/assets/sodium/shaders/include/chunk_vertex.glsl b/common/src/main/resources/assets/sodium/shaders/include/chunk_vertex.glsl index 3ce9949258..e392ffa901 100644 --- a/common/src/main/resources/assets/sodium/shaders/include/chunk_vertex.glsl +++ b/common/src/main/resources/assets/sodium/shaders/include/chunk_vertex.glsl @@ -3,6 +3,7 @@ vec3 _vert_position; // The block texture coordinate of the vertex vec2 _vert_tex_diffuse_coord; +vec2 _vert_tex_diffuse_coord_bias; // The light texture coordinate of the vertex vec2 _vert_tex_light_coord; @@ -28,10 +29,6 @@ const uint TEXTURE_MAX_VALUE = TEXTURE_MAX_COORD - 1u; const float VERTEX_SCALE = 32.0 / float(POSITION_MAX_COORD); const float VERTEX_OFFSET = -8.0; -// The amount of inset the texture coordinates from the edges of the texture, to avoid texture bleeding -const float TEXTURE_FUZZ_AMOUNT = 1.0 / 64.0; -const float TEXTURE_GROW_FACTOR = (1.0 - TEXTURE_FUZZ_AMOUNT) / TEXTURE_MAX_COORD; - in uvec2 a_Position; in vec4 a_Color; in uvec2 a_TexCoord; @@ -49,13 +46,14 @@ vec2 _get_texcoord() { } vec2 _get_texcoord_bias() { - return mix(vec2(-TEXTURE_GROW_FACTOR), vec2(TEXTURE_GROW_FACTOR), bvec2(a_TexCoord >> TEXTURE_BITS)); + return mix(vec2(-1.0), vec2(1.0), bvec2(a_TexCoord >> TEXTURE_BITS)); } void _vert_init() { _vert_position = (_deinterleave_u20x3(a_Position) * VERTEX_SCALE) + VERTEX_OFFSET; _vert_color = a_Color; - _vert_tex_diffuse_coord = _get_texcoord() + _get_texcoord_bias(); + _vert_tex_diffuse_coord = _get_texcoord(); + _vert_tex_diffuse_coord_bias = _get_texcoord_bias(); _vert_tex_light_coord = vec2(a_LightAndData.xy) / vec2(256.0); From a8d9e1f468aad4784ede83868eeb092324b84392 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Wed, 19 Feb 2025 16:22:31 -0600 Subject: [PATCH 057/215] Update list of incompatible mods --- fabric/src/main/resources/fabric.mod.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index b80dfa1aa9..4ccd5a11e1 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -94,14 +94,14 @@ "custom_hud" : "<3.4.2", "farsight" : "<=1.21-4.3", "iceberg" : "<1.2.7", - "iris" : "<1.8.1", + "iris" : "<1.8.7", "movingelevators" : "<=1.4.7", "notenoughcrashes" : "<4.4.8", "noxesium" : "<2.3.3", "reeses-sodium-options" : "<1.8.0", "sodium-extra" : "<0.6.0", "sspb" : "<4.0.0", - "moreculling": "<1.0.0", + "moreculling": "<1.0.4", "simply-no-shading": "<7.6.2" }, "provides" : [ From fd41efa81b88269d2021ad4c187fb34e2c2d083a Mon Sep 17 00:00:00 2001 From: JellySquid Date: Wed, 19 Feb 2025 16:23:34 -0600 Subject: [PATCH 058/215] Bump version to 0.6.9 --- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index aab51ecdd8..4b55391309 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -10,7 +10,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ - var MOD_VERSION: String = "0.6.7" + var MOD_VERSION: String = "0.6.9" fun createVersionString(project: Project): String { val builder = StringBuilder() From f3c5775b0905e9c3f4fbf3712a4e1d91a5898358 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sun, 9 Mar 2025 13:21:33 -0500 Subject: [PATCH 059/215] Re-enable block entity data fetching This was probably disabled by accident during a port and is required for some mods. --- .../mods/sodium/neoforge/block/NeoForgeBlockAccess.java | 2 +- .../mods/sodium/neoforge/level/NeoForgeLevelAccess.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/block/NeoForgeBlockAccess.java b/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/block/NeoForgeBlockAccess.java index 3712c8217e..793fdf9e84 100644 --- a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/block/NeoForgeBlockAccess.java +++ b/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/block/NeoForgeBlockAccess.java @@ -37,7 +37,7 @@ public boolean shouldShowFluidOverlay(BlockState block, BlockAndTintGetter level @Override public boolean platformHasBlockData() { - return false; + return true; } @Override diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/level/NeoForgeLevelAccess.java b/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/level/NeoForgeLevelAccess.java index beedc67b4f..540957fe48 100644 --- a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/level/NeoForgeLevelAccess.java +++ b/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/level/NeoForgeLevelAccess.java @@ -2,6 +2,7 @@ import net.caffeinemc.mods.sodium.client.services.PlatformLevelAccess; import net.caffeinemc.mods.sodium.client.world.SodiumAuxiliaryLightManager; +import net.fabricmc.fabric.api.blockview.v2.RenderDataBlockEntity; import net.minecraft.core.SectionPos; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.LevelChunk; @@ -10,7 +11,7 @@ public class NeoForgeLevelAccess implements PlatformLevelAccess { @Override public @Nullable Object getBlockEntityData(BlockEntity blockEntity) { - return null; + return ((RenderDataBlockEntity) blockEntity).getRenderData(); } @Override From 281f81e282ce7934534fd49f12a1dc5148824c29 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sun, 16 Feb 2025 15:36:52 -0600 Subject: [PATCH 060/215] Modify startup warning shown when using unsupported launchers --- common/src/main/resources/assets/sodium/lang/en_us.json | 1 - .../client/compatibility/checks/PostLaunchChecks.java | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/common/src/main/resources/assets/sodium/lang/en_us.json b/common/src/main/resources/assets/sodium/lang/en_us.json index d1d1e6b6a7..4ab5645094 100644 --- a/common/src/main/resources/assets/sodium/lang/en_us.json +++ b/common/src/main/resources/assets/sodium/lang/en_us.json @@ -59,7 +59,6 @@ "sodium.options.buttons.donate": "Buy us a coffee!", "sodium.console.game_restart": "The game must be restarted to apply one or more video settings!", "sodium.console.broken_nvidia_driver": "Your NVIDIA graphics drivers are out of date!\n * This will cause severe performance issues and crashes when Sodium is installed.\n * Please update your graphics drivers to the latest version (version 536.23 or newer.)", - "sodium.console.pojav_launcher": "PojavLauncher is not supported when using Sodium.\n * You are very likely to run into extreme performance issues, graphical bugs, and crashes.\n * You will be on your own if you decide to continue -- we will not help you with any bugs or crashes!", "sodium.console.core_shaders_error": "The following resource packs are incompatible with Sodium:", "sodium.console.core_shaders_warn": "The following resource packs may be incompatible with Sodium:", "sodium.console.core_shaders_info": "Check the game log for detailed information.", diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PostLaunchChecks.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PostLaunchChecks.java index 907b877afe..6180199456 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PostLaunchChecks.java +++ b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PostLaunchChecks.java @@ -21,10 +21,8 @@ public static void onContextInitialized(NativeWindowHandle window, GlContextInfo // FIXME: This can be determined earlier, but we can't access the GUI classes in pre-launch if (isUsingPojavLauncher()) { - Console.instance().logMessage(MessageLevel.SEVERE, "sodium.console.pojav_launcher", true, 30.0); - LOGGER.error("It appears that PojavLauncher is being used with an OpenGL compatibility layer. This will " + - "likely cause severe performance issues, graphical issues, and crashes when used with Sodium. This " + - "configuration is not supported -- you are on your own!"); + throw new RuntimeException("It appears that you are using PojavLauncher, which is not supported when " + + "using Sodium. Please check your mods list."); } } From d9c20ed93b4fefcd696b83286f430c584ac750c2 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Wed, 5 Mar 2025 12:58:11 -0600 Subject: [PATCH 061/215] Mark BetterEnd <=21.0.11 as incompatible BetterEnd illegally accesses the client's World object from the chunk meshing threads, which causes memory corruption and cryptic crashes. These crashes are reported to us frequently and are difficult for users to debug. Since there is no workaround for the time being, and it is unlikely there will be an update on BetterEnd's part to resolve the issue in the near future, mark all current versions of the mod as incompatible. See #2869 for more information. --- fabric/src/main/resources/fabric.mod.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 4ccd5a11e1..05f43d540e 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -102,7 +102,8 @@ "sodium-extra" : "<0.6.0", "sspb" : "<4.0.0", "moreculling": "<1.0.4", - "simply-no-shading": "<7.6.2" + "simply-no-shading": "<7.6.2", + "betterend": "<=21.0.11" }, "provides" : [ "indium" From ac2579f4ed37616531a93b070ac9d3356a1373a7 Mon Sep 17 00:00:00 2001 From: katie! <136929759+katietheqt@users.noreply.github.com> Date: Sat, 29 Mar 2025 22:12:29 +0000 Subject: [PATCH 062/215] Add `rendertype_clouds` to the shader program blacklist (#3063) --- .../mods/sodium/client/checks/ResourcePackScanner.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/ResourcePackScanner.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/ResourcePackScanner.java index 6f9ad8117c..ff6be4cb8c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/ResourcePackScanner.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/ResourcePackScanner.java @@ -38,7 +38,10 @@ public class ResourcePackScanner { "rendertype_translucent.json", "rendertype_tripwire.vsh", "rendertype_tripwire.fsh", - "rendertype_tripwire.json" + "rendertype_tripwire.json", + "rendertype_clouds.vsh", + "rendertype_clouds.fsh", + "rendertype_clouds.json" ); private static final Set SHADER_INCLUDE_BLACKLIST = Set.of( From 737c645dea7b246eda7a7515b231a86befe046e6 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Thu, 3 Apr 2025 13:35:35 -0500 Subject: [PATCH 063/215] Bump version to 0.6.13 --- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 4b55391309..4a09090f85 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -10,7 +10,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ - var MOD_VERSION: String = "0.6.9" + var MOD_VERSION: String = "0.6.13" fun createVersionString(project: Project): String { val builder = StringBuilder() From 678c4689efe1b45af1ea8593f7136f40ce922e1c Mon Sep 17 00:00:00 2001 From: IMS212 Date: Thu, 3 Apr 2025 12:34:13 -0700 Subject: [PATCH 064/215] Update Forgified Fabric API --- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- neoforge/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 4a09090f85..9906d57c02 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -2,7 +2,7 @@ import org.gradle.api.Project object BuildConfig { val MINECRAFT_VERSION: String = "1.21.1" - val NEOFORGE_VERSION: String = "21.1.90" + val NEOFORGE_VERSION: String = "21.1.143" val FABRIC_LOADER_VERSION: String = "0.16.9" val FABRIC_API_VERSION: String = "0.110.0+1.21.1" diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index e2703e8483..d9dfb42273 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -54,7 +54,7 @@ dependencies { } addEmbeddedFabricModule("org.sinytra.forgified-fabric-api:fabric-api-base:0.4.42+d1308ded19") - addEmbeddedFabricModule("org.sinytra.forgified-fabric-api:fabric-renderer-api-v1:3.4.0+acb05a3919") + addEmbeddedFabricModule("org.sinytra.forgified-fabric-api:fabric-renderer-api-v1:3.4.0+9c40919e19") addEmbeddedFabricModule("org.sinytra.forgified-fabric-api:fabric-rendering-data-attachment-v1:0.3.48+73761d2e19") addEmbeddedFabricModule("org.sinytra.forgified-fabric-api:fabric-block-view-api-v2:1.0.10+9afaaf8c19") From 8672650501117e72f5a809867092378fb5bc908a Mon Sep 17 00:00:00 2001 From: JellySquid Date: Thu, 3 Apr 2025 18:26:30 -0500 Subject: [PATCH 065/215] Don't allow debug options to be enabled in production --- .../mods/sodium/client/SodiumClientMod.java | 10 ++++--- .../client/gui/SodiumGameOptionPages.java | 27 +++++-------------- .../sodium/client/gui/SodiumGameOptions.java | 26 ++++++++++++------ .../render/chunk/DefaultChunkRenderer.java | 2 +- .../render/chunk/RenderSectionManager.java | 4 +-- .../tasks/ChunkBuilderMeshingTask.java | 2 +- .../TranslucentGeometryCollector.java | 4 +-- .../trigger/SortTriggering.java | 2 +- .../resources/assets/sodium/lang/en_us.json | 2 -- 9 files changed, 38 insertions(+), 41 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java index 9dba7b3641..3731c70020 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java @@ -1,11 +1,11 @@ package net.caffeinemc.mods.sodium.client; +import net.caffeinemc.mods.sodium.client.console.Console; +import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; import net.caffeinemc.mods.sodium.client.data.fingerprint.FingerprintMeasure; import net.caffeinemc.mods.sodium.client.data.fingerprint.HashedFingerprint; import net.caffeinemc.mods.sodium.client.gui.SodiumGameOptions; -import net.caffeinemc.mods.sodium.client.console.Console; -import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; -import net.minecraft.network.chat.Component; +import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -107,4 +107,8 @@ private static void updateFingerprint() { } } } + + public static boolean allowDebuggingOptions() { + return PlatformRuntimeInformation.getInstance().isDevelopmentEnvironment(); + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java index b97e3fa433..1c0e355aba 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java @@ -6,20 +6,18 @@ import com.mojang.blaze3d.platform.VideoMode; import com.mojang.blaze3d.platform.Window; import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds; import net.caffeinemc.mods.sodium.client.gl.arena.staging.MappedStagingBuffer; import net.caffeinemc.mods.sodium.client.gl.device.RenderDevice; import net.caffeinemc.mods.sodium.client.gui.options.*; import net.caffeinemc.mods.sodium.client.gui.options.binding.compat.VanillaBooleanOptionBinding; -import net.caffeinemc.mods.sodium.client.gui.options.control.*; +import net.caffeinemc.mods.sodium.client.gui.options.control.ControlValueFormatter; +import net.caffeinemc.mods.sodium.client.gui.options.control.CyclingControl; +import net.caffeinemc.mods.sodium.client.gui.options.control.SliderControl; +import net.caffeinemc.mods.sodium.client.gui.options.control.TickBoxControl; import net.caffeinemc.mods.sodium.client.gui.options.storage.MinecraftOptionsStorage; import net.caffeinemc.mods.sodium.client.gui.options.storage.SodiumOptionsStorage; -import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds; -import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; -import net.minecraft.client.AttackIndicatorStatus; -import net.minecraft.client.CloudStatus; -import net.minecraft.client.GraphicsStatus; -import net.minecraft.client.Minecraft; -import net.minecraft.client.ParticleStatus; +import net.minecraft.client.*; import net.minecraft.network.chat.Component; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GLCapabilities; @@ -336,19 +334,6 @@ public static OptionPage performance() { .build()) .build()); - if (PlatformRuntimeInformation.getInstance().isDevelopmentEnvironment()) { - groups.add(OptionGroup.createBuilder() - .add(OptionImpl.createBuilder(boolean.class, sodiumOpts) - .setName(Component.translatable("sodium.options.sort_behavior.name")) - .setTooltip(Component.translatable("sodium.options.sort_behavior.tooltip")) - .setControl(TickBoxControl::new) - .setBinding((opts, value) -> opts.performance.sortingEnabled = value, opts -> opts.performance.sortingEnabled) - .setImpact(OptionImpact.LOW) - .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) - .build()) - .build()); - } - return new OptionPage(Component.translatable("sodium.options.pages.performance"), ImmutableList.copyOf(groups)); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java index ec681430b8..0fa4bc88fb 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java @@ -5,11 +5,13 @@ import com.google.gson.GsonBuilder; import com.google.gson.annotations.SerializedName; import net.caffeinemc.mods.sodium.client.gui.options.TextProvider; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior; import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; import net.caffeinemc.mods.sodium.client.util.FileUtil; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior; import net.minecraft.client.GraphicsStatus; import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Modifier; @@ -24,6 +26,7 @@ public class SodiumGameOptions { public final AdvancedSettings advanced = new AdvancedSettings(); public final PerformanceSettings performance = new PerformanceSettings(); public final NotificationSettings notifications = new NotificationSettings(); + public @NotNull DebugSettings debug = new DebugSettings(); private boolean readOnly; @@ -45,13 +48,6 @@ public static class PerformanceSettings { public boolean useFogOcclusion = true; public boolean useBlockFaceCulling = true; public boolean useNoErrorGLContext = true; - - @SerializedName("sorting_enabled_v2") // reset the older option in configs before we started hiding it - public boolean sortingEnabled = true; - - public SortBehavior getSortBehavior() { - return this.sortingEnabled ? SortBehavior.DYNAMIC_DEFER_NEARBY_ZERO_FRAMES : SortBehavior.OFF; - } } public static class AdvancedSettings { @@ -61,6 +57,20 @@ public static class AdvancedSettings { public int cpuRenderAheadLimit = 3; } + public static class DebugSettings { + public boolean terrainSortingEnabled = true; + + @Deprecated(forRemoval = true) + public SortBehavior getSortBehavior() { + // TODO: This logic should not exist here, we need to move it into renderer initialization + if (PlatformRuntimeInformation.getInstance().isDevelopmentEnvironment()) { + return this.terrainSortingEnabled ? SortBehavior.DYNAMIC_DEFER_NEARBY_ZERO_FRAMES : SortBehavior.OFF; + } + + return SortBehavior.DYNAMIC_DEFER_NEARBY_ZERO_FRAMES; + } + } + public static class QualitySettings { public GraphicsQuality weatherQuality = GraphicsQuality.DEFAULT; public GraphicsQuality leavesQuality = GraphicsQuality.DEFAULT; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java index 1d1165c705..e2c9d16f8d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java @@ -101,7 +101,7 @@ public void render(ChunkRenderMatrices matrices, } private static boolean isTranslucentRenderPass(TerrainRenderPass renderPass) { - return renderPass.isTranslucent() && SodiumClientMod.options().performance.getSortBehavior() != SortBehavior.OFF; + return renderPass.isTranslucent() && SodiumClientMod.options().debug.getSortBehavior() != SortBehavior.OFF; } private static void fillCommandBuffer(MultiDrawBatch batch, diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index b955b6b08a..2644c5430f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -415,7 +415,7 @@ public void updateChunks(boolean updateImmediately) { // if zero frame delay is allowed, submit important sorts with the current frame blocking collector. // otherwise submit with the collector that the next frame is blocking on. - if (SodiumClientMod.options().performance.getSortBehavior().getDeferMode() == DeferMode.ZERO_FRAMES) { + if (SodiumClientMod.options().debug.getSortBehavior().getDeferMode() == DeferMode.ZERO_FRAMES) { this.submitSectionTasks(thisFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector); } else { this.submitSectionTasks(nextFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector); @@ -577,7 +577,7 @@ public void scheduleSort(long sectionPos, boolean isDirectTrigger) { if (section != null) { var pendingUpdate = ChunkUpdateType.SORT; - var priorityMode = SodiumClientMod.options().performance.getSortBehavior().getPriorityMode(); + var priorityMode = SodiumClientMod.options().debug.getSortBehavior().getPriorityMode(); if (priorityMode == PriorityMode.ALL || priorityMode == PriorityMode.NEARBY && this.shouldPrioritizeTask(section, NEARBY_SORT_DISTANCE)) { pendingUpdate = ChunkUpdateType.IMPORTANT_SORT; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java index f3992c935a..64b85e6a70 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java @@ -81,7 +81,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke BlockPos.MutableBlockPos modelOffset = new BlockPos.MutableBlockPos(); TranslucentGeometryCollector collector; - if (SodiumClientMod.options().performance.getSortBehavior() != SortBehavior.OFF) { + if (SodiumClientMod.options().debug.getSortBehavior() != SortBehavior.OFF) { collector = new TranslucentGeometryCollector(render.getPosition()); } else { collector = null; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java index 2125b23b45..6c02bc1fa8 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java @@ -284,7 +284,7 @@ public void appendQuad(int packedNormal, ChunkVertexEncoder.Vertex[] vertices, M * @param sortType the sort type to filter */ private static SortType filterSortType(SortType sortType) { - SortBehavior sortBehavior = SodiumClientMod.options().performance.getSortBehavior(); + SortBehavior sortBehavior = SodiumClientMod.options().debug.getSortBehavior(); switch (sortBehavior) { case OFF: return SortType.NONE; @@ -348,7 +348,7 @@ private SortType sortTypeHeuristic() { return SortType.NONE; } - SortBehavior sortBehavior = SodiumClientMod.options().performance.getSortBehavior(); + SortBehavior sortBehavior = SodiumClientMod.options().debug.getSortBehavior(); if (sortBehavior.getSortMode() == SortBehavior.SortMode.NONE) { return SortType.NONE; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java index b58cd10aa7..b5c33809c5 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java @@ -224,7 +224,7 @@ public void integrateTranslucentData(TranslucentData oldData, TranslucentData ne } public void addDebugStrings(List list) { - var sortBehavior = SodiumClientMod.options().performance.getSortBehavior(); + var sortBehavior = SodiumClientMod.options().debug.getSortBehavior(); if (sortBehavior.getSortMode() == SortBehavior.SortMode.NONE) { list.add("TS OFF"); } else { diff --git a/common/src/main/resources/assets/sodium/lang/en_us.json b/common/src/main/resources/assets/sodium/lang/en_us.json index 4ab5645094..7651c158c5 100644 --- a/common/src/main/resources/assets/sodium/lang/en_us.json +++ b/common/src/main/resources/assets/sodium/lang/en_us.json @@ -50,8 +50,6 @@ "sodium.options.chunk_update_threads.tooltip": "Specifies the number of threads to use for chunk building and sorting. Using more threads can speed up chunk loading and update speed, but may negatively impact frame times. The default value is usually good enough for all situations.", "sodium.options.always_defer_chunk_updates.name": "Always Defer Chunk Updates", "sodium.options.always_defer_chunk_updates.tooltip": "If enabled, rendering will never wait for chunk updates to finish, even if they are important. This can greatly improve frame rates in some scenarios, but it may create significant visual lag where blocks take a while to appear or disappear.", - "sodium.options.sort_behavior.name": "Translucency Sorting", - "sodium.options.sort_behavior.tooltip": "Enables translucency sorting. This avoids glitches in translucent blocks like water and glass when enabled and attempts to correctly present them even when the camera is in motion. This has a small performance impact on chunk loading and update speeds, but is usually not noticeable in frame rates.", "sodium.options.use_no_error_context.name": "Use No Error Context", "sodium.options.use_no_error_context.tooltip": "When enabled, the OpenGL context will be created with error checking disabled. This slightly improves rendering performance, but it can make debugging sudden unexplained crashes much harder.", "sodium.options.buttons.undo": "Undo", From 7b7f0d44a1f0e62c2e98f01e7834ecec94954e4a Mon Sep 17 00:00:00 2001 From: coco875 <59367621+coco875@users.noreply.github.com> Date: Thu, 24 Jul 2025 19:36:44 +0000 Subject: [PATCH 066/215] Use default classloader when resolving shader sources (#3132) This allows other mods to replace these sources more easily, even though doing so is still strictly unsupported. --- .../caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java index 03d04c0e97..302347fca3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java @@ -25,7 +25,7 @@ public static GlShader loadShader(ShaderType type, ResourceLocation name, Shader public static String getShaderSource(ResourceLocation name) { String path = String.format("/assets/%s/shaders/%s", name.getNamespace(), name.getPath()); - try (InputStream in = ShaderLoader.class.getResourceAsStream(path)) { + try (InputStream in = ShaderLoader.class.getClassLoader().getResourceAsStream(path)) { if (in == null) { throw new RuntimeException("Shader not found: " + path); } From 58ab808ac8a0b0f72979a8cf3fca9a851a2204a0 Mon Sep 17 00:00:00 2001 From: douira Date: Sat, 2 May 2026 23:22:19 +0200 Subject: [PATCH 067/215] Backport: Add modern publishing infrastructure --- .editorconfig | 2 +- .github/CODEOWNERS | 2 + ...ild-tag.yml => build-commit-nopublish.yml} | 25 ++--- .github/workflows/build-commit.yml | 27 ++++-- .github/workflows/build-pull-request.yml | 20 ++-- .github/workflows/build-release.yml | 32 ------- .github/workflows/publish-release.yml | 65 +++++++++++++ .gitignore | 3 + CHANGELOG.md | 7 ++ CONTRIBUTING.md | 6 +- README.md | 14 +-- build.gradle.kts | 87 ++++++++++++++++++ buildSrc/src/main/kotlin/BuildConfig.kt | 34 +++++-- .../kotlin/multiloader-platform.gradle.kts | 26 ++++-- common/build.gradle.kts | 2 +- fabric/build.gradle.kts | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 43504 -> 48966 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 14 +-- gradlew.bat | 3 +- neoforge/build.gradle.kts | 2 +- settings.gradle.kts | 1 + 22 files changed, 278 insertions(+), 98 deletions(-) create mode 100644 .github/CODEOWNERS rename .github/workflows/{build-tag.yml => build-commit-nopublish.yml} (63%) delete mode 100644 .github/workflows/build-release.yml create mode 100644 .github/workflows/publish-release.yml create mode 100644 CHANGELOG.md diff --git a/.editorconfig b/.editorconfig index 6fdb37b49c..c2362ce336 100644 --- a/.editorconfig +++ b/.editorconfig @@ -548,7 +548,7 @@ ij_json_keep_indents_on_empty_lines = false ij_json_keep_line_breaks = true ij_json_space_after_colon = true ij_json_space_after_comma = true -ij_json_space_before_colon = true +ij_json_space_before_colon = false ij_json_space_before_comma = false ij_json_spaces_within_braces = false ij_json_spaces_within_brackets = false diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..6c889429c3 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +/.github/CODEOWNERS @jellysquid3 +/.github/workflows @jellysquid3 diff --git a/.github/workflows/build-tag.yml b/.github/workflows/build-commit-nopublish.yml similarity index 63% rename from .github/workflows/build-tag.yml rename to .github/workflows/build-commit-nopublish.yml index 478c3b3d2b..38f050550e 100644 --- a/.github/workflows/build-tag.yml +++ b/.github/workflows/build-commit-nopublish.yml @@ -1,11 +1,11 @@ -# Used when a commit is tagged and pushed to the repository +# Used when a commit is pushed to the repository # This makes use of caching for faster builds and uploads the resulting artifacts -name: build-tag +name: build-commit-nopublish on: push: - tags: - - '*' + branches-ignore: + - dev jobs: build: @@ -17,23 +17,26 @@ jobs: # bash pattern expansion to grab branch name without slashes run: ref="${GITHUB_REF#refs/heads/}" && echo "branch=${ref////-}" >> $GITHUB_OUTPUT id: ref + - name: Checkout sources - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 + uses: actions/checkout@v6 + + - name: Setup Java 21 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 21 - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v5 with: - cache-read-only: true + cache-read-only: false - name: Execute Gradle build - run: ./gradlew build -Pbuild.release=true + run: ./gradlew build - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: sodium-artifacts-${{ steps.ref.outputs.branch }} - path: build/mods/*.jar \ No newline at end of file + path: build/mods/*.jar diff --git a/.github/workflows/build-commit.yml b/.github/workflows/build-commit.yml index b73702a561..cd737b6d7a 100644 --- a/.github/workflows/build-commit.yml +++ b/.github/workflows/build-commit.yml @@ -2,11 +2,15 @@ # This makes use of caching for faster builds and uploads the resulting artifacts name: build-commit -on: [ push ] +on: + push: + branches: + - dev jobs: build: runs-on: ubuntu-latest + environment: staging steps: - name: Extract current branch name @@ -14,20 +18,18 @@ jobs: # bash pattern expansion to grab branch name without slashes run: ref="${GITHUB_REF#refs/heads/}" && echo "branch=${ref////-}" >> $GITHUB_OUTPUT id: ref - - name: Checkout sources - uses: actions/checkout@v4 - - name: Validate Gradle Wrapper - uses: gradle/actions/wrapper-validation@v3 + - name: Checkout sources + uses: actions/checkout@v6 - name: Setup Java 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 21 - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v5 with: cache-read-only: false @@ -35,7 +37,14 @@ jobs: run: ./gradlew build - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: sodium-artifacts-${{ steps.ref.outputs.branch }} - path: build/mods/*.jar \ No newline at end of file + path: build/mods/*.jar + + - name: Publish Release Snapshot to CaffeineMC Maven + env: + ORG_GRADLE_PROJECT_caffeineMCMavenUsername: ${{ secrets.CAFFEINEMC_MAVEN_USERNAME }} + ORG_GRADLE_PROJECT_caffeineMCMavenPassword: ${{ secrets.CAFFEINEMC_MAVEN_PASSWORD }} + if: ${{ env.ORG_GRADLE_PROJECT_caffeineMCMavenUsername != '' && env.ORG_GRADLE_PROJECT_caffeineMCMavenPassword != '' }} + run: ./gradlew publishAllPublicationsToCaffeineMCRepository diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml index 045dd53920..2ae9dc38fc 100644 --- a/.github/workflows/build-pull-request.yml +++ b/.github/workflows/build-pull-request.yml @@ -9,15 +9,23 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: gradle/actions/wrapper-validation@v3 - - uses: actions/setup-java@v4 + - name: Extract current branch name + shell: bash + # bash pattern expansion to grab branch name without slashes + run: ref="${GITHUB_REF#refs/heads/}" && echo "branch=${ref////-}" >> $GITHUB_OUTPUT + id: ref + + - name: Checkout sources + uses: actions/checkout@v6 + + - name: Setup Java 21 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 21 - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 + uses: gradle/actions/setup-gradle@v5 with: cache-read-only: true @@ -25,7 +33,7 @@ jobs: run: ./gradlew build - name: Upload artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: sodium-artifacts-${{ steps.ref.outputs.branch }} - path: build/mods/*.jar \ No newline at end of file + path: build/mods/*.jar diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml deleted file mode 100644 index ab751d49b9..0000000000 --- a/.github/workflows/build-release.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Used when a release is pushed to GitHub -# This does not make use of any caching as to ensure a clean build -name: build-release - -on: - release: - types: - - published - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: 21 - - - name: Setup Gradle - uses: gradle/actions/setup-gradle@v4 - with: - cache-read-only: true - - - name: Execute Gradle build - run: ./gradlew build -Pbuild.release=true - - - name: Upload assets to GitHub - uses: AButler/upload-release-assets@v2.0 - with: - files: 'build/mods/*.jar' - repo-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000000..7fbe0020d6 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,65 @@ +# Used when a developer decides to publish a release +# This makes use of caching for faster builds and uploads the resulting artifacts +name: Release GH+MR+CF +permissions: + contents: write # Needed to publish a GitHub release + +on: + workflow_dispatch: + inputs: + platform: + type: choice + description: Platform + options: + - both + - fabric + - neoforge + +jobs: + build: + runs-on: ubuntu-latest + environment: + name: prod + + steps: + - name: Extract current branch name + shell: bash + # bash pattern expansion to grab branch name without slashes + run: ref="${GITHUB_REF#refs/heads/}" && echo "branch=${ref////-}" >> $GITHUB_OUTPUT + id: ref + + - name: Checkout sources + uses: actions/checkout@v6 + + - uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-read-only: true + + - name: Execute Gradle build + run: ./gradlew build -Pbuild.release=true + + - name: Upload artifacts + uses: actions/upload-artifact@v7 + with: + name: sodium-artifacts-${{ steps.ref.outputs.branch }} + path: build/mods/*.jar + + - name: Publish Modrinth, Curseforge and Github Releases + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }} + MODRINTH_API_KEY: ${{ secrets.MODRINTH_API_KEY }} + run: ./gradlew publishMods -Pbuild.release=true -Pbuild.release.platform=${{ inputs.platform }} + + - name: Publish Tag Release to CaffeineMC Maven + env: + ORG_GRADLE_PROJECT_caffeineMCMavenUsername: ${{ secrets.CAFFEINEMC_MAVEN_USERNAME }} + ORG_GRADLE_PROJECT_caffeineMCMavenPassword: ${{ secrets.CAFFEINEMC_MAVEN_PASSWORD }} + if: ${{ env.ORG_GRADLE_PROJECT_caffeineMCMavenUsername != '' && env.ORG_GRADLE_PROJECT_caffeineMCMavenPassword != '' }} + run: ./gradlew publishAllPublicationsToCaffeineMCRepository -Pbuild.release=true diff --git a/.gitignore b/.gitignore index 69cd50978c..ec97f0641b 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,6 @@ neoforge/runs .DS_Store .AppleDouble .LSOverride + +# Kotlin compiler working directory +.kotlin \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..1df59e9278 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +[ReleaseTag]() is automatically replaced with the release tag, e.g. mc26.1-0.8.9 +[MCVersion]() is automatically replaced with the minecraft version, e.g. 26.1 +[SodiumVersion]() is automatically replaced with the sodium version, e.g. 0.8.9 +Everything above the line is ignored and not included in the changelog. Everything below will be in the +changelog on GitHub, Modrinth and CurseForge. +---------- +Sodium [SodiumVersion]() fixes some bugs and updates to the latest version of NeoForge. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 60221b425c..9ca9757920 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,6 +3,10 @@ When submitting a pull request, you are granting [JellySquid](https://jellysquid.me) the right to license your contributions under the [Polyform Shield License (Version 1.0.0)](LICENSE.md). +Without exception, it is required that all code being submitted to the project is of your original work. We +will not accept pull requests which contain *any amount* of AI-generated code, regardless of the particular +circumstances. + If you have any questions about these terms, please [get in contact with us](https://caffeinemc.net/discord). ### Code Style @@ -56,4 +60,4 @@ style guidelines. If you're adding new Mixin patches to the project, please ensure that you have created appropriate entries to disable them in the config file. Mixins should always be self-contained and grouped into "patch sets" which are easy to isolate, -and where that is not possible, they should be placed into the "core" package. \ No newline at end of file +and where that is not possible, they should be placed into the "core" package. diff --git a/README.md b/README.md index e8af77f8ca..ab9929ea6a 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,10 @@ The latest stable release of Sodium can be downloaded from our official [Modrint #### Nightly builds (for developers) We also provide bleeding-edge builds ("nightlies") which are useful for testing the very latest changes before they're -packaged into a release. These builds are only provided for other mod developers and users with expert skills, and do -not come with any support or warranty. It is often the case they have issues and lack compatibility with other mods. +packaged into a release. These builds are primarily intended for other mod developers and users with expert skills, and do +not come with any support or warranty. -The latest nightly build for each current branch of development can be downloaded below. - -- Minecraft 1.21.4 (latest): [Download nightly](https://nightly.link/CaffeineMC/sodium/workflows/build-commit/dev/sodium-artifacts-dev.zip) or [View all builds](https://github.com/CaffeineMC/sodium/actions/workflows/build-commit.yml?query=branch%3Adev) -- Minecraft 1.21.3: [Download nightly](https://nightly.link/CaffeineMC/sodium/workflows/build-commit/1.21.3%2Fstable/sodium-artifacts-1.21.3-stable.zip) or [View all builds](https://github.com/CaffeineMC/sodium/actions/workflows/build-commit.yml?query=branch%3A1.21.3%2Fstable) -- Minecraft 1.21.1: [Download nightly](https://nightly.link/CaffeineMC/sodium/workflows/build-commit/1.21.1%2Fstable/sodium-artifacts-1.21.1-stable.zip) or [View all builds](https://github.com/CaffeineMC/sodium/actions/workflows/build-commit.yml?query=branch%3A1.21.1%2Fstable) +For a complete listing of available nightly builds, please see [the wiki page](https://github.com/CaffeineMC/sodium/wiki/Nightly-Builds). We also have a Maven repository for including Sodium in your development workspace or build process, for which you can also find [documentation on our wiki](https://github.com/CaffeineMC/sodium/wiki/CaffeineMC-Maven-&-Config-API). ### 🖥️ Installation @@ -77,8 +73,8 @@ not work with future versions of Sodium. #### OpenGL Compatibility Layers -Devices which need to use OpenGL translation layers (such as GL4ES, ANGLE, etc) are not supported and will very likely -not work with Sodium. These translation layers do not implement required functionality and they suffer from underlying +Devices which need to use OpenGL translation layers (such as GL4ES, ANGLE, etc.) are not supported and will very likely +not work with Sodium. These translation layers do not implement required functionality, and they suffer from underlying driver bugs which cannot be worked around. ## 🛠️ Building from sources diff --git a/build.gradle.kts b/build.gradle.kts index e69de29bb2..fb1b8fe2cc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -0,0 +1,87 @@ +import me.modmuss50.mpp.ReleaseType +import me.modmuss50.mpp.platforms.curseforge.CurseforgeOptions +import me.modmuss50.mpp.platforms.modrinth.ModrinthOptions +import java.util.* + +plugins { + id("me.modmuss50.mod-publish-plugin") version("1.1.0") +} + +gradle.projectsEvaluated { + publishMods { + if (!project.hasProperty("build.release")) { + return@publishMods println("Publishing is disabled, please use the CI publishing workflow") + } + + val releasePlatform: String = project.providers.gradleProperty("build.release.platform").orNull + ?: return@publishMods println("build.release.platform must be defined (expected: both, fabric, neoforge)") + + val modVersion = BuildConfig.createVersionString(project); + + type = when { + modVersion.contains("alpha") -> ReleaseType.ALPHA + modVersion.contains("beta") -> ReleaseType.BETA + else -> ReleaseType.STABLE + } + changelog = BuildConfig.getChangelog(project) + + val curseforgeShared = curseforgeOptions { + accessToken = project.providers.environmentVariable("CURSEFORGE_API_KEY") + projectId = BuildConfig.CURSEFORGE_PROJECT_ID + minecraftVersions.add(BuildConfig.MINECRAFT_VERSION) + } + + val modrinthShared = modrinthOptions { + accessToken = project.providers.environmentVariable("MODRINTH_API_KEY") + projectId = BuildConfig.MODRINTH_PROJECT_ID + minecraftVersions.add(BuildConfig.MINECRAFT_VERSION) + } + + setupFor("Fabric", releasePlatform, curseforgeShared, modrinthShared) + setupFor("NeoForge", releasePlatform, curseforgeShared, modrinthShared) + + github { + accessToken = project.providers.environmentVariable("GITHUB_TOKEN") + repository = "CaffeineMC/sodium" + commitish = BuildConfig.calculateGitHash(project) + version = BuildConfig.RELEASE_TAG + displayName = "Sodium ${BuildConfig.MOD_VERSION} for Minecraft ${BuildConfig.MINECRAFT_VERSION}" + file.unset() + file.unsetConvention() + + allowEmptyFiles = true + } + } +} + +fun me.modmuss50.mpp.ModPublishExtension.setupFor(loaderName: String, releasePlatform: String, curseforgeOptions: Provider, modrinthOptions: Provider) { + val loaderLowercase = loaderName.lowercase(Locale.ROOT) + + if (releasePlatform == "both" || releasePlatform == loaderLowercase) { + val jar = project(":$loaderLowercase").tasks.named("jar").get().archiveFile + + val releaseTitle = "Sodium ${BuildConfig.MOD_VERSION} for $loaderName ${BuildConfig.MINECRAFT_VERSION}" + val releaseVersion = "${BuildConfig.RELEASE_TAG}-$loaderLowercase" + + curseforge("curseforge$loaderName") { + from(curseforgeOptions) + + file.set(jar) + displayName = releaseTitle + version = releaseVersion + modLoaders.add(loaderLowercase) + + clientRequired = true + serverRequired = false + } + + modrinth("modrinth$loaderName") { + from(modrinthOptions) + + file.set(jar) + displayName = releaseTitle + version = releaseVersion + modLoaders.add(loaderLowercase) + } + } +} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 9906d57c02..3b37071f01 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -2,15 +2,20 @@ import org.gradle.api.Project object BuildConfig { val MINECRAFT_VERSION: String = "1.21.1" - val NEOFORGE_VERSION: String = "21.1.143" - val FABRIC_LOADER_VERSION: String = "0.16.9" - val FABRIC_API_VERSION: String = "0.110.0+1.21.1" + val NEOFORGE_VERSION: String = "21.1.228" + val FABRIC_LOADER_VERSION: String = "0.19.2" + val FABRIC_API_VERSION: String = "0.116.11+1.21.1" // This value can be set to null to disable Parchment. val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ - var MOD_VERSION: String = "0.6.13" + val MOD_VERSION: String = "0.8.11" + + val RELEASE_TAG: String = "mc$MINECRAFT_VERSION-$MOD_VERSION" + + val CURSEFORGE_PROJECT_ID = "394468" + val MODRINTH_PROJECT_ID = "AANobbMI" fun createVersionString(project: Project): String { val builder = StringBuilder() @@ -22,7 +27,7 @@ object BuildConfig { builder.append(MOD_VERSION) } else { builder.append(MOD_VERSION.substringBefore('-')) - builder.append("-snapshot") + builder.append("-SNAPSHOT") } builder.append("+mc").append(MINECRAFT_VERSION) @@ -37,4 +42,21 @@ object BuildConfig { return builder.toString() } -} \ No newline at end of file + + fun calculateGitHash(project: Project): String = try { + val output = project.providers.exec { + workingDir(project.projectDir) + commandLine("git", "rev-parse", "HEAD") + } + output.standardOutput.asText.get().trim() + } catch (_: Throwable) { + "unknown" + } + + fun getChangelog(project: Project): String = project.rootProject.file("CHANGELOG.md").readText() + .split("----------")[1] + .trim() + .replace("[ReleaseTag]()", RELEASE_TAG) + .replace("[MCVersion]()", MINECRAFT_VERSION) + .replace("[SodiumVersion]()", MOD_VERSION) +} diff --git a/buildSrc/src/main/kotlin/multiloader-platform.gradle.kts b/buildSrc/src/main/kotlin/multiloader-platform.gradle.kts index 006f9f3500..55b89b8cba 100644 --- a/buildSrc/src/main/kotlin/multiloader-platform.gradle.kts +++ b/buildSrc/src/main/kotlin/multiloader-platform.gradle.kts @@ -16,7 +16,7 @@ tasks { inputs.property("version", version) filesMatching(listOf("fabric.mod.json", "META-INF/neoforge.mods.toml")) { - expand(mapOf("version" to version)) + expand(mapOf("version" to inputs.properties["version"])) } } @@ -31,13 +31,23 @@ tasks { } publishing { - publications { - create("maven") { - groupId = project.group as String - artifactId = project.name as String - version = version - - from(components["java"]) + // Each platform is responsible for their own "publications". + + repositories { + val isReleaseBuild = project.hasProperty("build.release") + val caffeineMCMavenUsername: String? by project // reads from ORG_GRADLE_PROJECT_caffeineMCMavenUsername + val caffeineMCMavenPassword: String? by project // reads from ORG_GRADLE_PROJECT_caffeineMCMavenPassword + + maven { + name = "CaffeineMC" + url = uri("https://maven.caffeinemc.net".let { + if (isReleaseBuild) "$it/releases" else "$it/snapshots" + }) + + credentials { + username = caffeineMCMavenUsername + password = caffeineMCMavenPassword + } } } } diff --git a/common/build.gradle.kts b/common/build.gradle.kts index c56c417140..c57f91ebcc 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -2,7 +2,7 @@ plugins { id("multiloader-base") id("java-library") - id("fabric-loom") version ("1.8.9") + id("net.fabricmc.fabric-loom-remap") version ("1.16.1") } base { diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index d8c96b3940..58c79218ab 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("multiloader-platform") - id("fabric-loom") version ("1.8.9") + id("net.fabricmc.fabric-loom-remap") version ("1.16.1") } base { diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 2c3521197d7c4586c843d1d3e9090525f1898cde..d997cfc60f4cff0e7451d19d49a82fa986695d07 100644 GIT binary patch delta 40693 zcmXVXQ(#@~_jHrSIk8TX#egCqh=&qF9`37}mW&Ux zV7mU&n769QdpyXVAH@B#L5QQg!M#QATdPlDeiEXr`M64LTE@Hm#^Qw1+@y)X=$(BS!`~n zL7e1%g;bVB@B*?VXnA!}2L-`^|FF1uO=7-X9j*Adq71vU zY{eQc%Ph^T1k7h;+8G-&Cou5R@lQnuWg(g9 z|L>P=zsQS|f(1}DKx%7>7%G_h;mrX1a5+SYs~~OzD>*p{hbD?{R3>tX7z)%FrZTqh zlODIyj12VH#LsD>D~(v`>4B$n&nJxT2wxZ*IWL}9R^Pg&sa4B>_xFddJ^ZP-n?&FE znYvYM^foY3OOr9(rABEgH>~g|9|3D)Ty_e|n`oumwfXVxpo_zWcIC%)&3+A8zI8VV zMeEaN7{@~-BGZEAk)}b4O82P0A33Sks(m%LZ}g#kvq*Aq!}V@s7l^|YjAJ;KhZa%Z z3g>i=uRTB$jgEuz-9%%AFqyR(#P)s;HB9w6>gwfKn3hHak{YgeV~P;mFppjEmE}Gk66NHTd>&P8=3ee$o96wjov`BR7bexfTCGVt){xl@1>9ii0b&xF z7@X21W^^$G8C?GeNwi*yut4OKQhO`(A>2OvwJE|eP-~DEW}35xyJF{40K*=`Ds-dSi2 zed*o^u~h$e5uf7NjV2t?T#??o4qpe`G8@@eDJ#7>pz29LvYAU%%6K=*_-9BMO-Z4Z5GJw}!ZCnJ#3Z_e z@3BP0`QFL-!#EiLBxUmoR}>C>jj;`H{SfMQj-d$~yJNWlTr%fUIHvAU9WHho(LZ9z z=@KahfDquu-6&^VgJ|xgBczLwK8%7Ssdqn!ah-$c$?!*2U!{nx0MNIg9}xf5r*U=Y z-#ExGU)H|93><_fTWF`yW zmM+4sHQR?;YL*+ZH>7}RD2kZ@qV0HAMMF3^1-#3ooRy5DjEtO|_xGb=rZ10)rcZ88 zS3jS6%U!BSitVfMG$*uKt9u;V3FFs=GXsnef70#16C$`QdRytZ8-qCdQi<@YI4(j> zMy+^Dr?N`d;;!@C`~IechiD9%2GksQo57u}cgpFK5NIJ3=$$0EE0rg>BzCp>Iks7? znoK=$BMjv`LNl00TGmnl{|NIyOF6G$K##j3o&{dITBp&>BG!r)94^@;DrZf2>=cA? z_s&4O3z+NbmKmvUi^+ z&<^e)3|93Ho|sN&Jf@+V;y4Z~!pTQ_g>K>*)?)JRt2tMUEX{h)1XQhQoN*-_lJX1mm|8sL0^fS5@7S3 z{IhVjj6iH*lwjzov_v8D81rrc2Bxp-C>kW&N+ZcA(k89+d> zHI3`mazDhF*k%0=iOnu?V?R(7uw>h~yPR?Y_9^A@*5>VAlK z&6N)-{svfaxqF~h8hBL%QRJqu1aAS+f3aU#^Uh>; z%ftQ^tb_X1LB-s?(Ma2egmyQ>4?gA?@Ocfomi%1p`S*OZy>r9-dk3BD#&e6kk#Uaa zV?g$FSCCd4agV~AkiQjv;toi5gstv(oPLL)`Uw(w4=K2*nF1<>Pmj3X)sg9!mNon- zy2G37(iyh!;<+Zo8F&4wQvo7<=L-1VpAvtT4*&gDhf}Pxs;=+2c1|F&xo#v}SC;+f z6;^m+f+&p`$q#%tBru_me_|@xObOrIa&H}da%4oT=7M*j|2hth87!{) z^~)D9?Ei2+my7}|mq!80(fqE9zl0kA8v;A9jO_JwEz;ckD~U+w0HyH87iV->h}vFd z9)~0m-Ut@5SbjE0FuTVig-nYZg+dmi;ySy-Suwqb*Xm&| zR!UV%H01eq9DKR-W5~w;XMmBsuaE-Cwq>Fe{ z^nH%J7k7>K z>+(TOA$2>o`jQYsS`TV!#}SsrSC#M_ybCb}EZTZqF^bClQwXF0^>Gd1;ILR1toeHQ ztOFY%x;a_T)loayVtr_Rq&B!rAKsND6p=ljIW%oL3tBwwc%RMFZS^pjIvUn>`_s6& zT9S}bT6R)Uwb9(oPJ%hPIy;+ma06QzJ3n=Xj+1t;KV3l;_t<*Yxr*l>#06_4l%3W* zvOTCrAJX0=nuk`!5$Z!Z2$-V4^!Xf+@K7^lOo%j;Bm-Zd#)aGPaB})|k4(uy~=b%}qa8=hMpZO|nz&u>=W%nfDiun95Gw`A>Pz!H~je%Fstk$@Ck z#Xm5jRbilAwoj?mXtinPOs>w02Byi%XBvW*!IHF%_L+}xVXVK-x0^7;grxq_~EzkMQ&XKi-Uw&bChw0VRH3Ar`*lW2d`cu)Ze z6619-|1T_(s{S5$m|UmOUDGojrXO z_3fwolLJ<*iKnrTZ&F+I87zY0gN4Gp${O~)%;A;Q5+ps#LmHGJ4Dfj3T9 ztY;TOLa{iA#G9=xWOB@PQ={9VTQx&^vh0al&O@0f5oL1XXZOp`3N&M1%=s&jsOqyv z?RFFjbgj)kTkq>_;`aNq_|=D>s#kUlar2dLbO%{-?e?b;wa1R=Gk-HvKI0-=yney= z^)2>jft~N?v42bqyBx7$HzD{HV4bO%K1oZ)pM;1qdpB{#HwP2BoT{ynYv#(~2QA9Y zkO}%-z4i|w+fDXY=k{K|62?k^#{bP^@f;g{;M?POwa8x|eYj0&JDk7ql^-0TJa+_Y zZFk5ZhaxR$$4-jkTPL^82$xW;5k6s~ob8VeKX?F&V!A!b^z8A=6A2;BWd?R0V!~d8xj?;8agYToY^(z@x|({>HWbAfbU}7*B3jEhHb$y2 zc(xAl+4uD`Fs~!NeyfOwi1_?~Lm@C_!7F`iHGN@N2G_(fu7OWMvxmaN65B<`abSGk zFLZ-vV$&}c>l#~S-N3jp@S&F|OZuYHRiZ61?C(eI7T>7z& zVhH=?3opu-FUoS@1uOyZ?>ur)j+(vFoHAwrW3aoO#1JOh)-=LuL&|fIDMXPH)z!{k^@>Lwq$869Oa*_8M%Sjg=NJ2E)$Y`%kl1esT#ysI}V#`SBefe)d25#uuUb zO!Jzat@u%uiw#Keo&c)F*0oyKs&3Kce3tD?H~qA4JB(ZoGb+++iK0+(jiVh*K?x>_ zzZ={vD~z>G41M#ynvS%*IT%Gz#nAyz1B{l=f9TehXVj|cJ_^LkRmsRsd80-dePm#S zk+-cxs)2yZH4##xz0_i$dufNQ;uw0dMJ07$YL=ACD+CHnHQHw`S* z%cXo%#JDt!2(k|H;xEh%qqR^T+oPXkk$<{jdj3mtBLDRV1-s>oYr@62fDl=PbeeNSFNn2}566YP9 znP?_tB$$dVCaHw?Td^g~nAOA4t*aT%;HH_5Szw zfxdgvufJkU3sXpÐt5p(LLtXn?pxm3OZHd|13r);5)1uCW40m{;j zCC_~NBA9$l4}Fm8#+9-0Jn7l(n!)z_^X&=R2-6ji7aynjlW2i-$urcpT&=RZq*SA8 z|NJ}WV@*;O4}};iV<i5&#CUeBF z$Ru{Q()}<7)^>aIL%FzqebdO~)V8~j7@UWoY*(VzPGL>|3q!0#COp~eZqcgTeSl)9r($lK-fwBJEZY6U1legxWJL_?rT z$Uz~PsIU`%OaCW^ehbjXR4>1yLoz*P$upq0!mj~7D%auhDt zblbzCg;ph@lsWXEX;3tPEzh^3zAal&{c#q)(vYU%>Gl) z5X)-(OdhX<%{y>ni={IlD)K%reH}|4A!~22)cEq4*RCCb}xyu=b%Spp{ zLHw`GCBoc?7XN7pve5tAY)&W@KwzfKSz$RL#ja{@@(A-2SX2>>3W-YLsC*k#SchL7 z;Mm?X{gusfRcN6QP;Z4Q$~(lnN`!M})F!InllNwpcW#Cd*YAL@J-%KP+ov}-x}Q?0 zrY?%SNFG6EnAOlSiD{|@;85gSQi?+)8c{j=xS6og^dwi^1I}gKf}f4ppycoArns_~ z4Y-cR?M)MHrJr>H>NJBP1g1}6VzU|Z;*zH^EAY7EAeJpdP|GHYS|$|EEiqJMxP&?S z3n>=7mu@=!7@#z&P<+&?Zp3x|ZDUpqG*nU)HNL?AGTXd+wK0M zk3@#u5Uuf){ozGLIDcDo((uFk*qYoYmY47{cRB+9UX45mY}_k|bYjN+wO!uPzb%N!a*e}isT_nFJoI%&y~UYIE3 zC6x124)vl&f^oXw_Sa39R?8-jcG$iMOf%eBJsl9>pk^zrC>bfNI!_wa%goo~F5S3< zmym06-b1EYXHCNyEtDTqPc^ZB9+S~XMSuQIk;-)(cwvcpD!853zreeaRX#}%jI~mtMW1Hpgh!8VC?r;8UFNWk;&os&Jz8n2=**UPfiqJz0SdgcuiGsnnKN~=0cYt{ z4dYXkM!gisFxJu!P+}7Nv!wB3{uHPVE}e|7cZoGdx^FKyhacQshg|#54V`@+HuPFK z^xL=Ty@F~&J&-YkF*1_@L4zs_vvT35)Q2E(%a)!qOw9lD8#2nxld8c+44xp&F@#h{ zEKIHWX19(fXsj6cBw#EOPz5kBuzq2X8oJRjvB6*y!yhrVjiP`+`SG8DxMxSRN-Wc# zm(#}fn9alX>v8FlNC;wmkiP%u{T=5Z-X}%3Lv-~e$ie z_g}y7bD|*G>S}iwZ=l?Vf*3AB3(J?j^&fHc$MRfg9JLB?DS2u<1Al8g@*{+-t+z@@ z{eN>o+3laXV58Eh*RDp3-@H60bpM0JY%!?)=YZ)F2H^4QL3-S7GnM?f>%>9NV_PHL zz+HBga`}mtix$k~z70ca0H$uV7x}C->-q(p5cJYS0)lB;0Z9EsdKyOGOxo#y8KCL$ zM93w;NZ`s4#aF7HcaJijpJ(Q9vuytcbrP^G%jGjd?DEOxLCKx<^u{a3`pEvfwt(n} zO`5EVB)Hq4n7LS_zikS8EtPK&(tpnhfWYtXTo-@2*xAUtbjVZ5AFv`Y3TcDT8UoCR)ER zy-CT1R;bTtRjzQVWMb)3HgUX%hymxO+T3jFNU3lX3rKhiJ*V_Io19N?*$ey>*PHPb zfP=zp^UdfDe|kW_XK?Xvz|R;vts6NyPk4+Pz2+1-EM(^cTRMTzD*KaK@X6@!V{g@O zMZ*aA_((B=wh4tW4&4GF*X@DVz#bz`3i5J-)9mH2%zSZrxXAxWpz&}~Qm+5-V;Jgx zt5+Bq5Nv=-2#%+N0mm_;gS0TTfE&Y5R0G-+`Rv%{nKtl&3A6^Nor4i6knrg9l8txF zuF{f}Y|a*aC7MKf{uerer>2H}_Epe2TK0LbvqUpnGqa8styn=--|5(n{4DeQyMO&2 zf4iPya)ihpg6xl`QW|Nr2}wm5@}iNZa5ECt8?V=Wm*^0f=X#e9N_hxbf--+ z)@VxTlfQRpHtfB#>9t6BcIwD#cyW}peX}f`a=Aa}>C^E)d$qJ_SA^;Kqef*-8J2OX9} zIoSaF5QDBZdJq7X2z14hyV#~8dsIdQH<(zTR=^{~$&x&!aKKs=tY(@K8QFXNP0ViK z>(Vy`CEY@D>{-)w=MZzibll!LrJ@$pYo^7mwSGC^oUtF`*Xj2|(BrEKukQ!+4xO&PV zRB<6y0PuGQU@4iaMv8yKK^%rh_#gWW{4?x90=$>P&eD=U_cj@7n#vQjKSh^z5F}N|uG5c`mNI!v-ONq8W71=!sNn7KT^kp2+N!zt}@A zcS|q$L>KQqL|A~;sn9zR_v~Ga$;S!&2#4%3NAm_AGz^nRnJJy(RR{Dgxz{?>L|Wwy zjVcop3@@zs8|jTRd1ECZ4&!9n{J0wyQVKZu5<%st#?HsU z_C)M|SfP+unH$M@JvVuR5CAxqs^gW5aEn@HKelqj@zqg`+c*^QpnlyEm@F%8C})%` z^6osi+7vh>yEKh8*7GA~mLtmCxdTWKIVrY{{`^fgvv$d1?!w)?J%qqhxh=JfwHWT? zK?Nybr=j@=r|w>{B2$1w&rqB|B%Qwl?S~0b*1p+m_cd(KX(be0W04z>oCN0;AZ`ez z4~`VT{ABR#>H@a9HU8!EOF8HDsT6v`t7+Iq7xWIyYB0K+M}ILOekV(+z3xM2otk-4 z_|c{Jev9;TaJw!csy|S>9+g18*2oH%>^I2%*O5m_Wq}0TA#v1uWT>VrLX%zGT;SH6xWCIl{Ck^_rE!YA1@zL zU$j(=ydLgOx}MT{W0E#@!tk=;EnIu0nQgJ7GHbarLDu4krDHOV&N+0PW%0UE1QQO| z=W+V9%e@)dg=_ri^oK>zCBOyo7~2eMiJWf5X3fr>Jh~HJnsWvUUYWY}5nGs}REvF? zj&X)8mqbB977n!dNTk>R|)FBo}ebFmPn0@#xIma+HZ9ManL{ z7-(jflPmi^)4St)6kp$;U1-;|Dk+Qiw+2%hM9pq>VnJ53pO5g2rwuRNC zLfD)s0cpIDkyC7}(A6%tA1nD2uAe$!n?bCe9P5p>hzFbtdsC`xLtjx z3{0IIC-#+9U-{u7aB)JUh+aZBBw*qXo#dS5<%kI#gZw&b^X^Tc(j-b%U_h^)@OuFC z28-C*a-9t^roMMgw4=(HlPII@+ z92B1hQlI62Tdz}idL?FbBPOuW^zZjo5XKT%nFF3&B9{Y(^R!ogH^G#%w6ZhHX01D1=wxs_yw_wS0)qR#B0k8Z?1>xlCUoa3zQ2nG!EJU>HYybh3;_Frn2NH67If0Sd1Dm=&+T)VMi@899piw z0{PG4z8M+sc^^FDganF|9Wm=+{V9lcl4(@GqJl&(m8qVd#rRFw+s*stSpE$sXH*$? z{A59F-DRq)&pynoIL`XUuQ-wQ8sIYW+vmqU67Aw=0czi_3WmPx6y5JI5c1W`CR%DV zWHbsR{CG7&X;8swR>R;3m}1N=%%2=%xROnLvdY)Zl+Wr3weW=FtQxkIVK8V+xqg z&+BZpyC;TPSpy0e3rVGWV?(2YrVrTL6v`BnSizX$cVcgdII7B5U*D*&o3|$?4muCl zGoC-5pCFw=6atsh($m1U`y^}{rxQk^LQ*ge{z8L5A z!RnGP^8J;uuQ)_96-oq&87lC`b-3d$C~@23fsTAb7wMO~?@-cJ@v4$X%GoX4#fNSf z7x7=iUy+0g6Cd@0P^q`fL*Y+ktMcN^y}~3D*t4eYOn|@0r-U)1|^7@$Jq>s#804(?!=EbFd`?5*Hb-0s&i>0d;S0m!3jjmZNn6YFrM0N5g2$Rzb>Y z6Eot%G}7w`2QAyMQGxzu5V}SOwe$s?nv+(-&%TWi1GJZP-MaR~Ky)sBwD|e4NZL9I zKx9MR^l-HQ>*(T1-Xqh30vO66l*(gHL)*L`y^p1uRc1JJPnACDk?N8B6+?oBu&p+T z1d?@%gRESqy^a1hf^fy^}rO3HS^Z2YII9`5m* zyHsWKYP8b3DfPODPV6(rnT5sW;f=D?-vUU|(n6qccQp_i$uj-V({9PfgFAAu1t5gz z)KE>70gvKMhYGAvg&mXO^^kPI$D*Eaw7k6a0OKg$d=sSL%C*#Cq;d1*MsBP2zMSk< zRh(2tWZ4*ZZ6+2FXM&~dAln_x(q7zA9MB-t*q*L$e<9eiWXa+NPorQ)%%81aaWsZD zg4wODaERbyB`!uL>;yHp{{h*qAK530%!YD_hU%>F#4Y+;2d{v!%Kxtw3nmQ$S(0yW17hiV*0!xF&uwWCHI zUhRk;uZTK@;eCR`Bob`X9?vQEn!;EDEoH7A8Mv0J62Z#bO%*@S<2=);a}>3_{y#E52Mi{zS<+A+C4H)2t)26WvU_w8ffShB{ z(uVeKFf%eqQ!;*t<7mVuTMaOp3On(>+Wz>U0&Oq*ulQwGy7ZkDfedO-G}xP`ApY#$ z=5vAbKSURVUBOrl7wSr1yMjF%D$DrXVYy5GLI-$UKGy(Mn|gy-Doon3bEuaaNW%2*`VtDYY&3-*5nYBSnBZie$A zT^8geGaY}E7;Xols-c-HjCl)-;P}tMvp1~#rsJ-ic4Jy+@wAm%tHypp^j8>j5T?vw z$M5lj@{{>JVWu>v2;VBM)hAKcLxtO7L=^} z+Z4597b2-d(-t5os>4N%d3WVF2q(z8sv+e2vKx#@dr z!Tx%?)N=U+`C55<#D8^#y-0%Bet3a4_y|8$0iYyyBb(<{K_TpJ* zrlB&}c$89O(edp)ypW!4(mFW_rbd!LaHo7xV{&AH^jefGodUZlEo+-jy~f>J7i-hp zc=L9K>i&9LBWua5n3mD4ETg#@Ii2C4RxvF~6=W4iscB)MLfF#t(j|i94)%%}6=;6L zTgnFJ^U=4h{L|Dkc`?2yexxEdvOGPyNfG@SEU=DoTtP`-g7NNdXGC6q?+8bj7G*I` z*bLQf*+~oWS-88!oi?~O1Vwh|m3de#%--+p*RvAg*z4)|27T62lOIp=#^>WAWDX66oV*2Z?OjHY(RuC5yD&|m(&_T@{g#)d0~2Il7mzlCOXYkL+-`-DcLC>_qMEVs1@hh@7c0vLmL`~k}{CjW9|sDzoFipi(Tr{X>B zsf}XH)$N2S-zkBkx5?)m*Qt-|OQzot+_2F*px*b_rLarM=&1I-SXDRj%G9FBavM$C z%-ZElYw{|KNbveDuhwZca$2&Fs{Zb&Y?n%nl+;+1!BM1DO;R;&Q!%AqHj{y}EDmE* zEk~!Rp~EpR{HdJm4ZyxlWd<D>!_^ZsRA;ioeP z;t^YKq^*c5Np{P%SjrAY3ap!|hwy*z05v#Wm+vRzJ<;H9XQ>rOpCum}VBN>;b+uyq~^O1TW$0HT0fA z+-?6LLPv(#4xNHU34(aitzzyMS<~qN_5H0%l`&qk1>8FBcT+gKFmVAiFsV5<3}TQy z#;97gD0kq3&8{2SdXL7ve%R4iDhu-opy~?gL!Mra4iYaLM z8eAhf5&$4pY{FD9Nd_k6s6R%oN@FUbi}guF?TA-@)n%f~MT;V0^|JrvKH|i^$TIB8 z&E&?!{OvQZQWjVmSJHFer2k=XdaSkU5Df|JwL%f>10GaohE*>6+8JiCt;Fq__P$e@ zjXQE3)BY{+fb|Sas&?{K)jBN(p?g!vNJ)1K4kouk%>fEFzUd+#hXphrX*LOX_8}NS z=Dj9LhACS^GAPVyJJ;%D>Zrhp(`}s_Wg-)YSM}W?M!9_Ex1LGl@><(5re$OjJeuTM z2*(!wHq|Kso6qr{3?lI^#h`z3YEjl}>pamLiPXD+d$k-f&;F_sMV8Eg2xQ*zw&3n1 zdJoRnF$+}85G|m@1&Zbs51MQgj!tZvjOiqDlyKQa$&)wJ#g1r`-?#IH$vS|2 zo@*JB9A!xsPpg}?{xjd{rhV&UXwhb9djmTaP*7Z0sR~WFlS_I9*UGWm4x~{FBdKO? zOQSW}5v;M$gA+ak@*x62U*0nt`H*qC0>xYku7KgY@u;%=qWe@4X(-_{Yntq%%G8r1 zxxDMjV3(Dm;@Lzf1xHCvR63C%33BVI@dvQ$7Ut zWcw!04g8ID{+J7{IaV%&xIn2;j(j(##|+;u_vX0pGw}(VF)0{CXFRnVe{2v=Kb+SO z_K#4`?p8{8C+xZr6Su-~_DJ$2a99en-Gfd-xtKQOmf(sdwqRZh<5qZvk1%x!gueImS4jF*6Ua2Q+9YuIj#D+5KcTE<;b?n9I&ixMSIl#8VMBrS^ z{L0rEvu~Nyn@9=jrj7Zme=ie`ZhgsWr67LYXvtKz%nQVkTz9!)NsVh~6ZcXMK=AQE zH#NI3&InmI;ljX)-WJ%+^2k!`l88f0_(0=Fwj(=t;uR(`%G!08yJ~zGXhfyhN$T;` z2&w}zJFm9<`poh<;e18`7g6UNz|~2=VE@gSBh+_#hgC-Uhm5lDg5b4(tJmrOX+-oQ z@*q`xrGJSM0kY7+pTr1*mv#3gR%r|6%gTGfKnx7Hu&C1bxxarsjCeaTavlapP_DJE z)X`=9IIg8CJS)mYFt{>u*4d8MeW!AsU;fTneu+ng^IPBAs{M_!%DPE52frB9ia#I3 z{nt`fu1poE?yR$`A&v#uCXT zSC#F_Pb2~^x3(CBS~&UkWc#c2$}*dnm)5W|=#wyE>zq0*qvn$*X#27ASUcNYW2HW^ z>jozITlCh!8P{ayUSFq>3{BhHO_Isj`YUKesq)5)MafpTh$^ym{&IqEwvM+TOGqdh6;xmK***GBgpB1iAm4*+tP?8NZ#6oM}Ko(M59q7@&cJ zm-~jA_F(5LFnMuqV_}CJ;RmKu@!Fch_*A_!FPE)&12g#1|ot zka^>sSJR9EXH z7?gB6K+jJYA4kpSL#oI1R6{fsCHh6EU|(6Mxvk;P7%#fBU$gO?&m;zs`#>Ige-#SJwiv$XGXIVY=^?R zk&^L!TPMw`HrNRP|0a#ASXPJUQg$h|vbdH&Z+8*L2!3Peu+gfsIX1`u zeI7lj7wf;{8RbIDi2rvygpmKHOFFPnDjXPD1|IAo!35EG#ufb78=4jsOZ*0?Fu^pdKqt72Tgs~T+<13NsB}4r6;skXE)aN#nGcDOomQi} z&$}bK6MvnAq^SAgu9f+iW0{-rxR>$#zOqO4Wzslwr=!Dj$<`2;z`+52iZvDKmn}W8 zbc=|aF&^}7i|)~14wVp2>q>drBtG2y7uBB{=8Nzlm$Z%oZDtscwkcS1sa)r7wF6D} z$X1cR2OkdS?NA{C27+R0J1cQi;@e)F1!8C+n9*0OyDNh_E9hc-XDZAvH3!3iG_}07 zM&+-8`VC(l+6sRXT=fb=rOT; z5=&55#A1{eW;mU|X>dI-x$o4**>qnOe{`7nqf%kXdtS{sM)dxfK*pXu3W>nGvucO? zko>}nchgr;=(-NOg_Nc)3Jf?+=xESBQ$-JzY&5nLjz$UnP-<0ho-dD3no}lDP5+U z5EF$&-2$Wx?e*ikMoyyr&*T7XudW>{h;M)Q&yQ^U%L~HHXMLP8aooQ3s>DK(cFob< zX#$6&iCoWkiPn>|fRvec>J9_%ad7BRU~w&(3QTWu9&fPfl{jBg);UB4DWz4XIOrGu zY{(b2l82*sr>lA7(iLRn5)bBE=0rbL%IlyvSjl79?orDNP&|{Y(9X1@m1R>khbI$g zDsSj~v87_om-&G?Z()6{%R|Ha2i~=X%NR;|vJ1lV9FI9lnTInapP@#82v6Dh?m7d) z6WLJhdyG!U9o&7{bK5HlGvx^^lRo-mDqykufsVE0ZJkjtK-ztBmwdHcRt3Eyq$*N> z(8olVUDo$Y8qEoA@n=xyu)M@QE-P)ao6oM&=^zzUoZ4`lO2+NLCV8HN<80OZ|D?xO z)pSd_e^+PF|1V(nC@luk{g3|s1uAt@Yp~Tjx3K5!PX_5Q)iI;JAjlv%I0gR&mZobR zJkQRt$^T__Q3PMACFSM^o{A%dWckMj8$-o2-8>JoFSBoR*tX6ug$#dxZH=%F;~F3T z)u!c>m^Bq@ds5WSQ&;9TSEX(r4%>WGN|#G9!Wc@{$~5x*FaWelk9gZ=SoQRB%Re~N=l<NQgL%H_}YPsoGQ-T?+=;*Yl>OhfD5*yhcj{K*pZNw>F|qEKIC=1FR+Sybvrh108*W75))qC@m@&-fI%N$N z$<)#WI}~=j+o$L6^n|wnr;iX!yg_dS`!K+oV-WImW$Yqt`-yj&hkv7EQA;L$wb9bH z<$w_J+kHp|7qjC`99)c?l^pqqOQ}D9dOP#p%$qm!&%AjLKY#bV0M>{Wf!nXYe%rch z>ig8>9!*bw)wk9s`|F$QlSVd|&Zu_U&?8wRrIBxH8BMh`P7bP8Bsr)h_gML?Ro~jL zW-P6_J~t4_s<}v7>Nuxwt(sbUF4vmtO7i{rOoj|=P>q~PQqu?0x~7?FeSqA(nrU$_ z^4GPT-Lfu{()QM0=YtUN$Bn$1)HbCvn!sYi9Ec8om})AcMfaI%E~lB%ON@Pb#!yoV zJOb5Ms(aOFM%4$Rm-bz$C2a*>0dK|_7|=}0cTJ<9%b9GWaLzOaOwF>o(w431Qs9E= z1WG%uxJ2t$^BKEDZ=EDARa!&*&T@u=c3QIp=5;wX=IwMQ+O6ieXs)i=`wVkWPdcj^ zd0Rk#bO@Z1G<15!j!#k0)KI43#6(+T8GDOr4Z6x}rZ5!*>5}u)dfL8FU{*up_5kK% zfgiP4C@_CI-3m?>@M*ej4#hPZSkq9x8Ch)sEW%aav$&V(ri%P5<+HB+?>x^&?L z*^VNg3PM;u8>gXOAUJhi!3qI|$ct(FZO7_=D*{-B)w8h@4FVqBVb3q=E<*q{htVY0 zH4D^E@coL7@n4EF6;aSi({nP!>l&sSI+Zi+Y0k$5GFFV;Sq(Gbc^WiyG;WvnE)2kjswz}} zVnYA{%#mN02(}rv6V2e)PAkkOglR|r(^;}d$)Cz{8b_a0_C)V9)T)GD$eSa zWvI5x`1N83Rg$~KuvGYO%j z%S;K(CUcXi4rF3DHsuJ6-w^D9{d%Y70b(rtDB#{EexC*7rA3eDywlSWE-KJYFb)|dVI(Ugl9fh&!B1SQ~NSn(IC@*4+AV{ za|dlZ*OHS#@3l13hx~*Z;*_j?jKY8{J*%ckoN3c!2v^U>eq6(fEA?CD8(b&QG85V- z15nnVSFkQMOsd`PIbP|<4R{qR+qh#ViN1!{n5Egp9AX5@U^`){qwsZjy&tc|8(3!5 zRBB=x2sBQDd^y$CZsqD? z)l9}~CpozUqN%KE+$>Okf}Pg)8hf;8mzwQMsok2b_Nz&riWT-2=0f%aL*S9}9g1?4 z7&KB_RGl%gR&*K0o_ua*d`}SU8OmudZqnUyX4%H6v+Zgo6X(T7u)Q09xBBre+&0gP z+XX5Krwk9-G6gSXbbnrVA{E_K{Ggim;(b9jX749vQ>?6Jx-Tzhdd-XvAN;SE#x#K% z&xRZEAsONiyIqdF)`tl4z3G%phmW#Q7IUD72h)8xz&4{SK^%kAI`3j#%|v&&OWRE? zC6USYPr)Y$E8eA>Ss06d>IHiLhbngxmrQ%@eiEPZ<4$~9VD$^y!OhaoN|}SD@;Ci{ z3hp9Mi>`1>#bL&x$k?h{7W=f64+ZvTlegKBPiIo{COqoLi0q`iQb^}55P3n_RFvoW zM)7&6;vQa!4ec{8yiYE?U;eNOnbW6Q>7+EfL+>|yc%Zx_O{flk3cloCPNl^GJmg+Z zUnY8;a_PgvJOT>^!z-|0x~j_#>=As;k2XBYOJT?QZLyBn=1{Li8$(-qdbWmq_{JpB z2@(00wENrK(3YTMd7UoU-^KT;7`X2fq8l{T)XY#3qCXUowNg+iO19YW<9*%I37yoRKhC6g$WA3t`y z#fP8qy5gfFfS=1#K89Z~I?@8eOkeF7;KMK3M-)%w!;=9#;2!ih^txj=xxXv*kPceO z)z+?2@CV)|4BbXO$%mCGK~i2;+*K=zvvlB}@Mk~%h`$JbEO)_>HloQvd2ieFGAo~I zrrKSadHfXJ*4wjbH`~`mT~pQ<*HP==@pKjbhJUz?&xRpezD&m3v1vpace2_17oL%D zc~;=mlUc0bI7^A<5Iy4!_`-&r8>X!V&tuGw=lEno_=LvweT)=u8rkB<)7e}`>&RyF zwroAzj$c%NOsX9x@4zKeg~u;uiAw(9O!-6S1bL=y%nr>OFL(h_C5y~4;V&J}QMBH& zwhRq{kt4OzBoYb`!8_aq7E*jDWqb3_d- zN~)P;o^N(dCiVIODq)UGp_aWYLx$-S^NS5}OYymX?Gr3h^IT5$1e0$)#jsj*HLLl= zBBG7N_~a6IQZs+Qw*$$K{gZKbTroOoI8ixM<)RG*}wA zBAnQ@YZ&XBj-b8y2sVylbE30p7%vGQLD$kFh!t?zA;`ZfY(u;#eG4h+mWxKwn~)j` zxV%$z6|l3rfZZ1s(AVlIKx?fmV6fF2Zfagy=c%i#^A2Or!Ol?_iQrY?Vc7RMN@nwa zp`4``Yn(BN1ZA1p$aLlJtl|UX6SO7EO#Z0)k^x_%JpG z_m1G&3i`Fip{{qRW2hO$4GGB^#;cFwjq;ooj#@7&mOP6&qM6@*RA2>Ft>##RH{s3h z1{F<&hL;=nIXfhd;-(#U;{!k<))c&d<0w9|Blt1-OP>Tk9yyB7Bw9Vrj&~+vbsjN{ zLkD0tahCkx5qCjlohLXfKV!$x7CDACty0w5WMEFs_7h_USt0xkC zkbB%um~+jcI#28XLe%+{M69?~t4BoSR%1_!R?mU4$0HBHbBHT04}Nub0bjp=9cv@B zzP(d!)D`^ZFurpbKR5<|@JCz@CHT`;Pq?X$vAj3U5SZmSU0J!C$*_ z?t-oZ2uBX%x5sd?oDTk;lSlDa$+`>wEa2Y-A&M)TN5w3HKio7dyal1~9L)vc<4_Q@ z4~>dBiCQslSOo7IkBf!CaS7;u&yArQm5TiDRTxNxHzS@w<-+5VbtluJQ6|7+3Pw2X z%pS)qCo^g!>`TlsESKxr${4Ghb~aoS8FoX72s={qt7< z53nro?)bP_dt-E^J)qDrHVnIGtQmF`#GWrxFAB{da)@z7KFNeQ*q4cE_sJe4S&$fi z8$IbKv}VMv8OYf5Ml~LG*QK-mh;vo#7r&SJJ_AW#n)leH(Dgzh<%KSzLsAL%V!T$p zU#*!A4UM-tgg~JcWy+=<&nJPENV%4)q~nwITFE#jW$ljLc0y_|3aAl9gDloCDKL8| zhtl$8=vw>TL$Xs1(*g_I^_{JD<3(qGx4E_5sCU|}db6{)|GX|xZv1An(vh;q0{W)y zd!d&;5y(|mUkc3so%A&Ge20{VlEC!lIJbmzC>Ah-^8)#drB(Z^O~-{lRJD$hlmZPG z1&S`E2P)!u(j$T8%2_3=XQ2`<;c@|UnCHf$WrU7^`Cr_hnz_UkTpbBrgj5A zTxTzhPE!TuD*tSL6Sqdpr4n@H^O(YIfyrn5*u48GX#BwhSLfK+(osN>@4M`+V1g}R z@e5{NeZ*|J{0R#uxK_Tw#|exNxbq$u({g-HAol}MO9u$8t7l4+A6O!j)eaAnKb8oS z$)*v1Btc_zX>V>WV{Bn_b5&FY00961001?P!A`?442B&FbnL`4L>xdYs}L}%-MFw5 zLIMfSZtAw#(zHt2f`r(E@F*O303HhAg7Cre|JlFoukVjf0JwmufcNe8K7ExL>J7PE zE~PHyOzNg?jm6G1PSs6L%spAcK-{b_C|!|%-h{pma#^4aG?Q(qYHXDmcU)!*%okTY z>(hUK(Ob(PRH)8ak}HiP^2U`+2l9b$F;C~`^Hk+D$hQdy0n>-3_nK~uB>|_6FO$+^ zZYg>8*tX=8)vtW|Q@3c`(X}4`j$v28;Ti`_EV?qe%hsg381@Ck^g_DtcwuyW^2lHv z4`LWYuw?=VV+9fC9f*DaP)i30LH+M{_y7O^ER#VH9g{&=H-F7qd3;pWz5o5rEO&A< zKu8EMEHXlnY?BBoVh}N54I}{xONT|gnYl?uW{I-|;zHGGt*zT@Z7XW~thG&R?PhTj zO%?5e+S*lL?Y_M3i``eN=6%n(caoW80{Y%Rk59tA_nhDFcYfRXE$8G*uRZnz5uGdE z%XIhAqdQhzwSTzF2puxx;l)jhL*0w(7l#tDzDU$aM-uTsED<&_o;0JTkuovaYor39 zUNdwkm5HSmH+37)lv%&HuO|>Q`T`M2}U z+%%Jscn?PV&14E|VSH7?ISs1ffE;`)R|V{vtjMI&W~GRa7Kpm8G1YA<Be0<+CXS7`E;5?^O(H(Ga4;ma-|cycC=1HYX#aFv`D9gtJ@};b#={NFV#~(r#fnY ztt?I=iAG7Yal4W3g%QtYa)2TDPj#UXIhpd|!P*KsN2ldrpBd5uzI(%m{Ra$zJMNnbQUH)AgCr46)Er)Jv3G`%lr_8H0CR$x_6jk@knpw3&<{s`x`vrF~G9zkfTC^xMn(w-`x(cQO(4hp<7q5X=0_mZp|9 zcxV^&2*8*D7rCH_9`_Y-yJ9ZAhu$RpFsRc`sqp#vzScPqPa8+_7~hY5o4?l1-elsi z(Iu6x%yy}ya?sjJ+hMkN+DnGCdoy)ezR+RBOfQA4G3d>`zu|HtS>>S~Z2E@2WPbue zrz2*{LlL+Wj2|^*AWfzq=BgrM7IC0rQXZnHlrqM&?DY{*;v^)KeO8dO#E}l>r6gS- zXRy2dc^(srMi9ngF(V#sgF%6iGO;Z(lG1ja`spxsml2I74)2N|iYE@ow<)cH3L_p2 z(3K^Cxe8xB9=Zm*)FKx15su zZMMa4gcXcrPbLQ8cMkNyVl(rWRbc=mZ=$!A&|B$dFn@)I-hmK&MJ8gVJ#;HZ)_dr7 z7&kSLN}I8OG_i;N16x~>$)qFE#%OfQWk! zv}>d!FHB3Tr`|tfEQ8?t=>0m~OaD1pm&*L%JdJAf0Vr>r!e%4Y3vo62Aab~6)l|!X z#VQ=7tq`)^=)-a!q7O?a8GoEa2-6yU6apxPq~tcu=XPBp8Z~qA?ql?jP7l(@nS9m7 zVJz?eq)rder)1^PHi>H+9XfFdW30H^(maz!d^WQ zVv=%geui|)(r_*XD%-UpzRC!t(PK=Wi2OG!hS+N49mtXP~@RFa3^QlDhi6^nc~nsnq#L3GyejB#C&l z9mbhjih0f(<@PW1SIO<)kRTMdl3B&;KM=jDkQZbkhdZs0q~!h!d+A?RihCKM+QtYR zkO;5jx&g&ca}LukD__%TRHn|-Py*FRB%a!84tUXIp?rRj1=E~~qO@cp(J(SEqov}2 zhuu26WNG7;6@OJc49ue9PeEq2mrGa&2`)waNGGgGFHb`WgF&=O(@`BDEauef63>N-FJiT!mfSHX)1JS@C0h zRtYcVWx3v_5J2M^ooi))Q%=Zko)&TAOmRC)D;NsFBpDV1!jprGbx)XVnJ#<420K~|9ss*2>zFmkb` zv{(XK!CNGut%Yr_l0oBkS=Fg?234qec^j&1?%?f+-UV!Gyu)hcQrI73Rqw{ z-pRX4;EB7j*>W4+%Wsmq{Q(ZjD45z1>ywM^!+$R0T1HFaOhvB5{<;*~2m=QvWtTi@ z3<-f&WKmv$fU>8@h^nwQ-s&*o#C(fYKa#gUmWie#*vNjXz-sVtx6Y;vD~oKXaKcRa zWlPs<^qYPoK45_Y5}nMlDLczuFwADyr7e*-l!2xWAsDXTu(5ep+s=cVb~LYV+i-AK z=YNJB2RCnP-{9pK3RsSE+&Ur2X?}u1Ptgc*A73F&gaW8+B6SaAcez2MNq6wnR^l+&piQ zNpG*^>^JIs1HYB&l0D5kI$Aq6`CEJ9D1R-({!k{BMx$)0)h`|1FCE?=wa<~zLdUx! zJsAlbiBE!S@_YDgD8(UKb5|-6MO&{9F8J-LS!HxJj%Wgr|5n;4S-4Fe@*F`tgMZ#3 zBYrcZt65w`3j6S2gE}ifO5=AyWEr%V6%98Nb!dtG9-Z&x_hL;;3Z|mR9QIP{Y=21& zE4=eDzPkkI=y2v2L0XSqG@3BN8o#f&rxv5CF`Ay~aWj25kvz0B5;GGrI5X1O2l)OH zzK_w?%mJ_ckYaMstE-+u)?#fBe~3S<^ZOZX&x-0|Qd@4ax(IHorM!SD$p%L2Q-{Biz-PA>lB3^$_J34tiV$w)lG#cj{*L7 zlh%@_#;ut=yUvJ4J0r5_|S*kUjN<&c^KT&uXF;F0m_P^?lPP9O3gb{HzQw=!v<}!_UFtqed#- zYBfh*{;E|peht&m)i+Qvq<@TVb5{~c_>3t|(#J?Y&o9V8fo67EI?>#@uC+B+?Z2oF zuld`^0qyJ0^YC|bC#7Q-80}^%W%QWqBR!@paldb6Xl0bRyck(Nb%riZ1N{7uf28vd z_{U7HT{|}hR(Tj5st06S2GTN$&MroCUyGN2#y^)z_yy8MrY~&B1Al5)^}ZGvRDQ+3 zmNb8p%g1QcdmCFKJ+1MysdDY_fD~37$fT>t{ht3IasG&z>Q+St_WHBVFY#YwBlH9L z_BYvOA*Yq)o3F)4p>Fu6EL!g4f58^pcWm3TVckv-|9b+Ym1T29D zCHiWwa0{)3PY4gwf~hxL)pAYfOvzJjrb16Ewk2$8MdylUb!7$N)kUe8W_g7=jYVBu zaF%2r(TW+OOeamCDRnkPLx((~0@jQj3P+MDuc%b@i;x#@5q~u7NmFGHub69%`S}0#&l+N-i=x@`cPg#Gyqkg6V=km0ZCLwld16JJdl=)6}ng^ z&S6^pe{e%h$aYno{;i89QsyP{U_Cl8zWK4bn#f(li1WoNU91!r6!dI6dttS(CRQU7 zq@t$TCpY&N3BE>Lq>Bs1+FY|43jo2iM z<4BiBw4yLA;=wJ6L>imj=#x269h9Nw!p7OEi#8cGN}`AbQg--nP2o<88!@Ssv`iHH zCfr+!4zW!-==R((kbMoToW2d&N9u2fysPos*TQXHu}fYZFA?Z4D<^D|9LAdv9LIX~ zycv07K7VO%SySL;uh^%HpxEyv!+N_^%CfKU=6VWjYcpS_i%wx6`ye04&1D&F;_0w8 ziUmU=EG>u4Rhg2v@bIi7=>m4=RZqR1=n=gVT_#3Ytie7gh#HxAsMkz3Sfz`!mq#3u z76PUnVV0hz+swtBn21X~Bs}D??gXtmkLlvmTz^NHJt zTV;2bIc@s4H1Ei3I`cE1eW5GfjNohcQS!>mBd(KwO;O}rH}7ClyaicT+`!c6hfiRk zuz&xs5lI6`MdPvD=r>eI@uw3iIHT1S=%C#)!OC~Ey`}z0%Ac|BB|YNp1;JqaR7BhY zp2p?vMVAGs`oOVuun#fN7T2EosD8qwvA9F0odT`1Q~nJIoFCyKbO_DcP6>mCTpl`h zWMW-r(jF-rk0#2_DS}-{Qz^wkCFH>`i+^|F>nj*{;2A1+Wobs1Dzh{{OJ@e#vb3zc zCQFUSsJ3pH&U=&)7uyD@e9s6q2ixniw0?*-*SE>Zwnu3P)2ByhXVCdLX~C_Iy3X?5 zwZpV`i1wY>D#vSw=&}=pN`m=MX*m zD4k$xa{Jtm($h@G_*tJzzJHnNNsw;Rvh=lM{1Km4{tab{nIrT(a3$)u21lR6__wq4 zy<_A9ng#>@$fq65(oeJW>n7LW=tG$Qt(tF;^JQzY^oNxauo9quwm>Ug&VS3)+mEvJ zcZqZuNdHtweu?B92lZ+0aj@+V)4%Vgxd47u0lNpObc9BO=s8aWfCk7W52W^avg|lU zvm`CNkMUL(uxU4yNT?D8b)(NU!mgmNkMlAqYNbcX+T>AmJ%w}?J3t1E&(j17gQHKtQdbwS zD~S)jW=zeQ4Y5|DWO;#nKgZ{kY%Ln02ZJ3$>@U9~%S(=Pb(ZU3JeOr_+9cm{mUdTg zAj@Y5S8DeXbc4?oSzftkaP)b6RBArAqf(QCxSf|tGrJF3vyVv6u79iGRYSabn46ni za>-!epwBhL>$AM6f?KoPX033Ny!iCFhxw8{K4-A}D8|6op5wg7hnKy_sM7~;ZEkXx zAH_JlPqBQ!dA)QX>*F%#2WgJat-c&t4uLYlz#y3;Yz8a1XNY@GSg)7M*M~W=2Wt*< zO08chxov_l!0~BK@PC;=1!so&$P}|P)QB5YFqE6MgjI4j!Qzk_qAEGBI!T&ykem)V z*;L~5_)2nmP$SrLE?pLs>@m#b-G*dB=Z>I&LbU$fU3{*e;8r`qaQyP7q=oMP2QQe|* z&l;t28P!J-9z8{20Vct#@EoO0MSy;u0~$rZESZW1%lY-BPc?7-NT4}W03jq>0B4?x z(@`oV_t-R9lYeq}e%`P~52^{!e3cp{cmJe?QYG9uO53GAbeS_IA#f+rnE))s(5qBq zO=fk(6N!ua9m1@9AWZ1uB0;^M`^L+BrS3LiK?8lXsL4no#m{dWzGOq zJI|vU=Y>@3+)Z`PB-K0nDd4<@8l2Zsqw_X8+j%Es(B;Xgl_uTCz;`Lig1c%*_(TwH z`xLgE41Zul{Dpc1pCNu4?i_4C&R-68AnmJI^_47tUBxQw(&;ilsjwsySp|PHH>dmD zP1+z$8w*}qeGpWJ`CD23PLrpCzc9*O9-;gmDp;3s`OyX{I#qq z%YV0b;Sm1=`%kUzJ!tL3CQU{x&vAYkgb++rnt#-N&ZZL7Dn5+7B1gm>K37voy^IKw zRK28hoJYrq$;d6Ksld{Vlq5v3NzajUOHz7{R24`$YY|$U`bfAig}7LzYHW2 zw}19vg;9| zj4LZP14FLe^dvQJRbsZ+R){T97%4!FC4a!;`Vr90S{;h!6nH7855j7VE5j2Ozfvf<#UBjZgHY+Z#5YepY!)z&HYWbSZ z3ULL7Gh-Dg9C2FH-Pw#anLbh+AoZZc@)%=^pvy z7x{Y%l z%cp}KrRq7&QKp{djw44$v^EuG}1=8LhkoNve z)47yp@JuS>XmGSc2|nk)8_PaH$qb0O`uBJJIRHHGT# z@V|Huo?c=ehHpN^pXP`D2T)4~2ux=fO@bHz0PQl9(G?t%!EHHz+k2a4ua<7h7^9R8 zx~^%*U}XdtjE+I26kIoI*r47fx9uHCZb@#o;R6*B5Jh>2Ivy&%hKh>VrG@ekahsrm zC=VZ~C?YDNh^UB2{hu$%nwGZE-!DD)eBb%b`#a}+550cZy#S6;?Fu(seDTIL@2>B) zVi(w{czvWk)>q$uR3CGbgHFQo95)qCx^bK9X**$C8Jn8}Rwf)9uwxfwvdK(+q|ZuZ z?56s`{&3P73_HSOb#JQ`Z#|Z@={3dkec42U3z-2cd=ybT)$gQiJMEXDEy7 zYnqR4UK5Vn+w0$JLMa5g+-y2#Z*UT}!eTew-_oD9;t9KdWk=c?9JJFd?Wv4sB@#=I zGEk;4cbm1{YDrkB{+6?Px7jhzK!w5~dNu1giI$j~ie=MjJLR>s@tD<{unm|zxZO%D zO}H^Dajr9%mo~dYA9LIm!H-v{5}LS^@zy(Og_Cm@Qui2Qde8!Jz>ds8cAT z>*>FP8kToVjv=iJmKtGTslu#&+dJEmK<1-0w|KCBXlW2f;K%@$p+RB6ILj_ia_*F@ zlZe}C1C0T!5b*}tby`V#vIco_G7F8mr!Z)5Rh$4%luu7yIP2-#0 z3rwt5Fg-U<6~wV3UslI^Cy_lURbAcAH&D1a22km zm2ccPa4j>6&AHRw=>_o#tgXUzxSo|Yr58Sh!)4*qbZ)}!@3$%F;HfTPhu);L8*pPK zqj3|hUN5=Fpw`8Ub*9e5XQT%8NX`13LTFk}20l;EP-GBa6!I_NOAJFkn{^|9oi`~j z#8)joxglomy z3bTsB>M3s7TPd~Q!X2W`x0%q{)VrL)4w)0COXve;@ZcWgUhA&poK%msXJr# zVE)eCneRXOQaqZs<8H1sXY}QNGjT7Gw9SIOoz6k*dn>7f7~#19n8H*eYyUSr}% z3XS80B|N6>YL5i4A3v6ocHmfErNaJC0@#b6^1_fyyn{nz5RZ$?_TmYDij5`Q3|D?8 zbH!f#ym(`^m=cfwa>B-@fwa3LKMMYePHA(qiFjSg_3HYha@Fxp4b-ucG3S57OEX2L z7gNo^ZyBkK)n{)`vyd)nm{j8?N9h^-K7ilh*-5iRv1rUVOFSnx?~e+q*~Fje4mv60 zrXp1GFVgpHuh5=?_^Y^o=hyffRdX}VDNZ>i{?4&MQZDUMe~&fvh_^J%Q1U9ed zqaiz-RNUQ>F%{nkCdX^fa#Aem2bWsWHejW@>%4cPp^|I1kqH6 z!ou-WbcqZ&#R*YWN>&Z<6=SL@7P4bkuQt^z8ZXV)O1UYA`s$mj=I9|x&6NtiWt#L> z)d3YyHRQ=jCGAPKC^fYp{P>`%Rr7^%0WaDcwha{$7g&zBLHY$JzV@IxSS=2yMT(>K zY^qjr(|C_dX5-R-D;QLVsyaDz*o4kTrb)~5#Q4JlYN?*imt~fvOmzgCN1xtRIAMx} z*)nYsPh?EV4Qe@gt47|E@iXlyZl<$?o*f^*tg5MGgla#lWTRQMTQgz!;8kW>Fw{|O z5QT?cerfVxpI@aSN2_B3YL((JUg;FY2i37GAY3K$#_@80kg>fwd#4@CdQvRvcyp3Y zMjoyiDG$7QDk5UZ*t0wB9eV6mC+G=AomlJvTKdLp%5#!-i7h7u)XCCF4=L6XJ6>1X z`s(_~jS@J%&#!Yb)TfRwODA5(cB1#1O|_nZYU6X8N_2UA(VuAzZW2v7%t)c^%qDy7 zv|izZt(=p8A#Fza+1%KhpXD2fHS&A~;gZJa)~%tk zJ(#~@4;D7c&wli*_^)VPOu-N3kN> z*fWeKjjqh$nCe#k%i*|ToG^q%Ih?!;t5@XEwhPUFJTsraMbR8KjG!ZW<`CWQ2>jcz41DHe7PVR594$0FrJSQ3p?H03bRJ%nV$@VA;3t(9TT z-K;ftAYwU%PIf~1ok-#u6zqhr@-x{n9 z)>eIg9*2g^+Tf~aWR_OCDijFu>m%Kl2G#Ddr$d2=88Yw0H46EUPb%!f(ekxRv28CS zKk9$8I3yJ4ss8LRZlRfZU*z!R5q!0K_t=BfuVM&a&*AoP$QZ$pC^kYfcH^1u+RBPs z@JPtmkB6ExRWxE~c7`}OhkL}k_Z2xl5HUx8wbYOq3WN)x2bwpM;d9baqS@OpPK1^8R6ncZHJ2&zi9qmeRy z32^mGBly=HcrC}|Rlc06*u~i4acy&XxJH>YOm&W`K(yi>To{dp%6p>z8Wrp+t5LJN z%3CXPYF=$cPuH+ID5n-OZE|YKE@Z?Jo#KXw5#myP^}{{%*`pzYju=%-NjI#P(Vb6{ zU>_Pn6*cO}h*@?IjA*3NA2Pb=?#i5hTESpG)wvsU`CBB6R`O$hcto}46peq8m>Cur z-iO0NWkolY_tdE4CuK%cm$x*ot!)o1q@|}-ujcU_p|5T$+ zEd-bQScPl&UU&!Y!p)q#1>VMSTHp{zRDs{cehnYO!y5jA1Cc-(VFdn>Lx#YASJ{>c z*>D3I&SD=ED4j-Ny*f_A6V*lylWI^sji=Ow>Ix07R99(uwYpKmo79MgcdJJ=d{jNA zo(0qs>gO7NRy{A!ca`sY|7_KwVL*j_H~BuNae;#0;`@@u1qyzvZ;!?W3O?c+)wn>x z@AciUae;zA;M=Ehfr3Bi`<2Fj1q%MO?>UVN6#NC>OBxp__{+XmG%ir^|N1L5E|9pt z+P^?>4T;02PGi}<9CiQ0IR=&)=zJBk$2j)|43z7I)AfH>|KDbCxKY3utN648tl_9I zj4{^uX=w~xN~+f}*T7{;Egoa9sG6Q1iA1JDn}O;ntYmhp|U(hYHWe&ZD!HpUKJ#y(vjS`iR=Z>@pT&A(b$zwrh17NLhadzh7iq@?bf`25 zETks#BO^mi{;iQ&M#eu*Y%aB)|IP=+#meXx7{8WX>1&xp{#o;yg1n4D_WK$?N@MmL zJLxeh^$Y)97Fts2j(?$3vQ|b+Oq~3>T;#=VnHtd%;Z0(xkvXHohDP)i30lnTR?Y5@QM=K%l!P)h>@6q8916_d<+F@IatdH+uI zxRR_qY{yAN0=cncVoR2tgvJgEFUJYsSb1RQfk;ZYmagqfBwe9<700{=YuGy2*3q)H zNmpQW%xq;{vw<9%LSXBFveB-4cVl!L?H(;%JGO3v4ZQz%?v*V&GIU*j`RUy6obP<+ zJKy*J9>=e|_r>Rk=tJU8L4T+3zI$-%9nHg9`k0>2G$)$VBh4MnX){+avYKs}`FPIE z=$J3+SzWVqERJbbJUynTk6ERh)tng7vXtwHSA}%V51oKatLsEaSM;t2dq2Eo--y*W@WzR&O@) zwqDF@*{%^Vc4f`4n3kvHtC1bw*eee``_4Qnm#)9kTc%hGehS!{1V zD9F>+elSc+XjzC9su#5F|Dm@+jUif2^3Q-+@T?BV(a@YEe z8#f9Xt$9J$q1%$unTFZLhq;t=?U2o=+1CC(o7cNzAH$S=JAbO%eOb-21U0s`SILr- z+ro4Stz|2yg2L6uD%1>z=qC)zwxq#s3e$RO4N(hSItOl!P71XNYLc@h+sJnHnb|B* z2xMCdMFj=*T*015LYkn4iXM`a=b%Oh#X}UMPOxS%!z$q1`nLANbFC4kjkJli*eq!2 zyfp=ZO^vgndw+{XO`fSxcZhn}({+Zm!ze;Cvp5l^%bg1)a6v5t^f$F7=f}}DzW5b% zCGQ6^m&{dMp=$&whP9J#7pCphT1UOqC+L>zq<7Q|n2N@5i7laSXtg$|8B@2^ylJax zGjD4~Ue)pwU~_abbgNU{d7=P9Pkju`ojs-Mu(c7Jw4-892D(HWqf@Xv@@%xN&` z*)FrwNt;K4L>5R6dDlJ()NKcl`*zEL`m8s$ZHw5>k>)*VcJJG zu%QMK>I)jmwT}fem}>6FwbFhZi4b7l_P1YXkuV*kL#)b;;L94r0lJA10e#zR7-PF> z+E7y~?0<%0L$+2#s#w2Cp$~`XW=2>0T$|*z9Onz0vrY{d-@+$pf_8l{R`__W$XA^~ zjap+D?wc000yV`LnW*H%KDS^A+EN20AM8W`eCYb#_~tF$0UAXqkt~*;E)@-XqH8yD z8q(knV^rsGFc4xew?s=m4S#Q{ai;5s+A?68V}Fzl(X9lHS5|A+pD&bbh|sm1LMA7N zxyn0uyDdZoLNQu&c)LP&B_Dui&i3N~B)$;yzP7{L8ImVxB1GeKJEE#o$Y?fnSFqII z&tmVSyI7;UE8^sB_Ky|Kac!7$PC^#j9;W-~r&!2O zSAVWmWs>bBBb(t`@-rd2pOI8Q%h8X5B&j7+reh*!dM1xn8ry`-J+1lPv<-(;O{?z0LBld^b0(UR1VV@=u8N`;aTL4QvPTk1c~vY5{T@OMubtgyQQw)> zbC8P2{C#e3zDzG759Rd}w!1Jtwr48q%k&jye+3ok0(*_n=UQ>8l*c zuhQ3$aTe^yIp+5lHGVZ|PJc7PnepprUM+1zW(1Zc=+Yl4XF;<9 zxnt-aaM! zjs6bZr7WE@tAe`PlC@1&xy;z9#ZeT{j+P3)GS&;Vx3rzoQfA$ZwXZa+1aT_v;A|$ zC=1C!)QC&P1~wO-oejWhx|BuBcEHk$y`zvA7EvGs%P}B?XXA1@AmWu|bb(MsbU~D* z+k;~@NbDDf zu)(mndoC7B1#~!JkwQ|(%1u7vf6It)68eTw1c=4Yc|Cu@pRwjg_4*z9h*rwl6?)&i z?KuBtaetKEe9PRwEB#*uDPkDqxzhaMv1ymAzA;=>myecRyBI7Pp@&3Tj3Bqpw0Gm0 zr5dxh?hJ@As6&W(3W#G!t3~-R-EW3PjysSRfyk?`PHnQY35!E24tVe9$9HG`m^Ekp&a?Ns*O@c>yle0G zDJv*t(3yN0ms0$&sD$l8pdv;Rk{DNPdqS~!Nn4Q@oef#BayYasj|LCa?`ftBWfhGY zY*D5amRVZ0a0(s}{$?qL;!7$@mO|Hv9Rv2*HJY0G{0=SMT@XfiqR9uzw%^$>yXeoG zFGiKC(y{^>gzy8xJ<-L$@6QahwNY~7^1|&4Uf5255mhNlq89`Ev24PYkn3CHb?bo5 zK^6EX5^|;BV~GZ{Hl}111u9&PF#=YIAs$v6jjYx9f8H}v|(S7L>0qK_|t_;SDJhRyfO z#IF6XuD5|-brf$6@PXnf)6 zo6Q;>O|}&FiB;+EM9N{UNn%L{zWH(sGYM6ey)-m-=dqE~E@Ln@k&UKdRjy$;GQ^!g zO z@4J#-8A{vqr?~@nxMiVB*EdaETILAq@o`Fqv z(Y-Tp>jt9;}s*d z8a!^UU+VbDe+_YR9J4F(re#n_@Yhwb3E-j=w91tgxo}hw2l=qA0)Oq71Tj4Dp$J+< zia9yvA*mX3ocZ$mD*S1W!9{!eWRA@iThvSP?l_^VOX6#M=yC|a{#@hAy?N1j70pGz z=*)QPT?}241xKq%uFf zp9j~X%a|3Qi(4>hKayI@nGM!SU7D=3@G&SaXRY_e^P>VQJfMKY(kC56g=#QfB{@5? z=YeVok-|x(p%)N^WE%*Q^%wj<8XkZr`K_E{l;EFZwdbQc7WM|lbkM3mnd6*%6N1H~ z{3cG%!pidERGRhYQ*Ln!DmD7{aeRj6QQMSYsmVCjI9DBu>EFTVX5z|=UOldzsqE3M z7|;MJKI6UA%=;Nl_12i3k6wPxx$<#krKz`UV%dZ01VUK zWwHjM1ffX%vTSc$i8xs+qa4i*G|O=WnZrXv3!howD6~PE;GX8rJE)d1SNMTplohXO z5ZiI-PeKl{xn%2;vv;gdvyVRgR2v>_GBs^OehSBgAC8UHjZ-yr#|A7_kk+}XLWA!WQW^gqs9x9iRcL@cK`A$R)(R=9j$2r`tq^-;hc)(E7BQJaKGoFQl znOyh&{q9DH?99?E`!(ity)hl~fS|G@v5e9OB_6It18!Rnmy$Lhz*0%SRRq*ogz(~| z-~oB?FL@YkR3YzO(vNcF*hWdq(7-nkzp0iX6CJ4bcOr$t+_&Z{Y5_{2Mn46OSjo+l zsG*%Hk>oSDa(@B_8uHPw13qM53CTHgf6kW0WKd>RDo*Ger6uegjoMAehJENycNR># zWL4cW#`!vJ&Pa_4>-6SbPj{e8DVBP79?9CC?TVQSl8j^<1jTrvYbID}`m@bAVY;O$ zS&%*{c^+5`3{O64a1ij;BtffriK8|AL=5WKHkfM~dZn`_p__gt2H&mRnbMyQEhCcy zYIFC#X(~DV5L-Ds0i6;fCoXKK_wWc&r-Q!~%n!MVyc}T~OsE{C^QRUUB*C5tCD3iBrF-?Fl)Mr1lh&|la9R=Fa~ zH$mdl@_Rh-`|{a7$2s0>AD<*TT5NvS8%3oj`!IJ&b$hY#F=DM6AzZEuVZjVQ*fadU z`-0z;P($_NAu1>`_+e#3RlHH6!Q*A}d=&Dra>OzZp1jIW2!Lo<6~S_7T3B4@e$Xy- zBrkQKc(c|hxSI=3t_EDc&$zkr{R0THoFO=ooVQyN-9fz}eZK54`eA6X3V_c(jYRQQ9PpF*dW(TQ|_#w@fFPWG$S zxU&lc1fCmdl2wcMkX$^hoB53Mv#_63099bbqi6sN$?rPzZP4(w-S#)Aa2jUG3EBjA zecl9LFsTDZiRmF(E*egw9>!zGnYFf|${s^YN9s>9XD7m%!8j!Wce|tdbE3exyWOR)Ewy%&Vu)d(>gGcq`8rK(87VfDA}Sf&05cKeksQ( z9a{QckC@Rbl&+aW$za(@r3~G1=%mcaA-@yn38ST_sNTA#N-B9O=ISSrz;uV~!5nCx zFg9T`NsgF?qIrsk4!WZ70HX<6oNA3y4#h`MiZV%;?@lp3c}PZ`zamkSyBw&Kt2m2W z{F6=OaX8%3NMuty4$PxLR#~6E%*+Nvg#xkX%lxy8ahy^nu)_56d@#i*)RV(fzq?Cn zIePPDMZZ9?pI1>$>r2co*xyv}jr$J6PPA@CPR`Tu@*1~`kDrUCeZ@^;PW@uT{#@N4 zTqdzp=!uN5oRY4iGYp?Xy&DhhbuL=5Zk9ZcoU`25UY&MZOh2%&$G#!{sndfOAZTtr z%?pZ)O#UoE$Hf7kxFtPQ=RTVVOl;#tecnhnY+!g-H1-Qh1;|bCu6Sf0NDjM40PYHY zu;2G3gq(Rtp^|^DLX!=1g^}FFdx&2eSG94;bm~f-U&O`QtK-; zI@j$IuqI!4dHw2RrmU%dqZRJk<;MD9?JArMb_}(_v{iuDZPnGUlMGW&!zJAEDQ*jRl|welryDyHb0p~ngQ|;<*uZ}@XijBvm&Z&9nM~V z00o#5HBz&oF7C0tDp4irnN2vrY_k;kuE}{(RQv=Cvov=$&hYh_n2yb#0}5csO~hG> zj>SfS3S5PhfDLCqL5BGREtF8%P18M4T16BlQnnL#j|~RW`7`zh#X$4W9>gfGcS8Ut zIAf5d5x-)2o^oNaTM2q|O4{qagUZb} z$Yi?25Z;rP8()U~`!}W2vD#PAW%JF8sJ4%A!NGMFF}w4t)C-!|!Estifih8)zXmk6 zKQra5X1@4VjLw8{eIitUB_xmVMA2F3u*0#&nc5)Dntt<~C-GFV+zc?b1nk0pq`f6a zjysHZ=yu=~qBu^qfjzBci`u|Jt~FXUI=ISXTvdRgmIqdca(L9M2BeAcv!WW?eOHne zdv&lWs?Pjejc)cJvdz{;X*T>6dRLJEWIghO4^dUw8traUu#hAHy|d}=8IjI=$NkPo zI^lgU{VDQ0R}PYc4hWMBZ-p5xJf)2}Qlr~Ds%=ftpfI&Q;Gx%rQ75b8 zCh|O^+$M%1ih0!`go3#;?C9IKux}3UJ1NI|0Qku5pPmUv?R?1RL*|`Nn5dOeWmkPm zRj+TUaFOjO>`_tP>rv3~Lh2knRKqr;Nbh~$W@`r<2h=cdgNgPH@dV!++3aofP){TF zScA>$xnXwg-9sD+P;l?wXEmSb{brrBa6=Mg_^!@x%xu&CwQW7*n=_T6;Z8{-st%G7 zkJX(DZ7bh-dTCGe7=e=aZ%VovX9;(y*cIZ@k1N?SEF|ehfg054pa*c712|-^KYH$B zYVWi!5E>}%^~-htt0zDK6}=<&@kTH&^0iax`OP3;j#Roj#0wSu+kzz0LkA5)T5S5W zJuLiSnLSBbkQtx-w)SS{`TFc31T{B)8}{Jg`hf38fZSo|6J9h3&*96f_d+f&1shDp zhN(|?Y$8sbjUH??jeHEEKcXafCbbqncZxc68Vm*2q`J_bN_X~d$DLY?RJZ5*2(Co$ zAIQbhxxX~IOzg%2S>GtGMAIA!v_bqGX;(g6op&6f9;W0fo(I?%wAWn6;fQ*EshPP- z>6$9QDTolp`Aj8Z_@(@TBd3qIxBnGTfxamNKX%TxL*$IPS(vT61CsVjknWV^Tq9J3 zF$?;DV537y+S#=QW5UV*p|-lQ`~ca8a$v{?8H)s#Esw<33ZCSl3)$PF?j95W1tL@T zLndecu`d3c^U!D>zf~gcoRdx#ldDAG$D1o0)my{Kf87qEbMX?)2RdyZuhEIHg zw(D_cW|2l%@#j4Hu}M&uCd^=8S8Mm{X<}RH zPvfd+6YWL8t8<$5)I)Kua7I&s#jiE*3pjF?3liavK@XwYJe0O=23n$W^1`mKv*=#v z=8*HR$p%xsHrZ>omC_*Nnon4=N^8=11WhJ-U%pOg6mfc3&y@FCjRZSefGaE$TliE= z46Ld}gaSL-CjX&ZZ(QtV3;}DPrwe3^x1{K~Vmr3-_e|_=5aUG1AsJTLM0xPQYo`qT zPF8Kc_bu5vey8|CJ$NK1n~*TQ3F+{c${@g|4WhEnBM^SL1m`?#pan=-M^}Ndp}|`s zjB{!-zpkTf)8n?=0Z#1|ft)gFBAOneIv6}b=szy{`ggH~$&H64q(O3v<|Wue6^^YW z7qFC)gBC!_iaOF$g&^vthtEUoQwH`w@#PLG0URSSdv>u+^hCv}*)qCtAo>I8;yKBA z`AS(=L7_HdPZQ9QmT80N(v@7c~ z_gT*ve$Klu#{+-GR>blIWy5RN*{b=ng_R;J7^e7u<$G_kXhGSoQ;GOSA+;{1vK{wa z#RB&h6Y+Ac_RHuLS4+x5+d8`oUa~*lBnwQ=`-}_g@}7rsFmErh)s<3tnen}!Q)y1O z{Eq^&?X*jXp=Ti{@vGHEZtyp0)qv%G8Ki!^EWz# zL3G1VezjmKVjOeHsE%{iw7z_&7*Q9VgU4fJ$Gv^9yqPhzuW!@=t(%;wZAnb6xX85j z+MnFczmf699l3PD<^jq7fo?m}2p#kKL|#OF{}O-cLd?TEr#tWFTQ56C&zX8e_^k9e z!By2Yuj;}Dxk~Fvg_o+5cStX3=jh|gFJ+wK`U@T313$g?>h~zoOFVcaXKDDgE9F|+ zSZIG0o{<1YB_xWT!j}mGzm!_c za30hr>3K7k+jwk4*4}JihvnLYn4NL76x3%7?ZJhylUB{`_1!<@v>;`-H|3WsY9Xa<8x1jR%=af zwt|bzvSuTQWNod(K^yKj;n9I=ON6_xoqXNKAer?W2GcBLjnP|Lo4%WUUrh(`T;S<4 z3AlCgJTiCzV_3m$uT}z}5 zBpbkp(|PLa!05PxEvd4yH@=cHO2qh1ReP%YF_jOM#%#4i%r_BtO5rn;j5BywfRZ?a0%lXL&&q@ z!XEkjKM7BVeKElO)cTZ5zwbN}4nYWECUR?kpaVGM1`pjFxG5Y)TIkG zd3u<0HVFzo9sp*JPX#-|rG>qGiw`s}58I7H{M~&=6ij9YU?9%;f3FPuTM`7TE+L>+ zrBk_mh;Kwp5c%&xVMG8}9s@nhDTf0{0D{SDBV=G`0D#0jN`>?sL9l{43?=FIWZj=8@4hsQb z_+oUhmV6rEf9w(w{o}mXYNhxsC3L6t&kF2+=vWT6?m$ zlEJol(% zH2@6Bml)5tiq-#x*@6F-A_4&P_n;pEx1FPgEji-fh4J5O=I(b6CHj^WzN7vngaH8B zdwOFrw-gm@ub2k-pG+R&CrNXU63qEKB#%p&fd5HWA)>v1KlzdSmwHpe3H@{4 z)3VOLqyGN{7c=lbGjqgU?tU=WMF7}x(%)eI-3I{x)_c;L?{6t8n0g8ssirC_LInU| PK)jc55VQT7+i(8|KIGdw delta 35414 zcmXVXRajhI(` z*w5aMC@gKw}`v#_2vd(dA?ce-n8kQMX7-x`S zgh4)mn5FC$>C(00QZBG96^X~$G_(gEW;koQ!If8A7*e02J!RL7JhhNUm;RwN*c>q{XFRL^X)64H>wTCg9oT5^kvyxHK zI`pVXt>0nl(~5*II}kO2b^sOZ0B1UtZdsh_S&GVjWNt7p8aZdg7#%~C-qcsDmf^Om zVLgr85{q?va)HMQBo+#b4vTi8&q=PH!h1hfKp1B@cXJ-!y7GLs%*LPBNu_zvw}Lu+ z(q)@6o=Te{V+|$=)L@lWE&q;0@--SO&uhgesB4i-D%%G2Wb?n4Dg+Q#8AA#^yA{&G6mLiXXH_V<;)_kd~uoN?s=maW9-`syiw8$U;9&K=dc3mE5;AByknn z74IM(cLL|Im7PG1OqxnVn?=Va;!rHn?9&F!{-$DvU>5z%`i?$y>ByVn^r!R$hj9J# z^AA8!#YvN|nqX3lQQD2~YOS9GUmq)pb){!Cf=3lQn?{C812Pr>y6T%sgyvyisJe=0 zQI?l0dTjz0|HY~s15>5UKWIui_mhJ1i~7ZC6~qbJw5X6Unk2x;fQAkmSj2>x)*a;q zlx)YCXZ1!DO=!Ms${=E| z&G>?21%PAc56PKi%^CCHYWL1E)O1E&oNbLDvQNQ&Kz7+_Jwr!e&n*c2P2NWE<#Bp;E(Sw;dPHFA)R7{GU~RTtUOyRn8(6UuofsC`=F4R6zRz1T<;o@a zdJDT5zsjrKAB?88o-#b1{eh!b8dyEeCDHfc>e?y@2($0_Z&H#VX_~RnOs2b z06_Eqerj2TUkve5+1XEN#Llq=yza^>E!eiPKs|1-bf6!Dj!LR5YrN%zSnAhj=tvS< z!IFo-(Q)OR`vdD1(l!>8L$|*dS)MVTT*X{ok2()xWhn64$zT z9!@;bt7#U%OV}wR)}q#3J>U-}Tcss7)US9PY4grx=vH~$M=5MkloZ^V0aSAg4o|Rh zoa%(@?vOgOL90b29})uH+dHlhoabQ+tBCnIB$wMw)@R%i4#~@8G+HTcm_NnB9wxi3 zE8*iR0XWO?xoMSnv26VB&7Og$hPqrRjc5tujVGJG#?i``$}<==!=0miZa3K;4x$Rx z+|}-&SwB#?c@4Nt*>e()6U`N-u~U1c9n8g#=KyBYoDKl{Q_-$>j()CZ7h_H?2XU3 z&{L7|yb1BP5zLoPW1=?+$Z62@T@QhKYhz3*QHItOGT{NEUse@|#-*<|$gP$Y|S60%$vkpv4k%q#~MzyL_2U;^VLP1tE{;Z5=35=CLLwG^W z=IgKh_72@F5aRJj45U;&pA#hvTB;C>Cj}P}S^1p?|4|r|{xP9+v)P>J&}Mn*1Y#|H)Ch#oh z)o~QUUR#C+&V{}KC%mMUzs%i=akhRhZy{L0=->C0%bP#U;u%)9AkH^7#>@uSXOrU5 zEkDtp{1jeyTI}IdIM*G{0qnw?$B}1fVL7^TPks;U3rV^-388QJ1s5gq9cPwrn)bkO zCYKp9Q?7HfQ-jW!N3NW2e~5996i-AB8;Z#bf@jvd0qQ^qIAxckZ(n zemF8&Zc(a&rj6}ii8U8Df)5P;E2hX@Hhf074pEJb8iv&dZ6PJF-6`v}U=CxN;RVaobGq+B(UY0e`W^(KX z%BMK<7W&@=845jry8Dfu<|;U(wBz@)JrUvnxR%rU<9FG0>6+Uy&(rhw5amPWH4DTe zhGcc|-pNW?f$X3J&juY_94Z#CyfS*=26A^Fi?U#fPFy=c&>~R`g;roIpx@pZ)8x8~ zor5a2ucT>H!!73Z)bWKMx0whNVpt@nOUpt>Ey?VEhO!c zd-he|bXN-q$&g5@U`!FIt2(m}JdO}7TbJuO>Y}P_5Mql_g%-f}<_#6)10%Br^WR(j zUP)&OxqMt`UaT{y6yx5}1^H_wQCzy+~$XD&h~(pb3xU?&8t=`s-otE6x0PizHgKc8dz@ z>%C4D)-lGDq$l9B_;IGJ1AoZZ*^2{z0#t3{;9x31Bz-@XF#$*;v1SR_?@}38hw-PW zY>=LSs|?q1aRjkIv6GJ7Oz+Ev7(3pU?)7&ejS}O6{Q`h z4L9E2pQ5tMUn80;yr`9$cOc-|Df!&IWAR*LLzpaY-QN`y>bY`mwZURav;tpDLMn zHL|=ARtn#vUa0!RL$ zI|^zt?`hsf6UN+6(ClUi6I-COrXoM4q4$SXOWl>9T5JUrj-Oq&;(#u)gCzw>xH!Y9 z^LZ)~T#xpnP?CXUT8ztyc@Xy<;FEhX0ER@S#KGMsZBEudBY>X*kA{RkazWPxG%QQf zcZ3!$hvV2QI-n7Q*^N~`GvAf;g5UsS%%yD%HAI9hRBJ^2e%hj@E&Xx%`KlYFKj_DgCimnMr!kCoM`b!cmwM` zOI~B7|AsX1!jk1jj4ZF^?_5bJ@-0!~PsDDkz}&OZzlnLW=U~)Z5L>od3_~D3UBR5vB(LPmyGkbP%tN>l-oP`}jE>RwL;x$k1(8CyV@9vEm2DtQN z1Yre&Bb`bF^R>a6x}_6$*y=U8v*T?gmzdidh%rv>k%sL!vT?)YM))54Ih?l?9{Z?7 zNv=X)g+%D->~qV@oM@0KwmoXNu+vW!mS(=L`AGc>A?rjRrIp={#QW}2nJ8n-SN{V) zpd2L(cd*(gbWH{0V|YfT^Yp&aEAU6n7I`BDGg#X~Z0$v0+AnWoazXoFgMqJX^*K_L zuiMU*BvN;R+_{4bDKd@OU;TKw6cwrK{EV<#vO9iM<5*WMV?VMuC=LAV8-WPuzD0MG z#68yc={R0Tb#i2tZX$dY5z+9-jO7R5kuwD@k6h&0BkEH_F!t*>mD@`d@4~~UHnVuV})?WcO1&%fk37DfBwz=$ugW?-d{?qGbZ{LXfb6K68wY|l{PuIG*G zUuRN;&Q-%D;TZhmx|@Jsbd7jhT%EoMx05CcAz7Wezr)er#y!UlJyTVDxFnCxYXFAE zw&-n95m~uikA;`lK{8o63sIqw8RNmu`bxoW?}}DAl*QuxTJ4YeoT^~AMbghp6ce$E zdVd49!1NoF@wb;FPtWO$8QMCU#07jS;m`V%$EEkc5(G9{1$5P*y>g)#wID%-j7hbi zPRa9Y>f&?C?YuP9mZNZsK2ZS#YBLznKze=smT!J(|KaB238&{+x7lkDR~PD%i?| zy_YvA-oxWuzr3+UZZ#hGg{*&a3q`?=y88<5F+Zxbn8QjMW2NZu{toPac(>*XetUj{ z`$YkOpufDZ5MDNN^LT_bHsX$-S#gcubye@)3||*-Kc##If7a^-iIRDEg00d7`qof= z49cq9T8Sbu7Mf6FJy2;Z!tT4we|Cwl0fhyiq+i?iSg&IZ$^3j`vCL?Z^_iELB)x+P^MqF< z_m^^ij3;ef0D;i*qdPwrV7Jbv@UThZDxpO`0Di?cWP%wgE{M`dnC}i#cvm^Iio*`1 z5YY9?eu8k!*UB~xRP=-s$Hn~;D9>*fpNKP{9q|}xZlzV1`5!9O|v(Vl< zcj=z)4wfeIMYgGPr?cPIRjz~z$0eyeeF+9hV%ZI`=(??(5&WzVGyw+|SJy5plAA7E< z6A8=B`Zh;g{c$?NKqor8ULnyzk_+o+aLm+0XTol|+UnZ9xb8X+SLmg!W*#|m;;y7| zR4cpBevf{=J!~gQb@WG^#YR2yqWz3uQp95w#=ZuSDM)7=n;1N6l0}1cm zaS+^(Q+~m(w^DPAD<58J8@xNg-)B3)_zb!*fm5~>3i!LcANM+d1y*5scn`q7@PQ9u z`FzOm*T54@B>5`9IGPAOBE99?YR})SDhDjhXF@cOpCr5-ND1rv_E_` zurqTxTNbB??)S~;^Nx!@L#|IpZBxQGLqqLx4mW){C1f>XRsC+3A~`xR8aZ|VWSc0O z#WK$*DSrd!t#e$cqMj?hGi4uM8g|!{bO30;YNe5xy;@SX4|j`WMu2ef+iX8P(wII% zW(q0f@L0{#3R(UsK@Gzt8{ZD|qOe@;C{!)TlQsCOt6-X%n3&Ptru1a$-9iv6xf$u& zntXNvom))G9oKg2w$W#I$lM@OMz^WiE&!WIgsqKFy2SOj?`!Ftf^Jh|Y@Fj4x<(b3?BF|tenkBw z>}Qn!;I%R7TjAorjF*M~AIE^FRRAA$1VEH!`J9g`LW@jvJ_{B7h|{GMFQ&~a8O`Up zyfE9{`crM^7#zG6dS`g2ULN}PhnNZvjSNJh{Gt(<4eG}~e2KxhU$>i$%;==WP z5XH+ag#I3_%WCJ`Qzhj<%!Pq{zf>ox-!^F&HGy}3Ft!A!pH9KGu^lWIBmg$Zd8C{4 z+ZJT?eP>4VN&5MM{wGmPmg0BP%L}svl^D5AK98Iyc#Z*0_IY(xf#oNuHVV6kvGFGd}$E;5YKPg)g zErHAYbHKTn5Ul%P(Y9HV8Zj_j4p%;!0z&dc=|wA&27U{>gzyU5^@{%#Qc#zo$JHkH zoAp+h#V}4X9v%JImZ{~{()e&YJh9MnU2>k#;lXsWak? zRRz4A-#=Uer_IGEiRU)^XgM9 z4ENT$oTA5=yAb6W55#jM-05$@Jd4~584xD#=l|NFzsM22TxU|X5i9IIC-H#27AK$7VYIY9(&}2%9;y05|@+4*%f=|ZPQop#}Bxp^2sahB3p3og{p*;<9`AnK=q3G z%LD14!`p*~#fIE&iGXmE>YXFyK@!LlQn5xXC!-Nt?6=l&<+q1739a^KWu3KGbib+I zF2NM1T=^|yX|Sv4f|4vrkmSxcLQ*5Xc+WCpv|E|RzF%cE^dRp{>cITCzs*|ulQ<`+ zJo*9d?6h*Od?6+~#1aGjEiip$)Y11E{7e)QD716BRVX-`lPZ^(9Od>=jx5v8cU8!em1twPR>*R%EO*%FtS6^DT2+HwRD1sC z=in_K6QlmCaM1s0zy*w7!75|_tQ?%Kx~6%Xrfw}}V!g&0=c~FhbUj*RF_pTVK7EUW z?}X!&R2)oUN1>1S)gSTwe~?ja%smGN!X>)8PL8yVbU9wK)O@H#LjjPIkxtlp#7qVe zspmJGe^e+v`N;2pGNP8BOmvDt=cr7f6gP~gw3Zjrt1uIeZP_tm4i3~PC23=G?CA5( z>uDl*CMx1;s<|DHNlKE|UDI#C!bJ+P3XY&%l}J=(8eP=n(X@34(?9h82n-SfdC32e z5~B#J;Eu;9AeFs?rR#(v8PW@Jcj4cq7H#;86rEI>7Rnej!*%JRaIX67@q&F3G3+FV|TYT{SMF4e1n zI8D!m9fWyAPW5k>uEMBbA)C^zvLA%x8nEccioi6XDU8*a`&AdgM&sh)QbFd zu?K-KSPgP>S%OO}pMHOjS>Rhyz+~^+PPPR4)ccOi?D~1fb32tO;WLdJbzh3Iq1wPw z=ku_-)bpZp2+f~)gqS!gfhV*309By4r~kz-Ze%39^k214{8#NQ%$i`lQREayTpX>r zX19)ilGCrW&*9TcKQITr%KtvUFxOd%J-EQ*k|gc7yb7g;#|A%s5KeM-pqu$D1I^9$ zu{_A)<7oIN-RJgv_-zdNH=Xs4-OzN6RtaG)Jr_9GpGvGFzh<0XFQLLg(d|Y3=>&Vf zi1w0@5-h~j-WRl!9Y=y!*CNGLYWN_NwveB^;_lns`q`y=I>NnG7I`*4eyt%$lG2ci`C;@fMkOnh`Wy?oDccBco>w z&;Te37z%tr<8;CoLz5th%9Y}-Ya5{=v5 z(gzC<{ZrsakA07;rv%|&fN1+tL!WNjj`b|ejJ!EHgoRmCOv6xLw%RSztfi6$#JrPy za-1st`JE#=_S6!#47l_R*h40MIdBly+IIgLHR(+x!1$h+d%9K?b(L}VOJO#r$9i1% zzBAb|%XrD7w-#`T1^PVg&kzX53xot(K)?s~!Z^gpyQ~>s!nUfY&jwT=Q}D7~+graR zOLP(gH84zhD^M1lf^&3Gy0@HbE}j?HW481ggPn~s41wCXFVzHayqC@ zDUcj_uU)SpL4Vp^H{oGDJtLBZI-?ziN6|l6@e@NX#+U3u1mK-(m>Ct$zK%%mVyMhS z3d_j6qh-|u;^^u13`TF&Gaga-_JJ`rTOFo2CDJnqO7&`x$B4A2f|X(d832ho4m!=H zF8W$TW94+f+y%J4II<&B#~Smvg_ftuNsA zY0z&+oh$nSx&)@U+CW^*g%qt2C92{@5AyZgtjWG%ld$|tumvTXKi|9;J-9R~6PDE2 zYTS|EY>IpqQ_oP{oektyj7|G4`Ddgi!N;^J{BMOhaVX1-GF}+w1sV}En+t!P&)XY0 zQsJ57jwWz7?K7r~-Mamr0l*YMDH+j4&G=2C>~m2m-I_JU9{%cGNHealOrVJ}Nt>nG z7^#9Cnz_@wqN40n-`3~XNU+1E7Awej%95N0uM?5pYB`o20Lha;fI!3V&-tWSQPg%S zisYO4d%eG>&GI~qwlffM@r^zljWYTP^G> zTF)tGUrYmCee?ODSu}EYIkPJBP7+2P$q?4vzr8bs97752t0Vgi29tL-#MlpzlKIfK zdl1kB#pZ}c4;p^H28xC3M8VuwA$g1MGuAX5&K5K9V^m|GuHg6JTL+qK;4I4Z zytZ%PJMSba-zQ+O5{vIC-Zw=y!7H0B9{w+sssXlMpFCfa9{8=()x~)oXpf2YOn%in? z(-GnUY11tKo=jCpguRF%%*Dd^X{4Pc0MW=h686wLk+|O5`)$TvefxT~2=mXdG)*Om z-ei@Zz^Km_E7&S)AHmMBro1SN8T+Hrp{>p&B=Lvxks#(0T=8Plr(Pvy!qGCur6fiM zW$U0^aMmI<7o1Ms7Oy~H^ns+embVL7H#PoZ)plteV{&Xjy9_pOr7=QdS$WX|!xQbH z3rXpJN8YC}S2Jp*a$I(vlcIy-ru#)g%sm%@iqRhBg3htMJi^XOy=;JyI1Dr3Nt#-p z3;`689x)HxL)OK+9#CL{H8IS~pv;c&@k9Y$cHJlP{)9azI4&xZViy&FytWE}f$qkf zEXSmI7MSmSi+&6YRMErB9s~wvZ_1?^#YuA!xrsNOUoeDGaQ{Hu0r>yK6JD-d7bDhC z+#(6cJfZ9fx#vg1vH&u_r;2ARF|b!hK$B7aaxH?P_9)zZDslZ;Q98d{!hcqOL5gft zE0<@W91`F;igMGxC9Fmf+cZ_~W?UsTY`$A;Joaa^;ElgjnnEyl( zp8aNNS8SKYPeZ+tT1VZC)-5?_HJa|`Aq@Pb&}*#3i1W){wL+JEy6_hU+2z^Ha_e+( zvkWDi98w(i4497M3g>vYA-0Y}XVpCJAwJ`lHZv`Dc16Gt?I02!gXY5x6-Mr@Oz z7$(XVHld)SfJ8Dd{gnMN2wb{1FaB`-br7q$T+a^;GEjYZ^^POe_$X~tG{sDRToKNa zhpey%>|?USyoH~)%Ym+dqIo_l2u9C+3mF7UuGhtx`;ccJT7U3k|Ivxzk$P;4J{|)= z3##6s@0w35m`y!;=#3@6?u_H1B#q0^AWX>#B@EoqATFI0dcOQNdP4Q0z?m@apygB$ zv29los5VW;syd*vo%`k8Hxis55!r{H2tLNxEk8n$XZH=Iaw#D&FlmnXx$FjiNooQP z_&{{czjYKV-5H>r@jzy@7bWsNY`>~h3ha-5y~q+gm&PcU_&i+lMWfPuk)d_l&^O>4 z;8?qGeXdQrLH7h{Pul4fBlBCM)D>u3d_Zdb50kLavye+zZ0`ID0BN6lg>mZHzaD8+ z;MddPlCH=vD)w?Xz0ZjkFG#B2eW3V?BaemY8`{)q(IP{o<6^nnx03k`>Sdv4;=y=@ z8!K}BL4ORViM%x7C!ZzK-3>Xh8O3RP0@n>~sI=`zNFU!2{u`A}**&89|G~@l|5>)f z0{q~(aarhBoRCSN{R+_D-u;vUMTHGg5&0PO8J-6Z2flZovrS=0F@Zf1NBmCI7X_b! z_P>dpm#)>-we2CxaUwhYGB=jxgO9ekr>8d(=i>|oNsMm#=h7YWPgYIEo)ZhNee5 zOCM+`um|XZn%dk?`+5uDh?qf#WE+F^LDYKq?wo|$zmTE6lb!hb97_kW1e)}Y3w8A4 zb1~@)#_4EQj+GyFfX-i+2X)7yUeh1~&b`1g4fl<;`lzv?k${Fc?+7h^i8nq;b z9ITuDiiBd;%uWAD9w~IADnlhXWZi3?w8GoYB*FOit-LSP2tlX9D~TTRO18-Dt=HV& z5Fibw!s;9P$_ITY+I2Q~ft#NJm*DyRtlhj-QklCggOWdA-8O!K7n3b5|5FgIlR>Ly zAS-9Ovs4PIVPL|7NS4}!~7lW;r>a!$6st#Vv@2gRt~;905Zq&@T-tB6ZYG)Wjd5J>u%xm_n$+I zJ2OXuZ6P5{_ctoHu4DF9FM@<^7bxN_#KrN#xA3-WKmYj}8cukr1f!_@3IKr&H_vMH zgyuL8PXWArI=i5Nzb0wmA2K{pjpvudD>iS()-Pv33a2XlFJ~k>`L^c_!VYZUIu@p3o%L-?zLw}kmZAL(kg>hsQ~Vp0E2&-Y&up@~)?>JJ z7IY&AM9-jVG0zsbCuVrx89fzSJ_5>K{!ch~qEy0p z|3}-h{?E)n1Sx@)6_i%LV)%W-k{45zwtj-SfTxHW7$S#PFBjVv*JzTy>oicxqB?bN zw3W)M_#5{Yki(bnvNaq)rr~ZE;C)1D!Fk!`#UT(2zPNxnGtu8V{PSmV`hhjxMwS~D zU5pE@MNDIl!``eMg=k+wHcEh&JJ=n&Nnk;*IgOc?(t}db# zfLhvG&mwRY^Ck+Ton6jDZLlIfOgn6lo&lAc9kP-ky+Uytmi8vEy}TPHPp~|I#385Q zQH1xGTtIWh7^&v8&LA=ZtnGWsmbXfzucv8BAta4n-meB<=f<3Tq4Pf)w$?~D zOrAX1|NP-yw5(3m+ZBzVf)yY$P6kg9ZQX@Yf*?E!6!<-54$xz~O8?&uLYLJ9@$ zWAoeP{@OCu0WSToLtGNYVwl$Xs*y1=sS9H}#vWZn&q&c-O1DOUaess3Gy^0VfN}Ng z=Xt?+9CgXzue2atX9C};PFm?6oZ@MUij!WC#$x7OF7a_9^TRfs?e!|< zxwHr_-bJRazb{im-+S4A5 zQky3?T*aU!TGCAn(=10dp!CrC`3h3)wvjDLsDf3qkEnxaV#)Rszf8AJJHERaM`8F& zj?~64iG8yuwI@GI$WArri$#zMQc1XF_umXuwTk^2GWM9xT{-nrFCH((1$z8V%6*(! z=x4=NkX}w=^X^yXS|8S^+Ui=aQmSHzU3_=iZ$1=EZrppV%IX>FS=D8;NE$IYC|`?(0;`>ZK(#$wi;0RyofYC2F?TPIl#A=DAU z)rHCKm17~9sxL-NT@|BTYw7fjr7=ig$<`GA1^HCTycB2xf~d7JgS`}#OOkJ^*P7vz zw+6TI*ZzL2Ec@2_`m>YahO4)%r{R_06B&*cY*Rte750BMmnAWi5%VuLr2oYR5rm!? zYmAt9sezaXRb&IJIL>O~yfIs!EV#F7@cR)p+eWN=vTuWGrIKDy*b?0FG{l$T&(>4Q+6YVYpy*$-L4adFGGeb z2C%%*H9*q!juy=})8^JDI^R`2_Nrz+TV2tXLvKMBxE}Bvs-39wKfx6wnn^;MQW;ar z4Q(aLkh8xCF36b~V!(_!x7FLax7=Z)G?f z*Gg-U{w?iilHD}VC0YR6QS|4Ol-3jPzYB1C9Akz^mfD)a-$BIv^$0%Z3nf(j74G97vxZSt zvspx9QWbK1;g5fNl9ek!LUhj#7@_Y*AGwaD0F{z|vON5Fo)fmU-@d@(E)b|b^|c46 zn7LU2Iaq_Yjf>bEz7ElhjuEo57IcCDIxnc-zU+Wih?zaC(E2Q-54ALrPlHl3q)8#u z9;WRRg)xvq@%jB0+(u1?Il$gkd5meArt>1t)$-||YjAWcCY={omeJJnTI7IDX2kF! z@}CDb4LC0xH{Tcv==S@W4wO+rO2U`^McrSC)JBrKAHD91No8HmiBI%4g(l1l&76cP zivpbR_eM&&6xSwTdsv`f_e{z`%hzueOURvt)3=wkttFUwa)AR^k9rjRHSuW-4u zhjzfh5qWH=GFsx{bU=+MJTTm3Si8h1Jk+MucKivQtzhd@MQ~U7aK@rP)QOF!D@Xuk zvTTwr>nk>XGGe$;4H$Qva%Azi{=Ttb6 zL1vHf4I|rX;MbK*clMnj5E*q2jhxlAphTw`*|5EAnh<7q`tx$i^I=!p$g`t6N3Lf9 zdCilYSZ&NoQYF$B@~vE#`+;duMqN)$Sr7S}lag}%@2+653f>?}jplWueEe!onC-l3 zLEld#h9h!hwL}Z!z*_dMZ-$l2-0+?3N>T6_rDl9$(qnWgZW7Y5t+Y9cij=cRYL!-w zeMW#$PzyCGo8xjI`SGY~1!bFcuqX03z5u-o25rHVV!adGd2^!B`3DvtxW>^Tv1Ls`S(ytIai`cId#e~W*_ga zQG^f2toC%EHs4qFCLC>qJT%%X#8_h$*4J&F>v0Jy`R!{xb)d*o7nCf6MU8FOP6BxGn zrwhFgNs_PSjXXOv|K5*E@{P_1#R`vCr1e$?0_r(Oga&UpJI+tPAv>5DNQfW=26ce; zb)NIsB&pG-m4a0uf=WvEca?dW3qE(;;^0erH!YJV)Vmme$vwEhpCU(%K{`^OR*soX z-2s06;U2VuH||;GiSNSX^+-Y@&vtOr{8(^wx+lQ*KsTc9FNra~%o47>(;5W3w4kZ9?vPxK)1yAgl<=Rm%qkBi|< zx_a}xJnBq}bC?ckMUh9*`KbAWMp3cqYiBT~SCBuAnEL{AA1$@ZiLN27`Tba(VB@Q3H(R5rO5IAEcUzB zUWEn_7gIG7gaL(Zk5kiOH%OM>c>n5x?@X+SPjOR5mt0bApBf5J(j09qr@-k(Pq)&j zzPnK70C=d7sV-$gSQvCNR$m+jGiZ9|{AfQBL^{OFu)P`Xz;PYMG~kYjWCw?Gc(Tl>*3+xpE>x)qZiKj0P3^A^rocgr_Z1D=g$nyZ^acY*y zk%_Sn33`$Ibu~H0%Z!^^4)`<)7!=_Cw)u%6KW&D4ZX=RqyJKUyy+ge=wB$X9Uy>}8 zdWt^bU4}A6ZRjj6A2}1^=`eo}o;*>Hm*O;bAdOSdYq3_=7<_g`>F=s4G1S()2>KL> z*BkxQ@5(s*(Z<;7O(a1V+{X=>M@iq^n?+-3HZ4UrV8qj1_0^6*>DLl~QYEbh*$CP@ zB?xnObJ?S+IFze&Y(7rWB}_*%Be#|VZiGQiTq~tTs@6xR)?-f$q3n-0&PYOlNLp&+ z6EzuR*_tqQpdV#IS%=%~jd2Uerw(+wfJ##{JabwnRe3gEO}hY&LuYaRe(7rBr=LNT z#25i0Hqw^wCxKuo&4~Jj@wuI7m!CX^y9y^YzekT>`{~h=nY2Exc`IcQXYH*H6$nyw zG8&6$K+!^0Y~-8W_=T6TC)N_fGj>xU*e6tUz8LTo`3tWu* zN`+^uBG1L&fd{lX*z*%!n69uTZ2Q5Tz{|eEXGZHIJp==GOmnB>uS;?gD9`HZ_{OHt z9&AF|8PA;CM$BZ*g^4}zZozVavnU>JG-Cqjg)L|A(Xn`<({>zK4GwEj!!L8t_uLie zAB654x|IIv4_|2%T@Ur(hree=Xd!Vyzo5>1jJD2@0BYil61TH#MEZ2L!d;rSx<4%B zR5w>=yX1mpWcT&Ey4LH@!Cv1#m}OrW=}enxaCjvVrVwXex6x*5bH|#QBzUZ#C7aIC zp}K!&x)0uS@Ug+NE0O(_ILmpXf9X8rDn0dOA4vtzcO2 z$F9LztUPyvSb?-yQKaL_Q7;mE^XJ0gPvtZymdoOhiuVM*dv4VSI>EgGPSTPY3WgS2 zl?)oXIwgLR?!?(TI`kUm&-Rmo)=xL6!fAQZ+`L%Q)0S5{&eduF{Ba?z{iv1MJw}Hw zUCY>IOo>!SK28JalS}~MPX$tVZ!o@eFv}v?T3P^>#5hR_=bI2v=1-d9v_K> z9xGhwnxej$2s5(mEebIS3x?N3-n}CPF>*(R3?_t6{iqlaTg8#98&vC@Pv;xp>yc11 zi11_@f?=vC0q&Ce()2uy+lXRy%Q1yz7@{YG$$6^I?xgj-#=X`9D6)HEOS>LP{eL-u zszH%9bjsQ#f)%o z$^vWeSEp&G=0O?yKV$+_l4Xa-d)c|>pJJ5nw|9D3>XUH^Wpci}MNlKI1FU5ee6EPV zvPNPSc;G`udP%q8AdxT~sPIAACvXAHFJ1IE`3I@wWo7fxc@2w(SHr}I<6c9$LIn3O z<=U?76<^=*hs)CW?j0XHq@%!7#Ot!*OdGU!+^4cMpIWCGF1#uWz2pmM3Q^2W3W{pj z*_M~%4rGaDe5GoGHRX^@AMuS}l;s7%Ut7Nuj!~Q^wz39ksI?~L7djww0X&R3{5}#W z7La2dXZ3kQ0Nm~h#vD)rPwhh$#_|?63XcVZQ;t@bhv|k=n^@7m({?dTI_9=AZOWQD zM9Uo>SX%QWLkrKlR@sC1(mv~y-{(n(xPHbIehIRrvAd8s8g$pW_=bor)oDWr-VTe4 z69o$tcEwKs9F`J#d!Z|*7C4_r%hQJ z>Sk#(2YbWvYw#t?iY~c6j$QG0ZeWh0CB(`iGB*vZSw!-M3sC5D zzm*Vg?D0oee0Uw6ZRNmP;|Xsub1G}33+jN~3jq-jAM+zC=_6cTAm;?w26jKcUM{Eh zzQq2Ls&9T4N$Zt*C9}ouv1L0B6@Ax%Xd~#wj#tzl^Ge00MdD*_ zkpb>-S$qX$zu&i8+JBc@A_1g+)H9~r zLi4eK7E`%ztCkC&{XYP+Kuo`XE>Lm;|IAfYG5r>y_}@@V2M8Dhunc7h00RIj5|aUo zGn1ELD1Si|Jwq$qexO)UP*h}9C<)t*;zLDZf>Pk22Gd#-pPK3J?RM#YWp=lQ82KUo z3uA&t6Muj|%6PYEjN*eYGjq?JbMLu#=G*trUjf|5iom<$<96eX-j~*h0$bnIt%1I- zTcIDho=n^@F#OOa#ua%aW8%x9j16l@)+kQ>SbyIfNH3;!J#q|RMuwZ^p#H-Lc7KDp zs_{!dNIj2%cqol~86|MsfJnK4!|0e)%(WPA)Hmu4!=|zRR)Y{Ib;49xwCj2#uo^1I zbd^;059L^zo(vrGpnphKQoyvp!cKE{yW4uv z+kb0s@3fk|Zl~Gq?H@dA3RGLa6`dq=_DDe6vOG6%lg9$N+S*Hj`M*g|QrELd6;KhF z-kNYLIFE7(Gq@m7Oxap}$lf$u{KHk}C{D;P;F3Vuq2##=xu4`nV5N4}$=X?{g3Gv4 z!W`zga5jv<7BK!x`_nV0xQc6;(M9gmtYx2$R>KXBlJJx&FjxC$@g>~Kl*<)pC>C)J zw*~S~`LXlM92EG23C_-Ulaq!L%Dms@Xcbd@0v5ku=G8~cR;!<|aDwaAo4lMr|A0I1 zfr%`~>lAW708mQ@2tjif3E2Sv0I~v;0cBHvrCAAl8|9UMZ*r8mn`dMz!bEwp6+-;88iwj9#m=9gqf-}m18-hF)Y%=-^NN<_<~UZ&fxymIS* z%FC;|wa8vQ8LbLdMS7}gt0G3CKNiLg8rf8TL|+$+>r4xcRBwH6N{hzz`u!=bzh6()uQz{g zw|=#2v7}6PrkR&$`?UJFmh7$H+=G%4j;%{1d}}$~2Q{ z8V+iPvMh<2PMdVZ*e-~dlSidlRKYZ7Dkzy|GnIjC$cUK6gklOrlX|AUYikIE=8#vV zQ|MGC_xK%|PGfRpjIOP1lhE3LHlI#cX&(8C(b{CHVshckPWVUyVpJ4R$7|b73u%!` z3+O|zN)L>ykiW=k7Mx5=n4J25rCInGQ>8yN(X6YhcetsR0xH!|9c*QB5;N)r&H61` zrVmCulgS2#;6MIiAqp~$hX-rR=q#0%(%DSqllNs>4wf>8<&mR$0f<-u_DWh+Mk^>- z&`W+trgNEO;Y%RmrZtxM=YiI_v1BZ>W`cO5Ug@SrYEr3znk}_%(NcPUGUKLJL7;)w zSwuUugzlyd)*9^P+*NmpmRhLLCOAM{f672`WMX<+p?2_<();6@2&z;XT3K1*+!CCW zGU8_1A~b(K)8dmOVv5r#nA~PLyd{oMkee=`Rbpp5lW$z0N8&NKbRwZ8qamaWAf)w_ zOko(+Z_(SS(hk}M>ud3UxUs)0xi@L-Pj2oP4iB$kc*sSx#|4;+@vB#%ZrIHt9>{`L zpwCFa|Dw>E(Qie`ijo;3G&NV&Y0yXy^$KqKPAG!~Ez>2ig_i2gCZK|C1O4!)S)mCj z2qfc_aM4}@TYRZP{RqlSvoSrRPzoz83c-YB>49`cPXvUa723ytG~FbV&BWsMp;#K( z?*N4A)H6N{(3kg!0iV(1%k=5KjTf~0{CZt)oiEUm7!bP+iGh7uJgZmNDRdc5i0bJ` zDwfwzc`0YOf<7$xys{9-=IM>8ls14E{1<3fOAB6@78Pl?5XhlomO=0u`iM$b(?=)k z(sY98IE~8mF(_|;jKT&j-3M`Hx(-?0vTC|%z+x4S5-Nsl*ZOhX$LSNoN&a;bA#BU^ zZxFy#2wZB8e>}I%Mm%mMa}c?SZdU0=`gbO;zch^Hv$v{rJ$+KtqR=F+^B|v6>00_U z)AA1rhJ{UfkCC#%xij0H-vW+xkIJT(0>$?qG`LfjGofE zMRNO3CM70*WsH=NYP^El^6OB~=jqNe`W$_sK&5D3rY|9zp}pwP4`j^nM(7UGU1juT z`U>hq(p)aCCwcF2(^u&p0rxeg+7Hy1_2|rK8E}3t{57EsbnEz?%52Vic*$f8cJ8MK zRJxBIWRe1Z0fmk*1wQb&#vTZm&qp#1i2yiRoj;_~Fg*;d1OhuRYS>+)&^PcSp=D59 z({6fHa8pA^Q5w?O?sVS0EB$RHv-LOWaRL1;WL_g#B<+rqECC-Vszq>|esS!#>6lR2 zlT6G0d3>3kMmEc{EBvA{1qsjep9C+(TzrR~Rp}Xj`Yx#X&r4V5_1RFjM4{)P(pWO8 zAK2UjFN5;h-1L2VLFnoS!k62oQs)l^$bX?pHIj|_G|tpi%5(l%ZeOC81-GxDK$zSL zW=&pSMfO^Vx**Cq+^Hp&7V#H#(@(7u_cNsGJVs!*K=?(WKQ#GiEMT^#QX=4frP6Dn zbe2VbjARClXnK=A;HK9_Lv2onE`{E;!NO*j2fG%|0}pI|KX2uOx3J--VU_0U3>Chmr4p3*2;EX!t$|K{HJ{1#`3}qi&W&PjMgH zid#~%bjs|=cP^t%)x?4@wzJyJGAk-O*(DSTMW1^z-Z3c~jI|f+MpfWxOdmQq9GPbz zA%rFriD$WZCYKi)=VAbvD^#u&xtbdkK4prWC}M>%K-4e>2vQhBgRMV1v8~L1 zr|Buneo-#x`Ha!xM#gASQA(>aW5jUckj8i%g=BmM6_p&BlNa(Ll@~Cki|PF-Jq^zp z?FT0oe^GGV?B{A16pL{~DTIQX&B5Y&4v74aZcX%O2Hac^|Km!=Okq#QF4Nt-3=W2c zvnJ);(bBYx&k+!q8%`frI?)jHYH>4v;9Czw^t`oJGR?JE^`Q{@64`hr1{e2Ptw){0 zL6ujDfIB@86*csG|qKq=31AhsEVCm3Q)P zG5H=q9KsRzh@{uRm|v<&kjP(uezYFYBU#Z#aW{NlB%8%0wK6eBS!e1hM;P_b3HR@b zp~e+OpAq>JqqZmhuh= zba?)$C6L>a=?n%_nJ+J%V+#A?K0uV^M7QZ|A4X`GG~1|?nI~SQ@|BFCYWE2lK7lbx zZWi&9Kj|8kui%fM5sAh`gV~+6TE^)U?t}Ose@vL=S{SKb;p>qUFu!KntiH<4pRkGq zrYlBn!ZanPwI01I6=RxzKgG4oDCwK{W}#pVRnsy?V`p<)TfR}?Tg__}#vo;DZ#hTd zPr(C=Z^PR4bXx1xTVlPsC~1eRWMvv9DQ?-8PMxeu(*Qr8;X72moiPhJy0)zgtW;Qx zKOoyQkP+TDyA;ixO>X`?-zk)UlIqO%N0IqK!N0RfRID%Ymj%s#!9vYLkKb3{6zgqE zW^^+_U;=VRO%6n+Fv)$D?-4kdd7S<>lML*2ugZ7xeWHhYT)aIX8Y$$0nd8mZq@_{0 zj)<&oa1OTEvUT&u*5+*r4^MzJZ>#uW3vvXIm&N)m>_@D%N3Asr?lEian}`JcKqQ_` z$M%_5w~dhqRM@V6C80&cZNqrqi$TCtQj1$xY;hy97wW2Soe~}T{}w;tf>VB*>9nZ> zZsAgyF>|C&7)-^URw^X&)JpD^%!ZZ~o>uuOe#Y_&^CAZ|q-b!>-|q0U{9Tn~{vM24 z7mw^!_<2|}u{Vlg-pwyqc^^|qSq!~?3jKtULKE^sYaOG1$Ejl!w`P+WBvEvuro<%F0^BfeWQY!hjf3D%0ZA4;<`3rik5B{ZpSO`K4-sj*(?P zELN9qjN@818Rm4XsRB}YWWqFXvo1Vn|jmZ^0tb;iZ_Gu^x?x76w@sM)u%ajP$X zmMkofFP*-{i_(kh6bVz46FC+IeFCo~^izV@!n7o{NUdldq;)6;`F>d3-Ye1u@u{%H z71g*q7HK280BI9by$`+zzN5bVS}X$~Gy9L$YM*9iFki+ni$M_7F>^UZ!58lsw90(3 zv@dIYVo{{?arBs$FtGMP7Z}Wa)>R~bgzvhoKgoClakOL7!T_`cq1x&U1>gpRC z^{fgd)H*iynj;bp!g#f&8MzKiQA~}gL@cTMBEsGJQNT7?)*(|S!Ia2VCC7sCi4&KU$l-cEu9L>m4 zWsc_N=!|eEM~lm=b5wswezj(p&UuJdGld4JeESGBgxhO!w04MEYC}tE3cuIm-^TJ# zc}I@64pCQ*F0}rJMrcQP*RGC#A=+&}LYm_dstJc}<&jQ%x!#$hSb5D6G777gVl^#R zw-;8jSlKUD!sP=1EWi8+T{TPxN9fvc&^|)fhXaS{h8*28M7Ismp%MB_IOqs??L+k0 zhvv+4;{EGPkAZYh&e7pR{3Wkko)coytPvUpu0S1s89e?h)*s2y zV~5BKG#Scy!Yl8|(Nj74UeGak{2uH-Z|>KL{h19pdU3e`tOV$fEYM1a{bKX&i5&e{ zAngazPfR4AK(dd4dS&Mr*p>(A=eN@w0RPeib`Db62)z~_qW?Y`02&~J3Z(WbIeOy| zRR~(*y};YdK%REcC7@h?Ce%l~juCn@>~-xa|LqX{E=O!?)h7Yj)%6;srpVv<#g$Xd+28w7|~34*}j@uRSl zR?O_X;*`jgeB}X}1V)Zoyf63K!4%tvS?w618^QSymz8I8JpC|#dvZK`%-YmeWNQ!4 z$?@Fqa^()1CFps0UXIV$K6v~EuPKUGwpA)Z8rgnv-qhyygI;?$AdXyI9ua!t>Dv!; zjaaBVM4etZU_;PR9>IDz=rnm)YQIMKg!SWW`xodG;dc0C%kc^@gQyuKeS}wqJ-m8| z&pm3rt`V;faPxM554R3;8_qgLl_Intk^?wiC*-Gqhc^v##}vCW%oPWyh|mm##m^cK zZyDmwVGctEvEX$St?d{K_IL$)VJI1&!mj_`u;5J!i&_b~5m zAK?VR1GR@a4NDC3{yjr{*$7_|Zb#&e@RcCxs2Sp`14I0OQNV{g4)b+_KCK1A9{zZa zZwN1!?+}hX-RrEhbS`Y?;TsEkDrAxeX`0t*tCUU41i~OQ%(vwDwxHtSPY?5F!$Fr9 zub<2D7jt~q2;U7qvM*P1{Pju&Jl#KYVU8a(-Al&L!*DKfI=!{WaJ;bI?Ue~r6nRm5Q9zyHP>N28V;%jYA&xM zB&1Xe#1_|jT{YtfJzh*G|LPp2d6?nY13V=-l!Xs^4G9+z3I*#L7a2zghJVBD3g>@4dm=_(T3Umz9 z6}NOT&83Cpm%8ou+KGoR~! zoNdlNJVVaS=5w3#BJ@%NNI}gfDcph}#WWwL#yiGjiCb`{wZjn39XP4Y-J};54 z5l{3nJ@~JVHkrN6Dw1dm*=PsZNk`8UBPR`@^SKH=&&}v{?j)5^PU$P;rh8}_-AmKy zIhsK)&`efo7MD}G^fmHG4^xG7lq#iv$7!~09-U(I(Hz?%^4hBDRNHczYip*{Y+Goa zt&is0l61Q5qvW$)M`zf6KvlMPXo1~E3+*y3vd^Fo*~@9M-A{gdEmhmkrX}_jRAX(AoBP=p6fdv>YS%3dcfP=?KtQD#w`= zbhOcG$7VX$k)Q_0K3d}#pz|D`rnQcHsnJqSlPlDjE+gAl(BAL7 za0)1h4#6P`sEC3hGSB&vtZA3c_4iB9J>TQJzVrRh`SyPN;KKkGid6#JFS~5bl1r+) z^w1_F9#IXntk;a{j%mgHF)M7)c*2Mpx^2*8k8b-zJw|Agos8Mlfo?r& z8}-$_5r0hY^_wii=vsP0xN8xuO)Sao?@mUeG+_7W{sp`w9x>yFkuc*C8r^IpY|=&J zOBxn6Eb)hp&DEEx5CU4el}v<;Rc6!>m}Vs+jgf>Njv9ZBeF?p{*GM$B#BE29M&~S0 zP#`dIqrO>hjOy`7;yU+V63COnc6Jaz5XtjQ6~5nHe{o*b}jg%hBI)c!12epNx@lUZF=DuR*V90HYa2oR*!;-_N}&K#1yQd$QcQ` z*X4)IUQJdyWUHaa$bz+4SA=$)OLx3mH=}>agmD(dL61<%l;%sA^AKch=Mz%o5vX7T zC0#EMLP8lA3wz$6~=H_%!RgG!N(!p4ZN?VIi?3jLF|NgR z1deez@Kwy_fg31}Q7aNLNYT`Mcc@iPlC~RhQxOIJ>*V!HP9I9Es&E!6s#JWFVWg8` zXS;y!h>{fCOpzg#UfjaVzlB>V;^~BxwXkGN3UI7$#~pm=-zM1QFFo zt`*sv;>Cj)W+@Mmw^^;HCA)vSjf4?iW9YJ8Jxu46ook8rCNpr7oqi-+>oNxCEK%@S zo`aHw(;LFFH!NNK<&uF92rL}MNeyZ6nhzm4sA=Dl$rmjhTZrXT@jKJ zZl%u8i|06GyYX{U8;V*sjr@X}f!+8ex!7zaqv5K!5OAwaLs2KxCV`KgjUe@qy{ANr!&tCeYmh<28&H0^xXi)JgIY%zr zRy;sPzLv!qxpQq#!s<)6nt$M$WH_19;l&#qg#-8_*=*Sjaq2)+{E13BXI8=@#~cE; z>BQyr=^+(8U;nhTe7)LUxw@5f#9CBUFC_l+7CWwi=vV?BgVbh8z z;}Gbkvx>_D^=K_#Q7$SpF-c6OYC@*vTr;}FIo)jT{qqW+sN_vkM-?&>8q*zzou96W z8M2?AYtN0Vg1&z|-Evl7S)Mdnf5e<0EtoV{i`gVw%wYvfM&$|RH(hH*98UnAd0nN4 z#&*-`QIa)J)M}ze)KM9s4v6}$WUu2DegXg*Z6Np=0RY=@s*Ej0DCzJGs-i0qGi`n? z+6)ME*~ENSOM)Gv&FGW8u2?AB3qh@R<%sq*$+%<2jMIO&gp6LoO8+IYNX>h1B#>WKlS=gx_NTQE!IQ zTTD`ViAjG-FE;=#T3?1q^x|GgTrKVQ5S>vQ+_1q{uoD$^J29nxCo26rG0j)F6Eg-e z>pt*b392zWy{~Ww=_Kjy>uQHFH`rP`fGH`=8%ABQwsR2mlAWKz38hW+FNLLpST=yl z6i(fS#dRq(Z$ks^si0qFFojh^XbqkQm_H7(gtbxSLc@Q;}avSIgCH(CYoZf)pnvcG| z&~bl-SM(oz)u#nipZWm4ERg=VUSJy*@z>V`9-)u~G_wC291x$@S-NcyJIKv+EK;~_ z2zPe$AAFkZ^9-Org?s!yWeE4OVFTnwKVI)BFY?@u=X}bO*jq1G1p|r{r*ME%806?a zkd?SApbkr|KGmoBGe_Z1ubiK=lFoqwGK_!S!416Q(cmy1CkqF$r}U{oJTr)AQ`i?! zQ+VE|29$oZalndvJg~bynDt2MEPatY8p10n>@WTOA-A&gYG>)|(&IM|O^JX~(4>|Z zxh@Pg72P5Nd{x@L}mkDGs)$A1{AM zmka%6!bN_Gwqa2a^z6d>!Jx0OGw3c8p7w$=p|%$`c~YXd+|$`UD8{EmDP>JcOxXsT z2Q-cPn=Ax``wb>Lya%f z0X!h-W7PC9-HT@>eHr^DeGLaBeUsV;rXN!6B}z3`lXM)FFQ%1ZmZa5Usic1=i#3wQ zM6Y;NoFXm~S4n!cxK`5Z#STet7DJLgB=$+VPdqOU0OCdQlH?DFx0t%Faoy-1Css&W zB${12T(?S|Df73v?vy-J=KEa(l4r{NpzA@&Gi834>k-K_W&SbO6Ow1j{8O%1B+r!j z{jN78&y@MMUGGYsDf92SK9GMrQ|3Q(7fPNf@$M3L1@n>;Pk?zkf#*h467UL~NdVjd zH`b$op8SRM&h+3)0^u8=;w}Q!kD!SaC?=5giU`Ju7{jjcTDj_l| ziCFECv3wTm&9%+7rWaDry&r)Ps9dC76VRd3B(Rj4$d8N+HTkzjW*Hg(II+3Zdht6S z6c;OFP+;;}_N1?668UGXYYOr*hS~3H{3wmtuX@sFRO%Q0yDYS&(p`T;r(~^+n3y{G zb-Bok+cGu0rxKO#3oI=EHTVzLF9k}=^-Bj1suh$m;a~)#qZmTXK?P$)H7ziBz^{aL zZp!>K1E>`gSG9uSEI1sD^E%7jJW3qE#LCsx3no{eG1Yj+%oET@OMQ#dCs0cV2&x2= zN@@WB0OtV!08mQ<1Qe49MHQ3lj4^*x=XE~^NtS#O58D_b9*JUbkR{9J5ipp9)HWnB z7LQ^GRUjeP(zSg-y4St;3UIQ}ZX?^;ZtL2n4`>^*Y>Trk?aBtSQ(D-o$(D8Px^?ZI z-PUB?*1fv!{YdHme3Fc8%cR@*@zc5A_nq&2=R47Hp@$-JF4Fz*;SLw5}H=xZlhfOe<|?q;tg4vpJ*lw4;xW8BS-v<$8K97bHK^ z(i8eAy)&mK^y>s-s;V!}b2i z=5=M-ComP?ju>8FPIrG@rlwe1l`J*6BTTo`9b$zjQ@HxrAhp0D#u?M~TxGC_!?ccC zHCjt|F*PgJf@kJB`|Ml}cmsyrAjO#Kjr^E5p29w+#>$C`Q|54BoDv$fQ9D?3n32P9 zLPMIzu?LjNqggOH=1@T{9bMn* zu8(GI!;MLTtFPHal^S>VcJdiYqX0VU|Rn@A}C1xOlxCribxes z0~+n26qDaIA2$?e`opx3_KW!rAgbpzU)gFdjAKXh|5w``#F0R|c)Y)Du0_Ihhz^S? zk^pk%P>9|pIDx)xHH^_~+aA=^$M!<8K~Hy(71nJG(ouhnjtS=4X4=Hk^O71oNia2V z{HUCCoN3RSBS?mZCLw;l4W4a+D8qc)XJS`pUJ5X-f^1ytxwr`@si$lAE?{ z4G|o;O0l>`rr?;~c;{ZEFJ!!3=7=FdGJ?Q^xfNQh4A?i;IJ4}B+A?4olTK(fN++3C zRBP97jTL`9h%oegkn)lpW-4F8o2`*WW0mZHwHez`ko@>U1_;EC_6ig|Drn@=DMV9Y zEUSCaIf$kHei3(u#zm9I!Jf(4t`Vm1lltJ&lVHy(eIXE8sy9sUpmz%I_gA#8x^Zv8 z%w?r2{GdkX1SkzRIr>prRK@rqn9j2wG|rUv%t3$bbin=yy-TAXrguvzN8jL$hUrIX zzr^s5VM?H4;eM-QeRFr06@ifV(ocvk?_)~N@1c2ien56UjWXid6W%6ievIh)>dk|rIs##^kY67ib8Kw%#-oVFaXG7$ERyA9(NSJUvWiOA5H(!{ zuOpcWg&-?iqPhds%3%tFspHDqqr;A!N0fhiPQjHd=c>N1LoOEGRehVoPOdxJ>b6>y zc#o#+l8s8!(|NMeqjsy@0x{8^j0d00SqRZjp{Kj)&4UHYGxG+z9b-)72I*&J70?+8 ze?p_@=>-(>l6z5vYlP~<2%DU02b!mA{7mS)NS_eLe=t)sm&+Pmk?asOEKlkPQ)EUvvfC=;4M&*|I!w}(@V_)eUKLA_ zt^%`o0PM9LV|UKTLnk|?M3u!|f2S0?UqZsEIH9*NJS-8lzu;A6-rr-ot=dg9SASoluZUkF zH$7X;P=?kYX!K?JL-b~<#7wU;b;eS)O;@?h%sPPk{4xEBxb{!sm0AY|>Cb-vx6>$3 zF!*0c75H=dy8JvTyO8}g1w{$9T$p~5en}AeSLoCF>_RT9YPMpChUjl310o*$Qocj< zcT9{r{SUb7yw8~+eM{OAdn8QXmU#Ln`e$U@gLrUCREOwaE9Fi3=+LNRpVo&2-v188 zV4HG54by)LRQ`khtGXQSuL^(U{{cUGNIWPFFEct{U|ELOdH7(z3amvCe*k&Q@=9;e zrLneIoel2CfCMiPTmYnjjxjV!Ar1h1yQ-31h=b@RZt-play?)#cs=ZxOt;5oX)|*e z=7k*ASmQ;rO4_`=Z&gX-C2$fitvq+iGK1U*^*#IW!Bo{nON%KSxQu_BHE!bNGqbH&bnwg#gssR#jDVN{uEi3n(PZ%PF zZ|6J25_rBf0-u>e4sFe0*Km49ATi7>Kn0f9!uc|rRMR1Dtt6m1LW8^>qFlo}h$@br z=Rmpi;mI&>OF64Ba2tQeHI8utrh)v^wsZ0jii%x8UgZ8TC%K~@I(4E};GFW&(;WVo zv}3%HH;IhRkfD^(vt^DjZz(MyHLZxv8}qzPc(%itBkBwf_fC~sDBgh<3XAv5cxxfF z3<2U!03Xe&z`is!JDHbe;mNmfkH+_LFE*I2^mdL@7(@9D^Dcj6O04V-ko;Rpgp<%C zj5r8Zd0`sXoIjV$j)--;jA6Zy^D5&5v$o^>e%>Q?9GLm{i~p^lAn!%ZtF$I~>39XV zZxk0bROh^Bk9cE0AJBLozZIEmy7xG(yHWGztvfnr0(2ro1%>zsGMS^EMu+S$ zr=_;9WwZkg)wzE`H9sLf2Go^Xy6&h~a&%s2_T@_Cso1}lntF$aVFiFkvE3_hUg(B@& zXw@YJpL$wNYf78=0c@!QU6_a$>CPiXT7QAGDM}7Z(0z#_ZA=fmLUj{2z7@Ypo71UD zy8GHr-&TLKf6a5WCf@Adle3VglBt4>Z>;xF}}-S~B7< z(%B;Y0QR55{z-buw>8ilNM3u6I+D$S%?)(p>=eBx-HpvZj{7c*_?K=d()*7q?93us}1dq%FAFYLsW8ZTQ_XZLh`P2*6(NgS}qGcfGXVWpwsp#Rs}IuKbk* z`2}&)I^Vsk6S&Q4@oYS?dJ`NwMVBs6!1sURxdqVub#PvMu?$=^OJy5xEl0<5SLsSR zy%%a0i}Y#1-F3m;Ieh#Y12UgW?-R)|eX>ZuF-2cc!1>~NS|XSF-6In>zBoZg+ml!6 z%fk7Uw0LHcz8VQk(jOJ+Yu)|^|15ufl$KQd_1eUZZzj`aC%umU6F1&D5XVWcUvGae z(qCSZpX-QF4e{EmEVN9~6%bR5U*UT{eMHfcUo`jw*u?4r2s_$`}U{?NjvEm(u&<>B|%mq$Q3w zeshxk76<``8vh{+nX`@9CB6IE&z)I%IFjR^LH{s1p|e zppv=xa(g_jLU|xjWMAn-V7th$f({|LG8zzIE0g?cyW;%Dmtv%C+0@x zVxPE^yZzi9P%JAD6ynwHptuzP`Kox7*9h7XSMonCalv;Md0i9Vb-c*!mwA6fk?&T& zT}AHh4m8Bz{JllKcdNg?D^%a5MFQ;#1z|*}H^qHLzW)L}wp?2tY7RejtSh8<;Zw)Q zGJYUm|MbTxyj*McKlStlT9I5XlSWtQGN&-LTr2XyNU+`490rg?LYLMRnz-@oKqT1h zpCGqPyRXt4=_Woj$%n5eBEf%1AbFV|YT~!y`W4tA4~7zAsyNJf^vES-?=1teP3#7{ zHt{2{C=voUH1TnKCe;;(nmAjsbbOLWu2)NbnL+~hvk||1!7H!FuTz9Z=mZb3spzBd zZJy4ku}~SGp(l79#zI$P{0@3vjfKki1^R><3zhLF>5>`?m9a#xjD=6i_+|RM8Vi;2 z*XbK-EL6sCpaGPzP>k8f4Kh|##_imt#zJMB;ir|JrMPGW`rityK1vHXMLy18%qmMQ zAm4WZP)i30KR&5vs15)C+8dJrMI4h)o;80F=t3z%xd|2&*IQdyR=^LH8WYpRgrrep z4Mx6Aw}fxhSE$jN_`x6Gk20R2MM&C)-R$h{nfE#GnVgwFe}DZ3unAM( z^yK7C>62cU)*<-~eOtHo^)=lJyq4q2*a>{Y3mU}nkX(`x@nlm*hSenNFiN~g-`;Q5dw>RYT0OXvK4;<_A&n$p-%65n=wqR{bejviAOu@}cn>s#w3 zqd~{|=TQiObS+3ii(WV`2`mPoZQ7x1xMY3^WvfM@Sq*HPLJh+LQwQ=`ny&P1^Hu$T ztXM-zVD=*VoC&`n>n>>+6&N{69EyJh#GXLvspC8GGlAj!USU^YC|}skAcN~^Xqe0( zjqx#zAj>muU<=IUs~34|v06u2ahGbSeT-uAG|Vv*B!tB{@| zSD-83y8@d7*`1w%h8tHyeJmd+%ZJ?fd}Uzfh5vJX5)@T}RqkqqccH*!l{ekX#H&;I zR^iy-o@x*n<0q?{%;#c+zcZNN(cr&%T;m%^7vKNHRPG0+zd~JE%wV>w$#pf8#qXFt zMfw|VuC{UeT)2WeU16as%yvYC;~n9>cf~Ip6jkjsE86E3FB*Oa!AkVp3%gtuiUmHCjh+5s!xB zVH6~|=FdJb2AzjuWhGuKjSP+on}(?siN^5Jin2r--ia!^bPn@aqisWj!Tv(5MW}^I z!`pa)vT8$%LF-%@Rb4PR?A`nXR1wx&&;GmqjDgqd&${44rvoi**kRDPqA$P*nq=!@ zbbQ#RzDw?a)FW*kzd`r)H&BBu@V)_1O9u$*DU&`VlV74C0Wgz+VHST=d2@7SZBTM= zaAk6IX=QUrVQyq>WpXZKY+-YARa6B40RR910F6{zcN0|<{*FyMaT2t<L5<0c&p4 z2t^Gocma!z^nwjg2QPRsIZ1|2X0pyqT3B7O{2TPaS6+FVEX-Q+;F}M=`5XKf)_qdi zrWcoHt<22%_I>YfpWlD~{^eHym(XC?eDY+uxRI)GYgM?_)O5@3OTJ_saKQ&$BjxX}5R^4dwmSHO&qH^VOwK7GzvbbYkYda1B zeuEnqm5N)pT_L3H)(Kq`(x+I27bmACi$+zfrGAWpG3aHlEiHd>({=K)`KKq{TJ21kJ?pE+&-W zdkn|+i8N#wqIMu84?M|ma-VJK6;2Lv$j6X{!7y@IuAzUxpb_5|#iJmOix`dJeO#i7 zTmqBBzNR6oUMIzGX5hS&Xe#ebE;Sh{p%4xeLIY2Tz?JG2+dF_EJb#~eyZ z@Fkt=Y65=~>BHq7*@1a1sIe@PAq|?Lc!c5qbafkFDK0NFB=)4sZ8xx+V)l_GTI!O} zaA$|1eWhJ*+fG%j;yqfgSt&i4TyF?Bspv@JWNVk^$r`ctS`=UZOG|rnzOQ2i-%yj( z;=4p><3HN5FAFN=JAAKPJWvlBc=m`$eaM}L^wxiQWp|q@I_|=v>tlC`mip?ag^t{y zn=Wz=gTyEGqv0b;e}+1p{=g7PfX!?dF>}6yH$#kVVlel-i*eIj=-^Zr33D-TnAr}_ zgg6)CLWuMWOfuZczQ7d2Q%uvxM-1j-h?$XVA#Oa~`pcm2;aG?-%;`voTR(68nL8cH z6S`GIf)M4EXIsDJp3gr+l&}jgySQt1v0`Q)jF=sK8{&r$Jl8?>CFPgE8LXp*vzX|; zCy>DgWt~C3cizBHy|=1vfS#{m;2%&+2ME^VC9IkQ002$~lL3n}lP;$ve{D|_K@i7h zq0mx~(iRY;DsqT@fKs7=k?lbhL{|8I78X7|_cA3p(L9<~X5y1L3{K8Sf*xL|5gToDT;aYig?m8z^z zQ`XdEMJqC#*O|ho!7x~+e@3!UW`zr*Rx!kt5>*T~BUYtkg;tX?&%yG`3&ngIwR*qK&t{TERu@0|fDrKKw3=RE&t-)Xh-$i& zl5|>BSn5)z)hg3d?<~8msU=ye>CHWR!9yT;PU|$KP*qADf(V?zj^n^g~nykv^I)Uz3{78Ty81{n~ zZsS&7WH)#Ach3%Uf4kYUmj|zQeK0D3A8z$Q4~!A$cS^D1s=Ahvw9*%Wt z<42vTt%|niux3Zww13+oK)-d~G>VKHM0ov>KXKaUH(Cc)f5ckGj#z^bMqanI7kh>c z4jG*hPi&7LY7C*Ho7#^wUVt&67&ea4^1oBw%@h^ z+YZ+ee?b_!@DI?Nm~Ozd6Q6;k9S@=93-lzuLF9`ao`sYf9&zx;uK2iv&$;8{ z4nFUSFF5$3JHFuHORo5YgFkV{CmcNEicdQDvO7NM;484|f=_+6!)x%g1CL;L9DE%% zT=1xaKZ8v-+-@x1OZ;|0_a9J82MFd71j+6K002-1lL18>lWM9xf1X_uLRcEg4jQA- zHcc9K3lxeOWP*-1SuY4R02N^2r8xDFg9}7Kjp@xw6=*@*i9@L`73oqu)K^2yG;l>>Cwag0- z=8CTs4Bp~6e_U8aLGn@AFb&&vpBMMz0S{KgPaG(klqiJfMF0ytWaS}-c{-O8y9lOh z2Xuv@w!WdGK!4N}72fW}8mtw7Is&A)*(2#(H64Bz<^g#|yl6mBIBsNUF3Q>FNLEd* ztCEt>-1aOFWJTiSpNOEnL@F+XsmO6>I8BLRUD+ZOe@C2)N#$nbKyN$66u~+Vny{W> zfx$~GGSt+UYKyvU#3oUN&6HrGNV%2HkQ=dE2PD`LiG6&t*H8TOR;Kd#%fhTR9eKZtn z9LMKM*KxV5mnE63#V&W*^ob&Yg4SpPA2mpLymvXJ&rSnfZQW_U*ZSf0clKa-6$O zFhpf`J|Pdq-Q%krj+Xe)tM&~&9+`<5*+vfe(+)$EbzzS_g8~5n6<^pbP`-#4+*RJN@*r%~jzCWn@QmZuFjJhkx{+Ghh682ek;jK5S=U0;j zCZ8%|=4mCbo;DS=4IcaK6~5`?t%-f4yXDxhbN`jvhNrcHQ=1-Ke_QhO4~a)K2Q31{{Z0=(QQs_lEDq~E!lx(Wf$Ef zOksB0Ud^B$NdUDB7;}fb$2Zc=Nr6gY*kgO*hw2&K`x=uISxwKGbCR+CD+KI`Tux%n&h$iVx6;nycdj{lk5Rc3%-pqY!q=kh=pQ-;&e@p{A{vJvQUfD|`_Lq-$Y}dJ$+RS_#6<>Sjj#tUq&0HR@oOh_TjG63E zpM3MZ1^;2A|8R%33RC4qh!r`D;gF&yaD?6Df>QLW8NsyeH{ZJ@D_rLFu%8ShdXoE<%LpeacNeAu3QBnlF$n5 z%-1{>4?nN2!tZWG6tn?@KvPawNZO16qb`UILt#(^yUyF9Ky@t`G=^q{=Ab-D8~~hA zkOYfB5m)WN?~_(tP|$8Y7-E3(bj+nH-G+#YN3Gp*Bv24KP>WT_Fh({?;n?zp6P2ab z!6)m0lD|P*1b>9A%vK_>JhW_SucT%&xTKB%%F?l5RU4FQusH%MiAdo00YYr`++iRXt-BYl!}%s4Cq7A4b)XKpJ5nUhk`Vz0AxtRLq3gbK$+t$Oj)8RPj@I? zjD>o9zKxIE%yVg*&NKx4W|M1)jL-Mhhju8{QBN6M*kl7P`tiVaJOF@4fn=XWP!~~@ zJGlS=83jHMM8Nz=V+54{mH+@G6llsoz{X|`h!(Am!19lm0Kf^yDA>sZ-m4k0Kf`$ROj&p2>ZX&G0^%ZJScywU3$^FauEIMR$EYhGXelJl)g?L0#VA8 VAiXx68Xg`Y&7T3F@ASg`=s%himEiyY diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index df97d72b8b..c61a118f7d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index f5feea6d6b..0262dcbd52 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -86,8 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -115,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -173,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -206,15 +203,14 @@ fi DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # and any embedded shellness will be escaped. # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a21834..c4bdd3ab8e 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index d9dfb42273..77d3309f53 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("multiloader-platform") - id("net.neoforged.moddev") version("2.0.42-beta") + id("net.neoforged.moddev") version("2.0.141") } base { diff --git a/settings.gradle.kts b/settings.gradle.kts index 304b9d9281..0f606fd359 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,6 +2,7 @@ rootProject.name = "sodium" pluginManagement { repositories { + mavenLocal() maven { url = uri("https://maven.fabricmc.net/") } maven { url = uri("https://maven.neoforged.net/releases/") } gradlePluginPortal() From cb4d1bd3ce2b08ad9ae81660f65fdb6439c954a7 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 3 May 2026 00:45:48 +0200 Subject: [PATCH 068/215] Backport: Move the workarounds to "boot" --- common/build.gradle.kts | 34 +++- .../compatibility/checks/BugChecks.java | 0 .../checks/GraphicsDriverChecks.java | 0 .../compatibility/checks/ModuleScanner.java | 2 +- .../checks/PostLaunchChecks.java | 2 - .../compatibility/checks/PreLaunchChecks.java | 162 ++++++++++++++++++ .../environment/GlContextInfo.java | 1 - .../compatibility/environment/OsUtils.java | 0 .../probe/GraphicsAdapterInfo.java | 10 +- .../probe/GraphicsAdapterProbe.java | 3 +- .../probe/GraphicsAdapterVendor.java | 9 +- .../workarounds/Workarounds.java | 33 ++-- .../workarounds/amd/AmdWorkarounds.java | 74 ++++++++ .../workarounds/intel/IntelWorkarounds.java | 6 +- .../nvidia/NvidiaDriverVersion.java | 0 .../workarounds/nvidia/NvidiaWorkarounds.java | 4 +- .../mods/sodium/client/console/Console.java | 4 +- .../sodium/client/console/ConsoleSink.java | 4 +- .../client/console/message/Message.java | 0 .../client/console/message/MessageLevel.java | 0 .../sodium/client/platform/MessageBox.java | 5 +- .../client/platform/NativeWindowHandle.java | 0 .../client/platform/PlatformHelper.java | 10 +- .../sodium/client/platform/unix/Libc.java | 2 +- .../platform/windows/WindowsCommandLine.java | 23 ++- .../platform/windows/WindowsFileVersion.java | 4 +- .../client/platform/windows/api/Gdi32.java | 0 .../client/platform/windows/api/Kernel32.java | 9 +- .../client/platform/windows/api/Shell32.java | 2 +- .../client/platform/windows/api/User32.java | 0 .../platform/windows/api/d3dkmt/D3DKMT.java | 20 +-- .../api/d3dkmt/D3DKMTAdapterInfoStruct.java | 6 +- .../D3DKMTAdapterRegistryInfoStruct.java | 2 +- .../api/d3dkmt/D3DKMTEnumAdaptersStruct.java | 8 +- .../api/d3dkmt/D3DKMTOpenGLInfoStruct.java | 6 +- .../d3dkmt/D3DKMTQueryAdapterInfoStruct.java | 6 +- .../d3dkmt/D3DKMTQueryAdapterInfoType.java | 0 .../windows/api/msgbox/MsgBoxCallback.java | 0 .../windows/api/msgbox/MsgBoxCallbackI.java | 4 +- .../windows/api/msgbox/MsgBoxParamSw.java | 6 +- .../windows/api/version/LanguageCodePage.java | 0 .../windows/api/version/QueryResult.java | 0 .../platform/windows/api/version/Version.java | 2 +- .../version/VersionFixedFileInfoStruct.java | 0 .../windows/api/version/VersionInfo.java | 2 +- .../resources/META-INF/MANIFEST.MF | 0 .../mods/sodium/desktop/LaunchWarn.java | 2 +- .../compatibility/checks/PreLaunchChecks.java | 68 -------- fabric/build.gradle.kts | 4 +- neoforge/build.gradle.kts | 6 +- 50 files changed, 377 insertions(+), 168 deletions(-) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/compatibility/checks/BugChecks.java (100%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/compatibility/checks/GraphicsDriverChecks.java (100%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/compatibility/checks/ModuleScanner.java (99%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PostLaunchChecks.java (95%) create mode 100644 common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GlContextInfo.java (94%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/compatibility/environment/OsUtils.java (100%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterInfo.java (56%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterProbe.java (98%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java (95%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java (77%) create mode 100644 common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/amd/AmdWorkarounds.java rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java (91%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaDriverVersion.java (100%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java (98%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/console/Console.java (88%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/console/ConsoleSink.java (65%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/console/message/Message.java (100%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/console/message/MessageLevel.java (100%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/MessageBox.java (97%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/NativeWindowHandle.java (100%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java (82%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/unix/Libc.java (94%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsCommandLine.java (75%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsFileVersion.java (88%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Gdi32.java (100%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Kernel32.java (93%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Shell32.java (97%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/User32.java (100%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java (91%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterInfoStruct.java (94%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterRegistryInfoStruct.java (98%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTEnumAdaptersStruct.java (90%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTOpenGLInfoStruct.java (94%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoStruct.java (94%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoType.java (100%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallback.java (100%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallbackI.java (90%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxParamSw.java (95%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/LanguageCodePage.java (100%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/QueryResult.java (100%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/Version.java (98%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionFixedFileInfoStruct.java (100%) rename common/src/{workarounds => boot}/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionInfo.java (98%) rename common/src/{workarounds => boot}/resources/META-INF/MANIFEST.MF (100%) delete mode 100644 common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java diff --git a/common/build.gradle.kts b/common/build.gradle.kts index c57f91ebcc..85abfcec34 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -16,7 +16,7 @@ val configurationPreLaunch = configurations.create("preLaunchDeps") { sourceSets { val main = getByName("main") val api = create("api") - val workarounds = create("workarounds") + val boot = create("boot") api.apply { java { @@ -24,7 +24,7 @@ sourceSets { } } - workarounds.apply { + boot.apply { java { compileClasspath += configurationPreLaunch } @@ -33,15 +33,19 @@ sourceSets { main.apply { java { compileClasspath += api.output - compileClasspath += workarounds.output + compileClasspath += boot.output } } create("desktop") } +repositories { + mavenLocal() +} + dependencies { - minecraft(group = "com.mojang", name = "minecraft", version = BuildConfig.MINECRAFT_VERSION) + minecraft("com.mojang:minecraft:${BuildConfig.MINECRAFT_VERSION}") mappings(loom.layered { officialMojangMappings() @@ -74,7 +78,7 @@ dependencies { configurationPreLaunch("net.java.dev.jna:jna:5.14.0") configurationPreLaunch("net.java.dev.jna:jna-platform:5.14.0") configurationPreLaunch("org.slf4j:slf4j-api:2.0.9") - configurationPreLaunch("org.jetbrains:annotations:25.0.0") + configurationPreLaunch("org.jspecify:jspecify:1.0.0") } loom { @@ -97,6 +101,21 @@ fun exportSourceSetJava(name: String, sourceSet: SourceSet) { } } +fun exportSourceSetSources(name: String, sourceSet: SourceSet) { + val configuration = configurations.create("${name}Sources") { + isCanBeResolved = true + isCanBeConsumed = true + } + + val compileTask = tasks.register(sourceSet.getTaskName("process", "sources")) { + from(sourceSet.allSource) + into(file(project.layout.buildDirectory).resolve("sources").resolve(sourceSet.name)) + }.get() + artifacts.add(configuration.name, compileTask.destinationDir) { + builtBy(compileTask) + } +} + fun exportSourceSetResources(name: String, sourceSet: SourceSet) { val configuration = configurations.create("${name}Resources") { isCanBeResolved = true @@ -117,13 +136,14 @@ fun exportSourceSetResources(name: String, sourceSet: SourceSet) { // Exports the compiled output of the source set to the named configuration. fun exportSourceSet(name: String, sourceSet: SourceSet) { exportSourceSetJava(name, sourceSet) + exportSourceSetSources(name, sourceSet) exportSourceSetResources(name, sourceSet) } exportSourceSet("commonMain", sourceSets["main"]) exportSourceSet("commonApi", sourceSets["api"]) -exportSourceSet("commonEarlyLaunch", sourceSets["workarounds"]) +exportSourceSet("commonBoot", sourceSets["boot"]) exportSourceSet("commonDesktop", sourceSets["desktop"]) tasks.jar { enabled = false } -tasks.remapJar { enabled = false } \ No newline at end of file +tasks.remapJar { enabled = false } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/BugChecks.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/BugChecks.java similarity index 100% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/BugChecks.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/BugChecks.java diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/GraphicsDriverChecks.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/GraphicsDriverChecks.java similarity index 100% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/GraphicsDriverChecks.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/GraphicsDriverChecks.java diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/ModuleScanner.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/ModuleScanner.java similarity index 99% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/ModuleScanner.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/ModuleScanner.java index 8af1a8d70f..ba03bcd3f9 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/ModuleScanner.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/ModuleScanner.java @@ -7,7 +7,7 @@ import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; import net.caffeinemc.mods.sodium.client.platform.windows.api.Kernel32; import net.caffeinemc.mods.sodium.client.platform.windows.api.version.Version; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PostLaunchChecks.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PostLaunchChecks.java similarity index 95% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PostLaunchChecks.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PostLaunchChecks.java index 6180199456..79be746ff2 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PostLaunchChecks.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PostLaunchChecks.java @@ -2,8 +2,6 @@ import net.caffeinemc.mods.sodium.client.compatibility.environment.GlContextInfo; import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; -import net.caffeinemc.mods.sodium.client.console.Console; -import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; import net.caffeinemc.mods.sodium.client.platform.NativeWindowHandle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java new file mode 100644 index 0000000000..044c49fb64 --- /dev/null +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java @@ -0,0 +1,162 @@ +package net.caffeinemc.mods.sodium.client.compatibility.checks; + +import net.caffeinemc.mods.sodium.client.platform.PlatformHelper; +import org.lwjgl.Version; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.URL; +import java.security.CodeSource; +import java.security.ProtectionDomain; + +/** + * Performs OpenGL driver validation before the game creates an OpenGL context. This runs during the earliest possible + * opportunity at game startup, and uses a custom hardware prober to search for problematic drivers. + */ +public class PreLaunchChecks { + private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-PreLaunchChecks"); + + // These version constants are inlined at compile time. + private static final String REQUIRED_LWJGL_VERSION = + Version.VERSION_MAJOR + "." + Version.VERSION_MINOR + "." + Version.VERSION_REVISION; + + public static void checkEnvironment() { + if (BugChecks.ISSUE_2561) { + checkLwjglRuntimeVersion(); + } + } + + private static void checkLwjglRuntimeVersion() { + if (isUsingKnownCompatibleLwjglVersion()) { + return; + } + + + String launcher = getLauncherBrand(); + String codeSource = getLwjglCodeSource(); + String codeSourceFilename = null; + if (codeSource != null) { + String[] components = codeSource.split("/"); + codeSourceFilename = components[components.length-1]; + } + + if (codeSource != null) { + LOGGER.info("Problematic LWJGL version source: {}", codeSource); + } + + boolean isCustomLauncher = !launcher.equals("minecraft-launcher") && !launcher.equals("unknown"); + boolean isLikelyCausedByLauncher = false; + String isLikelyCausedByMod = null; + + if (isCustomLauncher) { + if (codeSourceFilename != null && (codeSourceFilename.startsWith("lwjgl-") || codeSource.contains("/lwjgl/"))) { + isLikelyCausedByLauncher = true; + } + } + if (!isLikelyCausedByLauncher) { + if (codeSource != null && codeSource.endsWith("/mods/"+codeSourceFilename)) { + isLikelyCausedByMod = codeSourceFilename; + } + } + + String advice; + if (isLikelyCausedByMod != null) { + advice = """ + This issue seems to be caused by ###MOD###. + + Removing ###MOD### from your mods folder may fix this issue.""" + .replace("###MOD###", isLikelyCausedByMod); + } else if (launcher.equalsIgnoreCase("prismlauncher")) { + advice = """ + It appears you are using Prism Launcher to start the game. You can \ + likely fix this problem by opening your instance settings and navigating to the Version \ + section in the sidebar."""; + } else if (isLikelyCausedByLauncher) { + advice = """ + You seem to be using ###LAUNCHER###. This issue is likely caused by ###LAUNCHER###. + + You must change the LWJGL version in your launcher to continue. \ + This is usually controlled by the settings for a profile or instance in your launcher. + + If you need assistance fixing the LWJGL version, you should contact ###LAUNCHER###, not Sodium.""" + .replace("###LAUNCHER###", launcher); + } else if (isCustomLauncher) { + advice = """ + You seem to be using ###LAUNCHER###. + + You must change the LWJGL version in your launcher to continue. \ + This is usually controlled by the settings for a profile or instance in your launcher.""" + .replace("###LAUNCHER###", launcher); + } else { + advice = """ + You must change the LWJGL version in your launcher to continue. \ + This is usually controlled by the settings for a profile or instance in your launcher."""; + } + + String message = """ + The game failed to start because the currently active LWJGL version is not \ + compatible. + + Installed version: ###CURRENT_VERSION### + Required version: ###REQUIRED_VERSION### + + ###ADVICE_STRING###""" + .replace("###CURRENT_VERSION###", Version.getVersion()) + .replace("###REQUIRED_VERSION###", REQUIRED_LWJGL_VERSION) + .replace("###ADVICE_STRING###", advice); + + PlatformHelper.showCriticalErrorAndClose(null, "Sodium Renderer - Unsupported LWJGL", message, + "https://link.caffeinemc.net/help/sodium/runtime-issue/lwjgl3/gh-2561"); + } + + private static String getLwjglCodeSource() { + try { + ProtectionDomain domain = Version.class.getProtectionDomain(); + if (domain != null) { + CodeSource source = domain.getCodeSource(); + if (source != null) { + URL location = source.getLocation(); + if (location != null) { + String path = location.getPath(); + if (path != null) { + path = path.replace('\\', '/'); + path = path.split("!")[0]; + return path; + } + } + } + } + } catch (Throwable t) { + LOGGER.error("Error while checking code source of LWJGL", t); + } + return null; + } + + private static boolean isUsingKnownCompatibleLwjglVersion() { + return Version.getVersion() + .startsWith(REQUIRED_LWJGL_VERSION); + } + + private static String getLauncherBrand() { + String brand = System.getProperty("minecraft.launcher.brand", "unknown"); + if (brand.equals("unknown")) { + // Lunar Client uses a custom set of launch arguments + // which don't include minecraft.launcher.brand + if (isClassLoaded("com.moonsworth.lunar.genesis.Genesis")) { + return "Lunar Client"; + } else if (System.getProperty("lunar.webosr.url") != null) { + return "Lunar Client"; + } + } + return brand; + } + + private static boolean isClassLoaded(String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GlContextInfo.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GlContextInfo.java similarity index 94% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GlContextInfo.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GlContextInfo.java index 7e8f484385..f4aa02f263 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GlContextInfo.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/GlContextInfo.java @@ -1,6 +1,5 @@ package net.caffeinemc.mods.sodium.client.compatibility.environment; -import org.jetbrains.annotations.Nullable; import org.lwjgl.opengl.GL11C; import java.util.Objects; diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/OsUtils.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/OsUtils.java similarity index 100% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/OsUtils.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/OsUtils.java diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterInfo.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterInfo.java similarity index 56% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterInfo.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterInfo.java index 665671c4e2..f2db7f2b37 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterInfo.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterInfo.java @@ -1,15 +1,15 @@ package net.caffeinemc.mods.sodium.client.compatibility.environment.probe; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NonNull; public interface GraphicsAdapterInfo { - @NotNull GraphicsAdapterVendor vendor(); + @NonNull GraphicsAdapterVendor vendor(); - @NotNull String name(); + @NonNull String name(); record LinuxPciAdapterInfo( - @NotNull GraphicsAdapterVendor vendor, - @NotNull String name, + @NonNull GraphicsAdapterVendor vendor, + @NonNull String name, String pciVendorId, String pciDeviceId diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterProbe.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterProbe.java similarity index 98% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterProbe.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterProbe.java index 4bfb59179a..4b767e5934 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterProbe.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterProbe.java @@ -2,8 +2,7 @@ import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; import net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt.D3DKMT; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java similarity index 95% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java index 436bee2ae7..9fa33dcb2b 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java @@ -1,9 +1,8 @@ package net.caffeinemc.mods.sodium.client.compatibility.environment.probe; import net.caffeinemc.mods.sodium.client.compatibility.environment.GlContextInfo; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NonNull; -import java.util.Locale; import java.util.regex.Pattern; public enum GraphicsAdapterVendor { @@ -32,7 +31,7 @@ public enum GraphicsAdapterVendor { private static final Pattern AMD_ICD_PATTERN = Pattern.compile("(atiglpxx|atig6pxx)\\.dll", Pattern.CASE_INSENSITIVE); - @NotNull + @NonNull static GraphicsAdapterVendor fromPciVendorId(String vendor) { if (vendor.contains("0x1002")) { return AMD; @@ -45,7 +44,7 @@ static GraphicsAdapterVendor fromPciVendorId(String vendor) { return UNKNOWN; } - @NotNull + @NonNull public static GraphicsAdapterVendor fromIcdName(String name) { if (matchesPattern(INTEL_ICD_PATTERN, name)) { return INTEL; @@ -58,7 +57,7 @@ public static GraphicsAdapterVendor fromIcdName(String name) { } } - @NotNull + @NonNull public static GraphicsAdapterVendor fromContext(GlContextInfo context) { var vendor = context.vendor(); diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java similarity index 77% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java index 659d98b6b3..57571c3f14 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/Workarounds.java @@ -1,6 +1,7 @@ package net.caffeinemc.mods.sodium.client.compatibility.workarounds; import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.amd.AmdWorkarounds; import net.caffeinemc.mods.sodium.client.compatibility.workarounds.intel.IntelWorkarounds; import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; import org.slf4j.Logger; @@ -8,7 +9,6 @@ import java.util.Collections; import java.util.EnumSet; -import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -41,23 +41,17 @@ private static Set findNecessaryWorkarounds() { workarounds.add(Reference.NVIDIA_THREADED_OPTIMIZATIONS_BROKEN); } + if (AmdWorkarounds.isAmdGraphicsCardPresent() && operatingSystem == OsUtils.OperatingSystem.WIN) { + workarounds.add(Reference.AMD_GAME_OPTIMIZATION_BROKEN); + } + if (IntelWorkarounds.isUsingIntelGen8OrOlder()) { workarounds.add(Reference.INTEL_FRAMEBUFFER_BLIT_CRASH_WHEN_UNFOCUSED); workarounds.add(Reference.INTEL_DEPTH_BUFFER_COMPARISON_UNRELIABLE); } if (operatingSystem == OsUtils.OperatingSystem.LINUX) { - var session = System.getenv("XDG_SESSION_TYPE"); - - if (session == null) { - LOGGER.warn("Unable to determine desktop session type because the environment variable XDG_SESSION_TYPE " + - "is not set! Your user session may not be configured correctly."); - } - - if (Objects.equals(session, "wayland")) { - // This will also apply under Xwayland, even though the problem does not happen there - workarounds.add(Reference.NO_ERROR_CONTEXT_UNSUPPORTED); - } + workarounds.add(Reference.NO_ERROR_CONTEXT_UNSUPPORTED); } return Collections.unmodifiableSet(workarounds); @@ -77,7 +71,8 @@ public enum Reference { NVIDIA_THREADED_OPTIMIZATIONS_BROKEN, /** - * Requesting a No Error Context causes a crash at startup when using a Wayland session. + * Requesting a No Error Context causes a crash at startup when using a Wayland session on GLFW + <3.4. * GitHub Issue */ NO_ERROR_CONTEXT_UNSUPPORTED, @@ -86,6 +81,7 @@ public enum Reference { * Intel's graphics driver for Gen8 and older seems to be faulty and causes a crash when calling * glFramebufferBlit after the window loses focus. * GitHub Issue + * GitHub Issue */ INTEL_FRAMEBUFFER_BLIT_CRASH_WHEN_UNFOCUSED, @@ -95,6 +91,15 @@ public enum Reference { * the base model. * GitHub Issue */ - INTEL_DEPTH_BUFFER_COMPARISON_UNRELIABLE + INTEL_DEPTH_BUFFER_COMPARISON_UNRELIABLE, + + /** + * AMD's graphics driver starting at 25.10.2 does not correctly handle glMapBufferRange + * when minecraft is detected, causing terrain rendering to go invisible if the launcher allows minecraft + * to be detected. Most commonly this happens with some third party PVP clients, + * but can happen with other launchers. + * GitHub Issue + */ + AMD_GAME_OPTIMIZATION_BROKEN } } diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/amd/AmdWorkarounds.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/amd/AmdWorkarounds.java new file mode 100644 index 0000000000..9b6ddbc396 --- /dev/null +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/amd/AmdWorkarounds.java @@ -0,0 +1,74 @@ +package net.caffeinemc.mods.sodium.client.compatibility.workarounds.amd; + +import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; +import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe; +import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterVendor; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; +import net.caffeinemc.mods.sodium.client.platform.windows.WindowsCommandLine; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AmdWorkarounds { + private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-AmdWorkarounds"); + + public static boolean isAmdGraphicsCardPresent() { + return GraphicsAdapterProbe.getAdapters() + .stream() + .anyMatch(adapter -> adapter.vendor() == GraphicsAdapterVendor.AMD); + } + + public static void undoEnvironmentChanges() { + if (OsUtils.getOs() == OsUtils.OperatingSystem.WIN) { + undoEnvironmentChanges$Windows(); + } + } + + private static void undoEnvironmentChanges$Windows() { + WindowsCommandLine.resetCommandLine(); + } + + public static void applyEnvironmentChanges() { + // We can't know if the OpenGL context will actually be initialized using the AMD ICD, but we need to + // modify the process environment *now* otherwise the driver will initialize with bad settings. For non-AMD + // drivers, these workarounds are not likely to cause issues. + if (!isAmdGraphicsCardPresent()) { + return; + } + + // Skip applying the AMD workaround if the user also has an Nvidia gpu, to avoid attempting to overwrite the + // process command line twice. + if (NvidiaWorkarounds.isNvidiaGraphicsCardPresent()) { + return; + } + + try { + if (OsUtils.getOs() == OsUtils.OperatingSystem.WIN) { + LOGGER.info("Modifying process environment to apply workarounds for the AMD graphics driver..."); + + applyEnvironmentChanges$Windows(); + } + } catch (Throwable t) { + LOGGER.error("Failed to modify the process environment", t); + logWarning(); + } + } + + + private static void applyEnvironmentChanges$Windows() { + // The new AMD drivers rely on parsing the command line arguments to detect Minecraft. + // When they do they apply an optimization that is broken with sodium present + // This stops AMD drivers from detecting the game + WindowsCommandLine.setCommandLine("net.caffeinemc.sodium / net.minecraft.client.main.Main /"); + } + + private static void logWarning() { + LOGGER.error("READ ME!"); + LOGGER.error("READ ME! The workarounds for the AMD Graphics Driver did not apply correctly!"); + LOGGER.error("READ ME! You may run into unexplained graphical issues."); + LOGGER.error("READ ME! More information about what went wrong can be found above this message."); + LOGGER.error("READ ME!"); + LOGGER.error("READ ME! Please help us understand why this problem occurred by opening a bug report on our issue tracker:"); + LOGGER.error("READ ME! https://github.com/CaffeineMC/sodium/issues"); + LOGGER.error("READ ME!"); + } +} diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java similarity index 91% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java index 390502d152..2dbec228fa 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java @@ -4,7 +4,7 @@ import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe; import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; import net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt.D3DKMT; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; public class IntelWorkarounds { // https://github.com/CaffeineMC/sodium/issues/899 @@ -15,7 +15,7 @@ public class IntelWorkarounds { for (var adapter : GraphicsAdapterProbe.getAdapters()) { if (adapter instanceof D3DKMT.WDDMAdapterInfo wddmAdapterInfo) { - @Nullable var driverName = wddmAdapterInfo.getOpenGlIcdName(); + String driverName = wddmAdapterInfo.getOpenGlIcdName(); if (driverName == null) { continue; @@ -45,7 +45,7 @@ public static boolean isUsingIntelGen8OrOlder() { for (var adapter : GraphicsAdapterProbe.getAdapters()) { if (adapter instanceof D3DKMT.WDDMAdapterInfo wddmAdapterInfo) { - @Nullable var driverName = wddmAdapterInfo.getOpenGlIcdName(); + var driverName = wddmAdapterInfo.getOpenGlIcdName(); // Intel OpenGL ICD for legacy GPUs if (driverName != null && driverName.matches("ig(7|75|8)icd(32|64)\\.dll")) { diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaDriverVersion.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaDriverVersion.java similarity index 100% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaDriverVersion.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaDriverVersion.java diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java similarity index 98% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java index d7c1be4da6..19ede53790 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java @@ -10,7 +10,7 @@ import net.caffeinemc.mods.sodium.client.platform.windows.WindowsCommandLine; import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; import net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt.D3DKMT; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL32C; import org.lwjgl.opengl.KHRDebug; @@ -128,7 +128,7 @@ public static void applyContextChanges(GlContextInfo context) { var capabilities = GL.getCapabilities(); if (capabilities.GL_KHR_debug) { - LOGGER.info("Enabling GL_DEBUG_OUTPUT_SYNCHRONOUS to force the NVIDIA driver to disable threaded" + + LOGGER.info("Enabling GL_DEBUG_OUTPUT_SYNCHRONOUS to force the NVIDIA driver to disable threaded " + "command submission"); GL32C.glEnable(KHRDebug.GL_DEBUG_OUTPUT_SYNCHRONOUS); } else { diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/console/Console.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/console/Console.java similarity index 88% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/console/Console.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/console/Console.java index 7e4fa1a760..af97420ad8 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/console/Console.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/console/Console.java @@ -2,7 +2,7 @@ import net.caffeinemc.mods.sodium.client.console.message.Message; import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NonNull; import java.util.ArrayDeque; import java.util.Deque; @@ -14,7 +14,7 @@ public class Console implements ConsoleSink { private final ArrayDeque messages = new ArrayDeque<>(); @Override - public void logMessage(@NotNull MessageLevel level, @NotNull String text, boolean translatable, double duration) { + public void logMessage(@NonNull MessageLevel level, @NonNull String text, boolean translatable, double duration) { Objects.requireNonNull(level); Objects.requireNonNull(text); diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/console/ConsoleSink.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/console/ConsoleSink.java similarity index 65% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/console/ConsoleSink.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/console/ConsoleSink.java index fb02fa03aa..289c2af8c0 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/console/ConsoleSink.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/console/ConsoleSink.java @@ -1,8 +1,8 @@ package net.caffeinemc.mods.sodium.client.console; import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NonNull; public interface ConsoleSink { - void logMessage(@NotNull MessageLevel level, @NotNull String text, boolean translatable, double duration); + void logMessage(@NonNull MessageLevel level, @NonNull String text, boolean translatable, double duration); } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/console/message/Message.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/console/message/Message.java similarity index 100% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/console/message/Message.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/console/message/Message.java diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/console/message/MessageLevel.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/console/message/MessageLevel.java similarity index 100% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/console/message/MessageLevel.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/console/message/MessageLevel.java diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/MessageBox.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/MessageBox.java similarity index 97% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/MessageBox.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/MessageBox.java index fe80d7a501..e5afb4f6b0 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/MessageBox.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/MessageBox.java @@ -5,13 +5,10 @@ import net.caffeinemc.mods.sodium.client.platform.windows.api.User32; import net.caffeinemc.mods.sodium.client.platform.windows.api.msgbox.MsgBoxCallback; import net.caffeinemc.mods.sodium.client.platform.windows.api.msgbox.MsgBoxParamSw; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; -import java.awt.*; -import java.io.IOException; -import java.net.URI; import java.nio.ByteBuffer; import java.util.Objects; diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/NativeWindowHandle.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/NativeWindowHandle.java similarity index 100% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/NativeWindowHandle.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/NativeWindowHandle.java diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java similarity index 82% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java index dfc1f2b77f..a1279bde7c 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.platform; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,9 +10,9 @@ public class PlatformHelper { public static void showCriticalErrorAndClose( @Nullable NativeWindowHandle window, - @NotNull String messageTitle, - @NotNull String messageBody, - @NotNull String helpUrl) + @NonNull String messageTitle, + @NonNull String messageBody, + @NonNull String helpUrl) { // Always print the information to the log file first, just in case we can't show the message box. LOGGER.error(""" diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/unix/Libc.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/unix/Libc.java similarity index 94% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/unix/Libc.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/unix/Libc.java index 45c522e3e5..66a7fad344 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/unix/Libc.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/unix/Libc.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.platform.unix; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import org.lwjgl.system.*; public class Libc { diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsCommandLine.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsCommandLine.java similarity index 75% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsCommandLine.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsCommandLine.java index 3165ad6d1e..721bb63263 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsCommandLine.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsCommandLine.java @@ -2,6 +2,7 @@ import net.caffeinemc.mods.sodium.client.platform.windows.api.Kernel32; import org.lwjgl.system.MemoryUtil; + import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.util.Objects; @@ -17,11 +18,15 @@ public static void setCommandLine(String modifiedCmdline) { // Pointer into the command-line arguments stored within the Windows process structure // We do not own this memory, and it should not be freed. var pCmdline = Kernel32.getCommandLine(); + var pCmdlineA = Kernel32.getCommandLineA(); // The original command-line the process was started with. var cmdline = MemoryUtil.memUTF16(pCmdline); var cmdlineLen = MemoryUtil.memLengthUTF16(cmdline, true); + var cmdlineA = MemoryUtil.memASCII(pCmdlineA); + var cmdLineLenA = MemoryUtil.memLengthASCII(cmdlineA, true); + if (MemoryUtil.memLengthUTF16(modifiedCmdline, true) > cmdlineLen) { // We can never write a string which is larger than what we were given, as there // may not be enough space remaining. Realistically, this should never happen, since @@ -30,12 +35,18 @@ public static void setCommandLine(String modifiedCmdline) { throw new BufferOverflowException(); } + if (MemoryUtil.memLengthASCII(modifiedCmdline, true) > cmdLineLenA) { + throw new BufferOverflowException(); + } + ByteBuffer buffer = MemoryUtil.memByteBuffer(pCmdline, cmdlineLen); + ByteBuffer bufferA = MemoryUtil.memByteBuffer(pCmdlineA, cmdLineLenA); // Write the new command line arguments into the process structure. // The Windows API documentation explicitly says this is forbidden, but it *does* give us a pointer // directly into the PEB structure, so... MemoryUtil.memUTF16(modifiedCmdline, true, buffer); + MemoryUtil.memASCII(modifiedCmdline, true, bufferA); // Make sure we can actually see our changes in the process structure // We don't know if this could ever actually happen, but since we're doing something pretty hacky @@ -43,8 +54,11 @@ public static void setCommandLine(String modifiedCmdline) { if (!Objects.equals(modifiedCmdline, MemoryUtil.memUTF16(pCmdline))) { throw new RuntimeException("Sanity check failed, the command line arguments did not appear to change"); } + if (!Objects.equals(modifiedCmdline, MemoryUtil.memASCII(pCmdlineA))) { + throw new RuntimeException("Sanity check failed, the command line arguments did not appear to change"); + } - ACTIVE_COMMAND_LINE_HOOK = new CommandLineHook(cmdline, buffer); + ACTIVE_COMMAND_LINE_HOOK = new CommandLineHook(cmdline, cmdlineA, buffer, bufferA); } public static void resetCommandLine() { @@ -56,13 +70,17 @@ public static void resetCommandLine() { private static class CommandLineHook { private final String cmdline; + private final String cmdlineA; private final ByteBuffer cmdlineBuf; + private final ByteBuffer cmdlineBufA; private boolean active = true; - private CommandLineHook(String cmdline, ByteBuffer cmdlineBuf) { + private CommandLineHook(String cmdline, String cmdlineA, ByteBuffer cmdlineBuf, ByteBuffer cmdlineBufA) { this.cmdline = cmdline; + this.cmdlineA = cmdlineA; this.cmdlineBuf = cmdlineBuf; + this.cmdlineBufA = cmdlineBufA; } public void uninstall() { @@ -73,6 +91,7 @@ public void uninstall() { // Restore the original value of the command line arguments // Must be null-terminated (as it was given to us) MemoryUtil.memUTF16(this.cmdline, true, this.cmdlineBuf); + MemoryUtil.memASCII(this.cmdlineA, true, this.cmdlineBufA); this.active = false; } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsFileVersion.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsFileVersion.java similarity index 88% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsFileVersion.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsFileVersion.java index 991a1171ea..ae8dca8dcd 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsFileVersion.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsFileVersion.java @@ -1,10 +1,10 @@ package net.caffeinemc.mods.sodium.client.platform.windows; import net.caffeinemc.mods.sodium.client.platform.windows.api.version.VersionFixedFileInfoStruct; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NonNull; public record WindowsFileVersion(int x, int y, int z, int w) { - public static @NotNull WindowsFileVersion fromFileVersion(VersionFixedFileInfoStruct fileVersion) { + public static @NonNull WindowsFileVersion fromFileVersion(VersionFixedFileInfoStruct fileVersion) { int x = (fileVersion.getFileVersionMostSignificantBits() >>> 16) & 0xffff; int y = (fileVersion.getFileVersionMostSignificantBits() >>> 0) & 0xffff; int z = (fileVersion.getFileVersionLeastSignificantBits() >>> 16) & 0xffff; diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Gdi32.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Gdi32.java similarity index 100% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Gdi32.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Gdi32.java diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Kernel32.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Kernel32.java similarity index 93% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Kernel32.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Kernel32.java index 4b1c62ae3a..b9e4537676 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Kernel32.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Kernel32.java @@ -1,8 +1,9 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import org.lwjgl.PointerBuffer; import org.lwjgl.system.*; + import java.nio.ByteBuffer; public class Kernel32 { @@ -14,6 +15,7 @@ public class Kernel32 { private static final int GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 1 << 2; private static final long PFN_GetCommandLineW; + private static final long PFN_GetCommandLineA; private static final long PFN_SetEnvironmentVariableW; private static final long PFN_GetModuleHandleExW; @@ -24,6 +26,7 @@ public class Kernel32 { static { PFN_GetCommandLineW = APIUtil.apiGetFunctionAddress(LIBRARY, "GetCommandLineW"); + PFN_GetCommandLineA = APIUtil.apiGetFunctionAddress(LIBRARY, "GetCommandLineA"); PFN_SetEnvironmentVariableW = APIUtil.apiGetFunctionAddress(LIBRARY, "SetEnvironmentVariableW"); PFN_GetModuleHandleExW = APIUtil.apiGetFunctionAddress(LIBRARY, "GetModuleHandleExW"); PFN_GetLastError = APIUtil.apiGetFunctionAddress(LIBRARY, "GetLastError"); @@ -50,6 +53,10 @@ public static long getCommandLine() { return JNI.callP(PFN_GetCommandLineW); } + public static long getCommandLineA() { + return JNI.callP(PFN_GetCommandLineA); + } + public static long getModuleHandleByNames(String[] names) { for (String name : names) { var handle = getModuleHandleByName(name); diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Shell32.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Shell32.java similarity index 97% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Shell32.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Shell32.java index 5409b7fcea..38441da31e 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Shell32.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Shell32.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api; import net.caffeinemc.mods.sodium.client.platform.NativeWindowHandle; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import org.lwjgl.system.JNI; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.SharedLibrary; diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/User32.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/User32.java similarity index 100% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/User32.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/User32.java diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java similarity index 91% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java index 6881b10ea7..dc9560809c 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java @@ -5,8 +5,8 @@ import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; import net.caffeinemc.mods.sodium.client.platform.windows.api.Gdi32; import net.caffeinemc.mods.sodium.client.platform.windows.api.version.Version; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.lwjgl.system.MemoryStack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,7 +46,7 @@ public static List findGraphicsAdapters() { } } - private static @NotNull ArrayList queryAdapters(@NotNull D3DKMTAdapterInfoStruct.Buffer adapterInfoBuffer) { + private static @NonNull ArrayList queryAdapters(D3DKMTAdapterInfoStruct.@NonNull Buffer adapterInfoBuffer) { var results = new ArrayList(); for (int adapterIndex = adapterInfoBuffer.position(); adapterIndex < adapterInfoBuffer.limit(); adapterIndex++) { @@ -63,7 +63,7 @@ public static List findGraphicsAdapters() { return results; } - private static void freeAdapters(@NotNull D3DKMTAdapterInfoStruct.Buffer adapterInfoBuffer) { + private static void freeAdapters(D3DKMTAdapterInfoStruct.@NonNull Buffer adapterInfoBuffer) { for (int adapterIndex = adapterInfoBuffer.position(); adapterIndex < adapterInfoBuffer.limit(); adapterIndex++) { var adapterInfo = adapterInfoBuffer.get(adapterIndex); apiCheckError("D3DKMTCloseAdapter", @@ -71,7 +71,7 @@ private static void freeAdapters(@NotNull D3DKMTAdapterInfoStruct.Buffer adapter } } - private static @Nullable D3DKMT.WDDMAdapterInfo getAdapterInfo(int adapter) { + private static D3DKMT.@Nullable WDDMAdapterInfo getAdapterInfo(int adapter) { int adapterType = queryAdapterType(adapter); if (!isSupportedAdapterType(adapterType)) { @@ -80,8 +80,8 @@ private static void freeAdapters(@NotNull D3DKMTAdapterInfoStruct.Buffer adapter String adapterName = queryFriendlyName(adapter); - @Nullable String driverFileName = queryDriverFileName(adapter); - @Nullable WindowsFileVersion driverVersion = null; + String driverFileName = queryDriverFileName(adapter); + WindowsFileVersion driverVersion = null; GraphicsAdapterVendor driverVendor = GraphicsAdapterVendor.UNKNOWN; @@ -133,7 +133,7 @@ private static boolean isSupportedAdapterType(int adapterType) { return WindowsFileVersion.fromFileVersion(fileVersion); } - private static @NotNull String queryFriendlyName(int adapter) { + private static @NonNull String queryFriendlyName(int adapter) { try (MemoryStack stack = MemoryStack.stackPush()) { D3DKMTAdapterRegistryInfoStruct registryInfo = D3DKMTAdapterRegistryInfoStruct.calloc(stack); d3dkmtQueryAdapterInfo(adapter, KMTQAITYPE_ADAPTERREGISTRYINFO, memByteBuffer(registryInfo)); @@ -183,8 +183,8 @@ private static void apiCheckError(String name, int error) { } public record WDDMAdapterInfo( - @NotNull GraphicsAdapterVendor vendor, - @NotNull String name, + @NonNull GraphicsAdapterVendor vendor, + @NonNull String name, int adapterType, @Nullable String openglIcdFilePath, @Nullable WindowsFileVersion openglIcdVersion diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterInfoStruct.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterInfoStruct.java similarity index 94% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterInfoStruct.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterInfoStruct.java index d57327a80f..c041ce7cf6 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterInfoStruct.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterInfoStruct.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NonNull; import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.Struct; import org.lwjgl.system.StructBuffer; @@ -75,12 +75,12 @@ protected Buffer(long address, int capacity) { } @Override - protected @NotNull D3DKMTAdapterInfoStruct getElementFactory() { + protected @NonNull D3DKMTAdapterInfoStruct getElementFactory() { return ELEMENT_FACTORY; } @Override - protected @NotNull Buffer self() { + protected @NonNull Buffer self() { return this; } } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterRegistryInfoStruct.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterRegistryInfoStruct.java similarity index 98% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterRegistryInfoStruct.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterRegistryInfoStruct.java index 1dba5ac999..0ab0e12d1f 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterRegistryInfoStruct.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterRegistryInfoStruct.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.Struct; diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTEnumAdaptersStruct.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTEnumAdaptersStruct.java similarity index 90% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTEnumAdaptersStruct.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTEnumAdaptersStruct.java index de36d44164..8fdc427a3d 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTEnumAdaptersStruct.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTEnumAdaptersStruct.java @@ -1,15 +1,13 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.Struct; import java.nio.ByteBuffer; -import static org.lwjgl.system.MemoryUtil.*; - // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/d3dkmthk/ns-d3dkmthk-_d3dkmt_enumadapters // typedef struct _D3DKMT_ENUMADAPTERS { // [in] ULONG NumAdapters; @@ -40,7 +38,7 @@ private D3DKMTEnumAdaptersStruct(long address, @Nullable ByteBuffer container) { } @Override - protected @NotNull D3DKMTEnumAdaptersStruct create(long address, ByteBuffer container) { + protected @NonNull D3DKMTEnumAdaptersStruct create(long address, ByteBuffer container) { return new D3DKMTEnumAdaptersStruct(address, container); } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTOpenGLInfoStruct.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTOpenGLInfoStruct.java similarity index 94% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTOpenGLInfoStruct.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTOpenGLInfoStruct.java index b7008787d9..7f56aba924 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTOpenGLInfoStruct.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTOpenGLInfoStruct.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.Struct; @@ -44,7 +44,7 @@ private D3DKMTOpenGLInfoStruct(long address, @Nullable ByteBuffer container) { } @Override - protected @NotNull D3DKMTOpenGLInfoStruct create(long address, ByteBuffer container) { + protected @NonNull D3DKMTOpenGLInfoStruct create(long address, ByteBuffer container) { return new D3DKMTOpenGLInfoStruct(address, container); } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoStruct.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoStruct.java similarity index 94% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoStruct.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoStruct.java index f211b8a241..08dfae60d4 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoStruct.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoStruct.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.Pointer; import org.lwjgl.system.Struct; @@ -48,7 +48,7 @@ private D3DKMTQueryAdapterInfoStruct(long address, @Nullable ByteBuffer containe } @Override - protected @NotNull D3DKMTAdapterInfoStruct create(long address, ByteBuffer container) { + protected @NonNull D3DKMTAdapterInfoStruct create(long address, ByteBuffer container) { return new D3DKMTAdapterInfoStruct(address, container); } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoType.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoType.java similarity index 100% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoType.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoType.java diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallback.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallback.java similarity index 100% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallback.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallback.java diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallbackI.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallbackI.java similarity index 90% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallbackI.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallbackI.java index 0994026e18..6b4c1310b7 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallbackI.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallbackI.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.msgbox; -import org.jetbrains.annotations.NotNull; +import org.jspecify.annotations.NonNull; import org.lwjgl.system.CallbackI; import org.lwjgl.system.NativeType; import org.lwjgl.system.libffi.FFICIF; @@ -19,7 +19,7 @@ public interface MsgBoxCallbackI extends CallbackI { ); @Override - default @NotNull FFICIF getCallInterface() { + default @NonNull FFICIF getCallInterface() { return CIF; } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxParamSw.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxParamSw.java similarity index 95% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxParamSw.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxParamSw.java index 5108bb138a..10b4cbebe7 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxParamSw.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxParamSw.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.msgbox; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.Pointer; @@ -62,7 +62,7 @@ private MsgBoxParamSw(long address, @Nullable ByteBuffer container) { } @Override - protected @NotNull MsgBoxParamSw create(long address, ByteBuffer container) { + protected @NonNull MsgBoxParamSw create(long address, ByteBuffer container) { return new MsgBoxParamSw(address, container); } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/LanguageCodePage.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/LanguageCodePage.java similarity index 100% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/LanguageCodePage.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/LanguageCodePage.java diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/QueryResult.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/QueryResult.java similarity index 100% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/QueryResult.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/QueryResult.java diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/Version.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/Version.java similarity index 98% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/Version.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/Version.java index ea7c10b339..aaff46c0bd 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/Version.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/Version.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.version; import net.caffeinemc.mods.sodium.client.platform.windows.api.Kernel32; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import org.lwjgl.PointerBuffer; import org.lwjgl.system.*; diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionFixedFileInfoStruct.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionFixedFileInfoStruct.java similarity index 100% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionFixedFileInfoStruct.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionFixedFileInfoStruct.java diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionInfo.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionInfo.java similarity index 98% rename from common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionInfo.java rename to common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionInfo.java index cdb95f2bda..d9e5d5c55f 100644 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionInfo.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionInfo.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.version; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import org.lwjgl.system.MemoryUtil; import java.io.Closeable; diff --git a/common/src/workarounds/resources/META-INF/MANIFEST.MF b/common/src/boot/resources/META-INF/MANIFEST.MF similarity index 100% rename from common/src/workarounds/resources/META-INF/MANIFEST.MF rename to common/src/boot/resources/META-INF/MANIFEST.MF diff --git a/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/LaunchWarn.java b/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/LaunchWarn.java index 1a612a41aa..71aea09e74 100644 --- a/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/LaunchWarn.java +++ b/common/src/desktop/java/net/caffeinemc/mods/sodium/desktop/LaunchWarn.java @@ -91,7 +91,7 @@ private static void showRichGraphicalDialog(BrowseUrlHandler browseUrlHandler) { } private static void showFallbackGraphicalDialog() { - // Fallback for Linux, etc users with no "default" browser + // Fallback for Linux, etc. users with no "default" browser showDialogBox(FALLBACK_MESSAGE, WINDOW_TITLE, JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null, null); } diff --git a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java b/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java deleted file mode 100644 index 5912c283d2..0000000000 --- a/common/src/workarounds/java/net/caffeinemc/mods/sodium/client/compatibility/checks/PreLaunchChecks.java +++ /dev/null @@ -1,68 +0,0 @@ -package net.caffeinemc.mods.sodium.client.compatibility.checks; - -import net.caffeinemc.mods.sodium.client.platform.PlatformHelper; -import org.lwjgl.Version; - -/** - * Performs OpenGL driver validation before the game creates an OpenGL context. This runs during the earliest possible - * opportunity at game startup, and uses a custom hardware prober to search for problematic drivers. - */ -public class PreLaunchChecks { - // These version constants are inlined at compile time. - private static final String REQUIRED_LWJGL_VERSION = - Version.VERSION_MAJOR + "." + Version.VERSION_MINOR + "." + Version.VERSION_REVISION; - - public static void checkEnvironment() { - if (BugChecks.ISSUE_2561) { - checkLwjglRuntimeVersion(); - } - } - - private static void checkLwjglRuntimeVersion() { - if (isUsingKnownCompatibleLwjglVersion()) { - return; - } - - String advice; - - if (isUsingPrismLauncher()) { - advice = """ - It appears you are using Prism Launcher to start the game. You can \ - likely fix this problem by opening your instance settings and navigating to the Version\ - section in the sidebar."""; - } else { - advice = """ - You must change the LWJGL version in your launcher to continue. \ - This is usually controlled by the settings for a profile or instance in your launcher."""; - } - - String message = """ - The game failed to start because the currently active LWJGL version is not \ - compatible. - - Installed version: ###CURRENT_VERSION### - Required version: ###REQUIRED_VERSION### - - ###ADVICE_STRING###""" - .replace("###CURRENT_VERSION###", Version.getVersion()) - .replace("###REQUIRED_VERSION###", REQUIRED_LWJGL_VERSION) - .replace("###ADVICE_STRING###", advice); - - PlatformHelper.showCriticalErrorAndClose(null, "Sodium Renderer - Unsupported LWJGL", message, - "https://link.caffeinemc.net/help/sodium/runtime-issue/lwjgl3/gh-2561"); - } - - private static boolean isUsingKnownCompatibleLwjglVersion() { - return Version.getVersion() - .startsWith(REQUIRED_LWJGL_VERSION); - } - - private static boolean isUsingPrismLauncher() { - return getLauncherBrand() - .equalsIgnoreCase("PrismLauncher"); - } - - private static String getLauncherBrand() { - return System.getProperty("minecraft.launcher.brand", "unknown"); - } -} diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 58c79218ab..1ff8f5ee9b 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -18,11 +18,11 @@ val configurationCommonModResources: Configuration = configurations.create("comm dependencies { configurationCommonModJava(project(path = ":common", configuration = "commonMainJava")) configurationCommonModJava(project(path = ":common", configuration = "commonApiJava")) - configurationCommonModJava(project(path = ":common", configuration = "commonEarlyLaunchJava")) + configurationCommonModJava(project(path = ":common", configuration = "commonBootJava")) configurationCommonModResources(project(path = ":common", configuration = "commonMainResources")) configurationCommonModResources(project(path = ":common", configuration = "commonApiResources")) - configurationCommonModResources(project(path = ":common", configuration = "commonEarlyLaunchResources")) + configurationCommonModResources(project(path = ":common", configuration = "commonBootResources")) } sourceSets.apply { diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index 77d3309f53..301a840cfe 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -42,11 +42,11 @@ val configurationCommonServiceResources: Configuration = configurations.create(" dependencies { configurationCommonModJava(project(path = ":common", configuration = "commonMainJava")) configurationCommonModJava(project(path = ":common", configuration = "commonApiJava")) - configurationCommonServiceJava(project(path = ":common", configuration = "commonEarlyLaunchJava")) + configurationCommonServiceJava(project(path = ":common", configuration = "commonBootJava")) configurationCommonModResources(project(path = ":common", configuration = "commonMainResources")) configurationCommonModResources(project(path = ":common", configuration = "commonApiResources")) - configurationCommonServiceResources(project(path = ":common", configuration = "commonEarlyLaunchResources")) + configurationCommonServiceResources(project(path = ":common", configuration = "commonBootResources")) fun addEmbeddedFabricModule(dependency: String) { dependencies.implementation(dependency) @@ -124,7 +124,7 @@ neoForge { create("sodium-service") { sourceSet(sourceSets["service"]) - sourceSet(project(":common").sourceSets["workarounds"]) + sourceSet(project(":common").sourceSets["boot"]) } } } From 66367ef0c8605478852b3fdd6f8ec51b46928c3c Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 3 May 2026 02:03:28 +0200 Subject: [PATCH 069/215] Update mixinextras to 0.5.3 --- common/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 85abfcec34..87c81205ed 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -54,8 +54,8 @@ dependencies { } }) - compileOnly("io.github.llamalad7:mixinextras-common:0.3.5") - annotationProcessor("io.github.llamalad7:mixinextras-common:0.3.5") + compileOnly("io.github.llamalad7:mixinextras-common:0.5.3") + annotationProcessor("io.github.llamalad7:mixinextras-common:0.5.3") compileOnly("net.fabricmc:sponge-mixin:0.13.2+mixin.0.8.5") compileOnly("net.fabricmc:fabric-loader:${BuildConfig.FABRIC_LOADER_VERSION}") From 57f75eb0ba085677f036ddc989a8cdc899b441c4 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 3 May 2026 02:03:43 +0200 Subject: [PATCH 070/215] Backport: Implement service loader inversion --- neoforge/build.gradle.kts | 54 ++++++++++++------- .../sodium/service/SodiumWorkarounds.java | 0 ...oforgespi.earlywindow.GraphicsBootstrapper | 0 .../mixin/core/model/quad/BakedQuadMixin.java | 0 .../model/MultiPartBakedModelMixin.java | 0 .../model/WeightedBakedModelMixin.java | 0 .../model/block/ModelBlockRendererMixin.java | 0 .../features/world/biome/BiomeMixin.java | 0 .../AbstractBlockRenderContextMixin.java | 0 .../neoforge/AuxiliaryLightManagerMixin.java | 0 .../neoforge/ChunkRenderTypeSetAccessor.java | 0 .../platform/neoforge/ClientHooksMixin.java | 0 .../platform/neoforge/EntrypointMixin.java | 0 .../platform/neoforge/LevelSliceMixin.java | 0 .../platform/neoforge/ModelDataMixin.java | 0 .../neoforge/ResourcePackLoaderMixin.java | 0 .../neoforge/SimpleBakedModelAccessor.java | 0 .../sodium/neoforge/ForgeMixinOverrides.java | 0 .../neoforge/NeoForgeRuntimeInformation.java | 0 .../mods/sodium/neoforge/SodiumForgeMod.java | 0 .../neoforge/block/NeoForgeBlockAccess.java | 1 - .../neoforge/level/NeoForgeLevelAccess.java | 0 .../level/NeoForgeLevelRenderHooks.java | 0 .../neoforge/model/NeoForgeModelAccess.java | 0 .../neoforge/render/FluidRendererImpl.java | 2 - .../neoforge/render/ForgeColorProviders.java | 1 - .../resources/META-INF/accesstransformer.cfg | 0 .../resources/META-INF/neoforge.mods.toml | 0 ...odium.client.services.FluidRendererFactory | 0 ...sodium.client.services.PlatformBlockAccess | 0 ...sodium.client.services.PlatformLevelAccess | 0 ...m.client.services.PlatformLevelRenderHooks | 0 ...ium.client.services.PlatformMixinOverrides | 0 ...sodium.client.services.PlatformModelAccess | 0 ...client.services.PlatformRuntimeInformation | 0 .../resources/sodium-neoforge.mixins.json | 0 36 files changed, 35 insertions(+), 23 deletions(-) rename neoforge/src/{service => main}/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java (100%) rename neoforge/src/{service => main}/resources/META-INF/services/net.neoforged.neoforgespi.earlywindow.GraphicsBootstrapper (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/mixin/core/model/quad/BakedQuadMixin.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/mixin/features/model/MultiPartBakedModelMixin.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/mixin/features/model/WeightedBakedModelMixin.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/mixin/features/world/biome/BiomeMixin.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AbstractBlockRenderContextMixin.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AuxiliaryLightManagerMixin.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ChunkRenderTypeSetAccessor.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ClientHooksMixin.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/EntrypointMixin.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/LevelSliceMixin.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ModelDataMixin.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ResourcePackLoaderMixin.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/SimpleBakedModelAccessor.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/neoforge/ForgeMixinOverrides.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/neoforge/NeoForgeRuntimeInformation.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/neoforge/SodiumForgeMod.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/neoforge/block/NeoForgeBlockAccess.java (98%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/neoforge/level/NeoForgeLevelAccess.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/neoforge/level/NeoForgeLevelRenderHooks.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/neoforge/model/NeoForgeModelAccess.java (100%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/neoforge/render/FluidRendererImpl.java (99%) rename neoforge/src/{main => mod}/java/net/caffeinemc/mods/sodium/neoforge/render/ForgeColorProviders.java (95%) rename neoforge/src/{main => mod}/resources/META-INF/accesstransformer.cfg (100%) rename neoforge/src/{main => mod}/resources/META-INF/neoforge.mods.toml (100%) rename neoforge/src/{main => mod}/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.FluidRendererFactory (100%) rename neoforge/src/{main => mod}/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformBlockAccess (100%) rename neoforge/src/{main => mod}/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformLevelAccess (100%) rename neoforge/src/{main => mod}/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformLevelRenderHooks (100%) rename neoforge/src/{main => mod}/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformMixinOverrides (100%) rename neoforge/src/{main => mod}/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformModelAccess (100%) rename neoforge/src/{main => mod}/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation (100%) rename neoforge/src/{main => mod}/resources/sodium-neoforge.mixins.json (100%) diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index 301a840cfe..cbaba51297 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -58,41 +58,43 @@ dependencies { addEmbeddedFabricModule("org.sinytra.forgified-fabric-api:fabric-rendering-data-attachment-v1:0.3.48+73761d2e19") addEmbeddedFabricModule("org.sinytra.forgified-fabric-api:fabric-block-view-api-v2:1.0.10+9afaaf8c19") - jarJar(project(":neoforge", "service")) + jarJar(project(":neoforge", "mod")) } -val serviceJar = tasks.create("serviceJar") { - from(configurationCommonServiceJava) - from(configurationCommonServiceResources) +val modJar = tasks.register("modJar") { + from(configurationCommonModJava) + from(configurationCommonModResources) - from(sourceSets["service"].output) + from(sourceSets["mod"].output) from(rootDir.resolve("LICENSE.md")) - manifest.attributes["FMLModType"] = "LIBRARY" + filesMatching(listOf("META-INF/neoforge.mods.toml")) { + expand(mapOf("version" to inputs.properties["version"])) + } - archiveClassifier = "service" + archiveClassifier = "mod" } -val configurationService: Configuration = configurations.create("service") { +val configurationMod: Configuration = configurations.create("mod") { isCanBeConsumed = true isCanBeResolved = true outgoing { - artifact(serviceJar) + artifact(modJar) } } sourceSets { - named("service") { - compileClasspath = sourceSets["main"].compileClasspath - runtimeClasspath = sourceSets["main"].runtimeClasspath - + named("main") { compileClasspath += configurationCommonServiceJava runtimeClasspath += configurationCommonServiceJava } - main { + create("mod") { + compileClasspath = sourceSets["main"].compileClasspath + runtimeClasspath = sourceSets["main"].runtimeClasspath + compileClasspath += configurationCommonModJava runtimeClasspath += configurationCommonModJava } @@ -117,13 +119,13 @@ neoForge { mods { create("sodium") { - sourceSet(sourceSets["main"]) + sourceSet(sourceSets["mod"]) sourceSet(project(":common").sourceSets["main"]) sourceSet(project(":common").sourceSets["api"]) } create("sodium-service") { - sourceSet(sourceSets["service"]) + sourceSet(sourceSets["main"]) sourceSet(project(":common").sourceSets["boot"]) } } @@ -131,12 +133,26 @@ neoForge { tasks { jar { - from(configurationCommonModJava) + from(configurationCommonServiceJava) + manifest.attributes["FMLModType"] = "LIBRARY" + manifest.attributes["Automatic-Module-Name"] = "sodium_service" + destinationDirectory.set(file(rootProject.layout.buildDirectory).resolve("mods")) + + from(sourceSets.getByName("mod").output.resourcesDir!!.resolve("META-INF/neoforge.mods.toml")) { + into("META-INF") + } + + from(project(":common").sourceSets.main.get().output.resourcesDir!!.resolve("sodium-icon.png")) } processResources { - from(configurationCommonModResources) + from(configurationCommonServiceResources) } -} + getByName("processModResources") { + filesMatching(listOf("META-INF/neoforge.mods.toml")) { + expand(mapOf("version" to BuildConfig.createVersionString(rootProject))) + } + } +} diff --git a/neoforge/src/service/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java b/neoforge/src/main/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java similarity index 100% rename from neoforge/src/service/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java rename to neoforge/src/main/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java diff --git a/neoforge/src/service/resources/META-INF/services/net.neoforged.neoforgespi.earlywindow.GraphicsBootstrapper b/neoforge/src/main/resources/META-INF/services/net.neoforged.neoforgespi.earlywindow.GraphicsBootstrapper similarity index 100% rename from neoforge/src/service/resources/META-INF/services/net.neoforged.neoforgespi.earlywindow.GraphicsBootstrapper rename to neoforge/src/main/resources/META-INF/services/net.neoforged.neoforgespi.earlywindow.GraphicsBootstrapper diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/core/model/quad/BakedQuadMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/core/model/quad/BakedQuadMixin.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/core/model/quad/BakedQuadMixin.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/core/model/quad/BakedQuadMixin.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/features/model/MultiPartBakedModelMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/model/MultiPartBakedModelMixin.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/features/model/MultiPartBakedModelMixin.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/model/MultiPartBakedModelMixin.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/features/model/WeightedBakedModelMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/model/WeightedBakedModelMixin.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/features/model/WeightedBakedModelMixin.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/model/WeightedBakedModelMixin.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/features/world/biome/BiomeMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/world/biome/BiomeMixin.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/features/world/biome/BiomeMixin.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/world/biome/BiomeMixin.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AbstractBlockRenderContextMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AbstractBlockRenderContextMixin.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AbstractBlockRenderContextMixin.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AbstractBlockRenderContextMixin.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AuxiliaryLightManagerMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AuxiliaryLightManagerMixin.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AuxiliaryLightManagerMixin.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AuxiliaryLightManagerMixin.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ChunkRenderTypeSetAccessor.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ChunkRenderTypeSetAccessor.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ChunkRenderTypeSetAccessor.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ChunkRenderTypeSetAccessor.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ClientHooksMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ClientHooksMixin.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ClientHooksMixin.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ClientHooksMixin.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/EntrypointMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/EntrypointMixin.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/EntrypointMixin.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/EntrypointMixin.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/LevelSliceMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/LevelSliceMixin.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/LevelSliceMixin.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/LevelSliceMixin.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ModelDataMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ModelDataMixin.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ModelDataMixin.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ModelDataMixin.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ResourcePackLoaderMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ResourcePackLoaderMixin.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ResourcePackLoaderMixin.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/ResourcePackLoaderMixin.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/SimpleBakedModelAccessor.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/SimpleBakedModelAccessor.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/SimpleBakedModelAccessor.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/SimpleBakedModelAccessor.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/ForgeMixinOverrides.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/ForgeMixinOverrides.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/ForgeMixinOverrides.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/ForgeMixinOverrides.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/NeoForgeRuntimeInformation.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/NeoForgeRuntimeInformation.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/NeoForgeRuntimeInformation.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/NeoForgeRuntimeInformation.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/SodiumForgeMod.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/SodiumForgeMod.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/SodiumForgeMod.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/SodiumForgeMod.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/block/NeoForgeBlockAccess.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/block/NeoForgeBlockAccess.java similarity index 98% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/block/NeoForgeBlockAccess.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/block/NeoForgeBlockAccess.java index 793fdf9e84..f857f6ab46 100644 --- a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/block/NeoForgeBlockAccess.java +++ b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/block/NeoForgeBlockAccess.java @@ -6,7 +6,6 @@ import net.caffeinemc.mods.sodium.client.services.PlatformBlockAccess; import net.caffeinemc.mods.sodium.client.services.SodiumModelData; import net.caffeinemc.mods.sodium.client.util.DirectionUtil; -import net.fabricmc.fabric.api.util.TriState; import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.resources.model.BakedModel; diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/level/NeoForgeLevelAccess.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/level/NeoForgeLevelAccess.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/level/NeoForgeLevelAccess.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/level/NeoForgeLevelAccess.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/level/NeoForgeLevelRenderHooks.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/level/NeoForgeLevelRenderHooks.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/level/NeoForgeLevelRenderHooks.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/level/NeoForgeLevelRenderHooks.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/model/NeoForgeModelAccess.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/model/NeoForgeModelAccess.java similarity index 100% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/model/NeoForgeModelAccess.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/model/NeoForgeModelAccess.java diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/render/FluidRendererImpl.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/FluidRendererImpl.java similarity index 99% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/render/FluidRendererImpl.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/FluidRendererImpl.java index 44e64c4540..4efe368348 100644 --- a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/render/FluidRendererImpl.java +++ b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/FluidRendererImpl.java @@ -21,8 +21,6 @@ import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions; import net.neoforged.neoforge.client.textures.FluidSpriteCache; -import java.util.Objects; - public class FluidRendererImpl extends FluidRenderer { // The current default context is set up before invoking FluidRenderHandler#renderFluid and cleared afterwards. private static final ThreadLocal CURRENT_DEFAULT_CONTEXT = ThreadLocal.withInitial(DefaultRenderContext::new); diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/render/ForgeColorProviders.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/ForgeColorProviders.java similarity index 95% rename from neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/render/ForgeColorProviders.java rename to neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/ForgeColorProviders.java index 53e4348db0..7e91918760 100644 --- a/neoforge/src/main/java/net/caffeinemc/mods/sodium/neoforge/render/ForgeColorProviders.java +++ b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/ForgeColorProviders.java @@ -1,6 +1,5 @@ package net.caffeinemc.mods.sodium.neoforge.render; -import net.caffeinemc.mods.sodium.api.util.ColorARGB; import net.caffeinemc.mods.sodium.client.model.color.ColorProvider; import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadView; import net.caffeinemc.mods.sodium.client.world.LevelSlice; diff --git a/neoforge/src/main/resources/META-INF/accesstransformer.cfg b/neoforge/src/mod/resources/META-INF/accesstransformer.cfg similarity index 100% rename from neoforge/src/main/resources/META-INF/accesstransformer.cfg rename to neoforge/src/mod/resources/META-INF/accesstransformer.cfg diff --git a/neoforge/src/main/resources/META-INF/neoforge.mods.toml b/neoforge/src/mod/resources/META-INF/neoforge.mods.toml similarity index 100% rename from neoforge/src/main/resources/META-INF/neoforge.mods.toml rename to neoforge/src/mod/resources/META-INF/neoforge.mods.toml diff --git a/neoforge/src/main/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.FluidRendererFactory b/neoforge/src/mod/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.FluidRendererFactory similarity index 100% rename from neoforge/src/main/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.FluidRendererFactory rename to neoforge/src/mod/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.FluidRendererFactory diff --git a/neoforge/src/main/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformBlockAccess b/neoforge/src/mod/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformBlockAccess similarity index 100% rename from neoforge/src/main/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformBlockAccess rename to neoforge/src/mod/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformBlockAccess diff --git a/neoforge/src/main/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformLevelAccess b/neoforge/src/mod/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformLevelAccess similarity index 100% rename from neoforge/src/main/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformLevelAccess rename to neoforge/src/mod/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformLevelAccess diff --git a/neoforge/src/main/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformLevelRenderHooks b/neoforge/src/mod/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformLevelRenderHooks similarity index 100% rename from neoforge/src/main/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformLevelRenderHooks rename to neoforge/src/mod/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformLevelRenderHooks diff --git a/neoforge/src/main/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformMixinOverrides b/neoforge/src/mod/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformMixinOverrides similarity index 100% rename from neoforge/src/main/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformMixinOverrides rename to neoforge/src/mod/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformMixinOverrides diff --git a/neoforge/src/main/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformModelAccess b/neoforge/src/mod/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformModelAccess similarity index 100% rename from neoforge/src/main/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformModelAccess rename to neoforge/src/mod/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformModelAccess diff --git a/neoforge/src/main/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation b/neoforge/src/mod/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation similarity index 100% rename from neoforge/src/main/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation rename to neoforge/src/mod/resources/META-INF/services/net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation diff --git a/neoforge/src/main/resources/sodium-neoforge.mixins.json b/neoforge/src/mod/resources/sodium-neoforge.mixins.json similarity index 100% rename from neoforge/src/main/resources/sodium-neoforge.mixins.json rename to neoforge/src/mod/resources/sodium-neoforge.mixins.json From 1e542f71f006ab6142178eb43cfe405a750099f1 Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 4 May 2026 14:28:25 +0200 Subject: [PATCH 071/215] Fix loading on neoforge --- .../service/SodiumServiceModLocator.java | 68 +++++++++++++++++++ ...forgespi.locating.IModFileCandidateLocator | 1 + 2 files changed, 69 insertions(+) create mode 100644 neoforge/src/main/java/net/caffeinemc/mods/sodium/service/SodiumServiceModLocator.java create mode 100644 neoforge/src/main/resources/META-INF/services/net.neoforged.neoforgespi.locating.IModFileCandidateLocator diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/service/SodiumServiceModLocator.java b/neoforge/src/main/java/net/caffeinemc/mods/sodium/service/SodiumServiceModLocator.java new file mode 100644 index 0000000000..c03044da92 --- /dev/null +++ b/neoforge/src/main/java/net/caffeinemc/mods/sodium/service/SodiumServiceModLocator.java @@ -0,0 +1,68 @@ +package net.caffeinemc.mods.sodium.service; + +import cpw.mods.jarhandling.JarContents; +import net.neoforged.neoforgespi.ILaunchContext; +import net.neoforged.neoforgespi.locating.IDiscoveryPipeline; +import net.neoforged.neoforgespi.locating.IModFileCandidateLocator; +import net.neoforged.neoforgespi.locating.IncompatibleFileReporting; +import net.neoforged.neoforgespi.locating.ModFileDiscoveryAttributes; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.*; +import java.util.Map; +import java.util.stream.Stream; + +/** + * Mounts the JiJ entries packaged inside the sodium service jar and hands them to FML's mod + * discovery pipeline. Refer to FML #375. + */ +public class SodiumServiceModLocator implements IModFileCandidateLocator { + private static final String JIJ_DIR = "META-INF/jarjar"; + + @Override + public void findCandidates(ILaunchContext context, IDiscoveryPipeline pipeline) { + Path jijDir = locateServiceRoot().resolve(JIJ_DIR); + try (Stream entries = Files.list(jijDir)) { + entries.filter(SodiumServiceModLocator::isJar).forEach(entry -> mountAndAdd(entry, pipeline)); + } catch (IOException e) { + throw new IllegalStateException("Failed to list JiJ entries in " + jijDir, e); + } + } + + @SuppressWarnings("resource") // innerFs ownership transfers to JarContents + private static void mountAndAdd(Path innerJarInsideService, IDiscoveryPipeline pipeline) { + try { + String specific = innerJarInsideService.toAbsolutePath().toUri().getRawSchemeSpecificPart(); + URI jijUri = new URI("jij:" + specific).normalize(); + FileSystem innerFs = FileSystems.newFileSystem(jijUri, Map.of("packagePath", innerJarInsideService)); + JarContents contents = JarContents.of(innerFs.getPath("/")); + pipeline.addJarContent(contents, ModFileDiscoveryAttributes.DEFAULT, IncompatibleFileReporting.WARN_ALWAYS); + } catch (URISyntaxException | IOException e) { + throw new IllegalStateException("Failed to add JiJ entry " + innerJarInsideService.getFileName() + " to mod discovery", e); + } + } + + private static boolean isJar(Path path) { + return path.getFileName().toString().toLowerCase().endsWith(".jar"); + } + + private static Path locateServiceRoot() { + var cs = SodiumServiceModLocator.class.getProtectionDomain().getCodeSource(); + if (cs == null || cs.getLocation() == null) { + throw new IllegalStateException("CodeSource unavailable; cannot resolve sodium service jar."); + } + URI csUri; + try { + csUri = cs.getLocation().toURI(); + } catch (URISyntaxException e) { + throw new IllegalStateException("Could not parse CodeSource location URI " + cs.getLocation(), e); + } + try { + return Paths.get(csUri); + } catch (Exception e) { + throw new IllegalStateException("Could not resolve sodium service jar from CodeSource URI " + csUri, e); + } + } +} diff --git a/neoforge/src/main/resources/META-INF/services/net.neoforged.neoforgespi.locating.IModFileCandidateLocator b/neoforge/src/main/resources/META-INF/services/net.neoforged.neoforgespi.locating.IModFileCandidateLocator new file mode 100644 index 0000000000..8b2c36f004 --- /dev/null +++ b/neoforge/src/main/resources/META-INF/services/net.neoforged.neoforgespi.locating.IModFileCandidateLocator @@ -0,0 +1 @@ +net.caffeinemc.mods.sodium.service.SodiumServiceModLocator From db55b77d178dc00fa69d0582cf2b5dc6593cc6a7 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 3 May 2026 03:03:02 +0200 Subject: [PATCH 072/215] jspecify annotations --- common/build.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index 87c81205ed..b4eeeaeb17 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -60,6 +60,9 @@ dependencies { compileOnly("net.fabricmc:sponge-mixin:0.13.2+mixin.0.8.5") compileOnly("net.fabricmc:fabric-loader:${BuildConfig.FABRIC_LOADER_VERSION}") + // Not shipped by Minecraft 1.21.1 + compileOnly("org.jspecify:jspecify:1.0.0") + fun addDependentFabricModule(name: String) { modCompileOnly(fabricApi.module(name, BuildConfig.FABRIC_API_VERSION)) } From 2c59307cf93d64376cad4f2cba4858d06bdca53e Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 3 May 2026 03:37:22 +0200 Subject: [PATCH 073/215] Fix precision issues in cloud rendering at far distances --- .../sodium/client/render/immediate/CloudRenderer.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java index 9a01b32127..88c1403fd0 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/CloudRenderer.java @@ -90,8 +90,8 @@ public void render(Camera camera, double cloudTime = (ticks + tickDelta) * 0.03F; // Translation of the clouds texture in world-space - float worldX = (float) (cameraPos.x() + cloudTime); - float worldZ = (float) (cameraPos.z() + 0.33D); + double worldX = cameraPos.x() + cloudTime; + double worldZ = cameraPos.z() + 0.33D; // The coordinates of the cloud cell which the camera is within int cellX = Mth.floor(worldX / CLOUD_WIDTH); @@ -126,9 +126,9 @@ public void render(Camera camera, } // Apply world->view transform - final float viewPosX = (worldX - (cellX * CLOUD_WIDTH)); + final float viewPosX = (float) (worldX - (cellX * CLOUD_WIDTH)); final float viewPosY = (float) cameraPos.y() - height; - final float viewPosZ = (worldZ - (cellZ * CLOUD_WIDTH)); + final float viewPosZ = (float) (worldZ - (cellZ * CLOUD_WIDTH)); poseStack.pushPose(); var poseEntry = poseStack.last(); From f1327c0071673082820e694db9d478037b46fe49 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 3 May 2026 03:44:35 +0200 Subject: [PATCH 074/215] Fix class loader rejecting paths that start with / --- .../caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java index 302347fca3..c5814c6322 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java @@ -1,11 +1,11 @@ package net.caffeinemc.mods.sodium.client.gl.shader; +import net.minecraft.resources.ResourceLocation; import org.apache.commons.io.IOUtils; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import net.minecraft.resources.ResourceLocation; public class ShaderLoader { /** @@ -23,7 +23,7 @@ public static GlShader loadShader(ShaderType type, ResourceLocation name, Shader } public static String getShaderSource(ResourceLocation name) { - String path = String.format("/assets/%s/shaders/%s", name.getNamespace(), name.getPath()); + String path = String.format("assets/%s/shaders/%s", name.getNamespace(), name.getPath()); try (InputStream in = ShaderLoader.class.getClassLoader().getResourceAsStream(path)) { if (in == null) { From be1bff69e885d65ba59de3045b32df16de52d39d Mon Sep 17 00:00:00 2001 From: MeeniMc <68366846+MeeniMc@users.noreply.github.com> Date: Thu, 18 Jan 2024 23:52:27 -0500 Subject: [PATCH 075/215] Use Vanilla terminology for biome blend config option (#2267) (e.g., 5x5 is a radius of 2) Signed-off-by: MeeniMc <68366846+MeeniMc@users.noreply.github.com> --- .../mods/sodium/client/gui/SodiumGameOptionPages.java | 2 +- .../gui/options/control/ControlValueFormatter.java | 11 ++++++++++- .../src/main/resources/assets/sodium/lang/en_us.json | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java index 1c0e355aba..681442d769 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java @@ -219,7 +219,7 @@ public static OptionPage quality() { .add(OptionImpl.createBuilder(int.class, vanillaOpts) .setName(Component.translatable("options.biomeBlendRadius")) .setTooltip(Component.translatable("sodium.options.biome_blend.tooltip")) - .setControl(option -> new SliderControl(option, 1, 7, 1, ControlValueFormatter.biomeBlend())) + .setControl(option -> new SliderControl(option, 0, 7, 1, ControlValueFormatter.biomeBlend())) .setBinding((opts, value) -> opts.biomeBlendRadius().set(value), opts -> opts.biomeBlendRadius().get()) .setImpact(OptionImpact.LOW) .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatter.java index c7d8e30507..c53ad046fe 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatter.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatter.java @@ -40,7 +40,16 @@ static ControlValueFormatter brightness() { } static ControlValueFormatter biomeBlend() { - return (v) -> (v == 0) ? Component.translatable("gui.none") : Component.translatable("sodium.options.biome_blend.value", v); + return (v) -> { + if (v < 0 || v > 7) { + return Component.translatable("parsing.int.invalid", v); + } else if (v == 0) { + return Component.translatable("gui.none"); + } else { + int sv = 2 * v + 1; + return Component.translatable("sodium.options.biome_blend.value", sv, sv); + } + }; } Component format(int value); diff --git a/common/src/main/resources/assets/sodium/lang/en_us.json b/common/src/main/resources/assets/sodium/lang/en_us.json index 7651c158c5..3b8856e48a 100644 --- a/common/src/main/resources/assets/sodium/lang/en_us.json +++ b/common/src/main/resources/assets/sodium/lang/en_us.json @@ -25,8 +25,8 @@ "sodium.options.leaves_quality.tooltip": "Controls whether leaves will be rendered as transparent (fancy) or opaque (fast).", "sodium.options.particle_quality.tooltip": "Controls the maximum number of particles which can be present on screen at any one time.", "sodium.options.smooth_lighting.tooltip": "Enables the smooth lighting and shading of blocks in the world. This can very slightly increase the amount of time it takes to load or update a chunk, but it doesn't affect frame rates.", - "sodium.options.biome_blend.value": "%s block(s)", - "sodium.options.biome_blend.tooltip": "The distance (in blocks) which biome colors are smoothly blended across. Using higher values will greatly increase the amount of time it takes to load or update chunks, for diminishing improvements in quality.", + "sodium.options.biome_blend.value": "%sx%s blocks", + "sodium.options.biome_blend.tooltip": "The area (in blocks) where biome colors are smoothly blended across. Default is 5x5. Higher values will greatly increase the amount of time it takes to load or update chunks, for diminishing improvements in quality.", "sodium.options.entity_distance.tooltip": "The render distance multiplier used by entity rendering. Smaller values decrease, and larger values increase, the maximum distance at which entities will be rendered.", "sodium.options.entity_shadows.tooltip": "If enabled, basic shadows will be rendered beneath mobs and other entities.", "sodium.options.vignette.name": "Vignette", From 8ba8548f3198e6379fec7c454602d89894c28321 Mon Sep 17 00:00:00 2001 From: haykam821 <24855774+haykam821@users.noreply.github.com> Date: Sat, 3 Feb 2024 00:50:25 -0500 Subject: [PATCH 076/215] Fix cycling controls not playing a sound when activated using the keyboard Fixes #2309 --- .../sodium/client/gui/options/control/CyclingControl.java | 6 +++--- .../sodium/client/gui/options/control/TickBoxControl.java | 7 ++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java index 7c1ce635d5..b39278e384 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java @@ -100,8 +100,6 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { public boolean mouseClicked(double mouseX, double mouseY, int button) { if (this.option.isAvailable() && button == 0 && this.dim.containsCursor(mouseX, mouseY)) { cycleControl(Screen.hasShiftDown()); - this.playClickSound(); - return true; } @@ -120,7 +118,9 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { return false; } - public void cycleControl(boolean reverse) { + private void cycleControl(boolean reverse) { + this.playClickSound(); + if (reverse) { this.currentIndex = (this.currentIndex + this.allowedValues.length - 1) % this.allowedValues.length; } else { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java index a77748105d..378f2789b1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java @@ -68,8 +68,6 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { public boolean mouseClicked(double mouseX, double mouseY, int button) { if (this.option.isAvailable() && button == 0 && this.dim.containsCursor(mouseX, mouseY)) { toggleControl(); - this.playClickSound(); - return true; } @@ -82,15 +80,14 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { if (CommonInputs.selected(keyCode)) { toggleControl(); - this.playClickSound(); - return true; } return false; } - public void toggleControl() { + private void toggleControl() { + this.playClickSound(); this.option.setValue(!this.option.getValue()); } } From dcb4721a1ce7fbd6c900c08d3d222bc315491072 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 3 May 2026 03:56:14 +0200 Subject: [PATCH 077/215] Update render code for chunk status map --- .../gui/screen/LevelLoadingScreenMixin.java | 67 +++++++++---------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/screen/LevelLoadingScreenMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/screen/LevelLoadingScreenMixin.java index 6f649536a0..0e09b202d9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/screen/LevelLoadingScreenMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/screen/LevelLoadingScreenMixin.java @@ -1,16 +1,14 @@ package net.caffeinemc.mods.sodium.mixin.features.gui.screen; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.*; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; -import net.caffeinemc.mods.sodium.api.vertex.format.common.ColorVertex; -import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; import net.caffeinemc.mods.sodium.api.util.ColorABGR; import net.caffeinemc.mods.sodium.api.util.ColorARGB; +import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; +import net.caffeinemc.mods.sodium.api.vertex.format.common.ColorVertex; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.LevelLoadingScreen; -import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.RenderType; import net.minecraft.server.level.progress.StoringChunkProgressListener; import net.minecraft.world.level.chunk.status.ChunkStatus; import org.joml.Matrix4f; @@ -47,7 +45,26 @@ public class LevelLoadingScreenMixin { * @author JellySquid */ @Overwrite - public static void renderChunks(GuiGraphics graphics, StoringChunkProgressListener tracker, int mapX, int mapY, int mapScale, int mapPadding) { + public static void renderChunks(GuiGraphics graphics, StoringChunkProgressListener listener, int mapX, int mapY, int mapScale, int mapPadding) { + Matrix4f pose = graphics.pose() + .last() + .pose(); + + // this is just missing the drawSpecial that doesn't exist on 1.21.1 but it works with bufferSource() + var writer = VertexBufferWriter.of(graphics.bufferSource().getBuffer(RenderType.gui())); + + sodium$drawChunkMap(listener, mapX, mapY, mapScale, mapPadding, writer, pose); + } + + @Unique + private static void sodium$drawChunkMap(StoringChunkProgressListener listener, + int mapX, + int mapY, + int mapScale, + int mapPadding, + VertexBufferWriter writer, + Matrix4f pose) + { if (STATUS_TO_COLOR_FAST == null) { STATUS_TO_COLOR_FAST = new Reference2IntOpenHashMap<>(COLORS.size()); STATUS_TO_COLOR_FAST.put(null, NULL_STATUS_COLOR); @@ -55,21 +72,8 @@ public static void renderChunks(GuiGraphics graphics, StoringChunkProgressListen .forEach(entry -> STATUS_TO_COLOR_FAST.put(entry.getKey(), ColorARGB.toABGR(entry.getIntValue(), 0xFF))); } - RenderSystem.setShader(GameRenderer::getPositionColorShader); - - Matrix4f matrix = graphics.pose().last().pose(); - - Tesselator tessellator = Tesselator.getInstance(); - - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - - BufferBuilder bufferBuilder = tessellator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR); - - var writer = VertexBufferWriter.of(bufferBuilder); - - int centerSize = tracker.getFullDiameter(); - int size = tracker.getDiameter(); + int centerSize = listener.getFullDiameter(); + int size = listener.getDiameter(); int tileSize = mapScale + mapPadding; @@ -77,10 +81,10 @@ public static void renderChunks(GuiGraphics graphics, StoringChunkProgressListen int mapRenderCenterSize = centerSize * tileSize - mapPadding; int radius = mapRenderCenterSize / 2 + 1; - addRect(writer, matrix, mapX - radius, mapY - radius, mapX - radius + 1, mapY + radius, DEFAULT_STATUS_COLOR); - addRect(writer, matrix, mapX + radius - 1, mapY - radius, mapX + radius, mapY + radius, DEFAULT_STATUS_COLOR); - addRect(writer, matrix, mapX - radius, mapY - radius, mapX + radius, mapY - radius + 1, DEFAULT_STATUS_COLOR); - addRect(writer, matrix, mapX - radius, mapY + radius - 1, mapX + radius, mapY + radius, DEFAULT_STATUS_COLOR); + addRect(writer, pose, mapX - radius, mapY - radius, mapX - radius + 1, mapY + radius, DEFAULT_STATUS_COLOR); + addRect(writer, pose, mapX + radius - 1, mapY - radius, mapX + radius, mapY + radius, DEFAULT_STATUS_COLOR); + addRect(writer, pose, mapX - radius, mapY - radius, mapX + radius, mapY - radius + 1, DEFAULT_STATUS_COLOR); + addRect(writer, pose, mapX - radius, mapY + radius - 1, mapX + radius, mapY + radius, DEFAULT_STATUS_COLOR); } int mapRenderSize = size * tileSize - mapPadding; @@ -96,7 +100,7 @@ public static void renderChunks(GuiGraphics graphics, StoringChunkProgressListen for (int z = 0; z < size; ++z) { int tileY = mapStartY + z * tileSize; - ChunkStatus status = tracker.getStatus(x, z); + ChunkStatus status = listener.getStatus(x, z); int color; if (prevStatus == status) { @@ -108,18 +112,9 @@ public static void renderChunks(GuiGraphics graphics, StoringChunkProgressListen prevColor = color; } - addRect(writer, matrix, tileX, tileY, tileX + mapScale, tileY + mapScale, color); + addRect(writer, pose, tileX, tileY, tileX + mapScale, tileY + mapScale, color); } } - - MeshData data = bufferBuilder.build(); - - if (data != null) { - BufferUploader.drawWithShader(data); - } - Tesselator.getInstance().clear(); - - RenderSystem.disableBlend(); } @Unique From 0af14bf83ce9b3b788d490f9c0966f9a2fe92f13 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Wed, 1 Jan 2025 20:54:24 -0600 Subject: [PATCH 078/215] Aggressively optimize entity rendering This is anywhere from 10 to 15% faster depending on what entities are being rendered. Most of the improvements come from more efficiently laying out the cuboid data and coalescing neighboring 32-bit values into 64-bit words. Furthermore, vertex positions are calculated by extracting vectors from the pose matrix and adding them to the origin vertex, which avoids many matrix multiplications. Co-authored-by: MoePus <547007249@qq.com> (cherry picked from commit 7f25220d810cd002a3a72b8b0a1e881d21855ca9) --- .../attributes/common/ColorAttribute.java | 2 +- .../attributes/common/NormalAttribute.java | 2 +- .../attributes/common/PositionAttribute.java | 12 +- .../immediate/model/EntityRenderer.java | 230 ++++++++---------- .../render/immediate/model/ModelCuboid.java | 158 +++++++++--- .../mods/sodium/client/util/Int2.java | 13 + 6 files changed, 250 insertions(+), 167 deletions(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/util/Int2.java diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/attributes/common/ColorAttribute.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/attributes/common/ColorAttribute.java index 0f484805a4..16373c6373 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/attributes/common/ColorAttribute.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/attributes/common/ColorAttribute.java @@ -4,7 +4,7 @@ public class ColorAttribute { public static void set(long ptr, int color) { - MemoryUtil.memPutInt(ptr + 0, color); + MemoryUtil.memPutInt(ptr, color); } public static int get(long ptr) { diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/attributes/common/NormalAttribute.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/attributes/common/NormalAttribute.java index 4f58ce9436..dc15bb9359 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/attributes/common/NormalAttribute.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/attributes/common/NormalAttribute.java @@ -4,7 +4,7 @@ public class NormalAttribute { public static void set(long ptr, int normal) { - MemoryUtil.memPutInt(ptr + 0, normal); + MemoryUtil.memPutInt(ptr, normal); } public static int get(long ptr) { diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/attributes/common/PositionAttribute.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/attributes/common/PositionAttribute.java index 9a597597b8..0aafec17ad 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/attributes/common/PositionAttribute.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/attributes/common/PositionAttribute.java @@ -4,20 +4,20 @@ public class PositionAttribute { public static void put(long ptr, float x, float y, float z) { - MemoryUtil.memPutFloat(ptr + 0, x); - MemoryUtil.memPutFloat(ptr + 4, y); - MemoryUtil.memPutFloat(ptr + 8, z); + MemoryUtil.memPutFloat(ptr + 0L, x); + MemoryUtil.memPutFloat(ptr + 4L, y); + MemoryUtil.memPutFloat(ptr + 8L, z); } public static float getX(long ptr) { - return MemoryUtil.memGetFloat(ptr + 0); + return MemoryUtil.memGetFloat(ptr + 0L); } public static float getY(long ptr) { - return MemoryUtil.memGetFloat(ptr + 4); + return MemoryUtil.memGetFloat(ptr + 4L); } public static float getZ(long ptr) { - return MemoryUtil.memGetFloat(ptr + 8); + return MemoryUtil.memGetFloat(ptr + 8L); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/EntityRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/EntityRenderer.java index fd7743dac3..2e7ed9076e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/EntityRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/EntityRenderer.java @@ -4,80 +4,31 @@ import net.caffeinemc.mods.sodium.api.math.MatrixHelper; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; import net.caffeinemc.mods.sodium.api.vertex.format.common.EntityVertex; +import net.caffeinemc.mods.sodium.client.util.Int2; import net.minecraft.core.Direction; import org.joml.Matrix3f; -import org.joml.Matrix4f; -import org.joml.Vector2f; -import org.joml.Vector3f; import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; import static net.caffeinemc.mods.sodium.client.render.immediate.model.ModelCuboid.*; public class EntityRenderer { - private static final int NUM_CUBE_VERTICES = 8; - private static final int NUM_CUBE_FACES = 6; - private static final int NUM_FACE_VERTICES = 4; - - private static final int - VERTEX_X1_Y1_Z1 = 0, - VERTEX_X2_Y1_Z1 = 1, - VERTEX_X2_Y2_Z1 = 2, - VERTEX_X1_Y2_Z1 = 3, - VERTEX_X1_Y1_Z2 = 4, - VERTEX_X2_Y1_Z2 = 5, - VERTEX_X2_Y2_Z2 = 6, - VERTEX_X1_Y2_Z2 = 7; - - private static final Matrix3f lastMatrix = new Matrix3f(); + private static final Matrix3f prevNormalMatrix = new Matrix3f(); private static final int VERTEX_BUFFER_BYTES = NUM_CUBE_FACES * NUM_FACE_VERTICES * EntityVertex.STRIDE; - private static final Vector3f[] CUBE_CORNERS = new Vector3f[NUM_CUBE_VERTICES]; - private static final int[][] CUBE_VERTICES = new int[NUM_CUBE_FACES][]; + private static final long[] CUBE_VERTEX_XY = new long[NUM_CUBE_VERTICES]; // (pos.x, pos.y) + private static final long[] CUBE_VERTEX_ZW = new long[NUM_CUBE_VERTICES]; // (pos.z, color) - private static final Vector3f[][] VERTEX_POSITIONS = new Vector3f[NUM_CUBE_FACES][NUM_FACE_VERTICES]; - private static final Vector3f[][] VERTEX_POSITIONS_MIRRORED = new Vector3f[NUM_CUBE_FACES][NUM_FACE_VERTICES]; - - private static final Vector2f[][] VERTEX_TEXTURES = new Vector2f[NUM_CUBE_FACES][NUM_FACE_VERTICES]; - private static final Vector2f[][] VERTEX_TEXTURES_MIRRORED = new Vector2f[NUM_CUBE_FACES][NUM_FACE_VERTICES]; - - private static final int[] CUBE_NORMALS = new int[NUM_CUBE_FACES]; - private static final int[] CUBE_NORMALS_MIRRORED = new int[NUM_CUBE_FACES]; - - static { - CUBE_VERTICES[FACE_NEG_Y] = new int[] { VERTEX_X2_Y1_Z2, VERTEX_X1_Y1_Z2, VERTEX_X1_Y1_Z1, VERTEX_X2_Y1_Z1 }; - CUBE_VERTICES[FACE_POS_Y] = new int[] { VERTEX_X2_Y2_Z1, VERTEX_X1_Y2_Z1, VERTEX_X1_Y2_Z2, VERTEX_X2_Y2_Z2 }; - CUBE_VERTICES[FACE_NEG_Z] = new int[] { VERTEX_X2_Y1_Z1, VERTEX_X1_Y1_Z1, VERTEX_X1_Y2_Z1, VERTEX_X2_Y2_Z1 }; - CUBE_VERTICES[FACE_POS_Z] = new int[] { VERTEX_X1_Y1_Z2, VERTEX_X2_Y1_Z2, VERTEX_X2_Y2_Z2, VERTEX_X1_Y2_Z2 }; - CUBE_VERTICES[FACE_NEG_X] = new int[] { VERTEX_X2_Y1_Z2, VERTEX_X2_Y1_Z1, VERTEX_X2_Y2_Z1, VERTEX_X2_Y2_Z2 }; - CUBE_VERTICES[FACE_POS_X] = new int[] { VERTEX_X1_Y1_Z1, VERTEX_X1_Y1_Z2, VERTEX_X1_Y2_Z2, VERTEX_X1_Y2_Z1 }; - - for (int cornerIndex = 0; cornerIndex < NUM_CUBE_VERTICES; cornerIndex++) { - CUBE_CORNERS[cornerIndex] = new Vector3f(); - } - - for (int quadIndex = 0; quadIndex < NUM_CUBE_FACES; quadIndex++) { - for (int vertexIndex = 0; vertexIndex < NUM_FACE_VERTICES; vertexIndex++) { - VERTEX_TEXTURES[quadIndex][vertexIndex] = new Vector2f(); - VERTEX_POSITIONS[quadIndex][vertexIndex] = CUBE_CORNERS[CUBE_VERTICES[quadIndex][vertexIndex]]; - } - } - - for (int quadIndex = 0; quadIndex < NUM_CUBE_FACES; quadIndex++) { - for (int vertexIndex = 0; vertexIndex < NUM_FACE_VERTICES; vertexIndex++) { - VERTEX_TEXTURES_MIRRORED[quadIndex][vertexIndex] = VERTEX_TEXTURES[quadIndex][3 - vertexIndex]; - VERTEX_POSITIONS_MIRRORED[quadIndex][vertexIndex] = VERTEX_POSITIONS[quadIndex][3 - vertexIndex]; - } - } - } + private static final int[] CUBE_FACE_NORMAL = new int[NUM_CUBE_FACES]; public static void renderCuboid(PoseStack.Pose matrices, VertexBufferWriter writer, ModelCuboid cuboid, int light, int overlay, int color) { + prepareVertices(matrices, cuboid, color); prepareNormalsIfChanged(matrices); - prepareVertices(matrices, cuboid); try (MemoryStack stack = MemoryStack.stackPush()) { - final var vertexBuffer = stack.nmalloc(16, VERTEX_BUFFER_BYTES); - final var vertexCount = emitQuads(vertexBuffer, cuboid, color, overlay, light); + final var vertexBuffer = stack.nmalloc(64, VERTEX_BUFFER_BYTES); + final var vertexCount = emitQuads(vertexBuffer, cuboid, overlay, light); if (vertexCount > 0) { writer.push(stack, vertexBuffer, vertexCount, EntityVertex.FORMAT); @@ -85,31 +36,30 @@ public static void renderCuboid(PoseStack.Pose matrices, VertexBufferWriter writ } } - private static int emitQuads(final long buffer, ModelCuboid cuboid, int color, int overlay, int light) { - final var positions = cuboid.mirror ? VERTEX_POSITIONS_MIRRORED : VERTEX_POSITIONS; - final var textures = cuboid.mirror ? VERTEX_TEXTURES_MIRRORED : VERTEX_TEXTURES; - final var normals = cuboid.mirror ? CUBE_NORMALS_MIRRORED : CUBE_NORMALS; - - var vertexCount = 0; + private static int emitQuads(final long buffer, ModelCuboid cuboid, int overlay, int light) { + // Pack the Overlay and Light coordinates into a 64-bit integer as they are next to each other + // in the vertex format. This eliminates another 32-bit memory write in the hot path. + final long packedOverlayLight = Int2.pack(overlay, light); long ptr = buffer; - for (int quadIndex = 0; quadIndex < NUM_CUBE_FACES; quadIndex++) { - if (!cuboid.shouldDrawFace(quadIndex)) { - continue; - } - - emitVertex(ptr, positions[quadIndex][0], color, textures[quadIndex][0], overlay, light, normals[quadIndex]); - ptr += EntityVertex.STRIDE; - - emitVertex(ptr, positions[quadIndex][1], color, textures[quadIndex][1], overlay, light, normals[quadIndex]); - ptr += EntityVertex.STRIDE; + final int[] normals = cuboid.normals; + final int[] positions = cuboid.positions; + final long[] textures = cuboid.textures; - emitVertex(ptr, positions[quadIndex][2], color, textures[quadIndex][2], overlay, light, normals[quadIndex]); - ptr += EntityVertex.STRIDE; + int vertexCount = 0; - emitVertex(ptr, positions[quadIndex][3], color, textures[quadIndex][3], overlay, light, normals[quadIndex]); - ptr += EntityVertex.STRIDE; + for (int faceIndex = 0; faceIndex < NUM_CUBE_FACES; faceIndex++) { + if (!cuboid.shouldDrawFace(faceIndex)) { + continue; + } + + final int elementOffset = faceIndex * NUM_FACE_VERTICES; + final int packedNormal = CUBE_FACE_NORMAL[normals[faceIndex]]; + ptr = writeVertex(ptr, positions[elementOffset + 0], textures[elementOffset + 0], packedOverlayLight, packedNormal); + ptr = writeVertex(ptr, positions[elementOffset + 1], textures[elementOffset + 1], packedOverlayLight, packedNormal); + ptr = writeVertex(ptr, positions[elementOffset + 2], textures[elementOffset + 2], packedOverlayLight, packedNormal); + ptr = writeVertex(ptr, positions[elementOffset + 3], textures[elementOffset + 3], packedOverlayLight, packedNormal); vertexCount += 4; } @@ -117,59 +67,89 @@ private static int emitQuads(final long buffer, ModelCuboid cuboid, int color, i return vertexCount; } - private static void emitVertex(long ptr, Vector3f pos, int color, Vector2f tex, int overlay, int light, int normal) { - EntityVertex.write(ptr, pos.x, pos.y, pos.z, color, tex.x, tex.y, overlay, light, normal); - } + private static long writeVertex(long ptr, int vertexIndex, long packedUv, long packedOverlayLight, int packedNormal) { + MemoryUtil.memPutLong(ptr + 0L, CUBE_VERTEX_XY[vertexIndex]); + MemoryUtil.memPutLong(ptr + 8L, CUBE_VERTEX_ZW[vertexIndex]); // overlaps with color attribute + MemoryUtil.memPutLong(ptr + 16L, packedUv); + MemoryUtil.memPutLong(ptr + 24L, packedOverlayLight); + MemoryUtil.memPutInt(ptr + 32L, packedNormal); - private static void prepareVertices(PoseStack.Pose matrices, ModelCuboid cuboid) { - buildVertexPosition(CUBE_CORNERS[VERTEX_X1_Y1_Z1], cuboid.x1, cuboid.y1, cuboid.z1, matrices.pose()); - buildVertexPosition(CUBE_CORNERS[VERTEX_X2_Y1_Z1], cuboid.x2, cuboid.y1, cuboid.z1, matrices.pose()); - buildVertexPosition(CUBE_CORNERS[VERTEX_X2_Y2_Z1], cuboid.x2, cuboid.y2, cuboid.z1, matrices.pose()); - buildVertexPosition(CUBE_CORNERS[VERTEX_X1_Y2_Z1], cuboid.x1, cuboid.y2, cuboid.z1, matrices.pose()); - buildVertexPosition(CUBE_CORNERS[VERTEX_X1_Y1_Z2], cuboid.x1, cuboid.y1, cuboid.z2, matrices.pose()); - buildVertexPosition(CUBE_CORNERS[VERTEX_X2_Y1_Z2], cuboid.x2, cuboid.y1, cuboid.z2, matrices.pose()); - buildVertexPosition(CUBE_CORNERS[VERTEX_X2_Y2_Z2], cuboid.x2, cuboid.y2, cuboid.z2, matrices.pose()); - buildVertexPosition(CUBE_CORNERS[VERTEX_X1_Y2_Z2], cuboid.x1, cuboid.y2, cuboid.z2, matrices.pose()); - - buildVertexTexCoord(VERTEX_TEXTURES[FACE_NEG_Y], cuboid.u1, cuboid.v0, cuboid.u2, cuboid.v1); - buildVertexTexCoord(VERTEX_TEXTURES[FACE_POS_Y], cuboid.u2, cuboid.v1, cuboid.u3, cuboid.v0); - buildVertexTexCoord(VERTEX_TEXTURES[FACE_NEG_Z], cuboid.u1, cuboid.v1, cuboid.u2, cuboid.v2); - buildVertexTexCoord(VERTEX_TEXTURES[FACE_POS_Z], cuboid.u4, cuboid.v1, cuboid.u5, cuboid.v2); - buildVertexTexCoord(VERTEX_TEXTURES[FACE_NEG_X], cuboid.u2, cuboid.v1, cuboid.u4, cuboid.v2); - buildVertexTexCoord(VERTEX_TEXTURES[FACE_POS_X], cuboid.u0, cuboid.v1, cuboid.u1, cuboid.v2); + return ptr + EntityVertex.STRIDE; } - public static void prepareNormalsIfChanged(PoseStack.Pose matrices) { - if (!matrices.normal().equals(lastMatrix)) { - lastMatrix.set(matrices.normal()); - - CUBE_NORMALS[FACE_NEG_Y] = MatrixHelper.transformNormal(matrices.normal(), matrices.trustedNormals, Direction.DOWN); - CUBE_NORMALS[FACE_POS_Y] = MatrixHelper.transformNormal(matrices.normal(), matrices.trustedNormals, Direction.UP); - CUBE_NORMALS[FACE_NEG_Z] = MatrixHelper.transformNormal(matrices.normal(), matrices.trustedNormals, Direction.NORTH); - CUBE_NORMALS[FACE_POS_Z] = MatrixHelper.transformNormal(matrices.normal(), matrices.trustedNormals, Direction.SOUTH); - CUBE_NORMALS[FACE_POS_X] = MatrixHelper.transformNormal(matrices.normal(), matrices.trustedNormals, Direction.WEST); - CUBE_NORMALS[FACE_NEG_X] = MatrixHelper.transformNormal(matrices.normal(), matrices.trustedNormals, Direction.EAST); - - // When mirroring is used, the normals for EAST and WEST are swapped. - CUBE_NORMALS_MIRRORED[FACE_NEG_Y] = CUBE_NORMALS[FACE_NEG_Y]; - CUBE_NORMALS_MIRRORED[FACE_POS_Y] = CUBE_NORMALS[FACE_POS_Y]; - CUBE_NORMALS_MIRRORED[FACE_NEG_Z] = CUBE_NORMALS[FACE_NEG_Z]; - CUBE_NORMALS_MIRRORED[FACE_POS_Z] = CUBE_NORMALS[FACE_POS_Z]; - CUBE_NORMALS_MIRRORED[FACE_POS_X] = CUBE_NORMALS[FACE_NEG_X]; // mirrored - CUBE_NORMALS_MIRRORED[FACE_NEG_X] = CUBE_NORMALS[FACE_POS_X]; // mirrored - } + private static void prepareVertices(PoseStack.Pose matrices, ModelCuboid cuboid, int color) { + var pose = matrices.pose(); + + float vxx = (pose.m00() * cuboid.sizeX), vxy = (pose.m01() * cuboid.sizeX), vxz = (pose.m02() * cuboid.sizeX); + float vyx = (pose.m10() * cuboid.sizeY), vyy = (pose.m11() * cuboid.sizeY), vyz = (pose.m12() * cuboid.sizeY); + float vzx = (pose.m20() * cuboid.sizeZ), vzy = (pose.m21() * cuboid.sizeZ), vzz = (pose.m22() * cuboid.sizeZ); + + // Compute the transformed origin point of the cuboid + float c000x = MatrixHelper.transformPositionX(pose, cuboid.originX, cuboid.originY, cuboid.originZ); + float c000y = MatrixHelper.transformPositionY(pose, cuboid.originX, cuboid.originY, cuboid.originZ); + float c000z = MatrixHelper.transformPositionZ(pose, cuboid.originX, cuboid.originY, cuboid.originZ); + setVertex(VERTEX_X0_Y0_Z0, c000x, c000y, c000z, color); + + // Add the pre-multiplied vectors to find the other 7 vertices + // This avoids needing to multiply each vertex position against the pose matrix, which eliminates many + // floating-point operations (going from 21 flops/vert to 3 flops/vert). + // Originally suggested by MoePus on GitHub in this pull request: + // https://github.com/CaffeineMC/sodium/pull/2960 + float c100x = c000x + vxx; + float c100y = c000y + vxy; + float c100z = c000z + vxz; + setVertex(VERTEX_X1_Y0_Z0, c100x, c100y, c100z, color); + + float c110x = c100x + vyx; + float c110y = c100y + vyy; + float c110z = c100z + vyz; + setVertex(VERTEX_X1_Y1_Z0, c110x, c110y, c110z, color); + + float c010x = c000x + vyx; + float c010y = c000y + vyy; + float c010z = c000z + vyz; + setVertex(VERTEX_X0_Y1_Z0, c010x, c010y, c010z, color); + + float c001x = c000x + vzx; + float c001y = c000y + vzy; + float c001z = c000z + vzz; + setVertex(VERTEX_X0_Y0_Z1, c001x, c001y, c001z, color); + + float c101x = c100x + vzx; + float c101y = c100y + vzy; + float c101z = c100z + vzz; + setVertex(VERTEX_X1_Y0_Z1, c101x, c101y, c101z, color); + + float c111x = c110x + vzx; + float c111y = c110y + vzy; + float c111z = c110z + vzz; + setVertex(VERTEX_X1_Y1_Z1, c111x, c111y, c111z, color); + + float c011x = c010x + vzx; + float c011y = c010y + vzy; + float c011z = c010z + vzz; + setVertex(VERTEX_X0_Y1_Z1, c011x, c011y, c011z, color); } - private static void buildVertexPosition(Vector3f vector, float x, float y, float z, Matrix4f matrix) { - vector.x = MatrixHelper.transformPositionX(matrix, x, y, z); - vector.y = MatrixHelper.transformPositionY(matrix, x, y, z); - vector.z = MatrixHelper.transformPositionZ(matrix, x, y, z); + private static void setVertex(int vertexIndex, float x, float y, float z, int color) { + // Since we have a spare element, pack the color into it. This makes the code a little obtuse, + // but it avoids another 32-bit memory write in the hot path, which helps a lot. + CUBE_VERTEX_XY[vertexIndex] = Int2.pack(Float.floatToRawIntBits(x), Float.floatToRawIntBits(y)); + CUBE_VERTEX_ZW[vertexIndex] = Int2.pack(Float.floatToRawIntBits(z), color); } - private static void buildVertexTexCoord(Vector2f[] uvs, float u1, float v1, float u2, float v2) { - uvs[0].set(u2, v1); - uvs[1].set(u1, v1); - uvs[2].set(u1, v2); - uvs[3].set(u2, v2); + private static void prepareNormalsIfChanged(PoseStack.Pose matrices) { + if (matrices.normal().equals(prevNormalMatrix)) { + return; + } + + CUBE_FACE_NORMAL[FACE_NEG_Y] = MatrixHelper.transformNormal(matrices.normal(), matrices.trustedNormals, Direction.DOWN); + CUBE_FACE_NORMAL[FACE_POS_Y] = MatrixHelper.transformNormal(matrices.normal(), matrices.trustedNormals, Direction.UP); + CUBE_FACE_NORMAL[FACE_NEG_Z] = MatrixHelper.transformNormal(matrices.normal(), matrices.trustedNormals, Direction.NORTH); + CUBE_FACE_NORMAL[FACE_POS_Z] = MatrixHelper.transformNormal(matrices.normal(), matrices.trustedNormals, Direction.SOUTH); + CUBE_FACE_NORMAL[FACE_POS_X] = MatrixHelper.transformNormal(matrices.normal(), matrices.trustedNormals, Direction.WEST); + CUBE_FACE_NORMAL[FACE_NEG_X] = MatrixHelper.transformNormal(matrices.normal(), matrices.trustedNormals, Direction.EAST); + + prevNormalMatrix.set(matrices.normal()); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/ModelCuboid.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/ModelCuboid.java index 4a13b857ca..4309d8bb8e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/ModelCuboid.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/ModelCuboid.java @@ -1,10 +1,27 @@ package net.caffeinemc.mods.sodium.client.render.immediate.model; import java.util.Set; + +import net.caffeinemc.mods.sodium.client.util.Int2; import net.minecraft.core.Direction; +import org.apache.commons.lang3.ArrayUtils; import org.jetbrains.annotations.NotNull; public class ModelCuboid { + public static final int NUM_CUBE_VERTICES = 8; + public static final int NUM_CUBE_FACES = 6; + public static final int NUM_FACE_VERTICES = 4; + + public static final int + VERTEX_X0_Y0_Z0 = 0, + VERTEX_X1_Y0_Z0 = 1, + VERTEX_X1_Y1_Z0 = 2, + VERTEX_X0_Y1_Z0 = 3, + VERTEX_X0_Y0_Z1 = 4, + VERTEX_X1_Y0_Z1 = 5, + VERTEX_X1_Y1_Z1 = 6, + VERTEX_X0_Y1_Z1 = 7; + // The ordering needs to be the same as Minecraft, otherwise some core shader replacements // will be unable to identify the facing. public static final int @@ -15,15 +32,18 @@ public class ModelCuboid { FACE_POS_X = 4, // EAST FACE_POS_Z = 5; // SOUTH - public final float x1, y1, z1; - public final float x2, y2, z2; + public final float originX, originY, originZ; + public final float sizeX, sizeY, sizeZ; - public final float u0, u1, u2, u3, u4, u5; - public final float v0, v1, v2; + // Bit-mask of visible faces + private final int cullMask; - private final int cullBitmask; + // Per-face attributes + public final int[] normals; - public final boolean mirror; + // Per-vertex attributes + public final int[] positions; + public final long[] textures; // UVs are packed into 64-bit integers to reduce the number of memory loads public ModelCuboid(int u, int v, float x1, float y1, float z1, @@ -50,51 +70,121 @@ public ModelCuboid(int u, int v, x1 = tmp; } - this.x1 = x1 / 16.0f; - this.y1 = y1 / 16.0f; - this.z1 = z1 / 16.0f; + x1 /= 16.0f; + y1 /= 16.0f; + z1 /= 16.0f; - this.x2 = x2 / 16.0f; - this.y2 = y2 / 16.0f; - this.z2 = z2 / 16.0f; + x2 /= 16.0f; + y2 /= 16.0f; + z2 /= 16.0f; + + this.originX = x1; + this.originY = y1; + this.originZ = z1; + + this.sizeX = x2 - x1; + this.sizeY = y2 - y1; + this.sizeZ = z2 - z1; var scaleU = 1.0f / textureWidth; var scaleV = 1.0f / textureHeight; - this.u0 = scaleU * (u); - this.u1 = scaleU * (u + sizeZ); - this.u2 = scaleU * (u + sizeZ + sizeX); - this.u3 = scaleU * (u + sizeZ + sizeX + sizeX); - this.u4 = scaleU * (u + sizeZ + sizeX + sizeZ); - this.u5 = scaleU * (u + sizeZ + sizeX + sizeZ + sizeX); + float u0 = scaleU * (u); + float u1 = scaleU * (u + sizeZ); + float u2 = scaleU * (u + sizeZ + sizeX); + float u3 = scaleU * (u + sizeZ + sizeX + sizeX); + float u4 = scaleU * (u + sizeZ + sizeX + sizeZ); + float u5 = scaleU * (u + sizeZ + sizeX + sizeZ + sizeX); + + float v0 = scaleV * (v); + float v1 = scaleV * (v + sizeZ); + float v2 = scaleV * (v + sizeZ + sizeY); + + this.cullMask = createCullMask(renderDirections); + + final int[] positions = new int[NUM_CUBE_FACES * NUM_FACE_VERTICES]; + final long[] textures = new long[NUM_CUBE_FACES * NUM_FACE_VERTICES]; + final int[] normals = new int[] { FACE_NEG_Y, FACE_POS_Y, FACE_NEG_X, FACE_NEG_Z, FACE_POS_X, FACE_POS_Z }; + + writeVertexList(positions, FACE_NEG_Y, VERTEX_X1_Y0_Z1, VERTEX_X0_Y0_Z1, VERTEX_X0_Y0_Z0, VERTEX_X1_Y0_Z0); + writeTexCoords(textures, FACE_NEG_Y, u1, v0, u2, v1); + + writeVertexList(positions, FACE_POS_Y, VERTEX_X1_Y1_Z0, VERTEX_X0_Y1_Z0, VERTEX_X0_Y1_Z1, VERTEX_X1_Y1_Z1); + writeTexCoords(textures, FACE_POS_Y, u2, v1, u3, v0); + + writeVertexList(positions, FACE_NEG_Z, VERTEX_X1_Y0_Z0, VERTEX_X0_Y0_Z0, VERTEX_X0_Y1_Z0, VERTEX_X1_Y1_Z0); + writeTexCoords(textures, FACE_NEG_Z, u1, v1, u2, v2); + + writeVertexList(positions, FACE_POS_Z, VERTEX_X0_Y0_Z1, VERTEX_X1_Y0_Z1, VERTEX_X1_Y1_Z1, VERTEX_X0_Y1_Z1); + writeTexCoords(textures, FACE_POS_Z, u4, v1, u5, v2); + + writeVertexList(positions, FACE_NEG_X, VERTEX_X1_Y0_Z1, VERTEX_X1_Y0_Z0, VERTEX_X1_Y1_Z0, VERTEX_X1_Y1_Z1); + writeTexCoords(textures, FACE_NEG_X, u2, v1, u4, v2); - this.v0 = scaleV * (v); - this.v1 = scaleV * (v + sizeZ); - this.v2 = scaleV * (v + sizeZ + sizeY); + writeVertexList(positions, FACE_POS_X, VERTEX_X0_Y0_Z0, VERTEX_X0_Y0_Z1, VERTEX_X0_Y1_Z1, VERTEX_X0_Y1_Z0); + writeTexCoords(textures, FACE_POS_X, u0, v1, u1, v2); - this.mirror = mirror; + if (mirror) { + reverseVertices(positions, textures); + + // When mirroring is used, the normals for EAST and WEST are swapped. + normals[FACE_POS_X] = FACE_NEG_X; + normals[FACE_NEG_X] = FACE_POS_X; + } + + this.normals = normals; + this.positions = positions; + this.textures = textures; + } + + private static int createCullMask(Set directions) { + int mask = 0; + + for (var direction : directions) { + mask |= 1 << getFaceIndex(direction); + } + + return mask; + } + + private static void reverseVertices(int[] vertices, long[] texCoords) { + for (int faceIndex = 0; faceIndex < NUM_CUBE_FACES; faceIndex++) { + final int vertexOffset = faceIndex * NUM_FACE_VERTICES; - int cullBitmask = 0; + ArrayUtils.swap(vertices, vertexOffset + 0, vertexOffset + 3); + ArrayUtils.swap(vertices, vertexOffset + 1, vertexOffset + 2); - for (var direction : renderDirections) { - cullBitmask |= 1 << getFaceIndex(direction); + ArrayUtils.swap(texCoords, vertexOffset + 0, vertexOffset + 3); + ArrayUtils.swap(texCoords, vertexOffset + 1, vertexOffset + 2); } + } + + private static void writeVertexList(int[] positions, int faceIndex, int i0, int i1, int i2, int i3) { + positions[(faceIndex * 4) + 0] = i0; + positions[(faceIndex * 4) + 1] = i1; + positions[(faceIndex * 4) + 2] = i2; + positions[(faceIndex * 4) + 3] = i3; + } - this.cullBitmask = cullBitmask; + private static void writeTexCoords(long[] textures, int faceIndex, float u1, float v1, float u2, float v2) { + textures[(faceIndex * 4) + 0] = Int2.pack(Float.floatToRawIntBits(u2), Float.floatToRawIntBits(v1)); + textures[(faceIndex * 4) + 1] = Int2.pack(Float.floatToRawIntBits(u1), Float.floatToRawIntBits(v1)); + textures[(faceIndex * 4) + 2] = Int2.pack(Float.floatToRawIntBits(u1), Float.floatToRawIntBits(v2)); + textures[(faceIndex * 4) + 3] = Int2.pack(Float.floatToRawIntBits(u2), Float.floatToRawIntBits(v2)); } public boolean shouldDrawFace(int faceIndex) { - return (this.cullBitmask & (1 << faceIndex)) != 0; + return (this.cullMask & (1 << faceIndex)) != 0; } - public static int getFaceIndex(@NotNull Direction dir) { + private static int getFaceIndex(@NotNull Direction dir) { return switch (dir) { - case DOWN -> FACE_NEG_Y; - case UP -> FACE_POS_Y; - case NORTH -> FACE_NEG_Z; - case SOUTH -> FACE_POS_Z; - case WEST -> FACE_NEG_X; - case EAST -> FACE_POS_X; + case DOWN -> FACE_NEG_Y; + case UP -> FACE_POS_Y; + case NORTH -> FACE_NEG_Z; + case SOUTH -> FACE_POS_Z; + case WEST -> FACE_NEG_X; + case EAST -> FACE_POS_X; }; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/Int2.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/Int2.java new file mode 100644 index 0000000000..a0198495ab --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/Int2.java @@ -0,0 +1,13 @@ +package net.caffeinemc.mods.sodium.client.util; + +import java.nio.ByteOrder; + +public class Int2 { + public static long pack(int a, int b) { + if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) { + return ((a & 0xFFFFFFFFL) << 0) | ((b & 0xFFFFFFFFL) << 32); + } else { + return ((a & 0xFFFFFFFFL) << 32) | ((b & 0xFFFFFFFFL) << 0); + } + } +} From 44c98d85fe9d5d0fdb4187ebeb5477410c1c491e Mon Sep 17 00:00:00 2001 From: JellySquid Date: Thu, 2 Jan 2025 11:52:27 -0600 Subject: [PATCH 079/215] Avoid quaternion transforms in particle rendering The billboard geometry can be computed using the camera's left and up vectors, saving some cycles. When rendering thousands of billboard particles, this was ~10% faster than baseline in my observation. Co-authored-by: MoePus <547007249@qq.com> (cherry picked from commit 1b0f7b9150ae7dfb092a011cce12300d2d8ab248) --- .../particle/SingleQuadParticleMixin.java | 111 ++++++++++++++---- 1 file changed, 87 insertions(+), 24 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/particle/SingleQuadParticleMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/particle/SingleQuadParticleMixin.java index 6dd1a3e2cc..413613d00c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/particle/SingleQuadParticleMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/particle/SingleQuadParticleMixin.java @@ -1,12 +1,17 @@ package net.caffeinemc.mods.sodium.mixin.features.render.particle; -import net.caffeinemc.mods.sodium.api.vertex.format.common.ParticleVertex; import com.mojang.blaze3d.vertex.VertexConsumer; import net.caffeinemc.mods.sodium.api.util.ColorABGR; +import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; +import net.caffeinemc.mods.sodium.api.vertex.format.common.ParticleVertex; import net.caffeinemc.mods.sodium.client.render.vertex.VertexConsumerUtils; +import net.minecraft.client.Camera; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.particle.Particle; import net.minecraft.client.particle.SingleQuadParticle; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.Vec3; +import org.joml.Math; import org.joml.Quaternionf; import org.joml.Vector3f; import org.lwjgl.system.MemoryStack; @@ -38,12 +43,63 @@ protected SingleQuadParticleMixin(ClientLevel level, double x, double y, double super(level, x, y, z); } + @Unique + private static final Vector3f TEMP_LEFT = new Vector3f(); + + @Unique + private static final Vector3f TEMP_UP = new Vector3f(); + + /** + * @reason Build vertex data using the left and up vectors to avoid quaternion calculations + * @author MoePus + */ + @Inject(method = "render(Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/client/Camera;F)V", at = @At("HEAD"), cancellable = true) + protected void render(VertexConsumer vertexConsumer, Camera camera, float tickDelta, CallbackInfo ci) { + final var writer = VertexConsumerUtils.convertOrLog(vertexConsumer); + + if (writer == null) { + return; + } + + ci.cancel(); + + float size = this.getQuadSize(tickDelta); + + Vector3f left = TEMP_LEFT; + left.set(camera.getLeftVector()) + .mul(size); + + Vector3f up = TEMP_UP; + up.set(camera.getUpVector()) + .mul(size); + + if (!Mth.equal(this.roll, 0.0f)) { + float roll = Mth.lerp(tickDelta, this.oRoll, this.roll); + + float sinRoll = Math.sin(roll); + float cosRoll = Math.cosFromSin(sinRoll, roll); + + float rv1x = Math.fma(cosRoll, left.x, sinRoll * up.x), + rv1y = Math.fma(cosRoll, left.y, sinRoll * up.y), + rv1z = Math.fma(cosRoll, left.z, sinRoll * up.z); + + float rv2x = Math.fma(-sinRoll, left.x, cosRoll * up.x), + rv2y = Math.fma(-sinRoll, left.y, cosRoll * up.y), + rv2z = Math.fma(-sinRoll, left.z, cosRoll * up.z); + + left.set(rv1x, rv1y, rv1z); + up.set(rv2x, rv2y, rv2z); + } + + this.sodium$emitVertices(writer, camera.getPosition(), left, up, tickDelta); + } + /** - * @reason Optimize function - * @author JellySquid + * @reason Build vertex data using the left and up vectors to avoid quaternion calculations + * @author MoePus */ - @Inject(method = "renderRotatedQuad(Lcom/mojang/blaze3d/vertex/VertexConsumer;Lorg/joml/Quaternionf;FFFF)V", at = @At("HEAD"), cancellable = true) - protected void renderRotatedQuad(VertexConsumer vertexConsumer, Quaternionf quaternionf, float x, float y, float z, float tickDelta, CallbackInfo ci) { + @Inject(method = "renderRotatedQuad(Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/client/Camera;Lorg/joml/Quaternionf;F)V", at = @At("HEAD"), cancellable = true) + protected void renderRotatedQuad(VertexConsumer vertexConsumer, Camera camera, Quaternionf quaternion, float tickDelta, CallbackInfo ci) { final var writer = VertexConsumerUtils.convertOrLog(vertexConsumer); if (writer == null) { @@ -53,45 +109,52 @@ protected void renderRotatedQuad(VertexConsumer vertexConsumer, Quaternionf quat ci.cancel(); float size = this.getQuadSize(tickDelta); + + // Some particle class implementations may call this function directly, in which case we cannot assume anything + // about the transform being used. However, we can still extract the left/up vectors from the quaternion, + // it's just slightly slower than using the camera's left/up vectors directly. + Vector3f left = TEMP_LEFT; + left.set(-size, 0.0f, 0.0f) + .rotate(quaternion); + + Vector3f up = TEMP_UP; + up.set(0.0f, size, 0.0f) + .rotate(quaternion); + + this.sodium$emitVertices(writer, camera.getPosition(), left, up, tickDelta); + } + + @Unique + private void sodium$emitVertices(VertexBufferWriter writer, Vec3 camera, Vector3f left, Vector3f up, float tickDelta) { float minU = this.getU0(); float maxU = this.getU1(); float minV = this.getV0(); float maxV = this.getV1(); - int light = this.getLightColor(tickDelta); + int light = this.getLightColor(tickDelta); int color = ColorABGR.pack(this.rCol, this.gCol, this.bCol, this.alpha); + float x = (float) (Mth.lerp(tickDelta, this.xo, this.x) - camera.x()); + float y = (float) (Mth.lerp(tickDelta, this.yo, this.y) - camera.y()); + float z = (float) (Mth.lerp(tickDelta, this.zo, this.z) - camera.z()); + try (MemoryStack stack = MemoryStack.stackPush()) { long buffer = stack.nmalloc(4 * ParticleVertex.STRIDE); long ptr = buffer; - this.sodium$writeVertex(ptr, quaternionf, x, y, z, 1.0F, -1.0F, size, maxU, maxV, color, light); + ParticleVertex.put(ptr, -left.x - up.x + x, -left.y - up.y + y, -left.z - up.z + z, maxU, maxV, color, light); ptr += ParticleVertex.STRIDE; - this.sodium$writeVertex(ptr, quaternionf, x, y, z, 1.0F, 1.0F, size, maxU, minV, color, light); + ParticleVertex.put(ptr, -left.x + up.x + x, -left.y + up.y + y, -left.z + up.z + z, maxU, minV, color, light); ptr += ParticleVertex.STRIDE; - this.sodium$writeVertex(ptr, quaternionf, x, y, z, -1.0F, 1.0F, size, minU, minV, color, light); + ParticleVertex.put(ptr, left.x + up.x + x, left.y + up.y + y, left.z + up.z + z, minU, minV, color, light); ptr += ParticleVertex.STRIDE; - this.sodium$writeVertex(ptr, quaternionf, x, y, z, -1.0F, -1.0F, size, minU, maxV, color, light); + ParticleVertex.put(ptr, left.x - up.x + x, left.y - up.y + y, left.z - up.z + z, minU, maxV, color, light); ptr += ParticleVertex.STRIDE; writer.push(stack, buffer, 4, ParticleVertex.FORMAT); } } - - @Unique - private final Vector3f sodium$scratchVertex = new Vector3f(); // not thread-safe - - @Unique - private void sodium$writeVertex(long ptr, Quaternionf quaternionf, float originX, float originY, float originZ, float posX, float posY, float size, float u, float v, int color, int light) { - final var vertex = this.sodium$scratchVertex; - vertex.set(posX, posY, 0.0f); - vertex.rotate(quaternionf); - vertex.mul(size); - vertex.add(originX, originY, originZ); - - ParticleVertex.put(ptr, vertex.x(), vertex.y(), vertex.z(), u, v, color, light); - } } From 882de8481fe1e3d878e4b00c4476ae9701deff19 Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 2 Jan 2025 19:57:59 +0100 Subject: [PATCH 080/215] Add shader source line annotations (#2691) (cherry picked from commit 3186b1bd27e8b59493ee098716031dbb2a12ee81) --- .../sodium/client/gl/shader/GlShader.java | 9 +- .../sodium/client/gl/shader/ShaderParser.java | 84 +++++++++++++++---- 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/GlShader.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/GlShader.java index 0cc4058234..fc71193b01 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/GlShader.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/GlShader.java @@ -7,6 +7,8 @@ import org.apache.logging.log4j.Logger; import org.lwjgl.opengl.GL20C; +import java.util.Arrays; + /** * A compiled OpenGL shader object. */ @@ -15,17 +17,18 @@ public class GlShader extends GlObject { private final ResourceLocation name; - public GlShader(ShaderType type, ResourceLocation name, String src) { + public GlShader(ShaderType type, ResourceLocation name, ShaderParser.ParsedShader parsedShader) { this.name = name; int handle = GL20C.glCreateShader(type.id); - ShaderWorkarounds.safeShaderSource(handle, src); + ShaderWorkarounds.safeShaderSource(handle, parsedShader.src()); GL20C.glCompileShader(handle); String log = GL20C.glGetShaderInfoLog(handle); if (!log.isEmpty()) { - LOGGER.warn("Shader compilation log for " + this.name + ": " + log); + LOGGER.warn("Shader compilation log for {}: {}", this.name, log); + LOGGER.warn("Include table: {}", Arrays.toString(parsedShader.includeIds())); } int result = GlStateManager.glGetShaderi(handle, GL20C.GL_COMPILE_STATUS); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderParser.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderParser.java index 1d0bd52569..4b35d936d6 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderParser.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderParser.java @@ -1,5 +1,9 @@ package net.caffeinemc.mods.sodium.client.gl.shader; +import it.unimi.dsi.fastutil.objects.Object2IntArrayMap; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import net.minecraft.resources.ResourceLocation; + import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; @@ -7,38 +11,77 @@ import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; -import net.minecraft.resources.ResourceLocation; public class ShaderParser { - public static String parseShader(String src, ShaderConstants constants) { - List lines = parseShader(src); - lines.addAll(1, constants.getDefineStrings()); + public record ParsedShader(String src, String[] includeIds) { + } + + public static ParsedShader parseShader(String src, ShaderConstants constants) { + var parser = new ShaderParser(); + parser.parseShader("_root", src); + parser.prependDefineStrings(constants); - return String.join("\n", lines); + return parser.finish(); } - public static List parseShader(String src) { - List builder = new LinkedList<>(); + private final Object2IntMap includeIds = new Object2IntArrayMap<>(); + private final List lines = new LinkedList<>(); + + private ShaderParser() { + } + + public void parseShader(String name, String src) { String line; + int lineNumber = 0; try (BufferedReader reader = new BufferedReader(new StringReader(src))) { while ((line = reader.readLine()) != null) { - if (line.startsWith("#import")) { - builder.addAll(resolveImport(line)); + lineNumber++; + if (line.startsWith("#version")) { + this.lines.add(line); + this.lines.add(lineDirectiveFor(name, lineNumber)); + } else if (line.startsWith("#import")) { + // add the original import statement as a comment for reference + this.lines.add("// START " + line); + + processImport(line); + + // reset the line directive to the current file + this.lines.add("// END " + line); + this.lines.add(lineDirectiveFor(name, lineNumber)); } else { - builder.add(line); + this.lines.add(line); } } } catch (IOException e) { throw new RuntimeException("Failed to read shader sources", e); } + } + + private String lineDirectiveFor(String name, int line) { + int idNumber; + if (!this.includeIds.containsKey(name)) { + idNumber = this.includeIds.size(); + this.includeIds.put(name, idNumber); + } else { + idNumber = this.includeIds.getInt(name); + } + return "#line " + (line + 1) + " " + idNumber; + } + + private void processImport(String line) { + ResourceLocation name = parseImport(line); - return builder; + // mark the start of the imported file + var nameString = name.toString(); + this.lines.add(lineDirectiveFor(nameString, 0)); + + parseShader(nameString, ShaderLoader.getShaderSource(name)); } private static final Pattern IMPORT_PATTERN = Pattern.compile("#import <(?.*):(?.*)>"); - private static List resolveImport(String line) { + private ResourceLocation parseImport(String line) { Matcher matcher = IMPORT_PATTERN.matcher(line); if (!matcher.matches()) { @@ -48,9 +91,20 @@ private static List resolveImport(String line) { String namespace = matcher.group("namespace"); String path = matcher.group("path"); - ResourceLocation name = ResourceLocation.fromNamespaceAndPath(namespace, path); - String source = ShaderLoader.getShaderSource(name); + return ResourceLocation.fromNamespaceAndPath(namespace, path); + } + + private void prependDefineStrings(ShaderConstants constants) { + this.lines.addAll(1, constants.getDefineStrings()); + } + + private ParsedShader finish() { + // convert include id map to a list ordered by id + var includeIds = new String[this.includeIds.size()]; + this.includeIds.forEach((name, id) -> { + includeIds[id] = name; + }); - return ShaderParser.parseShader(source); + return new ParsedShader(String.join("\n", this.lines), includeIds); } } From 1cf8c52b9754341c45f952dabfbb5ed93cecdb51 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Tue, 7 Jan 2025 15:29:16 -0600 Subject: [PATCH 081/215] Improve vertex sorting code and use radix sorts For larger arrays of floats (>80 elements), the radix sort is significantly faster. We also merge the process of calculating each quad's centroid into the metric calculation, so that we can avoid expensive object de-references, and so that it isn't necessary to allocate additional memory prior to sorting. (cherry picked from commit d74e4217fdbc3ad584f58f55d080d10ef3dc4f19) --- .../bsp_tree/InnerPartitionBSPNode.java | 44 ++---- .../data/DynamicTopoData.java | 29 ++-- .../data/StaticNormalRelativeData.java | 60 ++------ .../client/util/sorting/AbstractSort.java | 14 -- .../client/util/sorting/InsertionSort.java | 27 ---- .../sodium/client/util/sorting/MergeSort.java | 89 ----------- .../sodium/client/util/sorting/RadixSort.java | 91 ++++++++---- .../client/util/sorting/VertexSorters.java | 138 +++++++++++++++--- .../util/sorting/VertexSortingExtended.java | 12 ++ .../sorting/MeshDataAccessor.java | 12 ++ .../buffer_builder/sorting/MeshDataMixin.java | 47 ------ .../sorting/MultiBufferSourceMixin.java | 80 ++++++++++ .../sorting/VertexSortingMixin.java | 29 +++- .../main/resources/sodium-common.mixins.json | 3 +- 14 files changed, 352 insertions(+), 323 deletions(-) delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/AbstractSort.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/InsertionSort.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/MergeSort.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSortingExtended.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MeshDataAccessor.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MeshDataMixin.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MultiBufferSourceMixin.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java index 3ee6572093..613dff0616 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java @@ -530,43 +530,25 @@ static private BSPNode buildTopoMultiLeafNode(BSPWorkspace workspace, IntArrayLi } static private BSPNode buildSNRLeafNodeFromQuads(BSPWorkspace workspace, IntArrayList indexes, LongArrayList points) { - // in this case the points array is wrong, but its allocation can be reused + final var indexBuffer = indexes.elements(); + final var indexCount = indexes.size(); - int[] quadIndexes; + final var keys = new int[indexCount]; + final var perm = new int[indexCount]; - // adapted from SNR sorting code - if (RadixSort.useRadixSort(indexes.size())) { - final var keys = new int[indexes.size()]; - - for (int i = 0; i < indexes.size(); i++) { - var quadIndex = indexes.getInt(i); - keys[i] = MathUtil.floatToComparableInt(workspace.quads[quadIndex].getAccurateDotProduct()); - } - - quadIndexes = RadixSort.sort(keys); - - for (int i = 0; i < indexes.size(); i++) { - quadIndexes[i] = indexes.getInt(quadIndexes[i]); - } - } else { - final var sortData = points.elements(); - - for (int i = 0; i < indexes.size(); i++) { - var quadIndex = indexes.getInt(i); - int dotProductComponent = MathUtil.floatToComparableInt(workspace.quads[quadIndex].getAccurateDotProduct()); - sortData[i] = (long) dotProductComponent << 32 | quadIndex; - } - - Arrays.sort(sortData, 0, indexes.size()); + for (int i = 0; i < indexCount; i++) { + TQuad quad = workspace.quads[indexBuffer[i]]; + keys[i] = MathUtil.floatToComparableInt(quad.getAccurateDotProduct()); + perm[i] = i; + } - quadIndexes = new int[indexes.size()]; + RadixSort.sortIndirect(perm, keys); - for (int i = 0; i < indexes.size(); i++) { - quadIndexes[i] = (int) sortData[i]; - } + for (int i = 0; i < indexCount; i++) { + perm[i] = indexBuffer[perm[i]]; } - return new LeafMultiBSPNode(BSPSortState.compressIndexes(IntArrayList.wrap(quadIndexes), false)); + return new LeafMultiBSPNode(BSPSortState.compressIndexes(IntArrayList.wrap(perm), false)); } static private BSPNode buildSNRLeafNodeFromPoints(BSPWorkspace workspace, LongArrayList points) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java index 200ec74a93..2156bb3283 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java @@ -9,7 +9,6 @@ import org.joml.Vector3fc; import java.nio.IntBuffer; -import java.util.Arrays; import java.util.function.IntConsumer; /** @@ -221,30 +220,22 @@ void writeSort(CombinedCameraPos cameraPos, boolean initial) { */ static void distanceSortDirect(IntBuffer indexBuffer, TQuad[] quads, Vector3fc cameraPos) { if (quads.length <= 1) { + // Avoid allocations when there is nothing to sort. TranslucentData.writeQuadVertexIndexes(indexBuffer, 0); - } else if (RadixSort.useRadixSort(quads.length)) { + } else { final var keys = new int[quads.length]; + final var perm = new int[quads.length]; - for (int q = 0; q < quads.length; q++) { - keys[q] = ~Float.floatToRawIntBits(quads[q].getCenter().distanceSquared(cameraPos)); - } - - var indices = RadixSort.sort(keys); - - for (int i = 0; i < quads.length; i++) { - TranslucentData.writeQuadVertexIndexes(indexBuffer, indices[i]); - } - } else { - final var data = new long[quads.length]; - for (int q = 0; q < quads.length; q++) { - float distance = quads[q].getCenter().distanceSquared(cameraPos); - data[q] = (long) ~Float.floatToRawIntBits(distance) << 32 | q; + for (int idx = 0; idx < quads.length; idx++) { + var centroid = quads[idx].getCenter(); + keys[idx] = ~Float.floatToRawIntBits(centroid.distanceSquared(cameraPos)); + perm[idx] = idx; } - Arrays.sort(data); + RadixSort.sortIndirect(perm, keys); - for (int i = 0; i < quads.length; i++) { - TranslucentData.writeQuadVertexIndexes(indexBuffer, (int) data[i]); + for (int idx = 0; idx < quads.length; idx++) { + TranslucentData.writeQuadVertexIndexes(indexBuffer, perm[idx]); } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java index 888ccd2034..9998e67ce9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java @@ -44,31 +44,21 @@ private static StaticNormalRelativeData fromDoubleUnaligned(int[] vertexCounts, var indexBuffer = sorter.getIntBuffer(); if (quads.length <= 1) { + // Avoid allocations when there is nothing to sort. TranslucentData.writeQuadVertexIndexes(indexBuffer, 0); - } else if (RadixSort.useRadixSort(quads.length)) { + } else { final var keys = new int[quads.length]; + final var perm = new int[quads.length]; for (int q = 0; q < quads.length; q++) { keys[q] = MathUtil.floatToComparableInt(quads[q].getAccurateDotProduct()); + perm[q] = q; } - var indices = RadixSort.sort(keys); - - for (int i = 0; i < quads.length; i++) { - TranslucentData.writeQuadVertexIndexes(indexBuffer, indices[i]); - } - } else { - final var sortData = new long[quads.length]; - - for (int q = 0; q < quads.length; q++) { - int dotProductComponent = MathUtil.floatToComparableInt(quads[q].getAccurateDotProduct()); - sortData[q] = (long) dotProductComponent << 32 | q; - } - - Arrays.sort(sortData); + RadixSort.sortIndirect(perm, keys); for (int i = 0; i < quads.length; i++) { - TranslucentData.writeQuadVertexIndexes(indexBuffer, (int) sortData[i]); + TranslucentData.writeQuadVertexIndexes(indexBuffer, perm[i]); } } @@ -86,21 +76,14 @@ private static StaticNormalRelativeData fromMixed(int[] vertexCounts, var indexBuffer = sorter.getIntBuffer(); var maxQuadCount = 0; - boolean anyNeedsSortData = false; + for (var vertexCount : vertexCounts) { if (vertexCount != -1) { var quadCount = TranslucentData.vertexCountToQuadCount(vertexCount); maxQuadCount = Math.max(maxQuadCount, quadCount); - anyNeedsSortData |= !RadixSort.useRadixSort(quadCount) && quadCount > 1; } } - long[] sortData = null; - if (anyNeedsSortData) { - sortData = new long[maxQuadCount]; - } - - int quadIndex = 0; for (var vertexCount : vertexCounts) { if (vertexCount == -1 || vertexCount == 0) { continue; @@ -110,32 +93,19 @@ private static StaticNormalRelativeData fromMixed(int[] vertexCounts, if (count == 1) { TranslucentData.writeQuadVertexIndexes(indexBuffer, 0); - quadIndex++; - } else if (RadixSort.useRadixSort(count)) { + } else { final var keys = new int[count]; + final var perm = new int[count]; - for (int q = 0; q < count; q++) { - keys[q] = MathUtil.floatToComparableInt(quads[quadIndex++].getAccurateDotProduct()); + for (int idx = 0; idx < count; idx++) { + keys[idx] = MathUtil.floatToComparableInt(quads[idx].getAccurateDotProduct()); + perm[idx] = idx; } - var indices = RadixSort.sort(keys); - - for (int i = 0; i < count; i++) { - TranslucentData.writeQuadVertexIndexes(indexBuffer, indices[i]); - } - } else { - for (int i = 0; i < count; i++) { - var quad = quads[quadIndex++]; - int dotProductComponent = MathUtil.floatToComparableInt(quad.getAccurateDotProduct()); - sortData[i] = (long) dotProductComponent << 32 | i; - } - - if (count > 1) { - Arrays.sort(sortData, 0, count); - } + RadixSort.sortIndirect(perm, keys); - for (int i = 0; i < count; i++) { - TranslucentData.writeQuadVertexIndexes(indexBuffer, (int) sortData[i]); + for (int idx = 0; idx < count; idx++) { + TranslucentData.writeQuadVertexIndexes(indexBuffer, perm[idx]); } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/AbstractSort.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/AbstractSort.java deleted file mode 100644 index 93fd652f6e..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/AbstractSort.java +++ /dev/null @@ -1,14 +0,0 @@ -package net.caffeinemc.mods.sodium.client.util.sorting; - - -public class AbstractSort { - protected static int[] createIndexBuffer(int length) { - var indices = new int[length]; - - for (int i = 0; i < length; i++) { - indices[i] = i; - } - - return indices; - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/InsertionSort.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/InsertionSort.java deleted file mode 100644 index 08dfbc4ef9..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/InsertionSort.java +++ /dev/null @@ -1,27 +0,0 @@ -package net.caffeinemc.mods.sodium.client.util.sorting; - -public class InsertionSort extends AbstractSort { - public static void insertionSort(final int[] indices, final int fromIndex, final int toIndex, final float[] keys) { - int index = fromIndex; - - while (++index < toIndex) { - int t = indices[index]; - int j = index; - - int u = indices[j - 1]; - - while (keys[u] < keys[t]) { - indices[j] = u; - - if (fromIndex == j - 1) { - --j; - break; - } - - u = indices[--j - 1]; - } - - indices[j] = t; - } - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/MergeSort.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/MergeSort.java deleted file mode 100644 index 5083da0274..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/MergeSort.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2002-2017 Sebastiano Vigna - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * - * For the sorting and binary search code: - * - * Copyright (C) 1999 CERN - European Organization for Nuclear Research. - * - * Permission to use, copy, modify, distribute and sell this software and - * its documentation for any purpose is hereby granted without fee, - * provided that the above copyright notice appear in all copies and that - * both that copyright notice and this permission notice appear in - * supporting documentation. CERN makes no representations about the - * suitability of this software for any purpose. It is provided "as is" - * without expressed or implied warranty. - */ -package net.caffeinemc.mods.sodium.client.util.sorting; - - -/** - * Based upon {@link it.unimi.dsi.fastutil.ints.IntArrays} implementation, but it eliminates the use of a user-supplied - * function and instead sorts an array of floats directly. This helps to improve runtime performance. - */ -public class MergeSort extends AbstractSort { - private static final int INSERTION_SORT_THRESHOLD = 16; - - public static int[] mergeSort(float[] keys) { - var indices = createIndexBuffer(keys.length); - mergeSort(indices, keys); - - return indices; - } - - private static void mergeSort(final int[] indices, final float[] keys) { - mergeSort(indices, keys, 0, indices.length, null); - } - - private static void mergeSort(final int[] indices, final float[] keys, final int fromIndex, final int toIndex, int[] supp) { - int len = toIndex - fromIndex; - - // Insertion sort on smallest arrays - if (len < INSERTION_SORT_THRESHOLD) { - InsertionSort.insertionSort(indices, fromIndex, toIndex, keys); - return; - } - - if (supp == null) { - supp = indices.clone(); - } - - // Recursively sort halves of a into supp - final int mid = (fromIndex + toIndex) >>> 1; - mergeSort(supp, keys, fromIndex, mid, indices); - mergeSort(supp, keys, mid, toIndex, indices); - - // If list is already sorted, just copy from supp to indices. This is an - // optimization that results in faster sorts for nearly ordered lists. - if (keys[supp[mid]] <= keys[supp[mid - 1]]) { - System.arraycopy(supp, fromIndex, indices, fromIndex, len); - return; - } - - // Merge sorted halves (now in supp) into indices - int i = fromIndex, p = fromIndex, q = mid; - - while (i < toIndex) { - if (q >= toIndex || p < mid && keys[supp[q]] <= keys[supp[p]]) { - indices[i] = supp[p++]; - } else { - indices[i] = supp[q++]; - } - - i++; - } - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java index 00aedcb426..f96a056572 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java @@ -1,7 +1,9 @@ package net.caffeinemc.mods.sodium.client.util.sorting; -public class RadixSort extends AbstractSort { - public static final int RADIX_SORT_THRESHOLD = 64; +import it.unimi.dsi.fastutil.ints.IntArrays; + +public class RadixSort { + private static final int RADIX_SORT_THRESHOLD = 80; private static final int DIGIT_BITS = 8; private static final int RADIX_KEY_BITS = Integer.BYTES * 8; @@ -9,27 +11,15 @@ public class RadixSort extends AbstractSort { private static final int DIGIT_COUNT = (RADIX_KEY_BITS + DIGIT_BITS - 1) / DIGIT_BITS; private static final int DIGIT_MASK = (1 << DIGIT_BITS) - 1; - public static int[] sort(int[] keys) { - if (keys.length <= 1) { - return new int[keys.length]; - } - - return radixSort(keys, createHistogram(keys)); - } - - private static int[][] createHistogram(int[] keys) { - var histogram = new int[DIGIT_COUNT][BUCKET_COUNT]; - + private static void getHistogram(int[][] histogram, int[] keys) { for (final int key : keys) { for (int digit = 0; digit < DIGIT_COUNT; digit++) { histogram[digit][extractDigit(key, digit)] += 1; } } - - return histogram; } - private static void prefixSum(int[][] offsets) { + private static void prefixSums(int[][] offsets) { for (int digit = 0; digit < DIGIT_COUNT; digit++) { final var buckets = offsets[digit]; var sum = 0; @@ -42,13 +32,55 @@ private static void prefixSum(int[][] offsets) { } } - private static int[] radixSort(int[] keys, int[][] offsets) { - prefixSum(offsets); + /** + *

Sorts the specified array according to the natural ascending order using an unstable, out-of-place, indirect, + * 256-way LSD radix sort.

+ * + *

This algorithm is well suited for large arrays of integers, especially when they are uniformly distributed + * over the entire range of the data type.

+ * + *

This method implements an indirect sort. The elements of {@param perm} (which must be + * exactly the numbers in the interval {@code [0..perm.length)}) will be permuted so that + * {@code x[perm[i]] < x[perm[i + 1]]}.

+ * + *

While this radix sort is very fast on larger arrays, there is a certain amount of fixed cost involved in + * computing the histogram and prefix sums. Because of this, a fallback algorithm (currently quick sort) is used + * for very small arrays to ensure this method performs well for all inputs of all sizes.

+ * + * @param perm a permutation array indexing {@param keys}. + * @param keys the array of elements to be sorted. + */ + public static void sortIndirect(final int[] perm, final int[] keys) { + if (perm.length <= RADIX_SORT_THRESHOLD) { + smallSort(perm, keys); + return; + } + + int[][] offsets; + int[] next; + + try { + offsets = new int[DIGIT_COUNT][BUCKET_COUNT]; + next = new int[perm.length]; + } catch (OutOfMemoryError oom) { + // Not enough memory to perform an out-of-place sort, so use an in-place alternative. + fallbackInPlaceSort(perm, keys); + return; + } + + sortIndirect(perm, keys, offsets, next); + } - final var length = keys.length; + private static void sortIndirect(final int[] perm, + final int[] keys, + final int[][] offsets, + int[] next) + { + final int length = perm.length; + getHistogram(offsets, keys); + prefixSums(offsets); - int[] cur = createIndexBuffer(length); - int[] next = new int[length]; + int[] cur = perm; for (int digit = 0; digit < DIGIT_COUNT; digit++) { final var buckets = offsets[digit]; @@ -68,15 +100,22 @@ private static int[] radixSort(int[] keys, int[][] offsets) { cur = temp; } } + } + + private static void smallSort(int[] perm, int[] keys) { + if (perm.length <= 1) { + return; + } - return cur; + fallbackInPlaceSort(perm, keys); } - private static int extractDigit(int key, int digit) { - return ((key >>> (digit * DIGIT_BITS)) & DIGIT_MASK); + // Fallback sorting method which is guaranteed to be in-place and not require additional memory. + private static void fallbackInPlaceSort(int[] perm, int[] keys) { + IntArrays.quickSortIndirect(perm, keys); } - public static boolean useRadixSort(int length) { - return length >= RADIX_SORT_THRESHOLD; + private static int extractDigit(int key, int digit) { + return ((key >>> (digit * DIGIT_BITS)) & DIGIT_MASK); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java index 5a36dd1783..fab533b5cc 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java @@ -1,45 +1,141 @@ package net.caffeinemc.mods.sodium.client.util.sorting; import com.mojang.blaze3d.vertex.VertexSorting; +import net.caffeinemc.mods.sodium.client.util.MathUtil; +import org.apache.commons.lang3.Validate; +import org.jetbrains.annotations.NotNull; import org.joml.Vector3f; +import org.lwjgl.system.MemoryUtil; + +import java.nio.ByteBuffer; public class VertexSorters { - public static VertexSorting sortByDistance(Vector3f origin) { - return new SortByDistance(origin); + public static VertexSortingExtended distance(float x, float y, float z) { + if (x == 0.0f && y == 0.0f && z == 0.0f) { + return SortByDistanceToOrigin.INSTANCE; + } + + return new SortByDistanceToPoint(x, y, z); + } + + public static VertexSortingExtended orthographicZ() { + return SortByOrthographicZ.INSTANCE; } - private static class SortByDistance extends AbstractVertexSorter { - private final Vector3f origin; + // Slow, should only be used when none of the other classes apply. + public static VertexSortingExtended fallback(VertexSorting.DistanceFunction metric) { + return new SortByFallback(metric); + } + + private abstract static class AbstractSorter implements VertexSortingExtended { + @Override + public final int @NotNull [] sort(Vector3f[] centroids) { + final int length = centroids.length; + final var keys = new int[length]; + final var perm = new int[length]; + + for (int index = 0; index < length; index++) { + keys[index] = ~MathUtil.floatToComparableInt(this.applyMetric(centroids[index])); + perm[index] = index; + } - private SortByDistance(Vector3f origin) { - this.origin = origin; + RadixSort.sortIndirect(perm, keys); + + return perm; + } + } + + private static class SortByDistanceToPoint extends AbstractSorter { + private final float x, y, z; + + private SortByDistanceToPoint(float x, float y, float z) { + this.x = x; + this.y = y; + this.z = z; } @Override - protected float getKey(Vector3f position) { - return this.origin.distanceSquared(position); + public float applyMetric(float x, float y, float z) { + float dx = this.x - x; + float dy = this.y - y; + float dz = this.z - z; + + return (dx * dx) + (dy * dy) + (dz * dz); } } - /** - * Sorts the keys given by the subclass by descending value. - */ - private static abstract class AbstractVertexSorter implements VertexSorting { + private static class SortByDistanceToOrigin extends AbstractSorter { + private static final SortByDistanceToOrigin INSTANCE = new SortByDistanceToOrigin(); + @Override - public final int[] sort(Vector3f[] positions) { - return this.mergeSort(positions); + public float applyMetric(float x, float y, float z) { + return (x * x) + (y * y) + (z * z); } + } - private int[] mergeSort(Vector3f[] positions) { - final var keys = new float[positions.length]; + private static class SortByOrthographicZ extends AbstractSorter { + private static final SortByOrthographicZ INSTANCE = new SortByOrthographicZ(); - for (int index = 0; index < positions.length; index++) { - keys[index] = this.getKey(positions[index]); - } + @Override + public float applyMetric(float x, float y, float z) { + return -z; + } + } + + private static class SortByFallback extends AbstractSorter { + private final DistanceFunction function; + private final Vector3f scratch = new Vector3f(); + + private SortByFallback(DistanceFunction function) { + this.function = function; + } - return MergeSort.mergeSort(keys); + @Override + public float applyMetric(float x, float y, float z) { + return this.function.apply(this.scratch.set(x, y, z)); } + } + + public static int[] sort(ByteBuffer buffer, int vertexCount, int vertexStride, VertexSortingExtended sorting) { + Validate.isTrue(buffer.remaining() >= vertexStride * vertexCount, + "Vertex buffer is not large enough to contain all vertices"); + + long pVertex0 = MemoryUtil.memAddress(buffer); + long pVertex2 = MemoryUtil.memAddress(buffer, vertexStride * 2); + + int primitiveCount = vertexCount / 4; + int primitiveStride = vertexStride * 4; + + final int[] keys = new int[primitiveCount]; + final int[] perm = new int[primitiveCount]; + + for (int primitiveId = 0; primitiveId < primitiveCount; primitiveId++) { + // Position of vertex[0] + float v0x = MemoryUtil.memGetFloat(pVertex0 + 0L); + float v0y = MemoryUtil.memGetFloat(pVertex0 + 4L); + float v0z = MemoryUtil.memGetFloat(pVertex0 + 8L); + + // Position of vertex[2] + float v2x = MemoryUtil.memGetFloat(pVertex2 + 0L); + float v2y = MemoryUtil.memGetFloat(pVertex2 + 4L); + float v2z = MemoryUtil.memGetFloat(pVertex2 + 8L); + + // The centroid of the quad is calculated using the mid-point of the diagonal edge. This will not work + // for degenerate quads, but those are not sortable anyway. + float cx = (v0x + v2x) * 0.5F; + float cy = (v0y + v2y) * 0.5F; + float cz = (v0z + v2z) * 0.5F; + + // The sign bit of the metric is negated as we need back-to-front (descending) ordering. + keys[primitiveId] = MathUtil.floatToComparableInt(-sorting.applyMetric(cx, cy, cz)); + perm[primitiveId] = primitiveId; + + pVertex0 += primitiveStride; + pVertex2 += primitiveStride; + } + + RadixSort.sortIndirect(perm, keys); - protected abstract float getKey(Vector3f object); + return perm; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSortingExtended.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSortingExtended.java new file mode 100644 index 0000000000..b04bc9eb99 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSortingExtended.java @@ -0,0 +1,12 @@ +package net.caffeinemc.mods.sodium.client.util.sorting; + +import com.mojang.blaze3d.vertex.VertexSorting; +import org.joml.Vector3f; + +public interface VertexSortingExtended extends VertexSorting { + float applyMetric(float x, float y, float z); + + default float applyMetric(Vector3f vector) { + return this.applyMetric(vector.x, vector.y, vector.z); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MeshDataAccessor.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MeshDataAccessor.java new file mode 100644 index 0000000000..096dfd9437 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MeshDataAccessor.java @@ -0,0 +1,12 @@ +package net.caffeinemc.mods.sodium.mixin.features.render.immediate.buffer_builder.sorting; + +import com.mojang.blaze3d.vertex.ByteBufferBuilder; +import com.mojang.blaze3d.vertex.MeshData; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(MeshData.class) +public interface MeshDataAccessor { + @Accessor + void setIndexBuffer(ByteBufferBuilder.Result buffer); +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MeshDataMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MeshDataMixin.java deleted file mode 100644 index d05d3a9f06..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MeshDataMixin.java +++ /dev/null @@ -1,47 +0,0 @@ - -package net.caffeinemc.mods.sodium.mixin.features.render.immediate.buffer_builder.sorting; - -import com.mojang.blaze3d.vertex.MeshData; -import org.jetbrains.annotations.Nullable; -import org.joml.Vector3f; -import org.lwjgl.system.MemoryUtil; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; -import org.spongepowered.asm.mixin.Shadow; -import org.spongepowered.asm.mixin.Unique; -import com.mojang.blaze3d.vertex.BufferBuilder; -import com.mojang.blaze3d.vertex.VertexFormat; -import com.mojang.blaze3d.vertex.VertexSorting; -import java.nio.ByteBuffer; - -@Mixin(MeshData.class) -public abstract class MeshDataMixin { - /** - * @author JellySquid - * @reason Avoid slow memory accesses - */ - @Overwrite - private static Vector3f[] unpackQuadCentroids(ByteBuffer buffer, int vertices, VertexFormat format) { - int vertexStride = format.getVertexSize(); - int primitiveCount = vertices / 4; - - Vector3f[] centers = new Vector3f[primitiveCount]; - - for (int index = 0; index < primitiveCount; ++index) { - long v1 = MemoryUtil.memAddress(buffer, (((index * 4) + 0) * vertexStride)); - long v2 = MemoryUtil.memAddress(buffer, (((index * 4) + 2) * vertexStride)); - - float x1 = MemoryUtil.memGetFloat(v1 + 0); - float y1 = MemoryUtil.memGetFloat(v1 + 4); - float z1 = MemoryUtil.memGetFloat(v1 + 8); - - float x2 = MemoryUtil.memGetFloat(v2 + 0); - float y2 = MemoryUtil.memGetFloat(v2 + 4); - float z2 = MemoryUtil.memGetFloat(v2 + 8); - - centers[index] = new Vector3f((x1 + x2) * 0.5F, (y1 + y2) * 0.5F, (z1 + z2) * 0.5F); - } - - return centers; - } -} \ No newline at end of file diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MultiBufferSourceMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MultiBufferSourceMixin.java new file mode 100644 index 0000000000..be8650c005 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MultiBufferSourceMixin.java @@ -0,0 +1,80 @@ +package net.caffeinemc.mods.sodium.mixin.features.render.immediate.buffer_builder.sorting; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.mojang.blaze3d.vertex.ByteBufferBuilder; +import com.mojang.blaze3d.vertex.MeshData; +import com.mojang.blaze3d.vertex.VertexFormat; +import com.mojang.blaze3d.vertex.VertexSorting; +import net.caffeinemc.mods.sodium.client.util.sorting.VertexSorters; +import net.caffeinemc.mods.sodium.client.util.sorting.VertexSortingExtended; +import net.minecraft.client.renderer.MultiBufferSource; +import org.lwjgl.system.MemoryUtil; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(MultiBufferSource.BufferSource.class) +public class MultiBufferSourceMixin { + @Unique + private static final int VERTICES_PER_QUAD = 6; + + @WrapOperation( + method = "endBatch(Lnet/minecraft/client/renderer/RenderType;Lcom/mojang/blaze3d/vertex/BufferBuilder;)V", + at = @At( + value = "INVOKE", + target = "Lcom/mojang/blaze3d/vertex/MeshData;sortQuads(Lcom/mojang/blaze3d/vertex/ByteBufferBuilder;Lcom/mojang/blaze3d/vertex/VertexSorting;)Lcom/mojang/blaze3d/vertex/MeshData$SortState;" + ) + ) + private MeshData.SortState redirectSortQuads(MeshData meshData, ByteBufferBuilder bufferBuilder, VertexSorting sorting, Operation original) { + if (sorting instanceof VertexSortingExtended sortingExtended) { + var sortedPrimitiveIds = VertexSorters.sort(meshData.vertexBuffer(), meshData.drawState().vertexCount(), meshData.drawState().format().getVertexSize(), sortingExtended); + var sortedIndexBuffer = buildSortedIndexBuffer(meshData, bufferBuilder, sortedPrimitiveIds); + ((MeshDataAccessor) meshData).setIndexBuffer(sortedIndexBuffer); + } else { + original.call(meshData, bufferBuilder, sorting); + } + + return null; + } + + @Unique + private static ByteBufferBuilder.Result buildSortedIndexBuffer(MeshData meshData, ByteBufferBuilder bufferBuilder, int[] primitiveIds) { + final var indexType = meshData.drawState().indexType(); + final var ptr = bufferBuilder.reserve((primitiveIds.length * VERTICES_PER_QUAD) * indexType.bytes); + + if (indexType == VertexFormat.IndexType.SHORT) { + writeShortIndexBuffer(ptr, primitiveIds); + } else if (indexType == VertexFormat.IndexType.INT) { + writeIntIndexBuffer(ptr, primitiveIds); + } + + return bufferBuilder.build(); + } + + @Unique + private static void writeIntIndexBuffer(long ptr, int[] primitiveIds) { + for (int primitiveId : primitiveIds) { + MemoryUtil.memPutInt(ptr + 0L, (primitiveId * 4) + 0); + MemoryUtil.memPutInt(ptr + 4L, (primitiveId * 4) + 1); + MemoryUtil.memPutInt(ptr + 8L, (primitiveId * 4) + 2); + MemoryUtil.memPutInt(ptr + 12L, (primitiveId * 4) + 2); + MemoryUtil.memPutInt(ptr + 16L, (primitiveId * 4) + 3); + MemoryUtil.memPutInt(ptr + 20L, (primitiveId * 4) + 0); + ptr += 24L; + } + } + + @Unique + private static void writeShortIndexBuffer(long ptr, int[] primitiveIds) { + for (int primitiveId : primitiveIds) { + MemoryUtil.memPutShort(ptr + 0L, (short) ((primitiveId * 4) + 0)); + MemoryUtil.memPutShort(ptr + 2L, (short) ((primitiveId * 4) + 1)); + MemoryUtil.memPutShort(ptr + 4L, (short) ((primitiveId * 4) + 2)); + MemoryUtil.memPutShort(ptr + 6L, (short) ((primitiveId * 4) + 2)); + MemoryUtil.memPutShort(ptr + 8L, (short) ((primitiveId * 4) + 3)); + MemoryUtil.memPutShort(ptr + 10L, (short) ((primitiveId * 4) + 0)); + ptr += 12L; + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/VertexSortingMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/VertexSortingMixin.java index fddfd57503..98b719ab37 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/VertexSortingMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/VertexSortingMixin.java @@ -1,19 +1,42 @@ package net.caffeinemc.mods.sodium.mixin.features.render.immediate.buffer_builder.sorting; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.mojang.blaze3d.vertex.VertexSorting; import net.caffeinemc.mods.sodium.client.util.sorting.VertexSorters; -import org.joml.Vector3f; +import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.injection.At; @Mixin(VertexSorting.class) public interface VertexSortingMixin { + @SuppressWarnings("DiscouragedShift") // Not currently avoidable. + @ModifyExpressionValue( + method = "", + at = @At( + value = "FIELD", + target = "Lcom/mojang/blaze3d/vertex/VertexSorting;ORTHOGRAPHIC_Z:Lcom/mojang/blaze3d/vertex/VertexSorting;", + opcode = Opcodes.PUTSTATIC, + shift = At.Shift.BEFORE)) + private static VertexSorting modifyVertexSorting(VertexSorting original) { + return VertexSorters.orthographicZ(); + } + /** * @author JellySquid * @reason Optimize vertex sorting */ @Overwrite static VertexSorting byDistance(float x, float y, float z) { - return VertexSorters.sortByDistance(new Vector3f(x, y, z)); + return VertexSorters.distance(x, y, z); + } + + /** + * @author JellySquid + * @reason Optimize vertex sorting + */ + @Overwrite + static VertexSorting byDistance(VertexSorting.DistanceFunction function) { + return VertexSorters.fallback(function); } -} +} \ No newline at end of file diff --git a/common/src/main/resources/sodium-common.mixins.json b/common/src/main/resources/sodium-common.mixins.json index 95a130bd95..8f898afb30 100644 --- a/common/src/main/resources/sodium-common.mixins.json +++ b/common/src/main/resources/sodium-common.mixins.json @@ -57,7 +57,8 @@ "features.render.gui.outlines.LevelRendererMixin", "features.render.immediate.DirectionMixin", "features.render.immediate.buffer_builder.intrinsics.BufferBuilderMixin", - "features.render.immediate.buffer_builder.sorting.MeshDataMixin", + "features.render.immediate.buffer_builder.sorting.MeshDataAccessor", + "features.render.immediate.buffer_builder.sorting.MultiBufferSourceMixin", "features.render.immediate.buffer_builder.sorting.VertexSortingMixin", "features.render.immediate.matrix_stack.PoseStackMixin", "features.render.immediate.matrix_stack.VertexConsumerMixin", From f9e5e6d474e6a4e905c2398b64962a4ca5dd2be4 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 16 Feb 2025 02:37:59 +0100 Subject: [PATCH 082/215] Fix incorrect quad indexing in SNR sorting (#3027) This changes SNR sorting to use correct (continuous) indexing of the quads array and adds a comment explaining the tricky behavior. Resolves the regression introduced with d74e4217fd ("Improve vertex sorting code and use radix sorts"). (cherry picked from commit b097296d5ca9059d851156fcf87d017188ebe9de) --- .../data/StaticNormalRelativeData.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java index 9998e67ce9..ceddad0c84 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java @@ -6,8 +6,6 @@ import net.caffeinemc.mods.sodium.client.util.sorting.RadixSort; import net.minecraft.core.SectionPos; -import java.util.Arrays; - /** * Static normal relative sorting orders quads by the dot product of their * normal and position. (referred to as "distance" throughout the code) @@ -84,6 +82,10 @@ private static StaticNormalRelativeData fromMixed(int[] vertexCounts, } } + // The quad index is used to keep track of the position in the quad array. + // This is necessary because the emitted quad indexes in each facing start at zero, + // but the quads are stored in a single continuously indexed array. + int quadIndex = 0; for (var vertexCount : vertexCounts) { if (vertexCount == -1 || vertexCount == 0) { continue; @@ -93,12 +95,13 @@ private static StaticNormalRelativeData fromMixed(int[] vertexCounts, if (count == 1) { TranslucentData.writeQuadVertexIndexes(indexBuffer, 0); + quadIndex++; } else { final var keys = new int[count]; final var perm = new int[count]; for (int idx = 0; idx < count; idx++) { - keys[idx] = MathUtil.floatToComparableInt(quads[idx].getAccurateDotProduct()); + keys[idx] = MathUtil.floatToComparableInt(quads[quadIndex++].getAccurateDotProduct()); perm[idx] = idx; } From b71db171b73caae975f2bf2c961f94b4fb5e379f Mon Sep 17 00:00:00 2001 From: JellySquid Date: Fri, 24 Jan 2025 17:02:25 -0600 Subject: [PATCH 083/215] Allow configuring the Radix sort to be stable The radix sort is inherently stable, but the fallback sort it uses on small arrays (or when memory cannot be allocated) was not. This fixes issues with Banner rendering in Minecraft, since the model uses many identical overlapping cuboids, which must retain their draw order. (cherry picked from commit 4222e5d0bd0829658541a2b1e679f93ba0ff34eb) --- .../bsp_tree/InnerPartitionBSPNode.java | 2 +- .../data/DynamicTopoData.java | 2 +- .../data/StaticNormalRelativeData.java | 4 +-- .../sodium/client/util/sorting/RadixSort.java | 31 ++++++++++++------- .../client/util/sorting/VertexSorters.java | 4 +-- 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java index 613dff0616..b2b3ef5fa7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java @@ -542,7 +542,7 @@ static private BSPNode buildSNRLeafNodeFromQuads(BSPWorkspace workspace, IntArra perm[i] = i; } - RadixSort.sortIndirect(perm, keys); + RadixSort.sortIndirect(perm, keys, false); for (int i = 0; i < indexCount; i++) { perm[i] = indexBuffer[perm[i]]; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java index 2156bb3283..7dfb9086e6 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java @@ -232,7 +232,7 @@ static void distanceSortDirect(IntBuffer indexBuffer, TQuad[] quads, Vector3fc c perm[idx] = idx; } - RadixSort.sortIndirect(perm, keys); + RadixSort.sortIndirect(perm, keys, false); for (int idx = 0; idx < quads.length; idx++) { TranslucentData.writeQuadVertexIndexes(indexBuffer, perm[idx]); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java index ceddad0c84..400db92500 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java @@ -53,7 +53,7 @@ private static StaticNormalRelativeData fromDoubleUnaligned(int[] vertexCounts, perm[q] = q; } - RadixSort.sortIndirect(perm, keys); + RadixSort.sortIndirect(perm, keys, false); for (int i = 0; i < quads.length; i++) { TranslucentData.writeQuadVertexIndexes(indexBuffer, perm[i]); @@ -105,7 +105,7 @@ private static StaticNormalRelativeData fromMixed(int[] vertexCounts, perm[idx] = idx; } - RadixSort.sortIndirect(perm, keys); + RadixSort.sortIndirect(perm, keys, false); for (int idx = 0; idx < count; idx++) { TranslucentData.writeQuadVertexIndexes(indexBuffer, perm[idx]); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java index f96a056572..36dddff6c3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java @@ -33,26 +33,29 @@ private static void prefixSums(int[][] offsets) { } /** - *

Sorts the specified array according to the natural ascending order using an unstable, out-of-place, indirect, - * 256-way LSD radix sort.

- * - *

This algorithm is well suited for large arrays of integers, especially when they are uniformly distributed - * over the entire range of the data type.

+ *

Sorts the specified array according to the natural ascending order using an out-of-place, indirect, + * 256-way LSD radix sort. This algorithm is well suited for large arrays of integers, especially when they are + * uniformly distributed over the entire range of the data type.

* *

This method implements an indirect sort. The elements of {@param perm} (which must be * exactly the numbers in the interval {@code [0..perm.length)}) will be permuted so that * {@code x[perm[i]] < x[perm[i + 1]]}.

* - *

While this radix sort is very fast on larger arrays, there is a certain amount of fixed cost involved in + *

While this implementation is very fast on larger arrays, there is a certain amount of fixed cost involved in * computing the histogram and prefix sums. Because of this, a fallback algorithm (currently quick sort) is used * for very small arrays to ensure this method performs well for all inputs of all sizes.

* + *

If {@param stable} is true, the sorting algorithm is defined to be stable, and duplicate keys will be + * returned in the ordering given by {@param keys}. Using a stable sort is slower than an unstable sort, but the + * overall time complexity and memory requirements are the same.

+ * * @param perm a permutation array indexing {@param keys}. * @param keys the array of elements to be sorted. + * @param stable whether the sort is allowed to re-order duplicate keys */ - public static void sortIndirect(final int[] perm, final int[] keys) { + public static void sortIndirect(final int[] perm, final int[] keys, boolean stable) { if (perm.length <= RADIX_SORT_THRESHOLD) { - smallSort(perm, keys); + smallSort(perm, keys, stable); return; } @@ -64,7 +67,7 @@ public static void sortIndirect(final int[] perm, final int[] keys) { next = new int[perm.length]; } catch (OutOfMemoryError oom) { // Not enough memory to perform an out-of-place sort, so use an in-place alternative. - fallbackInPlaceSort(perm, keys); + fallbackSort(perm, keys, stable); return; } @@ -102,17 +105,21 @@ private static void sortIndirect(final int[] perm, } } - private static void smallSort(int[] perm, int[] keys) { + private static void smallSort(int[] perm, int[] keys, boolean stable) { if (perm.length <= 1) { return; } - fallbackInPlaceSort(perm, keys); + fallbackSort(perm, keys, stable); } // Fallback sorting method which is guaranteed to be in-place and not require additional memory. - private static void fallbackInPlaceSort(int[] perm, int[] keys) { + private static void fallbackSort(int[] perm, int[] keys, boolean stable) { IntArrays.quickSortIndirect(perm, keys); + + if (stable) { + IntArrays.stabilize(perm, keys); + } } private static int extractDigit(int key, int digit) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java index fab533b5cc..46f13ce057 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java @@ -39,7 +39,7 @@ private abstract static class AbstractSorter implements VertexSortingExtended { perm[index] = index; } - RadixSort.sortIndirect(perm, keys); + RadixSort.sortIndirect(perm, keys, true); return perm; } @@ -134,7 +134,7 @@ public static int[] sort(ByteBuffer buffer, int vertexCount, int vertexStride, V pVertex2 += primitiveStride; } - RadixSort.sortIndirect(perm, keys); + RadixSort.sortIndirect(perm, keys, true); return perm; } From fad7d64bba832060fead3059d2b0a5a9bb2d56eb Mon Sep 17 00:00:00 2001 From: JellySquid Date: Fri, 24 Jan 2025 22:16:47 -0600 Subject: [PATCH 084/215] Ensure arrays are unreachable in Radix sort fallback code (cherry picked from commit 010838ad92706c5eb4bd26541789ad6addae61e4) --- .../mods/sodium/client/util/sorting/RadixSort.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java index 36dddff6c3..a8b0a0c8d5 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/RadixSort.java @@ -65,13 +65,20 @@ public static void sortIndirect(final int[] perm, final int[] keys, boolean stab try { offsets = new int[DIGIT_COUNT][BUCKET_COUNT]; next = new int[perm.length]; + + sortIndirect(perm, keys, offsets, next); } catch (OutOfMemoryError oom) { // Not enough memory to perform an out-of-place sort, so use an in-place alternative. + // If the array is very large (hence causing the memory allocation failure), the sort might take a while. + // We need to make sure the allocations are no longer reachable and can be immediately reclaimed. + + // noinspection UnusedAssignment + offsets = null; + // noinspection UnusedAssignment + next = null; + fallbackSort(perm, keys, stable); - return; } - - sortIndirect(perm, keys, offsets, next); } private static void sortIndirect(final int[] perm, From be875c6d21f8bfe28ba28b8c5bb11882fbb8b7b6 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sat, 25 Jan 2025 01:02:56 -0600 Subject: [PATCH 085/215] Add debug mode for detecting illegal off-thread accesses This helped catch some bugs in other mods caused by calling OpenGL functions from off-thread. However, it is aggressive about prohibiting calls to certain "problematic" render functions in the game, and makes code that would previously *sometimes* work instead *always* fail. To enable, add the line `mixin.debug.checks.threading=true` to the `sodium-mixins.properties` configuration file. We recommend that mod pack authors enable this during testing, and encourage feedback on any issues encountered. Though please keep in mind that just because another mod crashes with this option enabled, does not mean that mod is inherently faulty. (cherry picked from commit c510742c7a27d88f3f3f6523358398e3550701fd) Only hook RenderSystem#recordRenderCall in debug mode The other assertions are not necessary in newer versions, since they all check the thread now. (cherry picked from commit 214b0ad01d29b5f16dbf5e97d10e233969ec9689) --- .../client/data/config/MixinConfig.java | 1 + .../client/render/util/RenderAsserts.java | 3 ++- .../checks/threading/MixinRenderSystem.java | 19 +++++++++++++++++++ .../main/resources/sodium-common.mixins.json | 1 + 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/mixin/debug/checks/threading/MixinRenderSystem.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java index c4434f2105..327dcf5fe0 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java @@ -28,6 +28,7 @@ protected MixinConfig() { this.addMixinRule("core", true); // TODO: Don't actually allow the user to disable this this.addMixinRule("debug.checks", false); + this.addMixinRule("debug.checks.threading", false); this.addMixinRule("features", true); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/util/RenderAsserts.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/util/RenderAsserts.java index ea1ba9021c..a20c687c5b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/util/RenderAsserts.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/util/RenderAsserts.java @@ -12,7 +12,8 @@ public class RenderAsserts { */ public static boolean validateCurrentThread() { if (!RenderSystem.isOnRenderThread()) { - throw new IllegalStateException("Accessing OpenGL functions from outside the main render thread is not supported when using Sodium"); + throw new IllegalStateException("Tried to access render state from outside the main render thread! " + + "This was very likely caused by another misbehaving mod -- make sure to examine the stack trace below."); } return true; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/debug/checks/threading/MixinRenderSystem.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/debug/checks/threading/MixinRenderSystem.java new file mode 100644 index 0000000000..e0b027bd0e --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/debug/checks/threading/MixinRenderSystem.java @@ -0,0 +1,19 @@ +package net.caffeinemc.mods.sodium.mixin.debug.checks.threading; + +import com.mojang.blaze3d.pipeline.RenderCall; +import com.mojang.blaze3d.systems.RenderSystem; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; + +@Mixin(RenderSystem.class) +public abstract class MixinRenderSystem { + /** + * @author JellySquid + * @reason Disallow the use of RenderSystem.recordRenderCall entirely + */ + @Overwrite(remap = false) + public static void recordRenderCall(RenderCall call) { + throw new UnsupportedOperationException("Usage of RenderSystem#recordRenderCall is likely a bug, " + + "which is handled as an error when Sodium is enabled in debug mode"); + } +} diff --git a/common/src/main/resources/sodium-common.mixins.json b/common/src/main/resources/sodium-common.mixins.json index 8f898afb30..5faa555c2a 100644 --- a/common/src/main/resources/sodium-common.mixins.json +++ b/common/src/main/resources/sodium-common.mixins.json @@ -36,6 +36,7 @@ "core.world.map.ClientChunkCacheMixin", "core.world.map.ClientPacketListenerMixin", "core.world.map.ClientLevelMixin", + "debug.checks.threading.MixinRenderSystem", "features.gui.hooks.console.GameRendererMixin", "features.gui.hooks.debug.DebugScreenOverlayMixin", "features.gui.hooks.settings.OptionsScreenMixin", From a6ac4a27a43613a7772c8c60596152daf987fa93 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sun, 26 Jan 2025 20:23:39 +0100 Subject: [PATCH 086/215] Don't dump shaders by default in "developer" workspaces This would show up in *other* people's developer workspaces, which is very noisy and disruptive. Instead, add the Java property `sodium.debug.shaders.dump`, which can be configured via Java runtime arguments. (cherry picked from commit 194cb111f90a7c6ef17d30fe8f56a110461ff71d) --- .../sodium/client/gl/shader/ShaderLoader.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java index c5814c6322..2bc5e3d78b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderLoader.java @@ -1,13 +1,21 @@ package net.caffeinemc.mods.sodium.client.gl.shader; +import com.google.common.base.Objects; import net.minecraft.resources.ResourceLocation; import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; public class ShaderLoader { + private static final Logger LOGGER = LoggerFactory.getLogger("Sodium-ShaderLoader"); + + private static final boolean OPTION_DEBUG_SHADERS = + Objects.equal(System.getProperty("sodium.debug.shaders.dump", "false"), "true"); + /** * Creates an OpenGL shader from GLSL sources. The GLSL source file should be made available on the classpath at the * path of `/assets/{namespace}/shaders/{path}`. User defines can be used to declare variables in the shader source @@ -19,7 +27,14 @@ public class ShaderLoader { * @return An OpenGL shader object compiled with the given user defines */ public static GlShader loadShader(ShaderType type, ResourceLocation name, ShaderConstants constants) { - return new GlShader(type, name, ShaderParser.parseShader(getShaderSource(name), constants)); + var parsedShader = ShaderParser.parseShader(getShaderSource(name), constants); + + if (OPTION_DEBUG_SHADERS) { + LOGGER.info("Loaded shader {} with constants {}", name, constants); + LOGGER.info(parsedShader.src()); + } + + return new GlShader(type, name, parsedShader); } public static String getShaderSource(ResourceLocation name) { From 8a75d61336c28b2e531b694c8d093e1639118b59 Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 11 Feb 2025 03:55:00 +0100 Subject: [PATCH 087/215] Combine draw commands to improve rendering performance (#2421) (cherry picked from commit c83c3fb30df0a6529c482c98f8ceff0501018644) --- .../client/gl/buffer/IndexedVertexData.java | 16 -- .../render/chunk/DefaultChunkRenderer.java | 106 ++++++++---- .../render/chunk/RenderSectionManager.java | 23 ++- .../render/chunk/SharedQuadIndexBuffer.java | 17 +- .../chunk/compile/ChunkBuildBuffers.java | 90 ++++++---- .../render/chunk/compile/ChunkSortOutput.java | 36 ++-- .../tasks/ChunkBuilderMeshingTask.java | 19 ++- .../chunk/data/BuiltSectionMeshParts.java | 19 ++- .../chunk/data/SectionRenderDataStorage.java | 157 ++++++++++++++---- .../chunk/data/SectionRenderDataUnsafe.java | 70 +++++--- .../chunk/region/RenderRegionManager.java | 62 ++++--- .../chunk/translucent_sorting/SortType.java | 13 +- .../TranslucentGeometryCollector.java | 2 +- .../data/AnyOrderData.java | 16 +- .../data/DynamicSorter.java | 2 +- .../data/PresentSorter.java | 23 +++ .../data/SharedIndexSorter.java | 27 +++ .../translucent_sorting/data/SortData.java | 5 - .../translucent_sorting/data/Sorter.java | 17 +- .../data/StaticSorter.java | 2 +- 20 files changed, 475 insertions(+), 247 deletions(-) delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gl/buffer/IndexedVertexData.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/PresentSorter.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SharedIndexSorter.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SortData.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/buffer/IndexedVertexData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/buffer/IndexedVertexData.java deleted file mode 100644 index 12b879ba99..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/buffer/IndexedVertexData.java +++ /dev/null @@ -1,16 +0,0 @@ -package net.caffeinemc.mods.sodium.client.gl.buffer; - -import net.caffeinemc.mods.sodium.client.gl.attribute.GlVertexFormat; -import net.caffeinemc.mods.sodium.client.util.NativeBuffer; - -/** - * Helper type for tagging the vertex format alongside the raw buffer data. - */ -public record IndexedVertexData(GlVertexFormat vertexFormat, - NativeBuffer vertexBuffer, - NativeBuffer indexBuffer) { - public void delete() { - this.vertexBuffer.free(); - this.indexBuffer.free(); - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java index e2c9d16f8d..42e4a53ba8 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java @@ -1,7 +1,6 @@ package net.caffeinemc.mods.sodium.client.render.chunk; import net.caffeinemc.mods.sodium.client.SodiumClientMod; -import net.caffeinemc.mods.sodium.client.gl.attribute.GlVertexAttributeBinding; import net.caffeinemc.mods.sodium.client.gl.device.CommandList; import net.caffeinemc.mods.sodium.client.gl.device.DrawCommandList; import net.caffeinemc.mods.sodium.client.gl.device.MultiDrawBatch; @@ -16,7 +15,6 @@ import net.caffeinemc.mods.sodium.client.render.chunk.lists.ChunkRenderList; import net.caffeinemc.mods.sodium.client.render.chunk.lists.ChunkRenderListIterable; import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; -import net.caffeinemc.mods.sodium.client.render.chunk.shader.ChunkShaderBindingPoints; import net.caffeinemc.mods.sodium.client.render.chunk.shader.ChunkShaderInterface; import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior; @@ -73,7 +71,7 @@ public void render(ChunkRenderMatrices matrices, continue; } - fillCommandBuffer(this.batch, region, storage, renderList, camera, renderPass, useBlockFaceCulling); + fillCommandBuffer(this.batch, region, storage, renderList, camera, renderPass, useBlockFaceCulling, useIndexedTessellation); if (this.batch.isEmpty()) { continue; @@ -110,7 +108,8 @@ private static void fillCommandBuffer(MultiDrawBatch batch, ChunkRenderList renderList, CameraTransform camera, TerrainRenderPass pass, - boolean useBlockFaceCulling) { + boolean useBlockFaceCulling, + boolean useIndexedTessellation) { batch.clear(); var iterator = renderList.sectionsWithGeometryIterator(pass.isTranslucent()); @@ -150,30 +149,48 @@ private static void fillCommandBuffer(MultiDrawBatch batch, continue; } - if (pass.isTranslucent()) { - addIndexedDrawCommands(batch, pMeshData, slices); + // it's necessary to sometimes not the locally-indexed command generator even for indexed tessellations since + // sometimes the index buffer is shared, but not globally shared. This means that translucent sections that + // are sharing an index buffer amongst them need to use the shared index command generator since it sets the + // same element offset for each draw command and doesn't increment it. Recall that in each draw command the indexing + // of the elements needs to start at 0 and thus starting somewhere further into the shared index buffer is invalid. + // there's also the optimization that draw commands can be combined when using a shared index buffer, be it + // globally shared or just shared within the region, which isn't possible with the locally-indexed command generator. + if (useIndexedTessellation && SectionRenderDataUnsafe.isLocalIndex(pMeshData)) { + addLocalIndexedDrawCommands(batch, pMeshData, slices); } else { - addNonIndexedDrawCommands(batch, pMeshData, slices); + addSharedIndexedDrawCommands(batch, pMeshData, slices); } } } /** - * Generates the draw commands for a chunk's meshes using the shared index buffer. + * Generates the draw commands for a chunk's meshes, where each mesh has a separate index buffer. This is used + * when rendering translucent geometry, as each geometry set needs a sorted index buffer. */ @SuppressWarnings("IntegerMultiplicationImplicitCastToLong") - private static void addNonIndexedDrawCommands(MultiDrawBatch batch, long pMeshData, int mask) { + private static void addLocalIndexedDrawCommands(MultiDrawBatch batch, long pMeshData, int mask) { final var pElementPointer = batch.pElementPointer; final var pBaseVertex = batch.pBaseVertex; final var pElementCount = batch.pElementCount; int size = batch.size; + long elementOffset = SectionRenderDataUnsafe.getBaseElement(pMeshData); + long baseVertex = SectionRenderDataUnsafe.getBaseVertex(pMeshData); + for (int facing = 0; facing < ModelQuadFacing.COUNT; facing++) { - // Uint32 -> Int32 cast is always safe and should be optimized away - MemoryUtil.memPutInt(pBaseVertex + (size << 2), (int) SectionRenderDataUnsafe.getVertexOffset(pMeshData, facing)); - MemoryUtil.memPutInt(pElementCount + (size << 2), (int) SectionRenderDataUnsafe.getElementCount(pMeshData, facing)); - MemoryUtil.memPutAddress(pElementPointer + (size << Pointer.POINTER_SHIFT), 0 /* using a shared index buffer */); + final long vertexCount = SectionRenderDataUnsafe.getVertexCount(pMeshData, facing); + final long elementCount = (vertexCount >> 2) * 6; + + MemoryUtil.memPutInt(pElementCount + (size << 2), UInt32.uncheckedDowncast(elementCount)); + MemoryUtil.memPutInt(pBaseVertex + (size << 2), UInt32.uncheckedDowncast(baseVertex)); + + // * 4 to convert to bytes (the index buffer contains integers) + MemoryUtil.memPutAddress(pElementPointer + (size << Pointer.POINTER_SHIFT), elementOffset << 2); + + baseVertex += vertexCount; + elementOffset += elementCount; size += (mask >> facing) & 1; } @@ -182,34 +199,57 @@ private static void addNonIndexedDrawCommands(MultiDrawBatch batch, long pMeshDa } /** - * Generates the draw commands for a chunk's meshes, where each mesh has a separate index buffer. This is used - * when rendering translucent geometry, as each geometry set needs a sorted index buffer. + * Generates the draw commands for a chunk's meshes using the shared index buffer. */ @SuppressWarnings("IntegerMultiplicationImplicitCastToLong") - private static void addIndexedDrawCommands(MultiDrawBatch batch, long pMeshData, int mask) { + private static void addSharedIndexedDrawCommands(MultiDrawBatch batch, long pMeshData, int mask) { final var pElementPointer = batch.pElementPointer; final var pBaseVertex = batch.pBaseVertex; final var pElementCount = batch.pElementCount; - int size = batch.size; - - long elementOffset = SectionRenderDataUnsafe.getBaseElement(pMeshData); + // this is either zero (global shared index buffer) or the offset to the location of the shared element buffer (region shared index buffer) + final var elementOffsetBytes = SectionRenderDataUnsafe.getBaseElement(pMeshData) << 2; + final var facingList = SectionRenderDataUnsafe.getFacingList(pMeshData); - for (int facing = 0; facing < ModelQuadFacing.COUNT; facing++) { - final long vertexOffset = SectionRenderDataUnsafe.getVertexOffset(pMeshData, facing); - final long elementCount = SectionRenderDataUnsafe.getElementCount(pMeshData, facing); - - // Uint32 -> Int32 cast is always safe and should be optimized away - MemoryUtil.memPutInt(pBaseVertex + (size << 2), UInt32.uncheckedDowncast(vertexOffset)); - MemoryUtil.memPutInt(pElementCount + (size << 2), UInt32.uncheckedDowncast(elementCount)); + int size = batch.size; + long groupVertexCount = 0; + long baseVertex = SectionRenderDataUnsafe.getBaseVertex(pMeshData); + int lastMaskBit = 0; + + for (int i = 0; i <= ModelQuadFacing.COUNT; i++) { + var maskBit = 0; + long vertexCount = 0; + if (i < ModelQuadFacing.COUNT) { + vertexCount = SectionRenderDataUnsafe.getVertexCount(pMeshData, i); + + // if there's no vertexes, the mask bit is just 0 + if (vertexCount != 0) { + var facing = (facingList >>> (i * 8)) & 0xFF; + maskBit = (mask >>> facing) & 1; + } + } - // * 4 to convert to bytes (the index buffer contains integers) - // the section render data storage for the indices stores the offset in indices (also called elements) - MemoryUtil.memPutAddress(pElementPointer + (size << Pointer.POINTER_SHIFT), elementOffset << 2); + if (maskBit == 0) { + if (lastMaskBit == 1) { + // delay writing out draw command if there's a zero-size group + if (i < ModelQuadFacing.COUNT && vertexCount == 0) { + continue; + } + + MemoryUtil.memPutInt(pElementCount + (size << 2), UInt32.uncheckedDowncast((groupVertexCount >> 2) * 6)); + MemoryUtil.memPutInt(pBaseVertex + (size << 2), UInt32.uncheckedDowncast(baseVertex)); + MemoryUtil.memPutAddress(pElementPointer + (size << Pointer.POINTER_SHIFT), elementOffsetBytes); + size++; + baseVertex += groupVertexCount; + groupVertexCount = 0; + } + + baseVertex += vertexCount; + } else { + groupVertexCount += vertexCount; + } - // adding the number of elements works because the index data has one index per element (which are the indices) - elementOffset += elementCount; - size += (mask >> facing) & 1; + lastMaskBit = maskBit; } batch.size = size; @@ -224,7 +264,7 @@ private static void addIndexedDrawCommands(MultiDrawBatch batch, long pMeshData, private static final int MODEL_NEG_Y = ModelQuadFacing.NEG_Y.ordinal(); private static final int MODEL_NEG_Z = ModelQuadFacing.NEG_Z.ordinal(); - private static int getVisibleFaces(int originX, int originY, int originZ, int chunkX, int chunkY, int chunkZ) { + public static int getVisibleFaces(int originX, int originY, int originZ, int chunkX, int chunkY, int chunkZ) { // This is carefully written so that we can keep everything branch-less. // // Normally, this would be a ridiculous way to handle the problem. But the Hotspot VM's diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index 2644c5430f..bb9207197e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -328,9 +328,9 @@ private boolean processChunkBuildResults(ArrayList results) { result.render.setTranslucentData(chunkBuildOutput.translucentData); } } else if (result instanceof ChunkSortOutput sortOutput - && sortOutput.getTopoSorter() != null + && sortOutput.getDynamicSorter() != null && result.render.getTranslucentData() instanceof DynamicTopoData data) { - this.sortTriggering.applyTriggerChanges(data, sortOutput.getTopoSorter(), result.render.getPosition(), this.cameraPosition); + this.sortTriggering.applyTriggerChanges(data, sortOutput.getDynamicSorter(), result.render.getPosition(), this.cameraPosition); } var job = result.render.getTaskCancellationToken(); @@ -678,8 +678,10 @@ public Collection getDebugStrings() { int count = 0; - long deviceUsed = 0; - long deviceAllocated = 0; + long geometryDeviceUsed = 0; + long geometryDeviceAllocated = 0; + long indexDeviceUsed = 0; + long indexDeviceAllocated = 0; for (var region : this.regions.getLoadedRegions()) { var resources = region.getResources(); @@ -688,15 +690,20 @@ public Collection getDebugStrings() { continue; } - var buffer = resources.getGeometryArena(); + var geometryArena = resources.getGeometryArena(); + geometryDeviceUsed += geometryArena.getDeviceUsedMemory(); + geometryDeviceAllocated += geometryArena.getDeviceAllocatedMemory(); - deviceUsed += buffer.getDeviceUsedMemory(); - deviceAllocated += buffer.getDeviceAllocatedMemory(); + var indexArena = resources.getIndexArena(); + indexDeviceUsed += indexArena.getDeviceUsedMemory(); + indexDeviceAllocated += indexArena.getDeviceAllocatedMemory(); count++; } - list.add(String.format("Geometry Pool: %d/%d MiB (%d buffers)", MathUtil.toMib(deviceUsed), MathUtil.toMib(deviceAllocated), count)); + list.add(String.format("Pools: Geometry %d/%d MiB, Index %d/%d MiB (%d buffers)", + MathUtil.toMib(geometryDeviceUsed), MathUtil.toMib(geometryDeviceAllocated), + MathUtil.toMib(indexDeviceUsed), MathUtil.toMib(indexDeviceAllocated), count)); list.add(String.format("Transfer Queue: %s", this.regions.getStagingBuffer().toString())); list.add(String.format("Chunk Builder: Permits=%02d (E %03d) | Busy=%02d | Total=%02d", diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java index e21dc96476..8985b13244 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/SharedQuadIndexBuffer.java @@ -7,6 +7,7 @@ import net.caffeinemc.mods.sodium.client.gl.device.CommandList; import net.caffeinemc.mods.sodium.client.gl.tessellation.GlIndexType; import net.caffeinemc.mods.sodium.client.gl.util.EnumBitField; +import net.caffeinemc.mods.sodium.client.util.NativeBuffer; import java.nio.ByteBuffer; import java.nio.IntBuffer; @@ -55,6 +56,14 @@ private void grow(CommandList commandList, int primitiveCount) { this.maxPrimitives = primitiveCount; } + public static NativeBuffer createIndexBuffer(IndexType indexType, int primitiveCount) { + var bufferSize = primitiveCount * indexType.getBytesPerElement() * ELEMENTS_PER_PRIMITIVE; + var buffer = new NativeBuffer(bufferSize); + + indexType.createIndexBuffer(buffer.getDirectBuffer(), primitiveCount); + + return buffer; + } public GlBuffer getBufferObject() { return this.buffer; @@ -64,14 +73,6 @@ public void delete(CommandList commandList) { commandList.deleteBuffer(this.buffer); } - public GlIndexType getIndexFormat() { - return this.indexType.getFormat(); - } - - public IndexType getIndexType() { - return this.indexType; - } - public enum IndexType { SHORT(GlIndexType.UNSIGNED_SHORT, 64 * 1024) { @Override 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 eae5389deb..f76fe115d0 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 @@ -13,10 +13,6 @@ import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexType; import net.caffeinemc.mods.sodium.client.util.NativeBuffer; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - /** * A collection of temporary buffers for each worker thread which will be used to build chunk meshes for given render * passes. This makes a best-effort attempt to pick a suitable size for each scratch buffer, but will never try to @@ -60,47 +56,79 @@ public ChunkModelBuilder get(TerrainRenderPass pass) { * have been rendered to pass the finished meshes over to the graphics card. This function can be called multiple * times to return multiple copies. */ - public BuiltSectionMeshParts createMesh(TerrainRenderPass pass, boolean forceUnassigned) { + public BuiltSectionMeshParts createMesh(TerrainRenderPass pass, int visibleSlices, boolean forceUnassigned, boolean sliceReordering) { var builder = this.builders.get(pass); + int[] vertexSegments = new int[ModelQuadFacing.COUNT << 1]; + int vertexTotal = 0; - List vertexBuffers = new ArrayList<>(); - int[] vertexCounts = new int[ModelQuadFacing.COUNT]; + // get the total vertex count to initialize the buffer + for (ModelQuadFacing facing : ModelQuadFacing.VALUES) { + vertexTotal += builder.getVertexBuffer(facing).count(); + } - int vertexSum = 0; + if (vertexTotal == 0) { + return null; + } - for (ModelQuadFacing facing : ModelQuadFacing.VALUES) { - var ordinal = facing.ordinal(); - var buffer = builder.getVertexBuffer(facing); + var mergedBuffer = new NativeBuffer(vertexTotal * this.vertexType.getVertexFormat().getStride()); + var mergedBufferBuilder = mergedBuffer.getDirectBuffer(); - if (buffer.isEmpty()) { - continue; + if (sliceReordering) { + // sliceReordering implies !forceUnassigned + + // write all currently visible slices first, and then the rest. + // start with unassigned as it will never become invisible + var unassignedBuffer = builder.getVertexBuffer(ModelQuadFacing.UNASSIGNED); + int vertexSegmentCount = 0; + vertexSegments[vertexSegmentCount++] = unassignedBuffer.count(); + vertexSegments[vertexSegmentCount++] = ModelQuadFacing.UNASSIGNED.ordinal(); + if (!unassignedBuffer.isEmpty()) { + mergedBufferBuilder.put(unassignedBuffer.slice()); } - vertexBuffers.add(buffer.slice()); - var bufferCount = buffer.count(); - if (!forceUnassigned) { - vertexCounts[ordinal] = bufferCount; - } + // write all visible and then invisible slices + for (var step = 0; step < 2; step++) { + for (ModelQuadFacing facing : ModelQuadFacing.VALUES) { + var facingIndex = facing.ordinal(); + if (facing == ModelQuadFacing.UNASSIGNED || ((visibleSlices >> facingIndex) & 1) == step) { + continue; + } - vertexSum += bufferCount; - } + var buffer = builder.getVertexBuffer(facing); - if (vertexSum == 0) { - return null; - } + // generate empty ranges to prevent SectionRenderData storage from making up indexes for null ranges + vertexSegments[vertexSegmentCount++] = buffer.count(); + vertexSegments[vertexSegmentCount++] = facingIndex; - if (forceUnassigned) { - vertexCounts[ModelQuadFacing.UNASSIGNED.ordinal()] = vertexSum; - } + if (!buffer.isEmpty()) { + mergedBufferBuilder.put(buffer.slice()); + } + } + } + } else { + // forceUnassigned implies !sliceReordering - var mergedBuffer = new NativeBuffer(vertexSum * this.vertexType.getVertexFormat().getStride()); - var mergedBufferBuilder = mergedBuffer.getDirectBuffer(); + if (forceUnassigned) { + var segmentIndex = ModelQuadFacing.UNASSIGNED.ordinal() << 1; + vertexSegments[segmentIndex] = vertexTotal; + vertexSegments[segmentIndex + 1] = ModelQuadFacing.UNASSIGNED.ordinal(); + } - for (var buffer : vertexBuffers) { - mergedBufferBuilder.put(buffer); + for (ModelQuadFacing facing : ModelQuadFacing.VALUES) { + var buffer = builder.getVertexBuffer(facing); + if (!buffer.isEmpty()) { + if (!forceUnassigned) { + var facingIndex = facing.ordinal(); + var segmentIndex = facingIndex << 1; + vertexSegments[segmentIndex] = buffer.count(); + vertexSegments[segmentIndex + 1] = facingIndex; + } + mergedBufferBuilder.put(buffer.slice()); + } + } } - return new BuiltSectionMeshParts(mergedBuffer, vertexCounts); + return new BuiltSectionMeshParts(mergedBuffer, vertexSegments); } public void destroy() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java index 52236a161e..1e17585537 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java @@ -2,14 +2,11 @@ import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.DynamicTopoData; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.SortData; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.Sorter; -import net.caffeinemc.mods.sodium.client.util.NativeBuffer; -public class ChunkSortOutput extends BuilderTaskOutput implements SortData { - private NativeBuffer indexBuffer; +public class ChunkSortOutput extends BuilderTaskOutput { + private Sorter sorter; private boolean reuseUploadedIndexData; - private DynamicTopoData.DynamicTopoSorter topoSorter; public ChunkSortOutput(RenderSection render, int buildTime) { super(render, buildTime); @@ -17,41 +14,34 @@ public ChunkSortOutput(RenderSection render, int buildTime) { public ChunkSortOutput(RenderSection render, int buildTime, Sorter data) { this(render, buildTime); - this.copyResultFrom(data); + this.setSorter(data); } - public void copyResultFrom(Sorter sorter) { - this.indexBuffer = sorter.getIndexBuffer(); + public void setSorter(Sorter sorter) { + this.sorter = sorter; this.reuseUploadedIndexData = false; - if (sorter instanceof DynamicTopoData.DynamicTopoSorter topoSorterInstance) { - this.topoSorter = topoSorterInstance; - } } - public void markAsReusingUploadedData() { - this.reuseUploadedIndexData = true; + public Sorter getSorter() { + return this.sorter; } - @Override - public NativeBuffer getIndexBuffer() { - return this.indexBuffer; + public void markAsReusingUploadedData() { + this.reuseUploadedIndexData = true; } - @Override public boolean isReusingUploadedIndexData() { return this.reuseUploadedIndexData; } - public DynamicTopoData.DynamicTopoSorter getTopoSorter() { - return this.topoSorter; + public DynamicTopoData.DynamicTopoSorter getDynamicSorter() { + return this.sorter instanceof DynamicTopoData.DynamicTopoSorter dynamicSorter ? dynamicSorter : null; } - @Override public void destroy() { super.destroy(); - - if (this.indexBuffer != null) { - this.indexBuffer.free(); + if (this.sorter != null) { + this.sorter.destroy(); } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java index 64b85e6a70..198bddb109 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java @@ -2,6 +2,7 @@ import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; import net.caffeinemc.mods.sodium.client.SodiumClientMod; +import net.caffeinemc.mods.sodium.client.render.chunk.DefaultChunkRenderer; import net.caffeinemc.mods.sodium.client.render.chunk.ExtendedBlockEntityType; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildBuffers; @@ -82,7 +83,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke TranslucentGeometryCollector collector; if (SodiumClientMod.options().debug.getSortBehavior() != SortBehavior.OFF) { - collector = new TranslucentGeometryCollector(render.getPosition()); + collector = new TranslucentGeometryCollector(this.render.getPosition()); } else { collector = null; } @@ -144,7 +145,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke throw fillCrashInfo(CrashReport.forThrowable(ex, "Encountered exception while building chunk meshes"), slice, blockPos); } - PlatformLevelRenderHooks.INSTANCE.runChunkMeshAppenders(renderContext.getRenderers(), type -> buffers.get(DefaultMaterials.forRenderLayer(type)).asFallbackVertexConsumer(DefaultMaterials.forRenderLayer(type), collector), + PlatformLevelRenderHooks.INSTANCE.runChunkMeshAppenders(this.renderContext.getRenderers(), type -> buffers.get(DefaultMaterials.forRenderLayer(type)).asFallbackVertexConsumer(DefaultMaterials.forRenderLayer(type), collector), slice); blockRenderer.release(); @@ -155,11 +156,17 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke } Map meshes = new Reference2ReferenceOpenHashMap<>(); + var visibleSlices = DefaultChunkRenderer.getVisibleFaces( + (int) this.absoluteCameraPos.x(), (int) this.absoluteCameraPos.y(), (int) this.absoluteCameraPos.z(), + this.render.getChunkX(), this.render.getChunkY(), this.render.getChunkZ()); for (TerrainRenderPass pass : DefaultTerrainRenderPasses.ALL) { - // consolidate all translucent geometry into UNASSIGNED so that it's rendered - // all together if it needs to share an index buffer between the directions - BuiltSectionMeshParts mesh = buffers.createMesh(pass, pass.isTranslucent() && sortType.needsDirectionMixing); + // if the translucent geometry needs to share an index buffer between the directions, + // consolidate all translucent geometry into UNASSIGNED + boolean translucentBehavior = collector != null && pass.isTranslucent(); + boolean forceUnassigned = translucentBehavior && sortType.needsDirectionMixing; + boolean sliceReordering = !translucentBehavior || sortType.allowSliceReordering; + BuiltSectionMeshParts mesh = buffers.createMesh(pass, visibleSlices, forceUnassigned, sliceReordering); if (mesh != null) { meshes.put(pass, mesh); @@ -191,7 +198,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke } else if (translucentData instanceof PresentTranslucentData present) { var sorter = present.getSorter(); sorter.writeIndexBuffer(this, true); - output.copyResultFrom(sorter); + output.setSorter(sorter); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java index ec250afb86..21a1736347 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java @@ -1,13 +1,14 @@ package net.caffeinemc.mods.sodium.client.render.chunk.data; +import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; import net.caffeinemc.mods.sodium.client.util.NativeBuffer; public class BuiltSectionMeshParts { - private final int[] vertexCounts; + private final int[] vertexSegments; private final NativeBuffer buffer; public BuiltSectionMeshParts(NativeBuffer buffer, int[] vertexCounts) { - this.vertexCounts = vertexCounts; + this.vertexSegments = vertexCounts; this.buffer = buffer; } @@ -15,7 +16,17 @@ public NativeBuffer getVertexData() { return this.buffer; } - public int[] getVertexCounts() { - return this.vertexCounts; + public int[] getVertexSegments() { + return this.vertexSegments; + } + + public int[] computeVertexCounts() { + var vertexCounts = new int[ModelQuadFacing.COUNT]; + + for (int i = 0; i < this.vertexSegments.length; i += 2) { + vertexCounts[this.vertexSegments[i + 1]] = this.vertexSegments[i]; + } + + return vertexCounts; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java index bcc184f714..99fbe9e69a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java @@ -1,13 +1,18 @@ package net.caffeinemc.mods.sodium.client.render.chunk.data; +import net.caffeinemc.mods.sodium.client.gl.arena.GlBufferArena; import net.caffeinemc.mods.sodium.client.gl.arena.GlBufferSegment; +import net.caffeinemc.mods.sodium.client.gl.arena.PendingUpload; +import net.caffeinemc.mods.sodium.client.gl.device.CommandList; import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; +import net.caffeinemc.mods.sodium.client.render.chunk.SharedQuadIndexBuffer; import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; import net.caffeinemc.mods.sodium.client.util.UInt32; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Arrays; +import java.util.stream.Stream; /** * The section render data storage stores the gl buffer segments of uploaded @@ -17,20 +22,24 @@ * buffer segments is stored in a natively allocated piece of memory referenced * by {@code pMeshDataArray} and accessed through * {@link SectionRenderDataUnsafe}. - * + *

* When the backing buffer (from the gl buffer arena) is resized, the storage * object is notified and then it updates the changed offsets of the buffer * segments. Since the index data's size and alignment directly corresponds to * that of the vertex data except for the vertex/index scaling of two thirds, * only an offset to the index data within the index data buffer arena is * stored. - * + *

* Index and vertex data storage can be managed separately since they may be * updated independently of each other (in both directions). */ public class SectionRenderDataStorage { private final @Nullable GlBufferSegment[] vertexAllocations; - private final @Nullable GlBufferSegment @Nullable[] elementAllocations; + private final @Nullable GlBufferSegment @Nullable [] elementAllocations; + private @Nullable GlBufferSegment sharedIndexAllocation; + private int sharedIndexCapacity = 0; + private boolean needsSharedIndexUpdate = false; + private final int[] sharedIndexUsage = new int[RenderRegion.REGION_SIZE]; private final long pMeshDataArray; @@ -46,8 +55,7 @@ public SectionRenderDataStorage(boolean storesIndices) { this.pMeshDataArray = SectionRenderDataUnsafe.allocateHeap(RenderRegion.REGION_SIZE); } - public void setVertexData(int localSectionIndex, - GlBufferSegment allocation, int[] vertexCounts) { + public void setVertexData(int localSectionIndex, GlBufferSegment allocation, int[] vertexSegments) { GlBufferSegment prev = this.vertexAllocations[localSectionIndex]; if (prev != null) { @@ -59,25 +67,25 @@ public void setVertexData(int localSectionIndex, var pMeshData = this.getDataPointer(localSectionIndex); int sliceMask = 0; + long facingList = 0; - long vertexOffset = allocation.getOffset(); + for (int i = 0; i < ModelQuadFacing.COUNT; i++) { + var segmentIndex = i << 1; - for (int facingIndex = 0; facingIndex < ModelQuadFacing.COUNT; facingIndex++) { - long vertexCount = vertexCounts[facingIndex]; + int facing = vertexSegments[segmentIndex + 1]; + facingList |= (long) facing << (i * 8); - SectionRenderDataUnsafe.setVertexOffset(pMeshData, facingIndex, - UInt32.downcast(vertexOffset)); - SectionRenderDataUnsafe.setElementCount(pMeshData, facingIndex, - UInt32.downcast((vertexCount >> 2) * 6)); + long vertexCount = UInt32.upcast(vertexSegments[segmentIndex]); + SectionRenderDataUnsafe.setVertexCount(pMeshData, i, vertexCount); if (vertexCount > 0) { - sliceMask |= 1 << facingIndex; + sliceMask |= 1 << facing; } - - vertexOffset += vertexCount; } + SectionRenderDataUnsafe.setBaseVertex(pMeshData, allocation.getOffset()); SectionRenderDataUnsafe.setSliceMask(pMeshData, sliceMask); + SectionRenderDataUnsafe.setFacingList(pMeshData, facingList); } public void setIndexData(int localSectionIndex, GlBufferSegment allocation) { @@ -95,7 +103,88 @@ public void setIndexData(int localSectionIndex, GlBufferSegment allocation) { var pMeshData = this.getDataPointer(localSectionIndex); - SectionRenderDataUnsafe.setBaseElement(pMeshData, allocation.getOffset()); + SectionRenderDataUnsafe.setLocalBaseElement(pMeshData, allocation.getOffset()); + } + + public void setSharedIndexUsage(int localSectionIndex, int newUsage) { + var previousUsage = this.sharedIndexUsage[localSectionIndex]; + if (previousUsage == newUsage) { + return; + } + + // mark for update if usage is down from max (may need to shrink buffer) + // or if usage increased beyond the max (need to grow buffer) + if (newUsage < previousUsage && previousUsage == this.sharedIndexCapacity || + newUsage > this.sharedIndexCapacity || + newUsage > 0 && this.sharedIndexAllocation == null) { + this.needsSharedIndexUpdate = true; + } else { + // just set the base element since no update is happening + var sharedBaseElement = this.sharedIndexAllocation.getOffset(); + var pMeshData = this.getDataPointer(localSectionIndex); + SectionRenderDataUnsafe.setSharedBaseElement(pMeshData, sharedBaseElement); + } + + this.sharedIndexUsage[localSectionIndex] = newUsage; + } + + public boolean needsSharedIndexUpdate() { + return this.needsSharedIndexUpdate; + } + + /** + * Updates the shared index data buffer to match the current usage. + * + * @param arena The buffer arena to allocate the new buffer from + * @return true if the arena resized itself + */ + public boolean updateSharedIndexData(CommandList commandList, GlBufferArena arena) { + // assumes this.needsSharedIndexUpdate is true when this is called + this.needsSharedIndexUpdate = false; + + // determine the new required capacity + int newCapacity = 0; + for (int i = 0; i < RenderRegion.REGION_SIZE; i++) { + newCapacity = Math.max(newCapacity, this.sharedIndexUsage[i]); + } + if (newCapacity == this.sharedIndexCapacity) { + return false; + } + + this.sharedIndexCapacity = newCapacity; + + // remove the existing allocation and exit if we don't need to create a new one + if (this.sharedIndexAllocation != null) { + this.sharedIndexAllocation.delete(); + this.sharedIndexAllocation = null; + } + if (this.sharedIndexCapacity == 0) { + return false; + } + + // add some base-level capacity to avoid resizing the buffer too often + if (this.sharedIndexCapacity < 128) { + this.sharedIndexCapacity += 32; + } + + // create and upload a new shared index buffer + var buffer = SharedQuadIndexBuffer.createIndexBuffer(SharedQuadIndexBuffer.IndexType.INTEGER, this.sharedIndexCapacity); + var pendingUpload = new PendingUpload(buffer); + var bufferChanged = arena.upload(commandList, Stream.of(pendingUpload)); + this.sharedIndexAllocation = pendingUpload.getResult(); + buffer.free(); + + // only write the base elements now if we're not going to do so again later because of the buffer resize + if (!bufferChanged) { + var sharedBaseElement = this.sharedIndexAllocation.getOffset(); + for (int i = 0; i < RenderRegion.REGION_SIZE; i++) { + if (this.sharedIndexUsage[i] > 0) { + SectionRenderDataUnsafe.setSharedBaseElement(this.getDataPointer(i), sharedBaseElement); + } + } + } + + return bufferChanged; } public void removeData(int localSectionIndex) { @@ -104,6 +193,8 @@ public void removeData(int localSectionIndex) { if (this.elementAllocations != null) { this.removeIndexData(localSectionIndex); } + + this.setSharedIndexUsage(localSectionIndex, 0); } public void removeVertexData(int localSectionIndex) { @@ -127,7 +218,7 @@ private void removeVertexData(int localSectionIndex, boolean retainIndexData) { SectionRenderDataUnsafe.clear(pMeshData); if (retainIndexData) { - SectionRenderDataUnsafe.setBaseElement(pMeshData, baseElement); + SectionRenderDataUnsafe.setLocalBaseElement(pMeshData, baseElement); } } @@ -160,27 +251,27 @@ private void updateMeshes(int sectionIndex) { return; } - long offset = allocation.getOffset(); var data = this.getDataPointer(sectionIndex); - - for (int facing = 0; facing < ModelQuadFacing.COUNT; facing++) { - SectionRenderDataUnsafe.setVertexOffset(data, facing, offset); - - var count = SectionRenderDataUnsafe.getElementCount(data, facing); - offset += (count / 6) * 4; // convert elements back into vertices - } + long offset = allocation.getOffset(); + SectionRenderDataUnsafe.setBaseVertex(data, offset); } public void onIndexBufferResized() { - if (this.elementAllocations == null) { - return; + long sharedBaseElement = 0; + if (this.sharedIndexAllocation != null) { + sharedBaseElement = this.sharedIndexAllocation.getOffset(); } - for (int sectionIndex = 0; sectionIndex < RenderRegion.REGION_SIZE; sectionIndex++) { - var allocation = this.elementAllocations[sectionIndex]; + for (int i = 0; i < RenderRegion.REGION_SIZE; i++) { + if (this.sharedIndexUsage[i] > 0) { + // update index sharing sections to use the new shared index buffer's offset + SectionRenderDataUnsafe.setSharedBaseElement(this.getDataPointer(i), sharedBaseElement); + } else if (this.elementAllocations != null) { + var allocation = this.elementAllocations[i]; - if (allocation != null) { - SectionRenderDataUnsafe.setBaseElement(this.getDataPointer(sectionIndex), allocation.getOffset()); + if (allocation != null) { + SectionRenderDataUnsafe.setLocalBaseElement(this.getDataPointer(i), allocation.getOffset()); + } } } } @@ -196,6 +287,10 @@ public void delete() { deleteAllocations(this.elementAllocations); } + if (this.sharedIndexAllocation != null) { + this.sharedIndexAllocation.delete(); + } + SectionRenderDataUnsafe.freeHeap(this.pMeshDataArray); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java index 665973beff..63176b9ec2 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java @@ -16,15 +16,13 @@ // Please never try to write performance critical code in Java. This is what it will do to you. And you will still be // three times slower than the most naive solution in literally any other language that LLVM can compile. -// struct SectionRenderData { // 64 bytes -// base_element: u32 -// mask: u32, -// ranges: [VertexRange; 7] -// } -// -// struct VertexRange { // 8 bytes -// offset: u32, -// count: u32 +// struct SectionRenderData { // 48 bytes +// base_element: u32, +// base_vertex: u32, +// is_local_index: u8, +// facing_list: u56, +// slice_mask: u32, +// vertex_count: [u32; 7] // } public class SectionRenderDataUnsafe { @@ -35,12 +33,14 @@ public class SectionRenderDataUnsafe { * Otherwise, indices should be sourced from the index buffer for the render region using the specified offset. */ private static final long OFFSET_BASE_ELEMENT = 0; + private static final long OFFSET_BASE_VERTEX = 4; + private static final long OFFSET_FACING_LIST = 8; + private static final long OFFSET_IS_LOCAL_INDEX = 15; + private static final long OFFSET_SLICE_MASK = 16; + private static final long OFFSET_ELEMENT_COUNTS = 20; - private static final long OFFSET_SLICE_MASK = 4; - private static final long OFFSET_SLICE_RANGES = 8; - - private static final long ALIGNMENT = 64; - private static final long STRIDE = 64; // cache-line friendly! :) + private static final long ALIGNMENT = 8; // 8 byte (64 bit) alignment for reading longs + private static final long STRIDE = 48; public static long allocateHeap(int count) { final var bytes = STRIDE * count; @@ -63,6 +63,20 @@ public static long heapPointer(long ptr, int index) { return ptr + (index * STRIDE); } + public static void setLocalBaseElement(long ptr, long value /* Uint32 */) { + MemoryUtil.memPutInt(ptr + OFFSET_BASE_ELEMENT, UInt32.downcast(value)); + MemoryUtil.memPutByte(ptr + OFFSET_IS_LOCAL_INDEX, (byte) 1); + } + + public static void setSharedBaseElement(long ptr, long value /* Uint32 */) { + MemoryUtil.memPutInt(ptr + OFFSET_BASE_ELEMENT, UInt32.downcast(value)); + MemoryUtil.memPutByte(ptr + OFFSET_IS_LOCAL_INDEX, (byte) 0); + } + + public static long getBaseElement(long ptr) { + return Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + OFFSET_BASE_ELEMENT)); + } + public static void setSliceMask(long ptr, int value) { MemoryUtil.memPutInt(ptr + OFFSET_SLICE_MASK, value); } @@ -71,27 +85,31 @@ public static int getSliceMask(long ptr) { return MemoryUtil.memGetInt(ptr + OFFSET_SLICE_MASK); } - public static void setBaseElement(long ptr, long value) { - MemoryUtil.memPutInt(ptr + OFFSET_BASE_ELEMENT, UInt32.downcast(value)); + public static void setFacingList(long ptr, long facingList) { + MemoryUtil.memPutLong(ptr + OFFSET_FACING_LIST, facingList); } - public static long getBaseElement(long ptr) { - return Integer.toUnsignedLong(MemoryUtil.memGetInt(ptr + OFFSET_BASE_ELEMENT)); + public static long getFacingList(long ptr) { + return MemoryUtil.memGetLong(ptr + OFFSET_FACING_LIST); + } + + public static boolean isLocalIndex(long ptr) { + return MemoryUtil.memGetByte(ptr + OFFSET_IS_LOCAL_INDEX) != 0; } - public static void setVertexOffset(long ptr, int facing, long value /* Uint32 */) { - MemoryUtil.memPutInt(ptr + OFFSET_SLICE_RANGES + (facing * 8L) + 0L, UInt32.downcast(value)); + public static void setBaseVertex(long ptr, long value /* Uint32 */) { + MemoryUtil.memPutInt(ptr + OFFSET_BASE_VERTEX, UInt32.downcast(value)); } - public static long /* Uint32 */ getVertexOffset(long ptr, int facing) { - return UInt32.upcast(MemoryUtil.memGetInt(ptr + OFFSET_SLICE_RANGES + (facing * 8L) + 0L)); + public static long /* Uint32 */ getBaseVertex(long ptr) { + return UInt32.upcast(MemoryUtil.memGetInt(ptr + OFFSET_BASE_VERTEX)); } - public static void setElementCount(long ptr, int facing, long value /* Uint32 */) { - MemoryUtil.memPutInt(ptr + OFFSET_SLICE_RANGES + (facing * 8L) + 4L, UInt32.downcast(value)); + public static void setVertexCount(long ptr, int index, long count /* Uint32 */) { + MemoryUtil.memPutInt(ptr + OFFSET_ELEMENT_COUNTS + (index * 4), UInt32.downcast(count)); } - public static long /* Uint32 */ getElementCount(long ptr, int facing) { - return UInt32.upcast(MemoryUtil.memGetInt(ptr + OFFSET_SLICE_RANGES + (facing * 8L) + 4L)); + public static long /* Uint32 */ getVertexCount(long ptr, int index) { + return UInt32.upcast(MemoryUtil.memGetInt(ptr + OFFSET_ELEMENT_COUNTS + (index * 4))); } } \ No newline at end of file diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java index 71665e93a2..50495eac74 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java @@ -17,10 +17,13 @@ import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionMeshParts; 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.translucent_sorting.data.SharedIndexSorter; import org.jetbrains.annotations.NotNull; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; public class RenderRegionManager { private final Long2ReferenceOpenHashMap regions = new Long2ReferenceOpenHashMap<>(); @@ -80,30 +83,42 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect if (mesh != null) { uploads.add(new PendingSectionMeshUpload(result.render, mesh, pass, - new PendingUpload(mesh.getVertexData()))); + new PendingUpload(mesh.getVertexData()))); } } } if (result instanceof ChunkSortOutput indexDataOutput && !indexDataOutput.isReusingUploadedIndexData()) { - var buffer = indexDataOutput.getIndexBuffer(); - - // when a non-present TranslucentData is used like NoData, the indexBuffer is null - if (buffer == null) { - continue; - } + var sorter = indexDataOutput.getSorter(); + if (sorter instanceof SharedIndexSorter sharedIndexSorter) { + var storage = region.createStorage(DefaultTerrainRenderPasses.TRANSLUCENT); + storage.removeIndexData(renderSectionIndex); + storage.setSharedIndexUsage(renderSectionIndex, sharedIndexSorter.quadCount()); + } else { + var storage = region.getStorage(DefaultTerrainRenderPasses.TRANSLUCENT); + if (storage != null) { + storage.removeIndexData(renderSectionIndex); + storage.setSharedIndexUsage(renderSectionIndex, 0); + } - indexUploads.add(new PendingSectionIndexBufferUpload(result.render, new PendingUpload(buffer))); + if (sorter == null) { + continue; + } + // when a non-present TranslucentData is used like NoData, the indexBuffer is null + var buffer = sorter.getIndexBuffer(); + if (buffer == null) { + continue; + } - var storage = region.getStorage(DefaultTerrainRenderPasses.TRANSLUCENT); - if (storage != null) { - storage.removeIndexData(renderSectionIndex); + indexUploads.add(new PendingSectionIndexBufferUpload(result.render, new PendingUpload(buffer))); } } } // If we have nothing to upload, abort! - if (uploads.isEmpty() && indexUploads.isEmpty()) { + var translucentStorage = region.getStorage(DefaultTerrainRenderPasses.TRANSLUCENT); + var needsSharedIndexUpdate = translucentStorage != null && translucentStorage.needsSharedIndexUpdate(); + if (uploads.isEmpty() && indexUploads.isEmpty() && !needsSharedIndexUpdate) { return; } @@ -124,24 +139,30 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect for (PendingSectionMeshUpload upload : uploads) { var storage = region.createStorage(upload.pass); storage.setVertexData(upload.section.getSectionIndex(), - upload.vertexUpload.getResult(), upload.meshData.getVertexCounts()); + upload.vertexUpload.getResult(), upload.meshData.getVertexSegments()); } } + var indexBufferChanged = false; + if (!indexUploads.isEmpty()) { var arena = resources.getIndexArena(); - boolean bufferChanged = arena.upload(commandList, indexUploads.stream() + indexBufferChanged = arena.upload(commandList, indexUploads.stream() .map(upload -> upload.indexBufferUpload)); - if (bufferChanged) { - region.refreshIndexedTesselation(commandList); - } - for (PendingSectionIndexBufferUpload upload : indexUploads) { var storage = region.createStorage(DefaultTerrainRenderPasses.TRANSLUCENT); storage.setIndexData(upload.section.getSectionIndex(), upload.indexBufferUpload.getResult()); } } + + if (needsSharedIndexUpdate) { + indexBufferChanged |= translucentStorage.updateSharedIndexData(commandList, resources.getIndexArena()); + } + + if (indexBufferChanged) { + region.refreshIndexedTesselation(commandList); + } } private Reference2ReferenceMap.FastEntrySet> createMeshUploadQueues(Collection results) { @@ -196,7 +217,6 @@ private record PendingSectionMeshUpload(RenderSection section, BuiltSectionMeshP private record PendingSectionIndexBufferUpload(RenderSection section, PendingUpload indexBufferUpload) { } - private static StagingBuffer createStagingBuffer(CommandList commandList) { if (SodiumClientMod.options().advanced.useAdvancedStagingBuffers && MappedStagingBuffer.isSupported(RenderDevice.INSTANCE)) { return new MappedStagingBuffer(commandList); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java index d00713259a..2980f99365 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java @@ -8,17 +8,17 @@ public enum SortType { /** * The section is fully empty, no index buffer is needed. */ - EMPTY_SECTION(false), + EMPTY_SECTION(false, true), /** * The section has no translucent geometry, no index buffer is needed. */ - NO_TRANSLUCENT(false), + NO_TRANSLUCENT(false, true), /** * No sorting is required and the sort order doesn't matter. */ - NONE(false), + NONE(false, true), /** * There is only one sort order. No active sorting is required, but an initial @@ -45,8 +45,15 @@ public enum SortType { DYNAMIC(true); public final boolean needsDirectionMixing; + public final boolean allowSliceReordering; SortType(boolean needsDirectionMixing) { this.needsDirectionMixing = needsDirectionMixing; + this.allowSliceReordering = false; + } + + SortType(boolean needsDirectionMixing, boolean allowSliceReordering) { + this.needsDirectionMixing = needsDirectionMixing; + this.allowSliceReordering = allowSliceReordering; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java index 6c02bc1fa8..b753453a5b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java @@ -561,7 +561,7 @@ public TranslucentData getTranslucentData( return NoData.forNoTranslucent(this.sectionPos); } - var vertexCounts = translucentMesh.getVertexCounts(); + var vertexCounts = translucentMesh.computeVertexCounts(); // re-use the original translucent data if it's the same. This reduces the // amount of generated and uploaded index data when sections are rebuilt without diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java index 34f923ff02..a8fff490e0 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java @@ -46,21 +46,7 @@ public Sorter getSorter() { public static AnyOrderData fromMesh(int[] vertexCounts, TQuad[] quads, SectionPos sectionPos) { var anyOrderData = new AnyOrderData(sectionPos, vertexCounts, quads.length); - var sorter = new StaticSorter(quads.length); - anyOrderData.sorterOnce = sorter; - var indexBuffer = sorter.getIntBuffer(); - - for (var vertexCount : vertexCounts) { - if (vertexCount <= 0) { - continue; - } - - int count = TranslucentData.vertexCountToQuadCount(vertexCount); - for (int i = 0; i < count; i++) { - TranslucentData.writeQuadVertexIndexes(indexBuffer, i); - } - } - + anyOrderData.sorterOnce = new SharedIndexSorter(quads.length); return anyOrderData; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java index 87539c346d..d184fb4ad3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; -abstract class DynamicSorter extends Sorter { +abstract class DynamicSorter extends PresentSorter { private final int quadCount; DynamicSorter(int quadCount) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/PresentSorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/PresentSorter.java new file mode 100644 index 0000000000..81358a07a4 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/PresentSorter.java @@ -0,0 +1,23 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; + +import net.caffeinemc.mods.sodium.client.util.NativeBuffer; + +public abstract class PresentSorter implements Sorter { + private NativeBuffer indexBuffer; + + @Override + public NativeBuffer getIndexBuffer() { + return this.indexBuffer; + } + + void initBufferWithQuadLength(int quadCount) { + this.indexBuffer = new NativeBuffer(TranslucentData.quadCountToIndexBytes(quadCount)); + } + + @Override + public void destroy() { + if (this.indexBuffer != null) { + this.indexBuffer.free(); + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SharedIndexSorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SharedIndexSorter.java new file mode 100644 index 0000000000..9dc709a761 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SharedIndexSorter.java @@ -0,0 +1,27 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; + +import net.caffeinemc.mods.sodium.client.util.NativeBuffer; + +import java.nio.IntBuffer; + +public record SharedIndexSorter(int quadCount) implements Sorter { + @Override + public NativeBuffer getIndexBuffer() { + return null; + } + + @Override + public IntBuffer getIntBuffer() { + return null; + } + + @Override + public void writeIndexBuffer(CombinedCameraPos cameraPos, boolean initial) { + // no-op + } + + @Override + public void destroy() { + // no-op + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SortData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SortData.java deleted file mode 100644 index c543a82335..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SortData.java +++ /dev/null @@ -1,5 +0,0 @@ -package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; - -public interface SortData extends PresentSortData { - boolean isReusingUploadedIndexData(); -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/Sorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/Sorter.java index 1103773793..545e58a2b9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/Sorter.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/Sorter.java @@ -1,18 +1,7 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; -import net.caffeinemc.mods.sodium.client.util.NativeBuffer; +public interface Sorter extends PresentSortData { + void writeIndexBuffer(CombinedCameraPos cameraPos, boolean initial); -public abstract class Sorter implements PresentSortData { - private NativeBuffer indexBuffer; - - public abstract void writeIndexBuffer(CombinedCameraPos cameraPos, boolean initial); - - @Override - public NativeBuffer getIndexBuffer() { - return this.indexBuffer; - } - - void initBufferWithQuadLength(int quadCount) { - this.indexBuffer = new NativeBuffer(TranslucentData.quadCountToIndexBytes(quadCount)); - } + void destroy(); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticSorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticSorter.java index fe6bc4d133..cbc09e19ed 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticSorter.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticSorter.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; -class StaticSorter extends Sorter { +class StaticSorter extends PresentSorter { StaticSorter(int quadCount) { this.initBufferWithQuadLength(quadCount); } From 5e1ca24b25fd712aff6c192e98416997b595e56f Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 11 Feb 2025 04:03:24 +0100 Subject: [PATCH 088/215] Fix some issues with translucency sort triggering (#2997) * Fixed a bug with the plane-based triggering system where sections added later would not be added to the triggering system even though they should have been. This was caused by a buggy equals implementation in AlignableNormal. * Removed AlignableNormal and replaced it with a Vector3f & Int pair in NormalList and NormalPlanes, * Associated refactors to remove custom hashing strategy usage again. * Fixed the wrong classification of two unaligned normals as SNR-sortable. (cherry picked from commit 8735972d7995b87dda3dd288c0ed9da2ff83ae11) --- .../quad/properties/ModelQuadFacing.java | 1 + .../translucent_sorting/AlignableNormal.java | 94 ------------------- .../TranslucentGeometryCollector.java | 5 +- .../data/DynamicTopoData.java | 6 +- .../data/TopoGraphSorting.java | 16 ++-- .../trigger/GFNITriggers.java | 14 +-- .../trigger/GeometryPlanes.java | 58 ++++++++---- .../translucent_sorting/trigger/Group.java | 7 +- .../trigger/NormalList.java | 69 ++++++++++---- .../trigger/NormalPlanes.java | 40 ++++---- .../trigger/SortTriggering.java | 19 ++-- 11 files changed, 144 insertions(+), 185 deletions(-) delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/AlignableNormal.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/properties/ModelQuadFacing.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/properties/ModelQuadFacing.java index 7c357054f7..026b438459 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/properties/ModelQuadFacing.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/properties/ModelQuadFacing.java @@ -23,6 +23,7 @@ public enum ModelQuadFacing { public static final int COUNT = VALUES.length; public static final int DIRECTIONS = VALUES.length - 1; + public static final int UNASSIGNED_ORDINAL = ModelQuadFacing.UNASSIGNED.ordinal(); public static final int NONE = 0; public static final int ALL = (1 << COUNT) - 1; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/AlignableNormal.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/AlignableNormal.java deleted file mode 100644 index ef492d0f1a..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/AlignableNormal.java +++ /dev/null @@ -1,94 +0,0 @@ -package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting; - -import org.joml.Vector3f; -import org.joml.Vector3fc; - -import it.unimi.dsi.fastutil.floats.FloatArrays; -import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; - -/** - * A normal vector that has additional information about its alignment. This is - * useful for better hashing and telling other code that the normal is aligned, - * which in turn enables many optimizations and fast paths to be taken. - */ -public class AlignableNormal extends Vector3f { - private static final AlignableNormal[] NORMALS = new AlignableNormal[ModelQuadFacing.DIRECTIONS]; - - static { - for (int i = 0; i < ModelQuadFacing.DIRECTIONS; i++) { - NORMALS[i] = new AlignableNormal(ModelQuadFacing.ALIGNED_NORMALS[i], i); - } - } - - private static final int UNASSIGNED = ModelQuadFacing.UNASSIGNED.ordinal(); - private final int alignedDirection; - - private AlignableNormal(Vector3fc v, int alignedDirection) { - super(v); - this.alignedDirection = alignedDirection; - } - - public static AlignableNormal fromAligned(int alignedDirection) { - return NORMALS[alignedDirection]; - } - - public static AlignableNormal fromUnaligned(Vector3fc v) { - return new AlignableNormal(v, UNASSIGNED); - } - - public int getAlignedDirection() { - return this.alignedDirection; - } - - public boolean isAligned() { - return this.alignedDirection != UNASSIGNED; - } - - @Override - public int hashCode() { - if (this.isAligned()) { - return this.alignedDirection; - } else { - return super.hashCode() + ModelQuadFacing.DIRECTIONS; - } - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!super.equals(obj)) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - AlignableNormal other = (AlignableNormal) obj; - return this.alignedDirection == other.alignedDirection; - } - - public static boolean queryRange(float[] sortedDistances, float start, float end) { - // test that there is actually an entry in the query range - int result = FloatArrays.binarySearch(sortedDistances, start); - if (result < 0) { - // recover the insertion point - int insertionPoint = -result - 1; - if (insertionPoint >= sortedDistances.length) { - // no entry in the query range - return false; - } - - // check if the entry at the insertion point, which is the next one greater than - // the start value, is less than or equal to the end value - if (sortedDistances[insertionPoint] <= end) { - // there is an entry in the query range - return true; - } - } else { - // exact match, trigger - return true; - } - return false; - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java index b753453a5b..f78aff1028 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java @@ -52,6 +52,7 @@ public class TranslucentGeometryCollector { // true if there are any unaligned quads private boolean hasUnaligned = false; + private int untrackedUnalignedNormalCount = 0; // a bitmap of the aligned facings present in the section private int alignedFacingBitmap = 0; @@ -273,6 +274,8 @@ public void appendQuad(int packedNormal, ChunkVertexEncoder.Vertex[] vertices, M } else if (this.unalignedBNormal == -1) { this.unalignedBNormal = packedNormal; this.unalignedBDistance1 = distance; + } else { + this.untrackedUnalignedNormalCount++; } } } @@ -356,7 +359,7 @@ private SortType sortTypeHeuristic() { int alignedNormalCount = Integer.bitCount(this.alignedFacingBitmap); int planeCount = getPlaneCount(alignedNormalCount); - int unalignedNormalCount = 0; + int unalignedNormalCount = this.untrackedUnalignedNormalCount; if (this.unalignedANormal != -1) { unalignedNormalCount++; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java index 7dfb9086e6..68c1bf3a12 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; -import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TQuad; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.GeometryPlanes; import net.caffeinemc.mods.sodium.client.util.sorting.RadixSort; @@ -42,11 +42,11 @@ public class DynamicTopoData extends DynamicData { private boolean pendingTriggerIsDirect; private final TQuad[] quads; - private final Object2ReferenceOpenHashMap distancesByNormal; + private final Object2ReferenceMap distancesByNormal; private DynamicTopoData(SectionPos sectionPos, int vertexCount, TQuad[] quads, GeometryPlanes geometryPlanes, Vector3dc initialCameraPos, - Object2ReferenceOpenHashMap distancesByNormal) { + Object2ReferenceMap distancesByNormal) { super(sectionPos, vertexCount, quads.length, geometryPlanes, initialCameraPos); this.quads = quads; this.distancesByNormal = distancesByNormal; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TopoGraphSorting.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TopoGraphSorting.java index eb8e9b3dea..478f51362a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TopoGraphSorting.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TopoGraphSorting.java @@ -1,9 +1,9 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; -import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.AlignableNormal; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TQuad; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.NormalList; import net.caffeinemc.mods.sodium.client.util.collections.BitArray; import org.joml.Vector3fc; @@ -92,17 +92,17 @@ public static boolean orthogonalQuadVisibleThrough(TQuad quadA, TQuad quadB) { return vis; } - private static boolean testSeparatorRange(Object2ReferenceOpenHashMap distancesByNormal, + private static boolean testSeparatorRange(Object2ReferenceMap distancesByNormal, Vector3fc normal, float start, float end) { var distances = distancesByNormal.get(normal); if (distances == null) { return false; } - return AlignableNormal.queryRange(distances, start, end); + return NormalList.queryRange(distances, start, end); } private static boolean visibilityWithSeparator(TQuad quadA, TQuad quadB, - Object2ReferenceOpenHashMap distancesByNormal, Vector3fc cameraPos) { + Object2ReferenceMap distancesByNormal, Vector3fc cameraPos) { // check if there is an aligned separator for (int direction = 0; direction < ModelQuadFacing.DIRECTIONS; direction++) { var facing = ModelQuadFacing.VALUES[direction]; @@ -163,7 +163,7 @@ private static boolean visibilityWithSeparator(TQuad quadA, TQuad quadB, * @return true if the other quad is visible through the first quad */ private static boolean quadVisibleThrough(TQuad quad, TQuad other, - Object2ReferenceOpenHashMap distancesByNormal, Vector3fc cameraPos) { + Object2ReferenceMap distancesByNormal, Vector3fc cameraPos) { if (quad == other) { return false; } @@ -250,7 +250,7 @@ private static boolean quadVisibleThrough(TQuad quad, TQuad other, */ public static boolean topoGraphSort( IntConsumer indexConsumer, TQuad[] allQuads, - Object2ReferenceOpenHashMap distancesByNormal, + Object2ReferenceMap distancesByNormal, Vector3fc cameraPos) { // if enabled, check for visibility and produce a mapping of indices TQuad[] quads; @@ -284,7 +284,7 @@ public static boolean topoGraphSort( return topoGraphSort(indexConsumer, quads, quadCount, activeToRealIndex, distancesByNormal, cameraPos); } - public static boolean topoGraphSort(IntConsumer indexConsumer, TQuad[] quads, int quadCount, int[] activeToRealIndex, Object2ReferenceOpenHashMap distancesByNormal, Vector3fc cameraPos) { + public static boolean topoGraphSort(IntConsumer indexConsumer, TQuad[] quads, int quadCount, int[] activeToRealIndex, Object2ReferenceMap distancesByNormal, Vector3fc cameraPos) { // special case for 0 to 2 quads if (quadCount == 0) { return true; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GFNITriggers.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GFNITriggers.java index ee3f591f42..61ab847648 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GFNITriggers.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GFNITriggers.java @@ -1,12 +1,12 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger; -import org.joml.Vector3fc; - +import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.DynamicData; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.TranslucentData; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.SortTriggering.SectionTriggers; import net.minecraft.core.SectionPos; +import org.joml.Vector3fc; /** * Performs triggering based on globally-indexed face planes, bucketed by their normals. @@ -20,7 +20,7 @@ class GFNITriggers implements SectionTriggers { /** * A map of all the normal lists, indexed by their normal. */ - private Object2ReferenceOpenHashMap normalLists = new Object2ReferenceOpenHashMap<>(); + private final Object2ReferenceMap normalLists = new Object2ReferenceOpenHashMap<>(); int getUniqueNormalCount() { return this.normalLists.size(); @@ -33,11 +33,11 @@ public void processTriggers(SortTriggering ts, CameraMovement movement) { } } - private void addSectionInNewNormalLists(DynamicData dynamicData, NormalPlanes normalPlanes) { + private void addSectionInNewNormalLists(NormalPlanes normalPlanes) { var normal = normalPlanes.normal; var normalList = this.normalLists.get(normal); if (normalList == null) { - normalList = new NormalList(normal); + normalList = new NormalList(normal, normalPlanes.alignedDirection); this.normalLists.put(normal, normalList); normalList.addSection(normalPlanes, normalPlanes.sectionPos.asLong()); } @@ -94,14 +94,14 @@ public void integrateSection(SortTriggering ts, SectionPos pos, DynamicData data if (aligned != null) { for (var normalPlane : aligned) { if (normalPlane != null) { - this.addSectionInNewNormalLists(data, normalPlane); + this.addSectionInNewNormalLists(normalPlane); } } } var unaligned = geometryPlanes.getUnaligned(); if (unaligned != null) { for (var normalPlane : unaligned) { - this.addSectionInNewNormalLists(data, normalPlane); + this.addSectionInNewNormalLists(normalPlane); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GeometryPlanes.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GeometryPlanes.java index 16acdf0230..0076a04e2c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GeometryPlanes.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GeometryPlanes.java @@ -1,13 +1,14 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger; -import java.util.Collection; - -import org.joml.Vector3fc; - +import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TQuad; import net.minecraft.core.SectionPos; +import org.joml.Vector3f; +import org.joml.Vector3fc; + +import java.util.Collection; /** * GeometryPlanes stores the NormalPlanes for different normals, both aligned @@ -15,7 +16,8 @@ */ public class GeometryPlanes { private NormalPlanes[] alignedPlanes; - private Object2ReferenceOpenHashMap unalignedPlanes; + private Object2ReferenceMap unalignedPlanes; + private final Vector3f unalignedNormalScratch = new Vector3f(); public NormalPlanes[] getAligned() { return this.alignedPlanes; @@ -35,27 +37,20 @@ public Collection getUnaligned() { return this.unalignedPlanes.values(); } - public Object2ReferenceOpenHashMap getUnalignedOrCreate() { + public Object2ReferenceMap getUnalignedOrCreate() { if (this.unalignedPlanes == null) { this.unalignedPlanes = new Object2ReferenceOpenHashMap<>(); } return this.unalignedPlanes; } - public Collection getUnalignedNormals() { - if (this.unalignedPlanes == null) { - return null; - } - return this.unalignedPlanes.keySet(); - } - NormalPlanes getPlanesForNormal(NormalList normalList) { var normal = normalList.getNormal(); - if (normal.isAligned()) { + if (normalList.isAligned()) { if (this.alignedPlanes == null) { return null; } - return this.alignedPlanes[normal.getAlignedDirection()]; + return this.alignedPlanes[normalList.getAlignedDirection()]; } else { if (this.unalignedPlanes == null) { return null; @@ -81,14 +76,37 @@ public void addDoubleSidedPlane(SectionPos sectionPos, int axis, float distance) public void addUnalignedPlane(SectionPos sectionPos, Vector3fc normal, float distance) { var unalignedDistances = this.getUnalignedOrCreate(); - var normalPlanes = unalignedDistances.get(normal); + + // create a copy of the vector where -0 is turned into +0, + // this avoids non-equality when comparing normals where just the sign on 0 is different + var cleanedNormal = this.cleanNormal(normal); + + var normalPlanes = unalignedDistances.get(cleanedNormal); if (normalPlanes == null) { - normalPlanes = new NormalPlanes(sectionPos, normal); - unalignedDistances.put(normal, normalPlanes); + normalPlanes = new NormalPlanes(sectionPos, new Vector3f(normal)); + + // NOTE: importantly use the cleaned normal here, not the cleanedNormal, which is mutable + unalignedDistances.put(normalPlanes.normal, normalPlanes); } normalPlanes.addPlaneMember(distance); } + private Vector3f cleanNormal(Vector3fc normal) { + var cleanedNormal = this.unalignedNormalScratch.set(normal); + + // convert any occurrences of 0 into +0, note that -0.0f == 0.0f + if (cleanedNormal.x == 0.0f) { + cleanedNormal.x = 0.0f; + } + if (cleanedNormal.y == 0.0f) { + cleanedNormal.y = 0.0f; + } + if (cleanedNormal.z == 0.0f) { + cleanedNormal.z = 0.0f; + } + return cleanedNormal; + } + public void addQuadPlane(SectionPos sectionPos, TQuad quad) { var facing = quad.useQuantizedFacing(); if (facing.isAligned()) { @@ -98,7 +116,7 @@ public void addQuadPlane(SectionPos sectionPos, TQuad quad) { } } - private void prepareAndInsert(Object2ReferenceOpenHashMap distancesByNormal) { + private void prepareAndInsert(Object2ReferenceMap distancesByNormal) { if (this.alignedPlanes != null) { for (var normalPlanes : this.alignedPlanes) { if (normalPlanes != null) { @@ -117,7 +135,7 @@ public void prepareIntegration() { this.prepareAndInsert(null); } - public Object2ReferenceOpenHashMap prepareAndGetDistances() { + public Object2ReferenceMap prepareAndGetDistances() { var distancesByNormal = new Object2ReferenceOpenHashMap(10); this.prepareAndInsert(distancesByNormal); return distancesByNormal; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/Group.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/Group.java index d669504b95..d5bcc8b1f4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/Group.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/Group.java @@ -1,8 +1,7 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger; import net.caffeinemc.mods.sodium.client.util.interval_tree.DoubleInterval; - -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.AlignableNormal; +import org.joml.Vector3fc; /** * A group represents a set of face planes of the same normal within a section. @@ -33,7 +32,7 @@ class Group { double baseDistance; - AlignableNormal normal; + Vector3fc normal; Group(NormalPlanes normalPlanes) { this.replaceWith(normalPlanes); @@ -50,7 +49,7 @@ void replaceWith(NormalPlanes normalPlanes) { private boolean planeTriggered(double start, double end) { return start < this.distances.getEnd() && end > this.distances.getStart() - && AlignableNormal.queryRange(this.facePlaneDistances, + && NormalList.queryRange(this.facePlaneDistances, (float) (start - this.baseDistance), (float) (end - this.baseDistance)); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalList.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalList.java index b1ed8928d7..35f3b36271 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalList.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalList.java @@ -1,20 +1,19 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger; -import java.util.Collection; - -import org.joml.Math; -import org.joml.Vector3dc; - +import it.unimi.dsi.fastutil.floats.FloatArrays; +import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceArraySet; +import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; +import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; import net.caffeinemc.mods.sodium.client.util.interval_tree.DoubleInterval; import net.caffeinemc.mods.sodium.client.util.interval_tree.Interval; import net.caffeinemc.mods.sodium.client.util.interval_tree.Interval.Bounded; import net.caffeinemc.mods.sodium.client.util.interval_tree.IntervalTree; +import org.joml.Vector3dc; +import org.joml.Vector3fc; -import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; -import it.unimi.dsi.fastutil.objects.ReferenceArraySet; -import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.AlignableNormal; +import java.util.Collection; /** * A normal list contains all the face planes that have the same normal. @@ -35,7 +34,8 @@ public class NormalList { /** * The normal of this normal list. */ - private final AlignableNormal normal; + private final Vector3fc normal; + private final int alignedDirection; /** * An interval tree of group intervals. Since this only stores intervals, the @@ -61,18 +61,27 @@ public class NormalList { * * @param normal The unit normal vector */ - NormalList(AlignableNormal normal) { + NormalList(Vector3fc normal, int alignedDirection) { this.normal = normal; + this.alignedDirection = alignedDirection; } - public AlignableNormal getNormal() { + public Vector3fc getNormal() { return this.normal; } + public boolean isAligned() { + return this.alignedDirection != ModelQuadFacing.UNASSIGNED_ORDINAL; + } + + public int getAlignedDirection() { + return this.alignedDirection; + } + private double normalDotDouble(Vector3dc v) { - return org.joml.Math.fma(this.normal.x, v.x(), - org.joml.Math.fma(this.normal.y, v.y(), - this.normal.z * v.z())); + return org.joml.Math.fma(this.normal.x(), v.x(), + org.joml.Math.fma(this.normal.y(), v.y(), + this.normal.z() * v.z())); } void processMovement(SortTriggering ts, CameraMovement movement) { @@ -89,8 +98,8 @@ void processMovement(SortTriggering ts, CameraMovement movement) { // perform the interval query on the group intervals and resolve each interval // to the collection of groups it maps to var interval = new DoubleInterval(start, end, Bounded.CLOSED); - for (Interval groupInterval : intervalTree.query(interval)) { - for (Group group : groupsByInterval.get(groupInterval)) { + for (Interval groupInterval : this.intervalTree.query(interval)) { + for (Group group : this.groupsByInterval.get(groupInterval)) { group.triggerRange(ts, start, end); } } @@ -174,4 +183,28 @@ void updateSection(NormalPlanes normalPlanes, long sectionPos) { group.replaceWith(normalPlanes); this.addGroupInterval(group); } + + public static boolean queryRange(float[] sortedDistances, float start, float end) { + // test that there is actually an entry in the query range + int result = FloatArrays.binarySearch(sortedDistances, start); + if (result < 0) { + // recover the insertion point + int insertionPoint = -result - 1; + if (insertionPoint >= sortedDistances.length) { + // no entry in the query range + return false; + } + + // check if the entry at the insertion point, which is the next one greater than + // the start value, is less than or equal to the end value + if (sortedDistances[insertionPoint] <= end) { + // there is an entry in the query range + return true; + } + } else { + // exact match, trigger + return true; + } + return false; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalPlanes.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalPlanes.java index 2ca59ea98f..4f84d50a82 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalPlanes.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalPlanes.java @@ -1,15 +1,14 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger; -import org.joml.Vector3fc; -import java.util.Arrays; - +import it.unimi.dsi.fastutil.floats.FloatOpenHashSet; +import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; +import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; import net.caffeinemc.mods.sodium.client.util.interval_tree.DoubleInterval; import net.caffeinemc.mods.sodium.client.util.interval_tree.Interval.Bounded; - -import it.unimi.dsi.fastutil.floats.FloatOpenHashSet; -import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.AlignableNormal; import net.minecraft.core.SectionPos; +import org.joml.Vector3fc; + +import java.util.Arrays; /** * NormalPlanes represents planes by a normal and a list of distances. Initially they're @@ -17,7 +16,10 @@ */ public class NormalPlanes { final FloatOpenHashSet relativeDistancesSet = new FloatOpenHashSet(16); - final AlignableNormal normal; + + final Vector3fc normal; + final int alignedDirection; + final SectionPos sectionPos; float[] relativeDistances; // relative to the base distance @@ -25,25 +27,23 @@ public class NormalPlanes { long relDistanceHash; double baseDistance; - private NormalPlanes(SectionPos sectionPos, AlignableNormal normal) { + private NormalPlanes(SectionPos sectionPos, Vector3fc normal, int alignedDirection) { this.sectionPos = sectionPos; + this.normal = normal; + this.alignedDirection = alignedDirection; } public NormalPlanes(SectionPos sectionPos, Vector3fc normal) { - this(sectionPos, AlignableNormal.fromUnaligned(normal)); + this(sectionPos, normal, ModelQuadFacing.UNASSIGNED_ORDINAL); } public NormalPlanes(SectionPos sectionPos, int alignedDirection) { - this(sectionPos, AlignableNormal.fromAligned(alignedDirection)); - } - - boolean addPlaneMember(float vertexX, float vertexY, float vertexZ) { - return this.addPlaneMember(this.normal.dot(vertexX, vertexY, vertexZ)); + this(sectionPos, ModelQuadFacing.ALIGNED_NORMALS[alignedDirection], alignedDirection); } - public boolean addPlaneMember(float distance) { - return this.relativeDistancesSet.add(distance); + public void addPlaneMember(float distance) { + this.relativeDistancesSet.add(distance); } public void prepareIntegration() { @@ -64,17 +64,17 @@ public void prepareIntegration() { } // sort the array ascending - Arrays.sort(relativeDistances); + Arrays.sort(this.relativeDistances); this.baseDistance = this.normal.dot( - sectionPos.minBlockX(), sectionPos.minBlockY(), sectionPos.minBlockZ()); + this.sectionPos.minBlockX(), this.sectionPos.minBlockY(), this.sectionPos.minBlockZ()); this.distanceRange = new DoubleInterval( this.relativeDistances[0] + this.baseDistance, this.relativeDistances[size - 1] + this.baseDistance, Bounded.CLOSED); } - public void prepareAndInsert(Object2ReferenceOpenHashMap distancesByNormal) { + public void prepareAndInsert(Object2ReferenceMap distancesByNormal) { this.prepareIntegration(); if (distancesByNormal != null) { distancesByNormal.put(this.normal, this.relativeDistances); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java index b5c33809c5..f28b478d1a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java @@ -1,19 +1,18 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger; -import java.util.List; -import java.util.function.BiConsumer; - -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.DynamicData; -import org.joml.Vector3dc; - import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.caffeinemc.mods.sodium.client.SodiumClientMod; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.AlignableNormal; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortType; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.DynamicData; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.DynamicTopoData; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.TranslucentData; import net.minecraft.core.SectionPos; +import org.joml.Vector3dc; +import org.joml.Vector3fc; + +import java.util.List; +import java.util.function.BiConsumer; /** * This class is a central point in translucency sorting. It counts the number @@ -49,7 +48,7 @@ public class SortTriggering { */ private int gfniTriggerCount = 0; private int directTriggerCount = 0; - private final ObjectOpenHashSet triggeredNormals = new ObjectOpenHashSet<>(); + private final ObjectOpenHashSet triggeredNormals = new ObjectOpenHashSet<>(); private int triggeredNormalCount = 0; /** @@ -99,7 +98,7 @@ private boolean isCatchingUp() { return this.catchupData != null; } - void triggerSectionGFNI(long sectionPos, AlignableNormal normal) { + void triggerSectionGFNI(long sectionPos, Vector3fc normal) { if (this.isCatchingUp()) { this.triggerSectionCatchup(sectionPos, false); return; From 99b184367ecb632283e5942acd943495946efd17 Mon Sep 17 00:00:00 2001 From: "Ryan G." Date: Mon, 10 Feb 2025 22:11:00 -0500 Subject: [PATCH 089/215] Add an epsilon to section distance check for oversized block models (#2823) The 0.125 epsilon for floating point error is likely unnecessary for this comparison, unlike the frustum cull. (cherry picked from commit 5639fd82759de7a9f8c5eb80b9ea5cab0a6cbd70) --- .../client/render/chunk/occlusion/OcclusionCuller.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java index e2cd453b2c..ed03ebef43 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java @@ -189,9 +189,10 @@ private static boolean isWithinRenderDistance(CameraTransform camera, RenderSect // coordinates of the point to compare (in view space) // this is the closest point within the bounding box to the center (0, 0, 0) - float dx = nearestToZero(ox, ox + 16) - camera.fracX; - float dy = nearestToZero(oy, oy + 16) - camera.fracY; - float dz = nearestToZero(oz, oz + 16) - camera.fracZ; + // the bounding box is expanded by 1 block in each direction due to the maximum allowed size of block models. + float dx = nearestToZero(ox - 1, ox + 17) - camera.fracX; + float dy = nearestToZero(oy - 1, oy + 17) - camera.fracY; + float dz = nearestToZero(oz - 1, oz + 17) - camera.fracZ; // vanilla's "cylindrical fog" algorithm // max(length(distance.xz), abs(distance.y)) From 4976933874f4a4813ac83c2789b672569d0f02ad Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 11 Feb 2025 05:44:27 +0100 Subject: [PATCH 090/215] Add caching for region draw batches (#2771) This allows the command buffer generation step to be skipped for regions that haven't changed between frames. (cherry picked from commit dd26190c84105d44c82661a61b288578a1e529f7) --- .../client/gl/device/GLRenderDevice.java | 5 +-- .../client/gl/device/MultiDrawBatch.java | 15 ++------ .../render/chunk/DefaultChunkRenderer.java | 17 +++++---- .../chunk/data/SectionRenderDataStorage.java | 11 ++++-- .../render/chunk/lists/ChunkRenderList.java | 19 ++++++++-- .../render/chunk/region/RenderRegion.java | 36 +++++++++++++++++++ .../chunk/region/RenderRegionManager.java | 13 ++++++- 7 files changed, 88 insertions(+), 28 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java index 95d3a9d8b7..0870f59114 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java @@ -1,5 +1,6 @@ package net.caffeinemc.mods.sodium.client.gl.device; +import com.mojang.blaze3d.vertex.BufferUploader; import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; import net.caffeinemc.mods.sodium.client.gl.array.GlVertexArray; import net.caffeinemc.mods.sodium.client.gl.buffer.*; @@ -9,7 +10,7 @@ import net.caffeinemc.mods.sodium.client.gl.tessellation.*; import net.caffeinemc.mods.sodium.client.gl.util.EnumBitField; import org.lwjgl.opengl.*; -import com.mojang.blaze3d.vertex.BufferUploader; + import java.nio.ByteBuffer; public class GLRenderDevice implements RenderDevice { @@ -288,7 +289,7 @@ public void multiDrawElementsBaseVertex(MultiDrawBatch batch, GlIndexType indexT batch.pElementCount, indexType.getFormatId(), batch.pElementPointer, - batch.size(), + batch.size, batch.pBaseVertex); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/MultiDrawBatch.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/MultiDrawBatch.java index ceda21721a..faf0dc2839 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/MultiDrawBatch.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/MultiDrawBatch.java @@ -3,6 +3,7 @@ import org.lwjgl.PointerBuffer; import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.Pointer; + import java.nio.IntBuffer; /** @@ -14,9 +15,8 @@ public final class MultiDrawBatch { public final long pElementCount; public final long pBaseVertex; - private final int capacity; - public int size; + public boolean isFilled; public MultiDrawBatch(int capacity) { this.pElementPointer = MemoryUtil.nmemAlignedAlloc(32, (long) capacity * Pointer.POINTER_SIZE); @@ -24,20 +24,11 @@ public MultiDrawBatch(int capacity) { this.pElementCount = MemoryUtil.nmemAlignedAlloc(32, (long) capacity * Integer.BYTES); this.pBaseVertex = MemoryUtil.nmemAlignedAlloc(32, (long) capacity * Integer.BYTES); - - this.capacity = capacity; - } - - public int size() { - return this.size; - } - - public int capacity() { - return this.capacity; } public void clear() { this.size = 0; + this.isFilled = false; } public void delete() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java index 42e4a53ba8..9f6f25aa08 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java @@ -28,14 +28,11 @@ import java.util.Iterator; public class DefaultChunkRenderer extends ShaderChunkRenderer { - private final MultiDrawBatch batch; - private final SharedQuadIndexBuffer sharedIndexBuffer; public DefaultChunkRenderer(RenderDevice device, ChunkVertexType vertexType) { super(device, vertexType); - this.batch = new MultiDrawBatch((ModelQuadFacing.COUNT * RenderRegion.REGION_SIZE) + 1); this.sharedIndexBuffer = new SharedQuadIndexBuffer(device.createCommandList(), SharedQuadIndexBuffer.IndexType.INTEGER); } @@ -71,16 +68,19 @@ public void render(ChunkRenderMatrices matrices, continue; } - fillCommandBuffer(this.batch, region, storage, renderList, camera, renderPass, useBlockFaceCulling, useIndexedTessellation); + var batch = region.getCachedBatch(renderPass); + if (!batch.isFilled) { + fillCommandBuffer(batch, region, storage, renderList, camera, renderPass, useBlockFaceCulling, useIndexedTessellation); + } - if (this.batch.isEmpty()) { + if (batch.isEmpty()) { continue; } // When the shared index buffer is being used, we must ensure the storage has been allocated *before* // the tessellation is prepared. if (!useIndexedTessellation) { - this.sharedIndexBuffer.ensureCapacity(commandList, this.batch.getIndexBufferSize()); + this.sharedIndexBuffer.ensureCapacity(commandList, batch.getIndexBufferSize()); } GlTessellation tessellation; @@ -92,7 +92,7 @@ public void render(ChunkRenderMatrices matrices, } setModelMatrixUniforms(shader, region, camera); - executeDrawBatch(commandList, tessellation, this.batch); + executeDrawBatch(commandList, tessellation, batch); } super.end(renderPass); @@ -110,7 +110,7 @@ private static void fillCommandBuffer(MultiDrawBatch batch, TerrainRenderPass pass, boolean useBlockFaceCulling, boolean useIndexedTessellation) { - batch.clear(); + batch.isFilled = true; var iterator = renderList.sectionsWithGeometryIterator(pass.isTranslucent()); @@ -362,6 +362,5 @@ public void delete(CommandList commandList) { super.delete(commandList); this.sharedIndexBuffer.delete(commandList); - this.batch.delete(); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java index 99fbe9e69a..2d3098fa19 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java @@ -106,14 +106,15 @@ public void setIndexData(int localSectionIndex, GlBufferSegment allocation) { SectionRenderDataUnsafe.setLocalBaseElement(pMeshData, allocation.getOffset()); } - public void setSharedIndexUsage(int localSectionIndex, int newUsage) { + public boolean setSharedIndexUsage(int localSectionIndex, int newUsage) { var previousUsage = this.sharedIndexUsage[localSectionIndex]; if (previousUsage == newUsage) { - return; + return false; } // mark for update if usage is down from max (may need to shrink buffer) // or if usage increased beyond the max (need to grow buffer) + boolean newlyUsingSharedIndexBuffer = false; if (newUsage < previousUsage && previousUsage == this.sharedIndexCapacity || newUsage > this.sharedIndexCapacity || newUsage > 0 && this.sharedIndexAllocation == null) { @@ -123,9 +124,15 @@ public void setSharedIndexUsage(int localSectionIndex, int newUsage) { var sharedBaseElement = this.sharedIndexAllocation.getOffset(); var pMeshData = this.getDataPointer(localSectionIndex); SectionRenderDataUnsafe.setSharedBaseElement(pMeshData, sharedBaseElement); + + if (previousUsage == 0 && newUsage > 0) { + newlyUsingSharedIndexBuffer = true; + } } this.sharedIndexUsage[localSectionIndex] = newUsage; + + return newlyUsingSharedIndexBuffer; } public boolean needsSharedIndexUpdate() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java index e45638b8ad..65ed9a3310 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java @@ -16,6 +16,7 @@ public class ChunkRenderList { private final byte[] sectionsWithGeometry = new byte[RenderRegion.REGION_SIZE]; private int sectionsWithGeometryCount = 0; + private int prevSectionsWithGeometryCount = 0; private final byte[] sectionsWithSprites = new byte[RenderRegion.REGION_SIZE]; private int sectionsWithSpritesCount = 0; @@ -32,6 +33,8 @@ public ChunkRenderList(RenderRegion region) { } public void reset(int frame) { + this.prevSectionsWithGeometryCount = this.sectionsWithGeometryCount; + this.sectionsWithGeometryCount = 0; this.sectionsWithSpritesCount = 0; this.sectionsWithEntitiesCount = 0; @@ -83,8 +86,14 @@ public void add(RenderSection render) { int index = render.getSectionIndex(); int flags = render.getFlags(); - this.sectionsWithGeometry[this.sectionsWithGeometryCount] = (byte) index; - this.sectionsWithGeometryCount += (flags >>> RenderSectionFlags.HAS_BLOCK_GEOMETRY) & 1; + if (((flags >>> RenderSectionFlags.HAS_BLOCK_GEOMETRY) & 1) != 0) { + var byteIndex = (byte) index; + if (this.sectionsWithGeometry[this.sectionsWithGeometryCount] != byteIndex) { + this.sectionsWithGeometry[this.sectionsWithGeometryCount] = byteIndex; + this.prevSectionsWithGeometryCount = -1; + } + this.sectionsWithGeometryCount++; + } this.sectionsWithSprites[this.sectionsWithSpritesCount] = (byte) index; this.sectionsWithSpritesCount += (flags >>> RenderSectionFlags.HAS_ANIMATED_SPRITES) & 1; @@ -93,6 +102,12 @@ public void add(RenderSection render) { this.sectionsWithEntitiesCount += (flags >>> RenderSectionFlags.HAS_BLOCK_ENTITIES) & 1; } + public boolean getAndResetCacheInvalidation() { + var cacheIsInvalidated = this.prevSectionsWithGeometryCount != this.sectionsWithGeometryCount; + this.prevSectionsWithGeometryCount = this.sectionsWithGeometryCount; + return cacheIsInvalidated; + } + public @Nullable ByteIterator sectionsWithGeometryIterator(boolean reverse) { if (this.sectionsWithGeometryCount == 0) { return null; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java index 29ca64b226..018394e5ba 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java @@ -5,7 +5,9 @@ import net.caffeinemc.mods.sodium.client.gl.arena.staging.StagingBuffer; import net.caffeinemc.mods.sodium.client.gl.buffer.GlBuffer; import net.caffeinemc.mods.sodium.client.gl.device.CommandList; +import net.caffeinemc.mods.sodium.client.gl.device.MultiDrawBatch; import net.caffeinemc.mods.sodium.client.gl.tessellation.GlTessellation; +import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.data.SectionRenderDataStorage; import net.caffeinemc.mods.sodium.client.render.chunk.lists.ChunkRenderList; @@ -51,6 +53,8 @@ public class RenderRegion { private final Map sectionRenderData = new Reference2ReferenceOpenHashMap<>(); private DeviceResources resources; + private final Map cachedBatches = new Reference2ReferenceOpenHashMap<>(); + public RenderRegion(int x, int y, int z, StagingBuffer stagingBuffer) { this.x = x; this.y = y; @@ -113,6 +117,38 @@ public void delete(CommandList commandList) { } Arrays.fill(this.sections, null); + + for (var batch : this.cachedBatches.values()) { + batch.delete(); + } + this.cachedBatches.clear(); + } + + public void clearAllCachedBatches() { + for (var batch : this.cachedBatches.values()) { + batch.clear(); + } + } + + public void clearCachedBatchFor(TerrainRenderPass pass) { + var batch = this.cachedBatches.remove(pass); + if (batch != null) { + batch.delete(); + } + } + + public MultiDrawBatch getCachedBatch(TerrainRenderPass pass) { + MultiDrawBatch batch = this.cachedBatches.get(pass); + if (batch != null) { + if (this.renderList.getAndResetCacheInvalidation()) { + batch.clear(); + } + return batch; + } + + batch = new MultiDrawBatch((ModelQuadFacing.COUNT * RenderRegion.REGION_SIZE) + 1); + this.cachedBatches.put(pass, batch); + return batch; } public boolean isEmpty() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java index 50495eac74..efcd429126 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java @@ -77,6 +77,7 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect if (storage != null) { storage.removeVertexData(renderSectionIndex); + region.clearCachedBatchFor(pass); } BuiltSectionMeshParts mesh = chunkBuildOutput.getMesh(pass); @@ -93,12 +94,20 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect if (sorter instanceof SharedIndexSorter sharedIndexSorter) { var storage = region.createStorage(DefaultTerrainRenderPasses.TRANSLUCENT); storage.removeIndexData(renderSectionIndex); - storage.setSharedIndexUsage(renderSectionIndex, sharedIndexSorter.quadCount()); + + // clear batch cache if it's newly using the shared index buffer and was not previously. + // updates to the shared index buffer which cause the batch cache to be invalidated are handled with needsSharedIndexUpdate + if (storage.setSharedIndexUsage(renderSectionIndex, sharedIndexSorter.quadCount())) { + region.clearCachedBatchFor(DefaultTerrainRenderPasses.TRANSLUCENT); + } } else { var storage = region.getStorage(DefaultTerrainRenderPasses.TRANSLUCENT); if (storage != null) { storage.removeIndexData(renderSectionIndex); storage.setSharedIndexUsage(renderSectionIndex, 0); + + // always clear batch cache on uploads of new index data + region.clearCachedBatchFor(DefaultTerrainRenderPasses.TRANSLUCENT); } if (sorter == null) { @@ -133,6 +142,7 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect // Once invalidated the tessellation will be re-created on the next attempted use if (bufferChanged) { region.refreshTesselation(commandList); + region.clearAllCachedBatches(); } // Collect the upload results @@ -162,6 +172,7 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect if (indexBufferChanged) { region.refreshIndexedTesselation(commandList); + region.clearCachedBatchFor(DefaultTerrainRenderPasses.TRANSLUCENT); } } From 8d0ce6a2687fc8847c974836cd832eba22fe3b8b Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 11 Feb 2025 20:43:18 +0100 Subject: [PATCH 091/215] Fix incomplete cache invalidation in region render list caching (#3018) (cherry picked from commit 2f2995e526054f3d47e3ea9be6ed5654ea4f7988) --- .../render/chunk/lists/ChunkRenderList.java | 33 +++++++++++-------- .../chunk/lists/VisibleChunkCollector.java | 3 +- .../render/chunk/region/RenderRegion.java | 7 ++-- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java index 65ed9a3310..b50b9c2026 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java @@ -11,10 +11,13 @@ import net.minecraft.util.Mth; import org.jetbrains.annotations.Nullable; +import java.util.Arrays; + public class ChunkRenderList { private final RenderRegion region; private final byte[] sectionsWithGeometry = new byte[RenderRegion.REGION_SIZE]; + private final byte[] prevSectionsWithGeometry = new byte[RenderRegion.REGION_SIZE]; private int sectionsWithGeometryCount = 0; private int prevSectionsWithGeometryCount = 0; @@ -46,6 +49,20 @@ public void reset(int frame) { // clamping the relative camera position to the region bounds means there can only be very few different distances private static final int SORTING_HISTOGRAM_SIZE = RenderRegion.REGION_WIDTH + RenderRegion.REGION_HEIGHT + RenderRegion.REGION_LENGTH - 2; + public void prepareForRender(SectionPos cameraPos, int[] sortItems) { + this.sortSections(cameraPos, sortItems); + + // invalidate batch cache if the render list changed + if (this.prevSectionsWithGeometryCount != this.sectionsWithGeometryCount || + !Arrays.equals(this.sectionsWithGeometry, this.prevSectionsWithGeometry)) { + this.region.clearAllCachedBatches(); + + // reset cache invalidation, the newly built batch will remain valid until the next change + this.prevSectionsWithGeometryCount = this.sectionsWithGeometryCount; + System.arraycopy(this.sectionsWithGeometry, 0, this.prevSectionsWithGeometry, 0, RenderRegion.REGION_SIZE); + } + } + public void sortSections(SectionPos cameraPos, int[] sortItems) { var cameraX = Mth.clamp(cameraPos.getX() - this.region.getChunkX(), 0, RenderRegion.REGION_WIDTH - 1); var cameraY = Mth.clamp(cameraPos.getY() - this.region.getChunkY(), 0, RenderRegion.REGION_HEIGHT - 1); @@ -86,14 +103,8 @@ public void add(RenderSection render) { int index = render.getSectionIndex(); int flags = render.getFlags(); - if (((flags >>> RenderSectionFlags.HAS_BLOCK_GEOMETRY) & 1) != 0) { - var byteIndex = (byte) index; - if (this.sectionsWithGeometry[this.sectionsWithGeometryCount] != byteIndex) { - this.sectionsWithGeometry[this.sectionsWithGeometryCount] = byteIndex; - this.prevSectionsWithGeometryCount = -1; - } - this.sectionsWithGeometryCount++; - } + this.sectionsWithGeometry[this.sectionsWithGeometryCount] = (byte) index; + this.sectionsWithGeometryCount += (flags >>> RenderSectionFlags.HAS_BLOCK_GEOMETRY) & 1; this.sectionsWithSprites[this.sectionsWithSpritesCount] = (byte) index; this.sectionsWithSpritesCount += (flags >>> RenderSectionFlags.HAS_ANIMATED_SPRITES) & 1; @@ -102,12 +113,6 @@ public void add(RenderSection render) { this.sectionsWithEntitiesCount += (flags >>> RenderSectionFlags.HAS_BLOCK_ENTITIES) & 1; } - public boolean getAndResetCacheInvalidation() { - var cacheIsInvalidated = this.prevSectionsWithGeometryCount != this.sectionsWithGeometryCount; - this.prevSectionsWithGeometryCount = this.sectionsWithGeometryCount; - return cacheIsInvalidated; - } - public @Nullable ByteIterator sectionsWithGeometryIterator(boolean reverse) { if (this.sectionsWithGeometryCount == 0) { return null; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java index 89cf22cf78..74c022fd03 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java @@ -97,8 +97,9 @@ public SortedRenderLists createRenderLists(Viewport viewport) { sorted.add(renderList); } + // sort sections and invalidate batch caches if the render lists changed for (var list : sorted) { - list.sortSections(sectionPos, sortItems); + list.prepareForRender(sectionPos, sortItems); } return new SortedRenderLists(sorted); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java index 018394e5ba..0b619bf96d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java @@ -131,18 +131,15 @@ public void clearAllCachedBatches() { } public void clearCachedBatchFor(TerrainRenderPass pass) { - var batch = this.cachedBatches.remove(pass); + var batch = this.cachedBatches.get(pass); if (batch != null) { - batch.delete(); + batch.clear(); } } public MultiDrawBatch getCachedBatch(TerrainRenderPass pass) { MultiDrawBatch batch = this.cachedBatches.get(pass); if (batch != null) { - if (this.renderList.getAndResetCacheInvalidation()) { - batch.clear(); - } return batch; } From bf96d2c89c9d3193df2f0d28c85a980a84ab9da6 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 16 Feb 2025 05:18:54 +0100 Subject: [PATCH 092/215] Handle camera movement in draw batch cache invalidation (#3028) Fixes draw batch cache invalidation by also taking the effect of relative camera position on which facings are rendered into account. (cherry picked from commit 446dddcf1bc1f35c89d20a996129d21f9c4f038f) --- .../render/chunk/lists/ChunkRenderList.java | 69 +++++++++++++------ 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java index b50b9c2026..4d6779c576 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java @@ -17,9 +17,13 @@ public class ChunkRenderList { private final RenderRegion region; private final byte[] sectionsWithGeometry = new byte[RenderRegion.REGION_SIZE]; - private final byte[] prevSectionsWithGeometry = new byte[RenderRegion.REGION_SIZE]; + private final long[] sectionsWithGeometryMap = new long[RenderRegion.REGION_SIZE / Long.SIZE]; + private final long[] prevSectionsWithGeometryMap = new long[RenderRegion.REGION_SIZE / Long.SIZE]; private int sectionsWithGeometryCount = 0; private int prevSectionsWithGeometryCount = 0; + private int lastRelativeCameraSectionX; + private int lastRelativeCameraSectionY; + private int lastRelativeCameraSectionZ; private final byte[] sectionsWithSprites = new byte[RenderRegion.REGION_SIZE]; private int sectionsWithSpritesCount = 0; @@ -37,6 +41,7 @@ public ChunkRenderList(RenderRegion region) { public void reset(int frame) { this.prevSectionsWithGeometryCount = this.sectionsWithGeometryCount; + Arrays.fill(this.sectionsWithGeometryMap, 0L); this.sectionsWithGeometryCount = 0; this.sectionsWithSpritesCount = 0; @@ -50,35 +55,57 @@ public void reset(int frame) { private static final int SORTING_HISTOGRAM_SIZE = RenderRegion.REGION_WIDTH + RenderRegion.REGION_HEIGHT + RenderRegion.REGION_LENGTH - 2; public void prepareForRender(SectionPos cameraPos, int[] sortItems) { - this.sortSections(cameraPos, sortItems); + // The relative coordinates are clamped to one section larger than the region bounds to also capture cache invalidation that happens + // when the camera moves from outside the region to inside the region (when seen on all axes independently). + // This type of cache invalidation stems from different facings of sections being rendered if the camera is aligned with them on an axis. + // For sorting only the position clamped to inside the region is used. + int relativeCameraSectionX = Mth.clamp(cameraPos.getX() - this.region.getChunkX(), -1, RenderRegion.REGION_WIDTH); + int relativeCameraSectionY = Mth.clamp(cameraPos.getY() - this.region.getChunkY(), -1, RenderRegion.REGION_HEIGHT); + int relativeCameraSectionZ = Mth.clamp(cameraPos.getZ() - this.region.getChunkZ(), -1, RenderRegion.REGION_LENGTH); // invalidate batch cache if the render list changed if (this.prevSectionsWithGeometryCount != this.sectionsWithGeometryCount || - !Arrays.equals(this.sectionsWithGeometry, this.prevSectionsWithGeometry)) { + relativeCameraSectionX != this.lastRelativeCameraSectionX || + relativeCameraSectionY != this.lastRelativeCameraSectionY || + relativeCameraSectionZ != this.lastRelativeCameraSectionZ || + !Arrays.equals(this.sectionsWithGeometryMap, this.prevSectionsWithGeometryMap)) { + // reset cache invalidation, the newly built batches will remain valid until the next change this.region.clearAllCachedBatches(); - // reset cache invalidation, the newly built batch will remain valid until the next change this.prevSectionsWithGeometryCount = this.sectionsWithGeometryCount; - System.arraycopy(this.sectionsWithGeometry, 0, this.prevSectionsWithGeometry, 0, RenderRegion.REGION_SIZE); + System.arraycopy(this.sectionsWithGeometryMap, 0, this.prevSectionsWithGeometryMap, 0, this.sectionsWithGeometryMap.length); + this.lastRelativeCameraSectionX = relativeCameraSectionX; + this.lastRelativeCameraSectionY = relativeCameraSectionY; + this.lastRelativeCameraSectionZ = relativeCameraSectionZ; + + this.sortSections(relativeCameraSectionX, relativeCameraSectionY, relativeCameraSectionZ, sortItems); } } - public void sortSections(SectionPos cameraPos, int[] sortItems) { - var cameraX = Mth.clamp(cameraPos.getX() - this.region.getChunkX(), 0, RenderRegion.REGION_WIDTH - 1); - var cameraY = Mth.clamp(cameraPos.getY() - this.region.getChunkY(), 0, RenderRegion.REGION_HEIGHT - 1); - var cameraZ = Mth.clamp(cameraPos.getZ() - this.region.getChunkZ(), 0, RenderRegion.REGION_LENGTH - 1); + public void sortSections(int relativeCameraSectionX, int relativeCameraSectionY, int relativeCameraSectionZ, int[] sortItems) { + relativeCameraSectionX = Mth.clamp(relativeCameraSectionX, 0, RenderRegion.REGION_WIDTH - 1); + relativeCameraSectionY = Mth.clamp(relativeCameraSectionY, 0, RenderRegion.REGION_HEIGHT - 1); + relativeCameraSectionZ = Mth.clamp(relativeCameraSectionZ, 0, RenderRegion.REGION_LENGTH - 1); int[] histogram = new int[SORTING_HISTOGRAM_SIZE]; - for (int i = 0; i < this.sectionsWithGeometryCount; i++) { - var index = this.sectionsWithGeometry[i] & 0xFF; // makes sure the byte -> int conversion is unsigned - var x = Math.abs(LocalSectionIndex.unpackX(index) - cameraX); - var y = Math.abs(LocalSectionIndex.unpackY(index) - cameraY); - var z = Math.abs(LocalSectionIndex.unpackZ(index) - cameraZ); - - var distance = x + y + z; - histogram[distance]++; - sortItems[i] = distance << 8 | index; + this.sectionsWithGeometryCount = 0; + for (int mapIndex = 0; mapIndex < this.sectionsWithGeometryMap.length; mapIndex++) { + var map = this.sectionsWithGeometryMap[mapIndex]; + var mapOffset = mapIndex << 6; + + while (map != 0) { + var index = Long.numberOfTrailingZeros(map) + mapOffset; + map &= map - 1; + + var x = Math.abs(LocalSectionIndex.unpackX(index) - relativeCameraSectionX); + var y = Math.abs(LocalSectionIndex.unpackY(index) - relativeCameraSectionY); + var z = Math.abs(LocalSectionIndex.unpackZ(index) - relativeCameraSectionZ); + + var distance = x + y + z; + histogram[distance]++; + sortItems[this.sectionsWithGeometryCount++] = distance << 8 | index; + } } // prefix sum to calculate indexes @@ -103,8 +130,10 @@ public void add(RenderSection render) { int index = render.getSectionIndex(); int flags = render.getFlags(); - this.sectionsWithGeometry[this.sectionsWithGeometryCount] = (byte) index; - this.sectionsWithGeometryCount += (flags >>> RenderSectionFlags.HAS_BLOCK_GEOMETRY) & 1; + if (((flags >>> RenderSectionFlags.HAS_BLOCK_GEOMETRY) & 1) == 1) { + this.sectionsWithGeometryMap[index >> 6] |= 1L << (index & 0b111111); + this.sectionsWithGeometryCount++; + } this.sectionsWithSprites[this.sectionsWithSpritesCount] = (byte) index; this.sectionsWithSpritesCount += (flags >>> RenderSectionFlags.HAS_ANIMATED_SPRITES) & 1; From 37eee8fa13ea7a24708dd50cad638aa7c80e0293 Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 18 Feb 2025 06:43:38 +0100 Subject: [PATCH 093/215] Fix visual corruption caused by indexing flags being trampled (#3031) Sections that don't upload another index buffer but change their vertex data inadvertently overwrite the flag that signals whether they use local or shared indexing, which led to visual corruption. Also, improve the Javadoc of `SortType` by updating outdated comments and simplifying the constructor. (cherry picked from commit d7e878b4b33bdf370465736f8888a2a67a5a4f2f) --- .../chunk/data/SectionRenderDataUnsafe.java | 3 +++ .../chunk/translucent_sorting/SortType.java | 23 ++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java index 63176b9ec2..cb9adba1a8 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java @@ -86,7 +86,10 @@ public static int getSliceMask(long ptr) { } public static void setFacingList(long ptr, long facingList) { + // rescue and re-write the local indexing flag to prevent it from being lost when a whole long is written for the facing list + byte isLocalIndexByte = MemoryUtil.memGetByte(ptr + OFFSET_IS_LOCAL_INDEX); MemoryUtil.memPutLong(ptr + OFFSET_FACING_LIST, facingList); + MemoryUtil.memPutByte(ptr + OFFSET_IS_LOCAL_INDEX, isLocalIndexByte); } public static long getFacingList(long ptr) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java index 2980f99365..e2eee4b915 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java @@ -3,6 +3,8 @@ /** * What type of sorting to use for a section. Calculated by a heuristic after * building a section. + * + * Invariant: !(needsDirectionMixing && allowSliceReordering) */ public enum SortType { /** @@ -21,39 +23,34 @@ public enum SortType { NONE(false, true), /** - * There is only one sort order. No active sorting is required, but an initial + * There is only one sort order. No active sorting is required, except for an initial * sort where quads of each facing are sorted according to their distances in * regard to their normal. - * - * Currently assumes that there are no UNASSIGNED quads present. If this - * changes, remove this note and adjust StaticTranslucentData and anything that - * reads from it to handle UNASSIGNED quads. */ - STATIC_NORMAL_RELATIVE(false), + STATIC_NORMAL_RELATIVE(false, false), /** * There is only one sort order and not active sorting is required, but * determining the static sort order involves doing a toplogical sort of the * quads. */ - STATIC_TOPO(true), + STATIC_TOPO(true, false), /** * There are multiple sort orders. Sorting is required every time GFNI triggers * this section. */ - DYNAMIC(true); + DYNAMIC(true, false); public final boolean needsDirectionMixing; public final boolean allowSliceReordering; - SortType(boolean needsDirectionMixing) { - this.needsDirectionMixing = needsDirectionMixing; - this.allowSliceReordering = false; - } - SortType(boolean needsDirectionMixing, boolean allowSliceReordering) { this.needsDirectionMixing = needsDirectionMixing; this.allowSliceReordering = allowSliceReordering; + + if (needsDirectionMixing && allowSliceReordering) { + throw new IllegalArgumentException("Invalid sort type"); + } } } From 18db9d4fd6c8fec6352ebcf4170ed1f32dd2909f Mon Sep 17 00:00:00 2001 From: douira Date: Fri, 28 Feb 2025 00:23:04 +0100 Subject: [PATCH 094/215] Fix translucency sort triggering with large camera coordinates (#3051) This fixes translucency sort triggering when very large camera coordinates are involved (~16 million blocks away from world center) by using full double precision in the calculation of the NormalPlanes's dot product offset. (cherry picked from commit fe5fe6cf2184741bbf85da8a183dc145ff06b288) --- .../trigger/NormalList.java | 23 ++++++++----------- .../trigger/NormalPlanes.java | 5 ++-- .../mods/sodium/client/util/MathUtil.java | 13 +++++++++++ 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalList.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalList.java index 35f3b36271..4ce9902f94 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalList.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalList.java @@ -6,6 +6,7 @@ import it.unimi.dsi.fastutil.objects.ReferenceArraySet; import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; +import net.caffeinemc.mods.sodium.client.util.MathUtil; import net.caffeinemc.mods.sodium.client.util.interval_tree.DoubleInterval; import net.caffeinemc.mods.sodium.client.util.interval_tree.Interval; import net.caffeinemc.mods.sodium.client.util.interval_tree.Interval.Bounded; @@ -58,8 +59,8 @@ public class NormalList { /** * Constructs a new normal list with the given unit normal vector and aligned * normal index. - * - * @param normal The unit normal vector + * + * @param normal The unit normal vector */ NormalList(Vector3fc normal, int alignedDirection) { this.normal = normal; @@ -78,18 +79,12 @@ public int getAlignedDirection() { return this.alignedDirection; } - private double normalDotDouble(Vector3dc v) { - return org.joml.Math.fma(this.normal.x(), v.x(), - org.joml.Math.fma(this.normal.y(), v.y(), - this.normal.z() * v.z())); - } - void processMovement(SortTriggering ts, CameraMovement movement) { // calculate the distance range of the movement with respect to the normal - double start = this.normalDotDouble(movement.start()); - double end = this.normalDotDouble(movement.end()); + double start = MathUtil.floatDoubleDot(this.normal, movement.start()); + double end = MathUtil.floatDoubleDot(this.normal, movement.end()); - // stop if the movement is reverse with regards to the normal + // stop if the movement is reverse in regard to the normal // since this means it's moving against the normal if (start >= end) { return; @@ -106,11 +101,13 @@ void processMovement(SortTriggering ts, CameraMovement movement) { } void processCatchup(SortTriggering ts, CameraMovement movement, long sectionPos) { - double start = this.normalDotDouble(movement.start()); - double end = this.normalDotDouble(movement.end()); + double start = MathUtil.floatDoubleDot(this.normal, movement.start()); + double end = MathUtil.floatDoubleDot(this.normal, movement.end()); + if (start >= end) { return; } + var group = this.groupsBySection.get(sectionPos); if (group != null) { group.triggerRange(ts, start, end); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalPlanes.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalPlanes.java index 4f84d50a82..a72a7ae8d7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalPlanes.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalPlanes.java @@ -3,6 +3,7 @@ import it.unimi.dsi.fastutil.floats.FloatOpenHashSet; import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; +import net.caffeinemc.mods.sodium.client.util.MathUtil; import net.caffeinemc.mods.sodium.client.util.interval_tree.DoubleInterval; import net.caffeinemc.mods.sodium.client.util.interval_tree.Interval.Bounded; import net.minecraft.core.SectionPos; @@ -66,8 +67,8 @@ public void prepareIntegration() { // sort the array ascending Arrays.sort(this.relativeDistances); - this.baseDistance = this.normal.dot( - this.sectionPos.minBlockX(), this.sectionPos.minBlockY(), this.sectionPos.minBlockZ()); + // make sure to use double-based dot product math here to prevent incorrect sort triggering + this.baseDistance = MathUtil.floatDoubleDot(this.normal, this.sectionPos.minBlockX(), this.sectionPos.minBlockY(), this.sectionPos.minBlockZ()); this.distanceRange = new DoubleInterval( this.relativeDistances[0] + this.baseDistance, this.relativeDistances[size - 1] + this.baseDistance, diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java index 01d267e128..a01fe12a2e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java @@ -1,5 +1,10 @@ package net.caffeinemc.mods.sodium.client.util; +import org.joml.Vector3dc; +import org.joml.Vector3fc; + +import static org.joml.Math.fma; + public class MathUtil { /** * @return True if the specified number is greater than zero and is a power of two, otherwise false @@ -43,4 +48,12 @@ public static int floatToComparableInt(float f) { public static float comparableIntToFloat(int i) { return Float.intBitsToFloat(i ^ ((i >> 31) & 0x7FFFFFFF)); } + + public static double floatDoubleDot(Vector3fc a, Vector3dc b) { + return fma(a.x(), b.x(), fma(a.y(), b.y(), a.z() * b.z())); + } + + public static double floatDoubleDot(Vector3fc a, double bx, double by, double bz) { + return fma(a.x(), bx, fma(a.y(), by, a.z() * bz)); + } } From df5dc247379e328c1f04591777f7171e5ee89ea5 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Fri, 21 Feb 2025 10:16:16 +0100 Subject: [PATCH 095/215] Fix a number of typological errors and grammar issues (cherry picked from commit 922a03c9c05cd4fe38346bbd1a1f64fab46109c4) --- .../mods/sodium/client/data/config/MixinConfig.java | 3 +-- .../mods/sodium/client/gl/arena/GlBufferArena.java | 2 +- .../mods/sodium/client/gl/attribute/GlVertexAttribute.java | 4 ++-- .../mods/sodium/client/gl/attribute/GlVertexFormat.java | 3 +-- .../caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java | 5 ++--- .../mods/sodium/client/render/SodiumWorldRenderer.java | 2 +- .../client/render/chunk/data/SectionRenderDataStorage.java | 2 +- .../client/render/chunk/shader/DefaultShaderInterface.java | 2 +- .../chunk/translucent_sorting/trigger/SortTriggering.java | 6 +++--- .../mods/sodium/client/render/frapi/mesh/QuadViewImpl.java | 2 +- .../sodium/client/services/PlatformRuntimeInformation.java | 2 +- .../mods/sodium/client/util/interval_tree/Interval.java | 4 ++-- .../sodium/mixin/core/model/colors/BlockColorsMixin.java | 2 +- .../mods/sodium/fabric/render/FluidRendererImpl.java | 2 +- .../mods/sodium/neoforge/render/FluidRendererImpl.java | 4 ++-- 15 files changed, 21 insertions(+), 24 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java index 327dcf5fe0..ca32f5ed5c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java @@ -1,7 +1,6 @@ package net.caffeinemc.mods.sodium.client.data.config; import net.caffeinemc.mods.sodium.client.services.PlatformMixinOverrides; -import net.caffeinemc.mods.sodium.client.services.Services; import net.caffeinemc.mods.sodium.mixin.MixinOption; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -154,7 +153,7 @@ protected void applyModOverride(PlatformMixinOverrides.MixinOverride override) { * Returns the effective option for the specified class name. This traverses the package path of the given mixin * and checks each root for configuration rules. If a configuration rule disables a package, all mixins located in * that package and its children will be disabled. The effective option is that of the highest-priority rule, either - * a enable rule at the end of the chain or a disable rule at the earliest point in the chain. + * an enable rule at the end of the chain or a disable rule at the earliest point in the chain. * * @return Null if no options matched the given mixin name, otherwise the effective option for this Mixin */ diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java index a2298ac47d..6606d47c1b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java @@ -265,7 +265,7 @@ public boolean upload(CommandList commandList, Stream stream) { // A linked list is used as we'll be randomly removing elements and want O(1) performance List queue = stream.collect(Collectors.toCollection(LinkedList::new)); - // Try to upload all of the data into free segments first + // Try to upload all the data into free segments first this.tryUploads(commandList, queue); // If we weren't able to upload some buffers, they will have been left behind in the queue diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/attribute/GlVertexAttribute.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/attribute/GlVertexAttribute.java index cf9c53282f..e55e894d83 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/attribute/GlVertexAttribute.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/attribute/GlVertexAttribute.java @@ -13,8 +13,8 @@ public class GlVertexAttribute { /** * @param format The format used * @param count The number of components in the vertex attribute - * @param normalized Specifies whether or not fixed-point data values should be normalized (true) or used directly - * as fixed-point values (false) + * @param normalized Specifies whether fixed-point data values should be normalized (true) or used directly + * as fixed-point values (false) * @param pointer The offset to the first component in the attribute */ public GlVertexAttribute(GlVertexAttributeFormat format, int count, boolean normalized, int pointer, int stride, boolean intType) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/attribute/GlVertexFormat.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/attribute/GlVertexFormat.java index f0d3e615c0..de24d23e1a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/attribute/GlVertexFormat.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/attribute/GlVertexFormat.java @@ -5,7 +5,6 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import net.caffeinemc.mods.sodium.client.render.vertex.VertexFormatAttribute; -import java.util.EnumMap; import java.util.Map; /** @@ -75,7 +74,7 @@ public Builder addElement(VertexFormatAttribute attribute, int binding, int poin } /** - * Adds an vertex attribute which will be bound to the given generic attribute type. + * Adds a vertex attribute which will be bound to the given generic attribute type. * * @param type The generic attribute type * @param attribute The attribute to bind diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java index aba63b7b0b..0d2c058c14 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java @@ -1,9 +1,9 @@ package net.caffeinemc.mods.sodium.client.gui; import net.caffeinemc.mods.sodium.client.SodiumClientMod; -import net.caffeinemc.mods.sodium.client.data.fingerprint.HashedFingerprint; import net.caffeinemc.mods.sodium.client.console.Console; import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; +import net.caffeinemc.mods.sodium.client.data.fingerprint.HashedFingerprint; import net.caffeinemc.mods.sodium.client.gui.options.*; import net.caffeinemc.mods.sodium.client.gui.options.control.Control; import net.caffeinemc.mods.sodium.client.gui.options.control.ControlElement; @@ -21,7 +21,6 @@ import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.options.VideoSettingsScreen; -import net.minecraft.locale.Language; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.FormattedText; import net.minecraft.util.FormattedCharSequence; @@ -316,7 +315,7 @@ private void renderOptionTooltip(GuiGraphics graphics, ControlElement element int boxYLimit = boxY + boxHeight; int boxYCutoff = this.height - 40; - // If the box is going to be cutoff on the Y-axis, move it back up the difference + // If the box is going to be cut off on the Y-axis, move it back up the difference if (boxYLimit > boxYCutoff) { boxY -= boxYLimit - boxYCutoff; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java index 428b7eed1c..8175fa71d8 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java @@ -473,7 +473,7 @@ public void iterateVisibleBlockEntities(Consumer blockEntityConsume private static final double MAX_ENTITY_CHECK_VOLUME = 16 * 16 * 16 * 15; /** - * Returns whether or not the entity intersects with any visible chunks in the graph. + * Returns whether the entity intersects with any visible chunks in the graph. * @return True if the entity is visible, otherwise false */ public boolean isEntityVisible(Entity entity) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java index 2d3098fa19..54c06ce179 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java @@ -24,7 +24,7 @@ * {@link SectionRenderDataUnsafe}. *

* When the backing buffer (from the gl buffer arena) is resized, the storage - * object is notified and then it updates the changed offsets of the buffer + * object is notified, and then it updates the changed offsets of the buffer * segments. Since the index data's size and alignment directly corresponds to * that of the vertex data except for the vertex/index scaling of two thirds, * only an offset to the index data within the index data buffer arena is diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/DefaultShaderInterface.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/DefaultShaderInterface.java index f039ca3a01..58ac975356 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/DefaultShaderInterface.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/DefaultShaderInterface.java @@ -28,7 +28,7 @@ public class DefaultShaderInterface implements ChunkShaderInterface { private final GlUniformFloat3v uniformRegionOffset; private final GlUniformFloat2v uniformTexCoordShrink; - // The fog shader component used by this program in order to setup the appropriate GL state + // The fog shader component used by this program in order to set up the appropriate GL state private final ChunkShaderFogComponent fogShader; public DefaultShaderInterface(ShaderBindingContext context, ChunkShaderOptions options) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java index f28b478d1a..bc3e781bed 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java @@ -29,7 +29,7 @@ public class SortTriggering { * To avoid generating a collection of the triggered sections, this callback is * used to process the triggered sections directly as they are queried from the * normal lists' interval trees. The callback is given the section coordinates, - * and a boolean indicating if the trigger was an direct trigger. + * and a boolean indicating if the trigger was a direct trigger. */ private BiConsumer triggerSectionCallback; @@ -38,7 +38,7 @@ public class SortTriggering { * later) it might not have the required trigger data registered yet so that it * might miss being triggered between being scheduled for rebuild and being * integrated. This is solved by catching up the section being integrated with - * the movement that has happened in the mean time. + * the movement that has happened in the meantime. */ private DynamicData catchupData = null; @@ -123,7 +123,7 @@ private void triggerSectionCatchup(long sectionPos, boolean isDirectTrigger) { // catchup triggering might be disabled if (this.triggerSectionCallback != null) { // do prepareTrigger here since it can't be done through the render section as - // it hasn't been put there yet or it contains an old data object + // it hasn't been put there yet, or it contains an old data object this.catchupData.prepareTrigger(isDirectTrigger); // schedule the section to be re-sorted diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java index fce995a97b..ac2823862d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java @@ -49,7 +49,7 @@ public class QuadViewImpl implements QuadView, ModelQuadView { /** Size and where it comes from will vary in subtypes. But in all cases quad is fully encoded to array. */ protected int[] data; - /** Beginning of the quad. Also the header index. */ + /** Beginning of the quad. Also, the header index. */ protected int baseIndex = 0; /** diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformRuntimeInformation.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformRuntimeInformation.java index e3326ae6e9..1bed9f7399 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformRuntimeInformation.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformRuntimeInformation.java @@ -29,7 +29,7 @@ static PlatformRuntimeInformation getInstance() { Path getConfigDirectory(); /** - * Returns if the platform has a early loading screen. + * Returns if the platform has an early loading screen. */ boolean platformHasEarlyLoadingScreen(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/Interval.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/Interval.java index 869bb7917e..3c677ed151 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/Interval.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/Interval.java @@ -533,7 +533,7 @@ private int compareEnds(Interval other) { *

*

* To ensure that this comparator can also be used in sets it considers the end points of the intervals, if the - * start points are the same. Otherwise the set will not be able to handle two different intervals, sharing + * start points are the same. Otherwise, the set will not be able to handle two different intervals, sharing * the same starting point, and omit one of the intervals. *

*

@@ -562,7 +562,7 @@ private int compareEnds(Interval other) { *

*

* To ensure that this comparator can also be used in sets it considers the start points of the intervals, if the - * end points are the same. Otherwise the set will not be able to handle two different intervals, sharing + * end points are the same. Otherwise, the set will not be able to handle two different intervals, sharing * the same end point, and omit one of the intervals. *

*

diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/model/colors/BlockColorsMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/model/colors/BlockColorsMixin.java index ca9a2f192e..92a74608f7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/model/colors/BlockColorsMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/model/colors/BlockColorsMixin.java @@ -26,7 +26,7 @@ public class BlockColorsMixin implements BlockColorsExtension { private void preRegisterColorProvider(BlockColor provider, Block[] blocks, CallbackInfo ci) { for (Block block : blocks) { // There will be one provider already registered for vanilla blocks, if we are replacing it, - // it means a mod is using custom logic and we need to disable per-vertex coloring + // it means a mod is using custom logic, and we need to disable per-vertex coloring if (this.blocksToColor.put(block, provider) != null) { this.overridenBlocks.add(block); SodiumClientMod.logger().info("Block {} had its color provider replaced with {} and will not use per-vertex coloring", BuiltInRegistries.BLOCK.getKey(block), provider.toString()); diff --git a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FluidRendererImpl.java b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FluidRendererImpl.java index 3c37a1947f..dee26d14e1 100644 --- a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FluidRendererImpl.java +++ b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FluidRendererImpl.java @@ -61,7 +61,7 @@ public void render(LevelSlice level, BlockState blockState, FluidState fluidStat // // The default implementation of FluidRenderHandler#renderFluid invokes vanilla FluidRenderer#render, but // Fabric API does not support invoking vanilla FluidRenderer#render from FluidRenderHandler#renderFluid - // directly and it does not support calling the default implementation of FluidRenderHandler#renderFluid (super) + // directly, and it does not support calling the default implementation of FluidRenderHandler#renderFluid (super) // more than once. Because of this, the parameters to vanilla FluidRenderer#render will be the same as those // initially passed to FluidRenderHandler#renderFluid, so they can be ignored. // diff --git a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/FluidRendererImpl.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/FluidRendererImpl.java index 4efe368348..d73cc8aaab 100644 --- a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/FluidRendererImpl.java +++ b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/FluidRendererImpl.java @@ -22,7 +22,7 @@ import net.neoforged.neoforge.client.textures.FluidSpriteCache; public class FluidRendererImpl extends FluidRenderer { - // The current default context is set up before invoking FluidRenderHandler#renderFluid and cleared afterwards. + // The current default context is set up before invoking FluidRenderHandler#renderFluid and cleared afterward. private static final ThreadLocal CURRENT_DEFAULT_CONTEXT = ThreadLocal.withInitial(DefaultRenderContext::new); private final ColorProviderRegistry colorProviderRegistry; @@ -50,7 +50,7 @@ public void render(LevelSlice level, BlockState blockState, FluidState fluidStat // // The default implementation of FluidRenderHandler#renderFluid invokes vanilla FluidRenderer#render, but // Fabric API does not support invoking vanilla FluidRenderer#render from FluidRenderHandler#renderFluid - // directly and it does not support calling the default implementation of FluidRenderHandler#renderFluid (super) + // directly, and it does not support calling the default implementation of FluidRenderHandler#renderFluid (super) // more than once. Because of this, the parameters to vanilla FluidRenderer#render will be the same as those // initially passed to FluidRenderHandler#renderFluid, so they can be ignored. // From 9b183cf2ba1add4eefd546ab425ef467e9f00e09 Mon Sep 17 00:00:00 2001 From: douira Date: Sat, 5 Apr 2025 21:54:37 +0200 Subject: [PATCH 096/215] Make sure the index data isn't removed when vertex data is removed (#3033) Also, refactor the data removal methods into one because they have significant overlap, and because they can share the same clear call if both types of data are being removed at the same time. (cherry picked from commit 5cdd7250db01e2243e9225be95c2e7914676a9fe) --- .../chunk/data/SectionRenderDataStorage.java | 68 +++++++++---------- .../chunk/data/SectionRenderDataUnsafe.java | 22 +++++- 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java index 54c06ce179..17ae86a7fb 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java @@ -90,7 +90,7 @@ public void setVertexData(int localSectionIndex, GlBufferSegment allocation, int public void setIndexData(int localSectionIndex, GlBufferSegment allocation) { if (this.elementAllocations == null) { - throw new IllegalStateException("Cannot set index data when storesIndices is false"); + throw new IllegalStateException("Cannot set index data on a render data storage that does not store indices"); } GlBufferSegment prev = this.elementAllocations[localSectionIndex]; @@ -194,55 +194,53 @@ public boolean updateSharedIndexData(CommandList commandList, GlBufferArena aren return bufferChanged; } - public void removeData(int localSectionIndex) { - this.removeVertexData(localSectionIndex, false); + private boolean storesIndexData() { + return this.elementAllocations != null; + } - if (this.elementAllocations != null) { - this.removeIndexData(localSectionIndex); + public void removeIndexData(int localSectionIndex) { + if (!this.storesIndexData()) { + throw new IllegalStateException("Cannot remove index data on a render data storage that does not store indices"); } - - this.setSharedIndexUsage(localSectionIndex, 0); + this.removeData(localSectionIndex, false, true); } public void removeVertexData(int localSectionIndex) { - this.removeVertexData(localSectionIndex, true); + this.removeData(localSectionIndex, true, false); } - private void removeVertexData(int localSectionIndex, boolean retainIndexData) { - GlBufferSegment prev = this.vertexAllocations[localSectionIndex]; - - if (prev == null) { - return; - } - - prev.delete(); - - this.vertexAllocations[localSectionIndex] = null; - - var pMeshData = this.getDataPointer(localSectionIndex); - - var baseElement = SectionRenderDataUnsafe.getBaseElement(pMeshData); - SectionRenderDataUnsafe.clear(pMeshData); + public void removeData(int localSectionIndex) { + this.removeData(localSectionIndex, true, true); + } - if (retainIndexData) { - SectionRenderDataUnsafe.setLocalBaseElement(pMeshData, baseElement); + private void removeData(int localSectionIndex, boolean removeVertexData, boolean removeIndexData) { + if (removeVertexData) { + GlBufferSegment prev = this.vertexAllocations[localSectionIndex]; + if (prev != null) { + prev.delete(); + this.vertexAllocations[localSectionIndex] = null; + } } - } + if (removeIndexData && this.storesIndexData()) { + GlBufferSegment prev = this.elementAllocations[localSectionIndex]; - public void removeIndexData(int localSectionIndex) { - final GlBufferSegment[] allocations = this.elementAllocations; + if (prev != null) { + prev.delete(); + this.elementAllocations[localSectionIndex] = null; + } - if (allocations == null) { - throw new IllegalStateException("Cannot remove index data when storesIndices is false"); + this.setSharedIndexUsage(localSectionIndex, 0); } - GlBufferSegment prev = allocations[localSectionIndex]; + var pMeshData = this.getDataPointer(localSectionIndex); - if (prev != null) { - prev.delete(); + if ((removeIndexData || !this.storesIndexData()) && removeVertexData) { + SectionRenderDataUnsafe.clearFull(pMeshData); + } else if (removeVertexData) { + SectionRenderDataUnsafe.clearVertexData(pMeshData); + } else if (removeIndexData) { + SectionRenderDataUnsafe.clearIndexData(pMeshData); } - - allocations[localSectionIndex] = null; } public void onBufferResized() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java index cb9adba1a8..e3b38045a4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java @@ -19,8 +19,7 @@ // struct SectionRenderData { // 48 bytes // base_element: u32, // base_vertex: u32, -// is_local_index: u8, -// facing_list: u56, +// (is_local_index | facing_list): (u8 | u56), // slice_mask: u32, // vertex_count: [u32; 7] // } @@ -55,10 +54,27 @@ public static void freeHeap(long pointer) { MemoryUtil.nmemAlignedFree(pointer); } - public static void clear(long pointer) { + public static void clearFull(long pointer) { MemoryUtil.memSet(pointer, 0x0, STRIDE); } + public static void clearVertexData(long ptr) { + // save the base element and the local indexing flag + int baseElement = MemoryUtil.memGetInt(ptr + OFFSET_BASE_ELEMENT); + byte isLocalIndexByte = MemoryUtil.memGetByte(ptr + OFFSET_IS_LOCAL_INDEX); + + clearFull(ptr); + + // restore the base element and the local indexing flag + MemoryUtil.memPutInt(ptr + OFFSET_BASE_ELEMENT, baseElement); + MemoryUtil.memPutByte(ptr + OFFSET_IS_LOCAL_INDEX, isLocalIndexByte); + } + + public static void clearIndexData(long ptr) { + MemoryUtil.memPutInt(ptr + OFFSET_BASE_ELEMENT, 0); + MemoryUtil.memPutByte(ptr + OFFSET_IS_LOCAL_INDEX, (byte)0); + } + public static long heapPointer(long ptr, int index) { return ptr + (index * STRIDE); } From db1926ab53afca1e717231aac422af5f3ffc2da0 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sun, 20 Apr 2025 01:29:53 -0500 Subject: [PATCH 097/215] Ensure fences are deleted in MappedStagingBuffer If the staging buffer was deleted before the fences were polled, they could be leaked. This may cause resource exhaustion in some cases. (cherry picked from commit d91d2801447155704f899dd602fd4ff2cfacc390) --- .../client/gl/arena/staging/MappedStagingBuffer.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/MappedStagingBuffer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/MappedStagingBuffer.java index 5252861cad..d70d4378dc 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/MappedStagingBuffer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/MappedStagingBuffer.java @@ -134,6 +134,13 @@ private static List consolidateCopies(PriorityQueue qu @Override public void delete(CommandList commandList) { + while (!this.fencedRegions.isEmpty()) { + var region = this.fencedRegions.dequeue(); + var fence = region.fence(); + fence.sync(); + fence.delete(); + } + this.mappedBuffer.delete(commandList); this.fallbackStagingBuffer.delete(commandList); this.pendingCopies.clear(); From dfe806b8e2a086632c731f07c252f7536d592411 Mon Sep 17 00:00:00 2001 From: douira Date: Sat, 3 May 2025 08:50:26 +0200 Subject: [PATCH 098/215] Avoid primitive boxing caused by enhanced for-loops in translucency sorting (#3135) (cherry picked from commit d8fe39c3d2a119d9638c3a5e338a9fbaf4de67fe) --- .../translucent_sorting/bsp_tree/InnerPartitionBSPNode.java | 6 ++++-- .../chunk/translucent_sorting/trigger/NormalPlanes.java | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java index b2b3ef5fa7..92d8dcc6d3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java @@ -245,7 +245,8 @@ static BSPNode build(BSPWorkspace workspace, IntArrayList indexes, int depth, BS // collect all the geometry's start and end points in this direction points.clear(); - for (int quadIndex : indexes) { + for (int i = 0, size = indexes.size(); i < size; i++) { + int quadIndex = indexes.getInt(i); var quad = workspace.quads[quadIndex]; var extents = quad.getExtents(); var posExtent = extents[axis]; @@ -288,7 +289,8 @@ static BSPNode build(BSPWorkspace workspace, IntArrayList indexes, int depth, BS IntArrayList quadsBefore = null; IntArrayList quadsOn = null; int thickness = 0; - for (long point : points) { + for (int i = 0, size = points.size(); i < size; i++) { + long point = points.getLong(i); switch (decodeType(point)) { case INTERVAL_START -> { // unless at the start, flush if there's a gap diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalPlanes.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalPlanes.java index a72a7ae8d7..124e2254e3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalPlanes.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalPlanes.java @@ -57,7 +57,8 @@ public void prepareIntegration() { var size = this.relativeDistancesSet.size(); this.relativeDistances = new float[this.relativeDistancesSet.size()]; int i = 0; - for (float relDistance : this.relativeDistancesSet) { + for (var it = this.relativeDistancesSet.iterator(); it.hasNext(); ) { + float relDistance = it.nextFloat(); this.relativeDistances[i++] = relDistance; long distanceBits = Double.doubleToLongBits(relDistance); From ab8fcb328c993922d39d175cffdd37d0aaf046e4 Mon Sep 17 00:00:00 2001 From: contaria Date: Sat, 29 Mar 2025 20:07:47 +0100 Subject: [PATCH 099/215] Don't show Fabulous graphics mode on unsupported systems (#3084) --- .../mods/sodium/client/gui/SodiumGameOptionPages.java | 8 +++++++- .../sodium/client/gui/options/control/CyclingControl.java | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java index 681442d769..61eaf40f9a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java @@ -160,7 +160,13 @@ public static OptionPage quality() { .add(OptionImpl.createBuilder(GraphicsStatus.class, vanillaOpts) .setName(Component.translatable("options.graphics")) .setTooltip(Component.translatable("sodium.options.graphics_quality.tooltip")) - .setControl(option -> new CyclingControl<>(option, GraphicsStatus.class, new Component[] { Component.translatable("options.graphics.fast"), Component.translatable("options.graphics.fancy"), Component.translatable("options.graphics.fabulous") })) + .setControl(option -> { + GraphicsStatus[] allowedValues = GraphicsStatus.values(); + if (Minecraft.getInstance().isRunning() && Minecraft.getInstance().getGpuWarnlistManager().isSkippingFabulous()) { + allowedValues = new GraphicsStatus[] { GraphicsStatus.FAST, GraphicsStatus.FANCY }; + } + return new CyclingControl<>(option, allowedValues, new Component[] { Component.translatable("options.graphics.fast"), Component.translatable("options.graphics.fancy"), Component.translatable("options.graphics.fabulous") }); + }) .setBinding( (opts, value) -> opts.graphicsMode().set(value), opts -> opts.graphicsMode().get()) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java index b39278e384..b83464d5ed 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java @@ -50,6 +50,12 @@ public CyclingControl(Option option, Class enumType, T[] allowedValues) { } } + public CyclingControl(Option option, T[] allowedValues, Component[] names) { + this.option = option; + this.allowedValues = allowedValues; + this.names = names; + } + @Override public Option getOption() { return this.option; From e36a9bc7ced1e53c46fbf83646199cd459eda232 Mon Sep 17 00:00:00 2001 From: IMS212 Date: Thu, 24 Jul 2025 11:53:12 -0700 Subject: [PATCH 100/215] Replace incorrect glWaitSync call --- .../java/net/caffeinemc/mods/sodium/client/gl/sync/GlFence.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/sync/GlFence.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/sync/GlFence.java index 589afa1d92..3e9f06a602 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/sync/GlFence.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/sync/GlFence.java @@ -37,7 +37,7 @@ public void sync() { public void sync(long timeout) { this.checkDisposed(); - GL32C.glWaitSync(this.id, GL32C.GL_SYNC_FLUSH_COMMANDS_BIT, timeout); + GL32C.glClientWaitSync(this.id, GL32C.GL_SYNC_FLUSH_COMMANDS_BIT, timeout); } public void delete() { From be5caaef929696bafe9c17669cec05caf7285baa Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 24 Jul 2025 21:33:54 +0200 Subject: [PATCH 101/215] Fix error in BuiltSectionMeshParts#computeVertexCounts (#3158) Also add documentation to explain how the vertex segments are structured. --- .../render/chunk/data/BuiltSectionMeshParts.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java index 21a1736347..b53454a936 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java @@ -3,6 +3,14 @@ import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; import net.caffeinemc.mods.sodium.client.util.NativeBuffer; +/** + * The array of vertex segments is structured as follows: + * - It consists of 2 * ModelQuadFacing.COUNT ints. + * - The first and every second int after that are vertex counts. + * - The second and every second int after that are the ModelQuadFacing index that the preceding count applies to. + * - If the vertex count is zero, the segment is not used and reading the facing index is undefined behavior. + * - The array of vertex segments starts with some number of filled segments, followed by empty segments for the rest of the fixed size. + */ public class BuiltSectionMeshParts { private final int[] vertexSegments; private final NativeBuffer buffer; @@ -24,7 +32,11 @@ public int[] computeVertexCounts() { var vertexCounts = new int[ModelQuadFacing.COUNT]; for (int i = 0; i < this.vertexSegments.length; i += 2) { - vertexCounts[this.vertexSegments[i + 1]] = this.vertexSegments[i]; + var vertexCount = this.vertexSegments[i]; + if (vertexCount == 0) { + continue; // Skip non-present segments + } + vertexCounts[this.vertexSegments[i + 1]] = vertexCount; } return vertexCounts; From 253ad9968b01f236dd2165157567cfad96a659cd Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 24 Jul 2025 21:35:47 +0200 Subject: [PATCH 102/215] Fix calculation of buffer sizes when resizing an arena (#3139) Also, fix the warning shown when allocations fail so that it uses elements instead of bytes. --- .../mods/sodium/client/gl/arena/GlBufferArena.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java index 6606d47c1b..5d861c2699 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java @@ -118,14 +118,15 @@ private List buildTransferList(List u } private void transferSegments(CommandList commandList, Collection list, long capacity) { - if (capacity >= (1L << 32)) { + long bufferSize = this.capacity * this.stride; + if (bufferSize >= (1L << 32)) { throw new IllegalArgumentException("Maximum arena buffer size is 4 GiB"); } GlMutableBuffer srcBufferObj = this.arenaBuffer; GlMutableBuffer dstBufferObj = commandList.createMutableBuffer(); - commandList.allocateStorage(dstBufferObj, capacity * this.stride, BUFFER_USAGE); + commandList.allocateStorage(dstBufferObj, bufferSize, BUFFER_USAGE); for (PendingBufferCopyCommand cmd : list) { commandList.copyBufferSubData(srcBufferObj, dstBufferObj, @@ -271,9 +272,13 @@ public boolean upload(CommandList commandList, Stream stream) { // If we weren't able to upload some buffers, they will have been left behind in the queue if (!queue.isEmpty()) { // Calculate the amount of memory needed for the remaining uploads - int remainingElements = queue.stream() + int remainingUploadSize = queue.stream() .mapToInt(upload -> upload.getDataBuffer().getLength()) .sum(); + + // Convert size to elements by dividing by the stride. + // This doesn't need a ceil since the upload buffers will be at least as big as required and have the same stride. + long remainingElements = remainingUploadSize / this.stride; // Ask the arena to grow to accommodate the remaining uploads // This will force a re-allocation and compaction, which will leave us a continuous free segment From e2fe0ec8ef0bdc66624651691dc68291662a0945 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Thu, 24 Jul 2025 16:13:12 -0500 Subject: [PATCH 103/215] Fix-up typo in GlBufferArena#transferSegments --- .../caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java index 5d861c2699..0820ae0a9f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java @@ -118,7 +118,7 @@ private List buildTransferList(List u } private void transferSegments(CommandList commandList, Collection list, long capacity) { - long bufferSize = this.capacity * this.stride; + long bufferSize = capacity * this.stride; if (bufferSize >= (1L << 32)) { throw new IllegalArgumentException("Maximum arena buffer size is 4 GiB"); } From 4523cf594e2682c3ee48697c44b65d33a186f993 Mon Sep 17 00:00:00 2001 From: contaria Date: Sat, 29 Mar 2025 23:34:38 +0100 Subject: [PATCH 104/215] Mark sprites wrapped in a SpriteCoordinateExpander as active (#3086) --- .../tracking/TextureAtlasSpriteMixin.java | 17 +++++++++++++++++ .../main/resources/sodium-common.mixins.json | 1 + 2 files changed, 18 insertions(+) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/TextureAtlasSpriteMixin.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/TextureAtlasSpriteMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/TextureAtlasSpriteMixin.java new file mode 100644 index 0000000000..3fa12457c7 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/animations/tracking/TextureAtlasSpriteMixin.java @@ -0,0 +1,17 @@ +package net.caffeinemc.mods.sodium.mixin.features.textures.animations.tracking; + +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +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.CallbackInfoReturnable; + +@Mixin(TextureAtlasSprite.class) +public abstract class TextureAtlasSpriteMixin { + @Inject(method = "wrap", at = @At("HEAD")) + private void markSpriteAsActive(CallbackInfoReturnable cir) { + SpriteUtil.INSTANCE.markSpriteActive((TextureAtlasSprite) (Object) this); + } +} diff --git a/common/src/main/resources/sodium-common.mixins.json b/common/src/main/resources/sodium-common.mixins.json index 5faa555c2a..abd6c3e28c 100644 --- a/common/src/main/resources/sodium-common.mixins.json +++ b/common/src/main/resources/sodium-common.mixins.json @@ -76,6 +76,7 @@ "features.textures.animations.tracking.ModelBlockRendererMixin", "features.textures.animations.tracking.GuiGraphicsMixin", "features.textures.animations.tracking.TextureAtlasMixin", + "features.textures.animations.tracking.TextureAtlasSpriteMixin", "features.textures.animations.tracking.TextureSheetParticleMixin", "features.textures.animations.tracking.AnimatedTextureAccessor", "features.textures.animations.tracking.SpriteContentsFrameInfoAccessor", From b9b697c65f06200dab165a4ec82f41ddee2a7442 Mon Sep 17 00:00:00 2001 From: contaria Date: Sun, 30 Mar 2025 18:41:25 +0200 Subject: [PATCH 105/215] Recalculate GUI scale limit every time widget is rebuilt (#3093) --- .../client/gui/SodiumGameOptionPages.java | 7 +-- .../control/DynamicMaxSliderControl.java | 49 +++++++++++++++++++ .../gui/options/control/SliderControl.java | 2 +- 3 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/DynamicMaxSliderControl.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java index 61eaf40f9a..f731bf989f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java @@ -11,10 +11,7 @@ import net.caffeinemc.mods.sodium.client.gl.device.RenderDevice; import net.caffeinemc.mods.sodium.client.gui.options.*; import net.caffeinemc.mods.sodium.client.gui.options.binding.compat.VanillaBooleanOptionBinding; -import net.caffeinemc.mods.sodium.client.gui.options.control.ControlValueFormatter; -import net.caffeinemc.mods.sodium.client.gui.options.control.CyclingControl; -import net.caffeinemc.mods.sodium.client.gui.options.control.SliderControl; -import net.caffeinemc.mods.sodium.client.gui.options.control.TickBoxControl; +import net.caffeinemc.mods.sodium.client.gui.options.control.*; import net.caffeinemc.mods.sodium.client.gui.options.storage.MinecraftOptionsStorage; import net.caffeinemc.mods.sodium.client.gui.options.storage.SodiumOptionsStorage; import net.minecraft.client.*; @@ -65,7 +62,7 @@ public static OptionPage general() { .add(OptionImpl.createBuilder(int.class, vanillaOpts) .setName(Component.translatable("options.guiScale")) .setTooltip(Component.translatable("sodium.options.gui_scale.tooltip")) - .setControl(option -> new SliderControl(option, 0, Minecraft.getInstance().getWindow().calculateScale(0, Minecraft.getInstance().isEnforceUnicode()), 1, ControlValueFormatter.guiScale())) + .setControl(option -> new DynamicMaxSliderControl(option, 0, () -> Minecraft.getInstance().getWindow().calculateScale(0, Minecraft.getInstance().isEnforceUnicode()), 1, ControlValueFormatter.guiScale())) .setBinding((opts, value) -> { opts.guiScale().set(value); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/DynamicMaxSliderControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/DynamicMaxSliderControl.java new file mode 100644 index 0000000000..eea659dce5 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/DynamicMaxSliderControl.java @@ -0,0 +1,49 @@ +package net.caffeinemc.mods.sodium.client.gui.options.control; + +import net.caffeinemc.mods.sodium.client.gui.options.Option; +import net.caffeinemc.mods.sodium.client.util.Dim2i; +import org.apache.commons.lang3.Validate; + +import java.util.function.IntSupplier; + +public class DynamicMaxSliderControl implements Control { + private final Option option; + + private final IntSupplier max; + private final int min, interval; + + private final ControlValueFormatter mode; + + public DynamicMaxSliderControl(Option option, int min, IntSupplier max, int interval, ControlValueFormatter mode) { + Validate.isTrue(interval > 0, "The slider interval must be greater than zero"); + Validate.notNull(mode, "The slider mode must not be null"); + + this.option = option; + this.min = min; + this.max = max; + this.interval = interval; + this.mode = mode; + } + + @Override + public ControlElement createElement(Dim2i dim) { + int min = this.min; + int max = this.max.getAsInt(); + int interval = this.interval; + + Validate.isTrue(max > min, "The maximum value must be greater than the minimum value"); + Validate.isTrue(((max - min) % interval) == 0, "The maximum value must be divisable by the interval"); + + return new SliderControl.Button(this.option, dim, min, max, interval, this.mode); + } + + @Override + public Option getOption() { + return this.option; + } + + @Override + public int getMaxWidth() { + return 170; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java index 99c379b493..4de249b018 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java @@ -45,7 +45,7 @@ public int getMaxWidth() { return 170; } - private static class Button extends ControlElement { + static class Button extends ControlElement { private static final int THUMB_WIDTH = 2, TRACK_HEIGHT = 1; private final Rect2i sliderBounds; From e5cdbdefbbd785bcd28a6e1c1387f12d1f0cbe77 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Mon, 28 Jul 2025 16:56:13 -0500 Subject: [PATCH 106/215] Only use accelerated sorting on quad lists Some render types (notably the profiler graph) are marked as being "sorted" even though they have a topology which cannot be sorted. Fixes #3200 --- .../sorting/MultiBufferSourceMixin.java | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MultiBufferSourceMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MultiBufferSourceMixin.java index be8650c005..4a6e9d0ee3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MultiBufferSourceMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MultiBufferSourceMixin.java @@ -28,32 +28,48 @@ public class MultiBufferSourceMixin { ) private MeshData.SortState redirectSortQuads(MeshData meshData, ByteBufferBuilder bufferBuilder, VertexSorting sorting, Operation original) { if (sorting instanceof VertexSortingExtended sortingExtended) { - var sortedPrimitiveIds = VertexSorters.sort(meshData.vertexBuffer(), meshData.drawState().vertexCount(), meshData.drawState().format().getVertexSize(), sortingExtended); - var sortedIndexBuffer = buildSortedIndexBuffer(meshData, bufferBuilder, sortedPrimitiveIds); - ((MeshDataAccessor) meshData).setIndexBuffer(sortedIndexBuffer); + // Replace the vertex sorting algorithm when it implements our accelerated sort. + acceleratedSort(meshData, bufferBuilder, sortingExtended); } else { - original.call(meshData, bufferBuilder, sorting); + return original.call(meshData, bufferBuilder, sorting); } + // The caller never uses the return value. return null; } + @Unique + private static void acceleratedSort(MeshData meshData, ByteBufferBuilder bufferBuilder, VertexSortingExtended sorting) { + final var drawState = meshData.drawState(); + + if (drawState.mode() != VertexFormat.Mode.QUADS) { + // Only quad lists can be sorted. + return; + } + + var sortedPrimitiveIds = VertexSorters.sort(meshData.vertexBuffer(), drawState.vertexCount(), drawState.format().getVertexSize(), sorting); + var sortedIndexBuffer = buildSortedIndexBuffer(meshData, bufferBuilder, sortedPrimitiveIds); + ((MeshDataAccessor) meshData).setIndexBuffer(sortedIndexBuffer); + } + @Unique private static ByteBufferBuilder.Result buildSortedIndexBuffer(MeshData meshData, ByteBufferBuilder bufferBuilder, int[] primitiveIds) { final var indexType = meshData.drawState().indexType(); final var ptr = bufferBuilder.reserve((primitiveIds.length * VERTICES_PER_QUAD) * indexType.bytes); if (indexType == VertexFormat.IndexType.SHORT) { - writeShortIndexBuffer(ptr, primitiveIds); + writeIndexBufferShort(ptr, primitiveIds); } else if (indexType == VertexFormat.IndexType.INT) { - writeIntIndexBuffer(ptr, primitiveIds); + writeIndexBufferInt(ptr, primitiveIds); + } else { + throw new UnsupportedOperationException(); } return bufferBuilder.build(); } @Unique - private static void writeIntIndexBuffer(long ptr, int[] primitiveIds) { + private static void writeIndexBufferInt(long ptr, int[] primitiveIds) { for (int primitiveId : primitiveIds) { MemoryUtil.memPutInt(ptr + 0L, (primitiveId * 4) + 0); MemoryUtil.memPutInt(ptr + 4L, (primitiveId * 4) + 1); @@ -66,7 +82,7 @@ private static void writeIntIndexBuffer(long ptr, int[] primitiveIds) { } @Unique - private static void writeShortIndexBuffer(long ptr, int[] primitiveIds) { + private static void writeIndexBufferShort(long ptr, int[] primitiveIds) { for (int primitiveId : primitiveIds) { MemoryUtil.memPutShort(ptr + 0L, (short) ((primitiveId * 4) + 0)); MemoryUtil.memPutShort(ptr + 2L, (short) ((primitiveId * 4) + 1)); From fc89e88af46dca9f905b829ed527af6cb304c391 Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 26 Aug 2025 20:48:02 +0200 Subject: [PATCH 107/215] Separate Fast/Fancy translation strings for weather and leaves (#2291) and make use of the existing separate ones for clouds --- .../client/gui/SodiumGameOptionPages.java | 10 ++--- .../sodium/client/gui/SodiumGameOptions.java | 37 +++++++++++++++---- .../resources/assets/sodium/lang/en_us.json | 4 ++ 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java index f731bf989f..4168b3e647 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java @@ -176,7 +176,7 @@ public static OptionPage quality() { .add(OptionImpl.createBuilder(CloudStatus.class, vanillaOpts) .setName(Component.translatable("options.renderClouds")) .setTooltip(Component.translatable("sodium.options.clouds_quality.tooltip")) - .setControl(option -> new CyclingControl<>(option, CloudStatus.class, new Component[] { Component.translatable("options.off"), Component.translatable("options.graphics.fast"), Component.translatable("options.graphics.fancy") })) + .setControl(option -> new CyclingControl<>(option, CloudStatus.class, new Component[] { Component.translatable("options.off"), Component.translatable("options.clouds.fast"), Component.translatable("options.clouds.fancy") })) .setBinding((opts, value) -> { opts.cloudStatus().set(value); @@ -189,17 +189,17 @@ public static OptionPage quality() { }, opts -> opts.cloudStatus().get()) .setImpact(OptionImpact.LOW) .build()) - .add(OptionImpl.createBuilder(SodiumGameOptions.GraphicsQuality.class, sodiumOpts) + .add(OptionImpl.createBuilder(SodiumGameOptions.WeatherQuality.class, sodiumOpts) .setName(Component.translatable("soundCategory.weather")) .setTooltip(Component.translatable("sodium.options.weather_quality.tooltip")) - .setControl(option -> new CyclingControl<>(option, SodiumGameOptions.GraphicsQuality.class)) + .setControl(option -> new CyclingControl<>(option, SodiumGameOptions.WeatherQuality.class)) .setBinding((opts, value) -> opts.quality.weatherQuality = value, opts -> opts.quality.weatherQuality) .setImpact(OptionImpact.MEDIUM) .build()) - .add(OptionImpl.createBuilder(SodiumGameOptions.GraphicsQuality.class, sodiumOpts) + .add(OptionImpl.createBuilder(SodiumGameOptions.LeavesQuality.class, sodiumOpts) .setName(Component.translatable("sodium.options.leaves_quality.name")) .setTooltip(Component.translatable("sodium.options.leaves_quality.tooltip")) - .setControl(option -> new CyclingControl<>(option, SodiumGameOptions.GraphicsQuality.class)) + .setControl(option -> new CyclingControl<>(option, SodiumGameOptions.LeavesQuality.class)) .setBinding((opts, value) -> opts.quality.leavesQuality = value, opts -> opts.quality.leavesQuality) .setImpact(OptionImpact.MEDIUM) .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java index 0fa4bc88fb..fbd0ad1ed6 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java @@ -72,8 +72,8 @@ public SortBehavior getSortBehavior() { } public static class QualitySettings { - public GraphicsQuality weatherQuality = GraphicsQuality.DEFAULT; - public GraphicsQuality leavesQuality = GraphicsQuality.DEFAULT; + public WeatherQuality weatherQuality = WeatherQuality.DEFAULT; + public LeavesQuality leavesQuality = LeavesQuality.DEFAULT; public boolean enableVignette = true; } @@ -83,14 +83,14 @@ public static class NotificationSettings { public boolean hasSeenDonationPrompt = false; } - public enum GraphicsQuality implements TextProvider { + public enum WeatherQuality implements TextProvider { DEFAULT("options.gamma.default"), - FANCY("options.clouds.fancy"), - FAST("options.clouds.fast"); + FANCY("sodium.options.weather_quality.fancy"), + FAST("sodium.options.weather_quality.fast"); private final Component name; - GraphicsQuality(String name) { + WeatherQuality(String name) { this.name = Component.translatable(name); } @@ -99,8 +99,29 @@ public Component getLocalizedName() { return this.name; } - public boolean isFancy(GraphicsStatus graphicsStatus) { - return (this == FANCY) || (this == DEFAULT && (graphicsStatus == GraphicsStatus.FANCY || graphicsStatus == GraphicsStatus.FABULOUS)); + public boolean isFancy(GraphicsStatus graphicsMode) { + return (this == FANCY) || (this == DEFAULT && (graphicsMode == GraphicsStatus.FANCY || graphicsMode == GraphicsStatus.FABULOUS)); + } + } + + public enum LeavesQuality implements TextProvider { + DEFAULT("options.gamma.default"), + FANCY("sodium.options.leaves_quality.fancy"), + FAST("sodium.options.leaves_quality.fast"); + + private final Component name; + + LeavesQuality(String name) { + this.name = Component.translatable(name); + } + + @Override + public Component getLocalizedName() { + return this.name; + } + + public boolean isFancy(GraphicsStatus graphicsMode) { + return (this == FANCY) || (this == DEFAULT && (graphicsMode == GraphicsStatus.FANCY || graphicsMode == GraphicsStatus.FABULOUS)); } } diff --git a/common/src/main/resources/assets/sodium/lang/en_us.json b/common/src/main/resources/assets/sodium/lang/en_us.json index 3b8856e48a..04e2d66b27 100644 --- a/common/src/main/resources/assets/sodium/lang/en_us.json +++ b/common/src/main/resources/assets/sodium/lang/en_us.json @@ -21,7 +21,11 @@ "sodium.options.graphics_quality.tooltip": "The default graphics quality controls some legacy options and is necessary for mod compatibility. If the options below are left to \"Default\", they will use this setting.", "sodium.options.clouds_quality.tooltip": "The quality level used for rendering clouds in the sky. Even though the options are labeled \"Fast\" and \"Fancy\", the effect on performance is negligible.", "sodium.options.weather_quality.tooltip": "Controls the distance that weather effects, such as rain and snow, will be rendered.", + "sodium.options.weather_quality.fast": "Fast", + "sodium.options.weather_quality.fancy": "Fancy", "sodium.options.leaves_quality.name": "Leaves", + "sodium.options.leaves_quality.fast": "Fast", + "sodium.options.leaves_quality.fancy": "Fancy", "sodium.options.leaves_quality.tooltip": "Controls whether leaves will be rendered as transparent (fancy) or opaque (fast).", "sodium.options.particle_quality.tooltip": "Controls the maximum number of particles which can be present on screen at any one time.", "sodium.options.smooth_lighting.tooltip": "Enables the smooth lighting and shading of blocks in the world. This can very slightly increase the amount of time it takes to load or update a chunk, but it doesn't affect frame rates.", From ee1a3d6350e8f91b6ab364f4d7fafc34835ee240 Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 26 Aug 2025 20:48:58 +0200 Subject: [PATCH 108/215] Implement translucency sorting with quad splitting (#2993) --- .../client/gui/SodiumGameOptionPages.java | 13 + .../sodium/client/gui/SodiumGameOptions.java | 3 + .../quad/properties/ModelQuadFacing.java | 19 +- .../chunk/compile/ChunkBuildBuffers.java | 41 +- .../compile/buffers/ChunkVertexConsumer.java | 6 +- .../chunk/compile/pipeline/BlockRenderer.java | 8 +- .../pipeline/DefaultFluidRenderer.java | 7 +- .../tasks/ChunkBuilderMeshingTask.java | 40 +- .../chunk/data/BuiltSectionMeshParts.java | 4 +- .../QuadSplittingMode.java | 49 ++ .../TranslucentGeometryCollector.java | 232 ++------ .../translucent_sorting/bsp_tree/BSPNode.java | 25 +- .../bsp_tree/BSPResult.java | 9 + .../bsp_tree/BSPWorkspace.java | 120 +++- .../bsp_tree/InnerBinaryPartitionBSPNode.java | 55 +- .../bsp_tree/InnerPartitionBSPNode.java | 519 +++++++++++++++++- .../bsp_tree/UpdatedQuadsList.java | 31 ++ .../data/AnyOrderData.java | 20 +- .../data/DynamicBSPData.java | 49 +- .../translucent_sorting/data/DynamicData.java | 6 +- .../data/DynamicTopoData.java | 19 +- .../data/MixedDirectionData.java | 18 - .../translucent_sorting/data/NoData.java | 7 + .../data/PresentTranslucentData.java | 29 +- .../data/SplitDirectionData.java | 22 - .../data/StaticNormalRelativeData.java | 43 +- .../data/StaticTopoData.java | 15 +- .../data/TopoGraphSorting.java | 49 +- .../data/TranslucentData.java | 24 +- .../translucent_sorting/quad/FullTQuad.java | 178 ++++++ .../quad/RegularTQuad.java | 85 +++ .../translucent_sorting/{ => quad}/TQuad.java | 200 +++++-- .../trigger/GeometryPlanes.java | 12 +- .../trigger/SortTriggering.java | 4 +- .../builder/ChunkMeshBufferBuilder.java | 7 +- .../vertex/format/ChunkVertexEncoder.java | 22 + .../resources/assets/sodium/lang/en_us.json | 4 + 37 files changed, 1524 insertions(+), 470 deletions(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/QuadSplittingMode.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/UpdatedQuadsList.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/MixedDirectionData.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SplitDirectionData.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/quad/FullTQuad.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/quad/RegularTQuad.java rename common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/{ => quad}/TQuad.java (55%) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java index 4168b3e647..b632b6df45 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java @@ -5,6 +5,7 @@ import com.mojang.blaze3d.platform.Monitor; import com.mojang.blaze3d.platform.VideoMode; import com.mojang.blaze3d.platform.Window; +import net.caffeinemc.mods.sodium.client.SodiumClientMod; import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds; import net.caffeinemc.mods.sodium.client.gl.arena.staging.MappedStagingBuffer; @@ -14,6 +15,7 @@ import net.caffeinemc.mods.sodium.client.gui.options.control.*; import net.caffeinemc.mods.sodium.client.gui.options.storage.MinecraftOptionsStorage; import net.caffeinemc.mods.sodium.client.gui.options.storage.SodiumOptionsStorage; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.QuadSplittingMode; import net.minecraft.client.*; import net.minecraft.network.chat.Component; import org.lwjgl.opengl.GL; @@ -337,6 +339,17 @@ public static OptionPage performance() { .build()) .build()); + groups.add(OptionGroup.createBuilder() + .add(OptionImpl.createBuilder(QuadSplittingMode.class, sodiumOpts) + .setName(Component.translatable("sodium.options.quad_splitting.name")) + .setTooltip(Component.translatable("sodium.options.quad_splitting.tooltip")) + .setControl(option -> new CyclingControl<>(option, QuadSplittingMode.class)) + .setBinding((opts, value) -> opts.performance.quadSplittingMode = value, opts -> opts.performance.quadSplittingMode) + .setImpact(OptionImpact.MEDIUM) + .setEnabled(() -> SodiumClientMod.options().debug.terrainSortingEnabled) + .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) + .build()).build()); + return new OptionPage(Component.translatable("sodium.options.pages.performance"), ImmutableList.copyOf(groups)); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java index fbd0ad1ed6..2c49f38832 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java @@ -5,6 +5,7 @@ import com.google.gson.GsonBuilder; import com.google.gson.annotations.SerializedName; import net.caffeinemc.mods.sodium.client.gui.options.TextProvider; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.QuadSplittingMode; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior; import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; import net.caffeinemc.mods.sodium.client.util.FileUtil; @@ -48,6 +49,8 @@ public static class PerformanceSettings { public boolean useFogOcclusion = true; public boolean useBlockFaceCulling = true; public boolean useNoErrorGLContext = true; + + public QuadSplittingMode quadSplittingMode = QuadSplittingMode.SAFE; } public static class AdvancedSettings { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/properties/ModelQuadFacing.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/properties/ModelQuadFacing.java index 026b438459..9fbdffbb75 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/properties/ModelQuadFacing.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/properties/ModelQuadFacing.java @@ -1,10 +1,9 @@ package net.caffeinemc.mods.sodium.client.model.quad.properties; -import net.caffeinemc.mods.sodium.client.util.DirectionUtil; import net.caffeinemc.mods.sodium.api.util.NormI8; +import net.caffeinemc.mods.sodium.client.util.DirectionUtil; import net.minecraft.core.Direction; import net.minecraft.util.Mth; -import org.joml.Math; import org.joml.Vector3f; import org.joml.Vector3fc; @@ -104,14 +103,22 @@ public int getPackedAlignedNormal() { return PACKED_ALIGNED_NORMALS[this.ordinal()]; } - public static ModelQuadFacing fromNormal(float x, float y, float z) { - if (!(Math.isFinite(x) && Math.isFinite(y) && Math.isFinite(z))) { - return ModelQuadFacing.UNASSIGNED; + public static ModelQuadFacing fromNormal(Vector3fc normal) { + for (Direction face : DirectionUtil.ALL_DIRECTIONS) { + var step = face.step(); + if (step.equals(normal, Mth.EPSILON)) { + return ModelQuadFacing.fromDirection(face); + } } + return ModelQuadFacing.UNASSIGNED; + } + + + public static ModelQuadFacing fromNormal(float x, float y, float z) { for (Direction face : DirectionUtil.ALL_DIRECTIONS) { var step = face.step(); - if (Mth.equal(Math.fma(x, step.x(), Math.fma(y, step.y(), z * step.z())), 1.0f)) { + if (Mth.equal(x, step.x()) && Mth.equal(y, step.y()) && Mth.equal(z, step.z())) { return ModelQuadFacing.fromDirection(face); } } 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 f76fe115d0..672b47784c 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 @@ -9,6 +9,8 @@ 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.Material; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.bsp_tree.UpdatedQuadsList; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.TranslucentData; import net.caffeinemc.mods.sodium.client.render.chunk.vertex.builder.ChunkMeshBufferBuilder; import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexType; import net.caffeinemc.mods.sodium.client.util.NativeBuffer; @@ -19,6 +21,8 @@ * shrink a buffer. */ public class ChunkBuildBuffers { + private static final int UNASSIGNED_SEGMENT_INDEX = ModelQuadFacing.UNASSIGNED.ordinal() << 1; + private final Reference2ReferenceOpenHashMap builders = new Reference2ReferenceOpenHashMap<>(); private final ChunkVertexType vertexType; @@ -51,6 +55,10 @@ public ChunkModelBuilder get(TerrainRenderPass pass) { return this.builders.get(pass); } + public static int[] makeVertexSegments() { + return new int[ModelQuadFacing.COUNT << 1]; + } + /** * 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 @@ -58,7 +66,7 @@ public ChunkModelBuilder get(TerrainRenderPass pass) { */ public BuiltSectionMeshParts createMesh(TerrainRenderPass pass, int visibleSlices, boolean forceUnassigned, boolean sliceReordering) { var builder = this.builders.get(pass); - int[] vertexSegments = new int[ModelQuadFacing.COUNT << 1]; + int[] vertexSegments = makeVertexSegments(); int vertexTotal = 0; // get the total vertex count to initialize the buffer @@ -109,9 +117,8 @@ public BuiltSectionMeshParts createMesh(TerrainRenderPass pass, int visibleSlice // forceUnassigned implies !sliceReordering if (forceUnassigned) { - var segmentIndex = ModelQuadFacing.UNASSIGNED.ordinal() << 1; - vertexSegments[segmentIndex] = vertexTotal; - vertexSegments[segmentIndex + 1] = ModelQuadFacing.UNASSIGNED.ordinal(); + vertexSegments[UNASSIGNED_SEGMENT_INDEX] = vertexTotal; + vertexSegments[UNASSIGNED_SEGMENT_INDEX + 1] = ModelQuadFacing.UNASSIGNED.ordinal(); } for (ModelQuadFacing facing : ModelQuadFacing.VALUES) { @@ -131,6 +138,32 @@ public BuiltSectionMeshParts createMesh(TerrainRenderPass pass, int visibleSlice return new BuiltSectionMeshParts(mergedBuffer, vertexSegments); } + public BuiltSectionMeshParts createModifiedTranslucentMesh(UpdatedQuadsList updatedQuads) { + // mesh modification assumes non-empty mesh with predetermined size + + var builder = this.builders.get(DefaultTerrainRenderPasses.TRANSLUCENT); + + var stride = this.vertexType.getVertexFormat().getStride(); + var vertexTotal = TranslucentData.quadCountToVertexCount(updatedQuads.getMeshQuadCount()); + var mergedBuffer = new NativeBuffer(vertexTotal * stride); + var mergedBufferBuilder = mergedBuffer.getDirectBuffer(); + + for (ModelQuadFacing facing : ModelQuadFacing.VALUES) { + var buffer = builder.getVertexBuffer(facing); + if (!buffer.isEmpty()) { + mergedBufferBuilder.put(buffer.slice()); + } + } + + updatedQuads.applyBufferUpdates(builder.getVertexBuffer(ModelQuadFacing.UNASSIGNED), mergedBufferBuilder); + + int[] vertexSegments = makeVertexSegments(); + vertexSegments[UNASSIGNED_SEGMENT_INDEX] = vertexTotal; + vertexSegments[UNASSIGNED_SEGMENT_INDEX + 1] = ModelQuadFacing.UNASSIGNED.ordinal(); + + return new BuiltSectionMeshParts(mergedBuffer, vertexSegments); + } + public void destroy() { for (var builder : this.builders.values()) { builder.destroy(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/ChunkVertexConsumer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/ChunkVertexConsumer.java index 48ec0c9a3e..ede996847e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/ChunkVertexConsumer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/ChunkVertexConsumer.java @@ -128,8 +128,10 @@ public VertexConsumer potentiallyEndVertex() { ModelQuadFacing cullFace = ModelQuadFacing.fromPackedNormal(normal); - if (this.material.isTranslucent() && this.collector != null) { - this.collector.appendQuad(normal, this.vertices, cullFace); + // let the collector intercept the quad but discard it if it's deemed invalid (i.e. not visible) + if (this.material.isTranslucent() && this.collector != null && + this.collector.appendQuad(this.vertices, cullFace, normal)) { + return this; } this.modelBuilder.getVertexBuffer(cullFace).push(this.vertices, this.material); 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 3ab498a662..07ff4dd682 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 @@ -205,9 +205,11 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material 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); + // collect all translucent quads into the translucency sorting system if enabled, + // and discard the quad if it's invalid (i.e. not visible) + if (pass.isTranslucent() && this.collector != null && + this.collector.appendQuad(vertices, normalFace, quad.getFaceNormal())) { + return; } // if there was a downgrade from translucent to cutout, the material bits' alpha cutoff needs to be updated diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java index 6b8ddea15b..13e1b818a7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java @@ -37,7 +37,7 @@ import org.apache.commons.lang3.mutable.MutableInt; public class DefaultFluidRenderer { - // TODO: allow this to be changed by vertex format, WARNING: make sure TranslucentGeometryCollector knows about EPSILON + // TODO: allow this to be changed by vertex format, WARNING: make sure TQuad knows about EPSILON // TODO: move fluid rendering to a separate render pass and control glPolygonOffset and glDepthFunc to fix this properly public static final float EPSILON = 0.001f; private static final float ALIGNED_EQUALS_EPSILON = 0.011f; @@ -424,7 +424,10 @@ private void writeQuad(ChunkModelBuilder builder, TranslucentGeometryCollector c normal = NormI8.flipPacked(normal); } - collector.appendQuad(normal, vertices, facing); + // discard the quad if it's invalid (i.e. not visible) + if (collector.appendQuad(vertices, facing, normal)) { + return; + } } var vertexBuffer = builder.getVertexBuffer(facing); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java index 198bddb109..e6a1855c4c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java @@ -44,7 +44,7 @@ /** * Rebuilds all the meshes of a chunk for each given render pass with non-occluded blocks. The result is then uploaded * to graphics memory on the main thread. - * + *

* This task takes a slice of the level from the thread it is created on. Since these slices require rather large * array allocations, they are pooled to ensure that the garbage collector doesn't become overloaded. */ @@ -155,12 +155,34 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke sortType = collector.finishRendering(); } + // cancellation opportunity right before translucent sorting + if (cancellationToken.isCancelled()) { + return null; + } + + boolean reuseUploadedData = false; + TranslucentData translucentData = null; + if (collector != null) { + var oldData = this.render.getTranslucentData(); + translucentData = collector.getTranslucentData(oldData, this); + reuseUploadedData = translucentData == oldData; + } + Map meshes = new Reference2ReferenceOpenHashMap<>(); var visibleSlices = DefaultChunkRenderer.getVisibleFaces( (int) this.absoluteCameraPos.x(), (int) this.absoluteCameraPos.y(), (int) this.absoluteCameraPos.z(), this.render.getChunkX(), this.render.getChunkY(), this.render.getChunkZ()); + if (translucentData != null && translucentData.meshesWereModified()) { + meshes.put(DefaultTerrainRenderPasses.TRANSLUCENT, buffers.createModifiedTranslucentMesh(translucentData.getUpdatedQuads())); + renderData.addRenderPass(DefaultTerrainRenderPasses.TRANSLUCENT); + } + for (TerrainRenderPass pass : DefaultTerrainRenderPasses.ALL) { + if (meshes.containsKey(pass)) { + continue; + } + // if the translucent geometry needs to share an index buffer between the directions, // consolidate all translucent geometry into UNASSIGNED boolean translucentBehavior = collector != null && pass.isTranslucent(); @@ -174,23 +196,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke } } - // cancellation opportunity right before translucent sorting - if (cancellationToken.isCancelled()) { - meshes.forEach((pass, mesh) -> mesh.getVertexData().free()); - return null; - } - renderData.setOcclusionData(occluder.resolve()); - - boolean reuseUploadedData = false; - TranslucentData translucentData = null; - if (collector != null) { - var oldData = this.render.getTranslucentData(); - translucentData = collector.getTranslucentData( - oldData, meshes.get(DefaultTerrainRenderPasses.TRANSLUCENT), this); - reuseUploadedData = translucentData == oldData; - } - var output = new ChunkBuildOutput(this.render, this.submitTime, translucentData, renderData.build(), meshes); if (collector != null) { if (reuseUploadedData) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java index b53454a936..ae98d13a35 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/BuiltSectionMeshParts.java @@ -15,8 +15,8 @@ public class BuiltSectionMeshParts { private final int[] vertexSegments; private final NativeBuffer buffer; - public BuiltSectionMeshParts(NativeBuffer buffer, int[] vertexCounts) { - this.vertexSegments = vertexCounts; + public BuiltSectionMeshParts(NativeBuffer buffer, int[] vertexSegments) { + this.vertexSegments = vertexSegments; this.buffer = buffer; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/QuadSplittingMode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/QuadSplittingMode.java new file mode 100644 index 0000000000..481606b4a1 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/QuadSplittingMode.java @@ -0,0 +1,49 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting; + +import net.caffeinemc.mods.sodium.client.gui.options.TextProvider; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; + +public enum QuadSplittingMode implements TextProvider { + OFF("/", 1.0f, true, "options.off"), + SAFE("S", 2.0f, true, "sodium.options.quad_splitting.safe"), + UNLIMITED("U", Float.POSITIVE_INFINITY, false, "sodium.options.quad_splitting.unlimited"); + + private final String shortName; + + // how much bigger the final geometry is allowed to be compared to the input geometry when performing quad splitting. + private final float maxAmplificationFactor; + private final boolean quantizeTriggerNormals; + private final Component name; + + QuadSplittingMode(String shortName, float maxAmplificationFactor, boolean quantizeTriggerNormals, String name) { + this.shortName = shortName; + this.maxAmplificationFactor = maxAmplificationFactor; + this.quantizeTriggerNormals = quantizeTriggerNormals; + this.name = Component.translatable(name); + } + + @Override + public Component getLocalizedName() { + return this.name; + } + + public String getShortName() { + return this.shortName; + } + + public boolean allowsSplitting() { + return this != OFF; + } + + public boolean quantizeTriggerNormals() { + return this.quantizeTriggerNormals; + } + + public int getMaxTotalQuads(int baseQuadCount) { + if (Float.isInfinite(this.maxAmplificationFactor)) { + return Integer.MAX_VALUE; + } + return Mth.ceil(baseQuadCount * this.maxAmplificationFactor); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java index f78aff1028..77de852f19 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java @@ -5,19 +5,17 @@ import net.caffeinemc.mods.sodium.client.SodiumClientMod; import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildOutput; -import net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline.DefaultFluidRenderer; import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionMeshParts; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.bsp_tree.BSPBuildFailureException; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.*; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.FullTQuad; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.RegularTQuad; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.TQuad; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.GeometryPlanes; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.SortTriggering; import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexEncoder; import net.minecraft.core.SectionPos; import net.minecraft.util.Mth; -import org.joml.Vector3f; -import org.joml.Vector3fc; - -import java.util.Arrays; /** * The translucent geometry collector collects the data from the renderers and @@ -47,8 +45,8 @@ * task result. */ public class TranslucentGeometryCollector { - private final SectionPos sectionPos; + private final QuadSplittingMode quadSplittingMode = SodiumClientMod.options().performance.quadSplittingMode; // true if there are any unaligned quads private boolean hasUnaligned = false; @@ -84,6 +82,7 @@ public class TranslucentGeometryCollector { @SuppressWarnings("unchecked") private ReferenceArrayList[] quadLists = new ReferenceArrayList[ModelQuadFacing.COUNT]; + private final int[] meshFacingCounts = new int[ModelQuadFacing.COUNT]; private TQuad[] quads; private SortType sortType; @@ -95,144 +94,46 @@ public TranslucentGeometryCollector(SectionPos sectionPos) { this.sectionPos = sectionPos; } - private static final float INV_QUANTIZE_EPSILON = 256f; - private static final float QUANTIZE_EPSILON = 1f / INV_QUANTIZE_EPSILON; - - static { - // ensure it fits with the fluid renderer epsilon and that it's a power-of-two - // fraction - var targetEpsilon = DefaultFluidRenderer.EPSILON * 2.1f; - if (QUANTIZE_EPSILON <= targetEpsilon && Integer.bitCount((int) INV_QUANTIZE_EPSILON) == 1) { - throw new RuntimeException("epsilon is invalid: " + QUANTIZE_EPSILON); - } - } - - public void appendQuad(int packedNormal, ChunkVertexEncoder.Vertex[] vertices, ModelQuadFacing facing) { - float xSum = 0; - float ySum = 0; - float zSum = 0; - - // keep track of distinct vertices to compute the center accurately for - // degenerate quads - float lastX = vertices[3].x; - float lastY = vertices[3].y; - float lastZ = vertices[3].z; - int uniqueVertexes = 0; - - float posXExtent = Float.NEGATIVE_INFINITY; - float posYExtent = Float.NEGATIVE_INFINITY; - float posZExtent = Float.NEGATIVE_INFINITY; - float negXExtent = Float.POSITIVE_INFINITY; - float negYExtent = Float.POSITIVE_INFINITY; - float negZExtent = Float.POSITIVE_INFINITY; - - for (int i = 0; i < 4; i++) { - float x = vertices[i].x; - float y = vertices[i].y; - float z = vertices[i].z; - - posXExtent = Math.max(posXExtent, x); - posYExtent = Math.max(posYExtent, y); - posZExtent = Math.max(posZExtent, z); - negXExtent = Math.min(negXExtent, x); - negYExtent = Math.min(negYExtent, y); - negZExtent = Math.min(negZExtent, z); - - if (x != lastX || y != lastY || z != lastZ) { - xSum += x; - ySum += y; - zSum += z; - uniqueVertexes++; - } - if (i != 3) { - lastX = x; - lastY = y; - lastZ = z; - } - } - - // shrink quad in non-normal directions to prevent intersections caused by - // epsilon offsets applied by FluidRenderer - if (facing != ModelQuadFacing.POS_X && facing != ModelQuadFacing.NEG_X) { - posXExtent -= QUANTIZE_EPSILON; - negXExtent += QUANTIZE_EPSILON; - if (negXExtent > posXExtent) { - negXExtent = posXExtent; - } - } - if (facing != ModelQuadFacing.POS_Y && facing != ModelQuadFacing.NEG_Y) { - posYExtent -= QUANTIZE_EPSILON; - negYExtent += QUANTIZE_EPSILON; - if (negYExtent > posYExtent) { - negYExtent = posYExtent; - } + /** + * Collects a quad to be sorted. Note that if the quad's geometry and/or its vertices make it axis-aligned but it still uses the {@link ModelQuadFacing#UNASSIGNED} facing, dynamic sorting will break. Checking every quad for this would slow things down. + * + * @param vertices the vertices of the quad + * @param facing the facing of the quad + * @param packedNormal the packed normal of the quad + * @return true if the quad is invalid and should be discarded from the model entirely, false otherwise. + */ + public boolean appendQuad(ChunkVertexEncoder.Vertex[] vertices, ModelQuadFacing facing, int packedNormal) { + TQuad quad; + if (this.isSplittingQuads()) { + quad = FullTQuad.fromVertices(vertices, facing, packedNormal); + } else { + quad = RegularTQuad.fromVertices(vertices, facing, packedNormal); } - if (facing != ModelQuadFacing.POS_Z && facing != ModelQuadFacing.NEG_Z) { - posZExtent -= QUANTIZE_EPSILON; - negZExtent += QUANTIZE_EPSILON; - if (negZExtent > posZExtent) { - negZExtent = posZExtent; - } + if (quad == null) { + return true; } - // POS_X, POS_Y, POS_Z, NEG_X, NEG_Y, NEG_Z - float[] extents = new float[] { posXExtent, posYExtent, posZExtent, negXExtent, negYExtent, negZExtent }; - int direction = facing.ordinal(); var quadList = this.quadLists[direction]; if (quadList == null) { quadList = new ReferenceArrayList<>(); this.quadLists[direction] = quadList; } - - Vector3fc center = null; - if (!facing.isAligned() || uniqueVertexes != 4) { - var centerX = xSum / uniqueVertexes; - var centerY = ySum / uniqueVertexes; - var centerZ = zSum / uniqueVertexes; - center = new Vector3f(centerX, centerY, centerZ); - } - - // check if we need to store vertex positions for this quad, only necessary if it's unaligned or rotated (yet aligned) - var needsVertexPositions = uniqueVertexes != 4 || !facing.isAligned(); - if (!needsVertexPositions) { - for (int i = 0; i < 4; i++) { - var vertex = vertices[i]; - if (vertex.x != posYExtent && vertex.x != negYExtent || - vertex.y != posZExtent && vertex.y != negZExtent || - vertex.z != posXExtent && vertex.z != negXExtent) { - needsVertexPositions = true; - break; - } - } - } - - float[] vertexPositions = null; - if (needsVertexPositions) { - vertexPositions = new float[12]; - for (int i = 0, itemIndex = 0; i < 4; i++) { - var vertex = vertices[i]; - vertexPositions[itemIndex++] = vertex.x; - vertexPositions[itemIndex++] = vertex.y; - vertexPositions[itemIndex++] = vertex.z; - } - } + quadList.add(quad); if (facing.isAligned()) { // only update global extents if there are no unaligned quads since this is only // used for the convex box test which doesn't work with unaligned quads anyway if (!this.hasUnaligned) { - this.extents[0] = Math.max(this.extents[0], posXExtent); - this.extents[1] = Math.max(this.extents[1], posYExtent); - this.extents[2] = Math.max(this.extents[2], posZExtent); - this.extents[3] = Math.min(this.extents[3], negXExtent); - this.extents[4] = Math.min(this.extents[4], negYExtent); - this.extents[5] = Math.min(this.extents[5], negZExtent); + var quadExtents = quad.getExtents(); + for (int i = 0; i < 3; i++) { + this.extents[i] = Math.max(this.extents[i], quadExtents[i]); + } + for (int i = 3; i < 6; i++) { + this.extents[i] = Math.min(this.extents[i], quadExtents[i]); + } } - var quad = TQuad.fromAligned(facing, extents, vertexPositions, center); - quadList.add(quad); - var extreme = this.alignedExtremes[direction]; var distance = quad.getAccurateDotProduct(); @@ -251,9 +152,6 @@ public void appendQuad(int packedNormal, ChunkVertexEncoder.Vertex[] vertices, M } else { this.hasUnaligned = true; - var quad = TQuad.fromUnaligned(facing, extents, vertexPositions, center, packedNormal); - quadList.add(quad); - // update the two unaligned normals that are tracked var distance = quad.getAccurateDotProduct(); if (packedNormal == this.unalignedANormal) { @@ -278,6 +176,12 @@ public void appendQuad(int packedNormal, ChunkVertexEncoder.Vertex[] vertices, M this.untrackedUnalignedNormalCount++; } } + + return false; + } + + public boolean isSplittingQuads() { + return this.quadSplittingMode.allowsSplitting(); } /** @@ -430,7 +334,6 @@ private SortType sortTypeHeuristic() { // use the given set of quad count limits to determine if a static topo sort // should be attempted - var attemptLimitIndex = Mth.clamp(normalCount, 2, STATIC_TOPO_SORT_ATTEMPT_LIMITS.length - 1); if (this.quads.length <= STATIC_TOPO_SORT_ATTEMPT_LIMITS[attemptLimitIndex]) { return SortType.STATIC_TOPO; @@ -475,6 +378,8 @@ public SortType finishRendering() { for (int direction = 0; direction < ModelQuadFacing.COUNT; direction++) { var quadList = this.quadLists[direction]; if (quadList != null) { + this.meshFacingCounts[direction] = quadList.size(); + for (var quad : quadList) { this.quads[quadIndex++] = quad; } @@ -489,32 +394,21 @@ public SortType finishRendering() { return this.sortType; } - private static int ensureUnassignedVertexCount(int[] vertexCounts) { - int vertexCount = vertexCounts[ModelQuadFacing.UNASSIGNED.ordinal()]; - - if (vertexCount == 0) { - throw new IllegalStateException("No unassigned data in mesh"); - } - - return vertexCount; - } - - private TranslucentData makeNewTranslucentData(int[] vertexCounts, CombinedCameraPos cameraPos, + private TranslucentData makeNewTranslucentData(CombinedCameraPos cameraPos, TranslucentData oldData) { if (this.sortType == SortType.NONE) { - return AnyOrderData.fromMesh(vertexCounts, this.quads, this.sectionPos); + return AnyOrderData.fromMesh(this.quads, this.sectionPos); } if (this.sortType == SortType.STATIC_NORMAL_RELATIVE) { var isDoubleUnaligned = this.alignedFacingBitmap == 0; - return StaticNormalRelativeData.fromMesh(vertexCounts, this.quads, this.sectionPos, isDoubleUnaligned); + return StaticNormalRelativeData.fromMesh(this.meshFacingCounts, this.quads, this.sectionPos, isDoubleUnaligned); } // from this point on we know the estimated sort type requires direction mixing // (no backface culling) and all vertices are in the UNASSIGNED direction. if (this.sortType == SortType.STATIC_TOPO) { - var vertexCount = ensureUnassignedVertexCount(vertexCounts); - var result = StaticTopoData.fromMesh(vertexCount, this.quads, this.sectionPos); + var result = StaticTopoData.fromMesh(this.quads, this.sectionPos, this.isSplittingQuads()); if (result != null) { return result; } @@ -525,18 +419,16 @@ private TranslucentData makeNewTranslucentData(int[] vertexCounts, CombinedCamer this.sortType = filterSortType(this.sortType); if (this.sortType == SortType.NONE) { - return AnyOrderData.fromMesh(vertexCounts, this.quads, this.sectionPos); + return AnyOrderData.fromMesh(this.quads, this.sectionPos); } if (this.sortType == SortType.DYNAMIC) { - var vertexCount = ensureUnassignedVertexCount(vertexCounts); try { - return DynamicBSPData.fromMesh( - vertexCount, cameraPos, this.quads, this.sectionPos, oldData); + return DynamicBSPData.fromMesh(cameraPos, this.quads, this.sectionPos, oldData, this.quadSplittingMode); } catch (BSPBuildFailureException e) { var geometryPlanes = GeometryPlanes.fromQuadLists(this.sectionPos, this.quads); return DynamicTopoData.fromMesh( - vertexCount, cameraPos, this.quads, this.sectionPos, + cameraPos, this.quads, this.sectionPos, geometryPlanes); } } @@ -544,11 +436,12 @@ private TranslucentData makeNewTranslucentData(int[] vertexCounts, CombinedCamer throw new IllegalStateException("Unknown sort type: " + this.sortType); } - private int getQuadHash(TQuad[] quads) { + public int getQuadHash() { if (this.quadHashPresent) { return this.quadHash; } + var quads = this.quads; for (int i = 0; i < quads.length; i++) { var quad = quads[i]; this.quadHash = this.quadHash * 31 + quad.getQuadHash() + i * 3; @@ -558,41 +451,24 @@ private int getQuadHash(TQuad[] quads) { } public TranslucentData getTranslucentData( - TranslucentData oldData, BuiltSectionMeshParts translucentMesh, CombinedCameraPos cameraPos) { - // means there is no translucent geometry - if (translucentMesh == null) { + TranslucentData oldData, CombinedCameraPos cameraPos) { + if (this.quads.length == 0) { return NoData.forNoTranslucent(this.sectionPos); } - var vertexCounts = translucentMesh.computeVertexCounts(); - // re-use the original translucent data if it's the same. This reduces the // amount of generated and uploaded index data when sections are rebuilt without // relevant changes to translucent geometry. Rebuilds happen when any part of // the section changes, including the here irrelevant cases of changes to opaque - // geometry or light levels. - if (oldData != null) { - // for the NONE sort type the ranges need to be the same, the actual geometry - // doesn't matter - if (this.sortType == SortType.NONE && oldData instanceof AnyOrderData oldAnyData - && oldAnyData.getQuadCount() == this.quads.length - && Arrays.equals(oldAnyData.getVertexCounts(), vertexCounts)) { - return oldAnyData; - } - - // for the other sort types the geometry needs to be the same (checked with - // length and hash) - if (oldData instanceof PresentTranslucentData oldPresentData) { - if (oldPresentData.getQuadCount() == this.quads.length - && oldPresentData.getQuadHash() == getQuadHash(this.quads)) { - return oldPresentData; - } - } + // geometry or non-geometric changes to translucent geometry. + // (except when quad splitting, where data is never reused) + if (oldData != null && oldData.oldDataMatches(this, this.sortType, this.quads)) { + return oldData; } - var newData = makeNewTranslucentData(vertexCounts, cameraPos, oldData); + var newData = this.makeNewTranslucentData(cameraPos, oldData); if (newData instanceof PresentTranslucentData presentData) { - presentData.setQuadHash(getQuadHash(this.quads)); + presentData.setQuadHash(this.getQuadHash()); } return newData; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java index 6362cbc07f..89aeb8b11c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java @@ -1,13 +1,13 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.bsp_tree; -import org.joml.Vector3fc; - import it.unimi.dsi.fastutil.ints.IntArrayList; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TQuad; +import net.caffeinemc.mods.sodium.api.util.NormI8; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.QuadSplittingMode; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.TopoGraphSorting; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.TQuad; import net.caffeinemc.mods.sodium.client.util.NativeBuffer; -import net.caffeinemc.mods.sodium.api.util.NormI8; import net.minecraft.core.SectionPos; +import org.joml.Vector3fc; /** * A node in the BSP tree. The BSP tree is made up of nodes that split quads @@ -32,13 +32,13 @@ public void collectSortedQuads(NativeBuffer nativeBuffer, Vector3fc cameraPos) { } public static BSPResult buildBSP(TQuad[] quads, SectionPos sectionPos, BSPNode oldRoot, - boolean prepareNodeReuse) { + boolean prepareNodeReuse, QuadSplittingMode quadSplittingMode) { // throw if there's too many quads InnerPartitionBSPNode.validateQuadCount(quads.length); // create a workspace and then the nodes figure out the recursive building. // throws if the BSP can't be built, null if none is necessary - var workspace = new BSPWorkspace(quads, sectionPos, prepareNodeReuse); + var workspace = new BSPWorkspace(quads, sectionPos, prepareNodeReuse, quadSplittingMode); // initialize the indexes to all quads int[] initialIndexes = new int[quads.length]; @@ -50,10 +50,11 @@ public static BSPResult buildBSP(TQuad[] quads, SectionPos sectionPos, BSPNode o var rootNode = BSPNode.build(workspace, allIndexes, -1, oldRoot); var result = workspace.result; result.setRootNode(rootNode); + result.setUpdatedQuadIndexes(workspace.getFinalizedUpdatedQuads()); return result; } - private static boolean doubleLeafPossible(TQuad quadA, TQuad quadB) { + private static boolean doubleLeafPossible(TQuad quadA, TQuad quadB, boolean failOnIntersection) { // check for coplanar or mutually invisible quads var facingA = quadA.getFacing(); var facingB = quadB.getFacing(); @@ -82,8 +83,8 @@ else if (facingA == facingB.getOpposite()) { // aligned otherwise mutually invisible else { - return !TopoGraphSorting.orthogonalQuadVisibleThrough(quadA, quadB) - && !TopoGraphSorting.orthogonalQuadVisibleThrough(quadB, quadA); + return !TopoGraphSorting.orthogonalQuadVisibleThrough(quadA, quadB, failOnIntersection) + && !TopoGraphSorting.orthogonalQuadVisibleThrough(quadB, quadA, failOnIntersection); } return false; @@ -100,10 +101,10 @@ static BSPNode build(BSPWorkspace workspace, IntArrayList indexes, int depth, BS } else if (indexes.size() == 2) { var quadIndexA = indexes.getInt(0); var quadIndexB = indexes.getInt(1); - var quadA = workspace.quads[quadIndexA]; - var quadB = workspace.quads[quadIndexB]; + var quadA = workspace.get(quadIndexA); + var quadB = workspace.get(quadIndexB); - if (doubleLeafPossible(quadA, quadB)) { + if (doubleLeafPossible(quadA, quadB, workspace.canSplitQuads())) { return new LeafDoubleBSPNode(quadIndexA, quadIndexB); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPResult.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPResult.java index 44911007b9..ec83ef2ae5 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPResult.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPResult.java @@ -8,6 +8,7 @@ */ public class BSPResult extends GeometryPlanes { private BSPNode rootNode; + private UpdatedQuadsList updatedQuadsList; public BSPNode getRootNode() { return this.rootNode; @@ -16,4 +17,12 @@ public BSPNode getRootNode() { public void setRootNode(BSPNode rootNode) { this.rootNode = rootNode; } + + public UpdatedQuadsList getUpdatedQuadsList() { + return this.updatedQuadsList; + } + + public void setUpdatedQuadIndexes(UpdatedQuadsList updatedQuadsList) { + this.updatedQuadsList = updatedQuadsList; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPWorkspace.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPWorkspace.java index 6860006cf4..8fdc24f36b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPWorkspace.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPWorkspace.java @@ -1,8 +1,12 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.bsp_tree; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TQuad; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.GeometryPlanes; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.QuadSplittingMode; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.FullTQuad; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.TQuad; import net.minecraft.core.SectionPos; +import org.joml.Vector3fc; /** * The BSP workspace holds the state during the BSP building process. (see also @@ -13,28 +17,116 @@ * global array instead of making a new one at each tree level doesn't appear to * have any performance benefit. */ -class BSPWorkspace { - /** - * All the quads in the section. - */ - final TQuad[] quads; - - final SectionPos sectionPos; - +class BSPWorkspace extends ObjectArrayList { final BSPResult result = new BSPResult(); + private final SectionPos sectionPos; final boolean prepareNodeReuse; + final boolean quantizeTriggerNormals; - BSPWorkspace(TQuad[] quads, SectionPos sectionPos, boolean prepareNodeReuse) { - this.quads = quads; + private int quadCount; + private final int maxQuadCount; + private IntArrayList availableQuadIndexes; + private UpdatedQuadsList updatedQuads; + + BSPWorkspace(TQuad[] quads, SectionPos sectionPos, boolean prepareNodeReuse, QuadSplittingMode quadSplittingMode) { + super(quads); this.sectionPos = sectionPos; this.prepareNodeReuse = prepareNodeReuse; + this.quantizeTriggerNormals = quadSplittingMode.quantizeTriggerNormals(); + + this.quadCount = quads.length; + if (quadSplittingMode.allowsSplitting()) { + this.maxQuadCount = quadSplittingMode.getMaxTotalQuads(this.quadCount); + } else { + this.maxQuadCount = this.quadCount; + } + } + + boolean canSplitQuads() { + return this.quadCount < this.maxQuadCount; } // TODO: better bidirectional triggering: integrate bidirectionality in GFNI if // top-level topo sorting isn't used anymore (and only use half as much memory - // by not storing it double) + // by not storing trigger planes twice) void addAlignedPartitionPlane(int axis, float distance) { - result.addDoubleSidedPlane(this.sectionPos, axis, distance); + this.result.addDoubleSidedAlignedPlane(this.sectionPos, axis, distance); + } + + void addUnalignedPartitionPlane(Vector3fc planeNormal, float distance) { + this.result.addDoubleSidedUnalignedPlane(this.sectionPos, planeNormal, distance); + } + + private void registerQuadUpdate(FullTQuad quad) { + if (quad.triggerAndSetUpdatedVertices()) { + if (this.updatedQuads == null) { + this.updatedQuads = new UpdatedQuadsList(); + } + this.updatedQuads.add(quad); + } + } + + public UpdatedQuadsList getFinalizedUpdatedQuads() { + if (this.updatedQuads != null) { + this.updatedQuads.setQuadCounts(this.size(), this.quadCount); + } + return this.updatedQuads; + } + + int pushQuad(FullTQuad quad) { + // null or invalid quads simply don't get added + if (quad == null || quad.isInvalid()) { + return -1; + } + + // take an index from the list of holes if there are any + int index; + if (this.availableQuadIndexes == null || this.availableQuadIndexes.isEmpty()) { + index = this.size(); + this.add(quad); + } else { + index = this.availableQuadIndexes.removeInt(this.availableQuadIndexes.size() - 1); + this.set(index, quad); + } + + quad.setWriteToIndex(index); + this.quadCount++; + + this.registerQuadUpdate(quad); + + return index; + } + + int updateQuad(FullTQuad quad, int quadIndex) { + if (quad == null) { + return -1; + } + + // invalid quads that have already been added to this list have to be removed + if (quad.isInvalid()) { + var lastIndex = this.size() - 1; + if (quadIndex == lastIndex) { + this.remove(lastIndex); + } else { + this.set(quadIndex, null); + if (this.availableQuadIndexes == null) { + this.availableQuadIndexes = new IntArrayList(); + } + this.availableQuadIndexes.add(quadIndex); + } + + quad.setNoWrite(); + this.registerQuadUpdate(quad); + + this.quadCount--; + + return -1; + } + + quad.setWriteToIndex(quadIndex); + this.registerQuadUpdate(quad); + + return quadIndex; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerBinaryPartitionBSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerBinaryPartitionBSPNode.java index 4151684714..cb84af2272 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerBinaryPartitionBSPNode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerBinaryPartitionBSPNode.java @@ -1,8 +1,7 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.bsp_tree; -import org.joml.Vector3fc; - import it.unimi.dsi.fastutil.ints.IntArrayList; +import org.joml.Vector3fc; /** * Partitions quads into two sides, each its own BSP node, of a partition plane @@ -17,7 +16,7 @@ class InnerBinaryPartitionBSPNode extends InnerPartitionBSPNode { private final int[] onPlaneQuads; InnerBinaryPartitionBSPNode(NodeReuseData reuseData, float planeDistance, int axis, - BSPNode inside, BSPNode outside, int[] onPlaneQuads) { + BSPNode inside, BSPNode outside, int[] onPlaneQuads) { super(reuseData, axis); this.planeDistance = planeDistance; this.inside = inside; @@ -25,9 +24,22 @@ class InnerBinaryPartitionBSPNode extends InnerPartitionBSPNode { this.onPlaneQuads = onPlaneQuads; } + InnerBinaryPartitionBSPNode(NodeReuseData reuseData, float planeDistance, Vector3fc planeNormal, + BSPNode inside, BSPNode outside, int[] onPlaneQuads) { + super(reuseData, planeNormal); + this.planeDistance = planeDistance; + this.inside = inside; + this.outside = outside; + this.onPlaneQuads = onPlaneQuads; + } + @Override void addPartitionPlanes(BSPWorkspace workspace) { - workspace.addAlignedPartitionPlane(this.axis, this.planeDistance); + if (this.axis == UNALIGNED_AXIS) { + workspace.addUnalignedPartitionPlane(this.planeNormal, this.planeDistance); + } else { + workspace.addAlignedPartitionPlane(this.axis, this.planeDistance); + } // also add the planes of the children if (this.inside instanceof InnerPartitionBSPNode insideChild) { @@ -99,4 +111,39 @@ static BSPNode buildFromPartitions(BSPWorkspace workspace, IntArrayList indexes, partitionDistance, axis, insideNode, outsideNode, onPlane); } + + static BSPNode buildFromParts(BSPWorkspace workspace, IntArrayList indexes, int depth, BSPNode oldNode, + IntArrayList inside, IntArrayList outside, IntArrayList onPlane, int axis, Vector3fc planeNormal, float partitionDistance) { + if (axis == UNALIGNED_AXIS) { + workspace.addUnalignedPartitionPlane(planeNormal, partitionDistance); + } else { + workspace.addAlignedPartitionPlane(axis, Math.abs(partitionDistance)); + } + + BSPNode oldInsideNode = null; + BSPNode oldOutsideNode = null; + if (oldNode instanceof InnerBinaryPartitionBSPNode binaryNode && + binaryNode.axis == axis && + (axis == UNALIGNED_AXIS || binaryNode.planeNormal.equals(planeNormal)) && + binaryNode.planeDistance == partitionDistance) { + oldInsideNode = binaryNode.inside; + oldOutsideNode = binaryNode.outside; + } + + BSPNode insideNode = null; + BSPNode outsideNode = null; + if (inside != null) { + insideNode = BSPNode.build(workspace, inside, depth, oldInsideNode); + } + if (outside != null) { + outsideNode = BSPNode.build(workspace, outside, depth, oldOutsideNode); + } + var onPlaneArr = BSPSortState.compressIndexes(onPlane); + + // always use the correct plane normal here because just specifying the axis causes the constructor to use a wrong and unsigned normal + return new InnerBinaryPartitionBSPNode( + prepareNodeReuse(workspace, indexes, depth), + partitionDistance, planeNormal, + insideNode, outsideNode, onPlaneArr); + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java index 92d8dcc6d3..413ac28b59 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java @@ -6,10 +6,13 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import net.caffeinemc.mods.sodium.api.util.ColorMixer; import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TQuad; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TranslucentGeometryCollector; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.TopoGraphSorting; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.FullTQuad; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.TQuad; +import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexEncoder; import net.caffeinemc.mods.sodium.client.util.MathUtil; import net.caffeinemc.mods.sodium.client.util.sorting.RadixSort; import net.minecraft.util.Mth; @@ -18,6 +21,9 @@ import java.util.Arrays; import java.util.Random; +import static net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexEncoder.Vertex.copyVertexTo; +import static net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexEncoder.Vertex.writeVertex; + /** * Performs aligned BSP partitioning of many nodes and constructs appropriate * BSP nodes based on the result. @@ -52,6 +58,7 @@ abstract class InnerPartitionBSPNode extends BSPNode { private static final int NODE_REUSE_THRESHOLD = 30; private static final int MAX_INTERSECTION_ATTEMPTS = 500; + protected static final int UNALIGNED_AXIS = -1; final Vector3fc planeNormal; final int axis; @@ -80,6 +87,12 @@ record NodeReuseData(float[][] quadExtents, int[] indexes, int indexCount, int m this.reuseData = reuseData; } + InnerPartitionBSPNode(NodeReuseData reuseData, Vector3fc planeNormal) { + this.planeNormal = planeNormal; + this.axis = UNALIGNED_AXIS; + this.reuseData = reuseData; + } + abstract void addPartitionPlanes(BSPWorkspace workspace); static NodeReuseData prepareNodeReuse(BSPWorkspace workspace, IntArrayList indexes, int depth) { @@ -91,7 +104,7 @@ static NodeReuseData prepareNodeReuse(BSPWorkspace workspace, IntArrayList index int maxIndex = -1; for (int i = 0; i < indexes.size(); i++) { var index = indexes.getInt(i); - var quad = workspace.quads[index]; + var quad = workspace.get(index); var extents = quad.getExtents(); quadExtents[i] = extents; maxIndex = Math.max(maxIndex, index); @@ -158,7 +171,7 @@ static InnerPartitionBSPNode attemptNodeReuse(BSPWorkspace workspace, IntArrayLi } for (int i = 0; i < newIndexes.size(); i++) { - if (!workspace.quads[newIndexes.getInt(i)].extentsEqual(oldExtents[i])) { + if (!workspace.get(newIndexes.getInt(i)).extentsEqual(oldExtents[i])) { return null; } } @@ -236,6 +249,15 @@ static BSPNode build(BSPWorkspace workspace, IntArrayList indexes, int depth, BS ReferenceArrayList partitions = new ReferenceArrayList<>(); LongArrayList points = new LongArrayList((int) (indexes.size() * 1.5)); + // keep track of global best splitting group for splitting quads if enabled + IntArrayList bestSplittingGroup = null; + IntArrayList splittingGroup = null; + boolean canSplitQuads = workspace.canSplitQuads(); + if (canSplitQuads) { + bestSplittingGroup = new IntArrayList(5); + splittingGroup = new IntArrayList(5); + } + // find any aligned partition, search each axis for (int axisCount = 0; axisCount < 3; axisCount++) { int axis = (axisCount + depth + 1) % 3; @@ -247,7 +269,7 @@ static BSPNode build(BSPWorkspace workspace, IntArrayList indexes, int depth, BS points.clear(); for (int i = 0, size = indexes.size(); i < size; i++) { int quadIndex = indexes.getInt(i); - var quad = workspace.quads[quadIndex]; + var quad = workspace.get(quadIndex); var extents = quad.getExtents(); var posExtent = extents[axis]; var negExtent = extents[oppositeDirection]; @@ -274,7 +296,7 @@ static BSPNode build(BSPWorkspace workspace, IntArrayList indexes, int depth, BS // this means the already generated points array can be used return buildSNRLeafNodeFromPoints(workspace, points); } else { - return buildSNRLeafNodeFromQuads(workspace, indexes, points); + return buildSNRLeafNodeFromQuads(workspace, indexes); } } } @@ -283,12 +305,25 @@ static BSPNode build(BSPWorkspace workspace, IntArrayList indexes, int depth, BS // longs directly has the same effect because of the encoding. Arrays.sort(points.elements(), 0, points.size()); - // find gaps - partitions.clear(); + // the current partition plane distance (dot product), + // updated when an interval ends or a side is encountered, used to add quads to quadsOn float distance = Float.NaN; + + // set of quads that are within the partition IntArrayList quadsBefore = null; + + // set of quads that are on the partition plane IntArrayList quadsOn = null; + + // number of overlapping intervals along the projection axis int thickness = 0; + + // lazily generate partitions by keeping track of the current interval thickness and the quads that are on the partition plane + partitions.clear(); + if (canSplitQuads) { + splittingGroup.clear(); + } + float splitDistance = Float.NaN; for (int i = 0, size = points.size(); i < size; i++) { long point = points.getLong(i); switch (decodeType(point)) { @@ -341,18 +376,32 @@ static BSPNode build(BSPWorkspace workspace, IntArrayList indexes, int depth, BS } quadsOn.add(pointQuadIndex); } else { + // add this point quad to the quads before the partition plane if (quadsBefore == null) { throw new IllegalStateException("there must be started intervals here"); } quadsBefore.add(pointQuadIndex); - if (quadsOn == null) { - distance = decodeDistance(point); + + // update the splitting group if the distance didn't change + if (canSplitQuads) { + var ownDistance = decodeDistance(point); + if (ownDistance == splitDistance || Float.isNaN(splitDistance)) { + splittingGroup.add(pointQuadIndex); + } else { + flushBestSplittingGroup(splittingGroup, bestSplittingGroup, axis); + } + splitDistance = ownDistance; } } } } } + // check if the splitting group needs to be flushed + if (canSplitQuads) { + flushBestSplittingGroup(splittingGroup, bestSplittingGroup, axis); + } + // check a different axis if everything is in one quadsBefore, // which means there are no gaps if (quadsBefore != null && quadsBefore.size() == indexes.size()) { @@ -386,17 +435,437 @@ static BSPNode build(BSPWorkspace workspace, IntArrayList indexes, int depth, BS partitions, axis, endsWithPlane); } - var intersectingHandling = handleIntersecting(workspace, indexes, depth, oldNode); - if (intersectingHandling != null) { - return intersectingHandling; + if (canSplitQuads) { + // try static topo sorting first because splitting quads is even more expensive + var multiLeafNode = buildTopoMultiLeafNode(workspace, indexes, true); + if (multiLeafNode != null) { + return multiLeafNode; + } + + // perform quad splitting to get a sortable result whether it's intersecting or just unsortable as-is + return handleUnsortableBySplitting(workspace, indexes, depth, oldNode, bestSplittingGroup); + } else { + var intersectingHandling = handleIntersecting(workspace, indexes, depth, oldNode); + if (intersectingHandling != null) { + return intersectingHandling; + } + + // attempt topo sorting on the geometry if intersection handling failed + var multiLeafNode = buildTopoMultiLeafNode(workspace, indexes, false); + if (multiLeafNode == null) { + throw new BSPBuildFailureException("No partition found but not intersecting and can't be statically topo sorted"); + } + return multiLeafNode; + } + } + + static void flushBestSplittingGroup(IntArrayList splittingGroup, IntArrayList bestSplittingGroup, int axis) { + var currentSize = splittingGroup.size(); + var newSize = bestSplittingGroup.size(); + + // use new splitting group if it's bigger but prefer y-axis for splitting if they're the same size + if (currentSize > newSize || currentSize == newSize && axis == 1) { + bestSplittingGroup.clear(); + bestSplittingGroup.addAll(splittingGroup); + } + splittingGroup.clear(); + } + + static private BSPNode handleUnsortableBySplitting(BSPWorkspace workspace, IntArrayList indexes, int depth, BSPNode oldNode, IntArrayList splittingGroup) { + // pick the first quad if there's no prepared splitting group + int representativeIndex; + if (splittingGroup.isEmpty()) { + representativeIndex = indexes.getInt(0); + splittingGroup.add(representativeIndex); + } else { + representativeIndex = splittingGroup.getInt(0); + } + var representative = (FullTQuad) workspace.get(representativeIndex); + var representativeFacing = representative.getFacing(); + int initialSplittingGroupSize = splittingGroup.size(); + + // split all quads by the splitting group's plane + var splitPlane = representative.getVeryAccurateNormal(); + var splitDistance = representative.getAccurateDotProduct(); + + IntArrayList inside = new IntArrayList(); + IntArrayList outside = new IntArrayList(); + + for (int candidateIndex : indexes) { + // eliminate quads that are already in the splitting group + var isInSplittingGroup = false; + for (int i = 0; i < initialSplittingGroupSize; i++) { + if (candidateIndex == splittingGroup.getInt(i)) { + isInSplittingGroup = true; + } + } + if (isInSplittingGroup) { + continue; + } + + var insideQuad = (FullTQuad) workspace.get(candidateIndex); + var quadFacing = insideQuad.getFacing(); + + // eliminate quads that lie in the split plane + if (quadFacing == representativeFacing && insideQuad.getAccurateDotProduct() == splitDistance && + (representativeFacing != ModelQuadFacing.UNASSIGNED || + insideQuad.getVeryAccurateNormal().equals(splitPlane))) { + splittingGroup.add(candidateIndex); + continue; + } + + // split the geometry with the plane + splitCandidate(workspace, splittingGroup, candidateIndex, insideQuad, splitPlane, splitDistance, outside, inside); + } + + ModelQuadFacing facing; + Vector3fc normal; + float dotProduct; + if (workspace.quantizeTriggerNormals) { + facing = representative.useQuantizedFacing(); + normal = representative.getQuantizedNormal(); + dotProduct = representative.getQuantizedDotProduct(); + } else { + facing = representativeFacing; + normal = splitPlane; + dotProduct = splitDistance; + } + int axis = UNALIGNED_AXIS; + if (facing.isAligned()) { + axis = facing.getAxis(); + } + + return InnerBinaryPartitionBSPNode.buildFromParts( + workspace, indexes, depth, oldNode, inside, outside, splittingGroup, axis, + normal, dotProduct); + } + + private static void splitCandidate(BSPWorkspace workspace, IntArrayList splittingGroup, int candidateIndex, FullTQuad insideQuad, Vector3fc splitPlane, float splitDistance, IntArrayList outside, IntArrayList inside) { + // Lines or points (2 or 1 vertices) should have been filtered out + var uniqueVertexMap = insideQuad.getUniqueVertexMap(); + var uniqueVertices = Integer.bitCount(uniqueVertexMap); + if (uniqueVertices < 3) { + throw new IllegalStateException("Unexpected quad with less than 3 unique vertices"); } - // attempt topo sorting on the geometry if intersection handling failed - var multiLeafNode = buildTopoMultiLeafNode(workspace, indexes); - if (multiLeafNode == null) { - throw new BSPBuildFailureException("No partition found but not intersecting and can't be statically topo sorted"); + var vertices = insideQuad.getVertices(); + + // calculate inside/outside for each vertex + int insideMapUnmasked = 0; + int onPlaneMapUnmasked = 0; + for (int i = 0; i < 4; i++) { + var vertex = vertices[i]; + var dot = splitPlane.dot(vertex.x, vertex.y, vertex.z); + var delta = dot - splitDistance; + if (Math.abs(delta) < TQuad.VERTEX_EPSILON) { + onPlaneMapUnmasked |= 1 << i; + } else if (delta < 0) { // dot < splitDistance + insideMapUnmasked |= 1 << i; + } + } + + // filter out the vertices that are duplicated to handle triangles + var insideMap = insideMapUnmasked & uniqueVertexMap; + var onPlaneMap = onPlaneMapUnmasked & uniqueVertexMap; + + // Quads or triangles that are fully on the plane are added to the splitting group. + // Bent quads are not dealt with by this and will simply produce unexpected behavior. + // Adding quads with three unique vertices on the plane to the splitting group doesn't work when floating point errors + // cause quads to have effectively three unique vertices without signaling so in their uniqueVertexMap. + if (onPlaneMap == uniqueVertexMap) { + splittingGroup.add(candidateIndex); + return; + } + + // the quad is outside if all vertices are either outside or on the plane + if (insideMap == 0) { + outside.add(candidateIndex); + return; + } + + // the quad is inside if all vertices are either inside or on the plane + if ((insideMap | onPlaneMap) == uniqueVertexMap) { // 0b1111 for quads + inside.add(candidateIndex); + return; + } + + var onPlaneCount = Integer.bitCount(onPlaneMap); + var insideCount = Integer.bitCount(insideMap); + + // cancel splitting after handling special cases if the new geometry limit has been reached + if (!workspace.canSplitQuads()) { + // put on, inside, outside based on which side has the most vertices + var outsideCount = 4 - insideCount - onPlaneCount; + if (onPlaneCount >= insideCount && onPlaneCount >= outsideCount) { + splittingGroup.add(candidateIndex); + } else if (insideCount >= outsideCount) { + inside.add(candidateIndex); + } else { + outside.add(candidateIndex); + } + return; + } + + FullTQuad outsideQuad = FullTQuad.splittingCopy(insideQuad); + FullTQuad secondOutsideQuad = null; + FullTQuad secondInsideQuad = null; + + if (uniqueVertices == 3) { + // TODO: deal with the rare and weird case where opposite vertices are identical (i.e. the quad is folded in half) + + // a vertex is on the split plane + var sameVertexMap = insideQuad.getSameVertexMap(); + if (onPlaneCount == 1) { + int duplicateIndex = -1; + boolean duplicateIsInside = false; + + // the duplicate vertex is inside or outside, find its index and side. + // if the duplicate vertex is on the plane, don't move it. + if ((onPlaneMapUnmasked & sameVertexMap) == 0) { + duplicateIsInside = (sameVertexMap & insideMapUnmasked) != 0; + duplicateIndex = Integer.numberOfTrailingZeros(sameVertexMap); + } + + var insideIndex = Integer.numberOfTrailingZeros(insideMap); + var outsideIndex = Integer.numberOfTrailingZeros(~(insideMap | onPlaneMap) & uniqueVertexMap); + splitTriangleVertex(insideIndex, outsideIndex, duplicateIndex, duplicateIsInside, insideQuad, outsideQuad, splitPlane, splitDistance); + } + + // even splitting if the two equal vertices are the corner that's being split off. + // at this point onPlaneCount == 0 + else if (Integer.bitCount(insideMapUnmasked) == 2) { + splitQuadEven(insideMapUnmasked, insideQuad, outsideQuad, splitPlane, splitDistance); + } + + // a single vertex is inside or outside (with the other three, including one duplicate, being on the other side) + else if (insideCount == 1) { + var cornerIndex = Integer.numberOfTrailingZeros(insideMap); + splitTriangleCorner(cornerIndex, insideQuad, outsideQuad, splitPlane, splitDistance); + } else { + var cornerIndex = Integer.numberOfTrailingZeros(~insideMapUnmasked); + splitTriangleCorner(cornerIndex, outsideQuad, insideQuad, splitPlane, splitDistance); + } + } else { // uniqueVertices == 4, masked == unmasked + // after dealing with the other cases, now two vertices being on the plane implies the quad is split exactly along its diagonal + // insideCount is 2, onPlaneMap is 0b0101 or 0b1010 + if (onPlaneCount == 2) { + // this case can be treated like even splitting if the two on-plane vertices are declared as each part of one of the sides + if (onPlaneMap == 0b0101) { + insideMap |= 0b0001; + } else { + insideMap |= 0b0010; + } + insideCount = 2; + } + + // one vertex being on the plane now implies the quad is split on a vertex and through an edge. + // if there is one vertex inside (and two outside), move the on-plane vertex inside to produce an even split case. + // in the other case nothing needs to be done since for splitting the 0-bits in the insideMap are treated as outside. + else if (onPlaneCount == 1 && insideCount == 1) { + insideMap |= onPlaneMap; + insideCount = 2; + } + + // split evenly with two quads or three quads (corner chopped off) depending on the orientation + if (insideCount == 2) { + splitQuadEven(insideMap, insideQuad, outsideQuad, splitPlane, splitDistance); + } else if (insideCount == 3) { + var cornerIndex = Integer.numberOfTrailingZeros(~insideMap); + secondInsideQuad = FullTQuad.splittingCopy(insideQuad); + + splitQuadOdd(cornerIndex, outsideQuad, secondInsideQuad, insideQuad, splitPlane, splitDistance); + } else { // insideCount == 1 + var cornerIndex = Integer.numberOfTrailingZeros(insideMap); + secondOutsideQuad = FullTQuad.splittingCopy(insideQuad); + + splitQuadOdd(cornerIndex, insideQuad, secondOutsideQuad, outsideQuad, splitPlane, splitDistance); + } + } + + addQuadIndex(inside, workspace.updateQuad(insideQuad, candidateIndex)); + addQuadIndex(outside, workspace.pushQuad(outsideQuad)); + addQuadIndex(inside, workspace.pushQuad(secondInsideQuad)); + addQuadIndex(outside, workspace.pushQuad(secondOutsideQuad)); + } + + static private void addQuadIndex(IntArrayList list, int index) { + if (index >= 0) { + list.add(index); + } + } + + static private void splitQuadEven(int vertexInsideMap, FullTQuad insideQuad, FullTQuad outsideQuad, Vector3fc splitPlane, float splitDistance) { + // the quad is split through two of its opposing edges, producing two regular quads + + // split the quad with the plane by iterating all the edges and checking for intersection + var insideVertices = insideQuad.getVertices(); + var outsideVertices = outsideQuad.getVertices(); + for (int indexA = 0; indexA < 4; indexA++) { + var indexB = (indexA + 1) & 0b11; + var insideA = (vertexInsideMap & (1 << indexA)) != 0; + var insideB = (vertexInsideMap & (1 << indexB)) != 0; + if (insideA == insideB) { + continue; + } + + // get the inner and outer vertices + int insideIndex, outsideIndex; + if (insideA) { + insideIndex = indexA; + outsideIndex = indexB; + } else { + insideIndex = indexB; + outsideIndex = indexA; + } + + interpolateAttributes(splitDistance, splitPlane, + insideVertices[insideIndex], outsideVertices[outsideIndex], + insideVertices[outsideIndex], outsideVertices[insideIndex]); + } + + insideQuad.updateSplitQuadAfterVertexModification(); + outsideQuad.updateSplitQuadAfterVertexModification(); + } + + static private void splitQuadOdd(int cornerIndex, FullTQuad cornerQuad, FullTQuad cutQuad, FullTQuad bulkQuad, Vector3fc splitPlane, float splitDistance) { + // the quad is split through two of its adjacent edges, producing three quads (two triangles and one quad) + + var cornerVertices = cornerQuad.getVertices(); // corner split off by the plane + var cutVertices = cutQuad.getVertices(); // quad between the corner and the bulk + var bulkVertices = bulkQuad.getVertices(); // quad that retains the three non-cut vertices + + var prevIndex = (cornerIndex - 1) & 0b11; + var nextIndex = (cornerIndex + 1) & 0b11; + var oppositeIndex = (cornerIndex + 2) & 0b11; + + // inverting the split plane based on whether the corner is inside or outside doesn't seem to be necessary, + // because it just works out in the interpolation, and the negative values cancel out + + var cornerVertex = cornerVertices[cornerIndex]; + + interpolateAttributes(splitDistance, splitPlane, + cornerVertex, bulkVertices[nextIndex], + cornerVertices[nextIndex], cutVertices[nextIndex], bulkVertices[cornerIndex]); + interpolateAttributes(splitDistance, splitPlane, + cornerVertex, bulkVertices[prevIndex], + cornerVertices[prevIndex], cornerVertices[oppositeIndex], cutVertices[cornerIndex]); + copyVertexTo(cutVertices[prevIndex], cutVertices[oppositeIndex]); + + cornerQuad.updateSplitQuadAfterVertexModification(); + cutQuad.updateSplitQuadAfterVertexModification(); + bulkQuad.updateSplitQuadAfterVertexModification(); + } + + static private void splitTriangleCorner(int cornerIndex, FullTQuad cornerQuad, FullTQuad bulkQuad, Vector3fc splitPlane, float splitDistance) { + // the triangle (degenerate quad) is split through two edges, producing two quads (one triangle and one quad) + + var cornerVertices = cornerQuad.getVertices(); // corner split off by the plane + var bulkVertices = bulkQuad.getVertices(); // quad that retains the other vertices + + var prevIndex = (cornerIndex - 1) & 0b11; + var nextIndex = (cornerIndex + 1) & 0b11; + var oppositeIndex = (cornerIndex + 2) & 0b11; + + var cornerVertex = cornerVertices[cornerIndex]; + + interpolateAttributes(splitDistance, splitPlane, + cornerVertex, bulkVertices[nextIndex], + cornerVertices[nextIndex], cornerVertices[oppositeIndex], bulkVertices[cornerIndex]); + copyVertexTo(bulkVertices[prevIndex], bulkVertices[oppositeIndex]); + interpolateAttributes(splitDistance, splitPlane, + cornerVertex, bulkVertices[prevIndex], + cornerVertices[prevIndex], bulkVertices[prevIndex]); + + cornerQuad.updateSplitQuadAfterVertexModification(); + bulkQuad.updateSplitQuadAfterVertexModification(); + } + + static private void splitTriangleVertex(int insideIndex, int outsideIndex, int duplicateIndex, boolean duplicateIsInside, FullTQuad insideQuad, FullTQuad outsideQuad, Vector3fc splitPlane, float splitDistance) { + // the triangle (degenerate quad) is split through one edge, producing two triangles + + var insideVertices = insideQuad.getVertices(); + var outsideVertices = outsideQuad.getVertices(); + + // the duplicate vertex of the opposite quad is moved to the center too + ChunkVertexEncoder.Vertex duplicateTarget = null; + if (duplicateIndex != -1) { + if (duplicateIsInside) { + duplicateTarget = outsideVertices[duplicateIndex]; + } else { + duplicateTarget = insideVertices[duplicateIndex]; + } + } + + interpolateAttributes(splitDistance, splitPlane, + insideVertices[insideIndex], outsideVertices[outsideIndex], + insideVertices[outsideIndex], outsideVertices[insideIndex], duplicateTarget); + + insideQuad.updateSplitQuadAfterVertexModification(); + outsideQuad.updateSplitQuadAfterVertexModification(); + } + + private static void interpolateAttributes(float splitDistance, Vector3fc splitPlane, ChunkVertexEncoder.Vertex inside, ChunkVertexEncoder.Vertex outside, ChunkVertexEncoder.Vertex targetA, ChunkVertexEncoder.Vertex targetB) { + interpolateAttributes(splitDistance, splitPlane, inside, outside, targetA, targetB, null); + } + + private static void interpolateAttributes(float splitDistance, Vector3fc splitPlane, ChunkVertexEncoder.Vertex inside, ChunkVertexEncoder.Vertex outside, ChunkVertexEncoder.Vertex targetA, ChunkVertexEncoder.Vertex targetB, ChunkVertexEncoder.Vertex targetC) { + // calculate the intersection point and interpolate attributes + var insideToOutsideX = outside.x - inside.x; + var insideToOutsideY = outside.y - inside.y; + var insideToOutsideZ = outside.z - inside.z; + + // use an epsilon in this check to prevent splitPlaneEdgeDot from being zero when a very small insideToOutside_ vanishes in the dot product + if (Math.abs(insideToOutsideX) < TQuad.VERTEX_EPSILON && + Math.abs(insideToOutsideY) < TQuad.VERTEX_EPSILON && + Math.abs(insideToOutsideZ) < TQuad.VERTEX_EPSILON) { + copyVertexToMultiple(inside, targetA, targetB, targetC); + return; + } + + var splitPlaneEdgeDot = splitPlane.dot(insideToOutsideX, insideToOutsideY, insideToOutsideZ); + + // the edge lies within the split plane if the dot product is zero + if (splitPlaneEdgeDot == 0) { + // this should never happen because we handle triangles correctly + throw new IllegalStateException("Quad with an edge in the split plane should have been handled earlier"); + } + + var outsideAmount = (splitDistance - splitPlane.dot(inside.x, inside.y, inside.z)) / splitPlaneEdgeDot; + + if (outsideAmount >= 1) { + copyVertexToMultiple(outside, targetA, targetB, targetC); + return; + } else if (outsideAmount <= 0) { + copyVertexToMultiple(inside, targetA, targetB, targetC); + return; + } + + var newX = inside.x + insideToOutsideX * outsideAmount; + var newY = inside.y + insideToOutsideY * outsideAmount; + var newZ = inside.z + insideToOutsideZ * outsideAmount; + + var newColor = ColorMixer.mix(inside.color, outside.color, outsideAmount); + var newAo = Mth.lerp(outsideAmount, inside.ao, outside.ao); + var newU = Mth.lerp(outsideAmount, inside.u, outside.u); + var newV = Mth.lerp(outsideAmount, inside.v, outside.v); + + var newLightBl = Mth.lerp(outsideAmount, inside.light & 0xFF, outside.light & 0xFF); + var newLightSl = Mth.lerp(outsideAmount, inside.light >> 16, outside.light >> 16); + var newLight = (((int) newLightSl & 0xFF) << 16) | ((int) newLightBl & 0xFF); + + writeVertex(targetA, newX, newY, newZ, newColor, newAo, newU, newV, newLight); + writeVertex(targetB, newX, newY, newZ, newColor, newAo, newU, newV, newLight); + if (targetC != null) { + writeVertex(targetC, newX, newY, newZ, newColor, newAo, newU, newV, newLight); + } + } + + private static void copyVertexToMultiple(ChunkVertexEncoder.Vertex from, ChunkVertexEncoder.Vertex targetA, ChunkVertexEncoder.Vertex targetB, ChunkVertexEncoder.Vertex targetC) { + copyVertexTo(from, targetA); + copyVertexTo(from, targetB); + if (targetC != null) { + copyVertexTo(from, targetC); } - return multiLeafNode; } static private BSPNode handleIntersecting(BSPWorkspace workspace, IntArrayList indexes, int depth, BSPNode oldNode) { @@ -437,8 +906,8 @@ static private BSPNode handleIntersecting(BSPWorkspace workspace, IntArrayList i break; } - var quadA = workspace.quads[indexes.getInt(i)]; - var quadB = workspace.quads[indexes.getInt(j)]; + var quadA = workspace.get(indexes.getInt(i)); + var quadB = workspace.get(indexes.getInt(j)); // aligned quads intersect if their bounding boxes intersect if (TQuad.extentsIntersect(quadA, quadB)) { @@ -505,7 +974,7 @@ public void accept(int value) { } } - static private BSPNode buildTopoMultiLeafNode(BSPWorkspace workspace, IntArrayList indexes) { + static private BSPNode buildTopoMultiLeafNode(BSPWorkspace workspace, IntArrayList indexes, boolean failOnIntersection) { var quadCount = indexes.size(); if (quadCount > TranslucentGeometryCollector.STATIC_TOPO_UNKNOWN_FALLBACK_LIMIT) { @@ -516,12 +985,12 @@ static private BSPNode buildTopoMultiLeafNode(BSPWorkspace workspace, IntArrayLi var activeToRealIndex = new int[quadCount]; for (int i = 0; i < indexes.size(); i++) { var quadIndex = indexes.getInt(i); - quads[i] = workspace.quads[quadIndex]; + quads[i] = workspace.get(quadIndex); activeToRealIndex[i] = quadIndex; } var indexWriter = new QuadIndexConsumerIntoArray(quadCount); - if (!TopoGraphSorting.topoGraphSort(indexWriter, quads, quads.length, activeToRealIndex, null, null)) { + if (!TopoGraphSorting.topoGraphSort(indexWriter, quads, quads.length, activeToRealIndex, null, null, failOnIntersection)) { return null; } @@ -531,7 +1000,7 @@ static private BSPNode buildTopoMultiLeafNode(BSPWorkspace workspace, IntArrayLi return new LeafMultiBSPNode(BSPSortState.compressIndexesInPlace(indexWriter.indexes, false)); } - static private BSPNode buildSNRLeafNodeFromQuads(BSPWorkspace workspace, IntArrayList indexes, LongArrayList points) { + static private BSPNode buildSNRLeafNodeFromQuads(BSPWorkspace workspace, IntArrayList indexes) { final var indexBuffer = indexes.elements(); final var indexCount = indexes.size(); @@ -539,7 +1008,7 @@ static private BSPNode buildSNRLeafNodeFromQuads(BSPWorkspace workspace, IntArra final var perm = new int[indexCount]; for (int i = 0; i < indexCount; i++) { - TQuad quad = workspace.quads[indexBuffer[i]]; + TQuad quad = workspace.get(indexBuffer[i]); keys[i] = MathUtil.floatToComparableInt(quad.getAccurateDotProduct()); perm[i] = i; } @@ -566,7 +1035,7 @@ static private BSPNode buildSNRLeafNodeFromPoints(BSPWorkspace workspace, LongAr // based one each quad's facing, order them forwards or backwards, // this means forwards is written from the start and backwards is written from the end var quadIndex = decodeQuadIndex(points.getLong(i)); - if (workspace.quads[quadIndex].getFacing().getSign() == 1) { + if (workspace.get(quadIndex).getFacing().getSign() == 1) { quadIndexes[forwards++] = quadIndex; } else { quadIndexes[backwards--] = quadIndex; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/UpdatedQuadsList.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/UpdatedQuadsList.java new file mode 100644 index 0000000000..2291133710 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/UpdatedQuadsList.java @@ -0,0 +1,31 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.bsp_tree; + +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.FullTQuad; +import net.caffeinemc.mods.sodium.client.render.chunk.vertex.builder.ChunkMeshBufferBuilder; + +import java.nio.ByteBuffer; + +public class UpdatedQuadsList extends ReferenceArrayList { + private int meshQuadCount; + private int indexQuadCount; + + public int getMeshQuadCount() { + return this.meshQuadCount; + } + + public int getIndexQuadCount() { + return this.indexQuadCount; + } + + public void setQuadCounts(int meshQuadCount, int indexQuadCount) { + this.meshQuadCount = meshQuadCount; + this.indexQuadCount = indexQuadCount; + } + + public void applyBufferUpdates(ChunkMeshBufferBuilder builder, ByteBuffer buffer) { + for (var quad : this) { + quad.writeToBuffer(builder, buffer); + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java index a8fff490e0..3e25926d17 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java @@ -1,7 +1,8 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortType; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TQuad; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TranslucentGeometryCollector; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.TQuad; import net.minecraft.core.SectionPos; /** @@ -18,11 +19,11 @@ * buffer segments and would need to be resized when a larger section wants to * use it. */ -public class AnyOrderData extends SplitDirectionData { +public class AnyOrderData extends PresentTranslucentData { private Sorter sorterOnce; - AnyOrderData(SectionPos sectionPos, int[] vertexCounts, int quadCount) { - super(sectionPos, vertexCounts, quadCount); + AnyOrderData(SectionPos sectionPos, int inputQuadCount) { + super(sectionPos, inputQuadCount); } @Override @@ -40,12 +41,17 @@ public Sorter getSorter() { return sorter; } + @Override + public boolean oldDataMatches(TranslucentGeometryCollector collector, SortType sortType, TQuad[] quads) { + // for the NONE sort type the ranges need to be the same, the actual geometry doesn't matter + return sortType == SortType.NONE && this.getInputQuadCount() == quads.length; + } + /** * Important: The vertex indexes must start at zero for each facing. */ - public static AnyOrderData fromMesh(int[] vertexCounts, - TQuad[] quads, SectionPos sectionPos) { - var anyOrderData = new AnyOrderData(sectionPos, vertexCounts, quads.length); + public static AnyOrderData fromMesh(TQuad[] quads, SectionPos sectionPos) { + var anyOrderData = new AnyOrderData(sectionPos, quads.length); anyOrderData.sorterOnce = new SharedIndexSorter(quads.length); return anyOrderData; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java index cfab09e721..a22d7b6357 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java @@ -1,28 +1,40 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; -import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionMeshParts; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TQuad; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.QuadSplittingMode; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortType; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TranslucentGeometryCollector; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.bsp_tree.BSPNode; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.bsp_tree.BSPResult; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.bsp_tree.UpdatedQuadsList; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.TQuad; import net.minecraft.core.SectionPos; import org.joml.Vector3dc; /** * Constructs a BSP tree of the quads and sorts them dynamically. - * + *

* Triggering is performed when the BSP tree's partition planes are crossed in * any direction (bidirectional). */ public class DynamicBSPData extends DynamicData { private static final int NODE_REUSE_MIN_GENERATION = 1; + private final int indexQuadCount; private final BSPNode rootNode; private final int generation; + private final UpdatedQuadsList updatedQuadsList; // TODO: delete reference after mesh task is done since this won't be needed anymore after that - private DynamicBSPData(SectionPos sectionPos, int vertexCount, BSPResult result, Vector3dc initialCameraPos, TQuad[] quads, int generation) { - super(sectionPos, vertexCount, quads.length, result, initialCameraPos); + private DynamicBSPData(SectionPos sectionPos, int inputQuadCount, BSPResult result, Vector3dc initialCameraPos, int generation) { + super(sectionPos, inputQuadCount, result, initialCameraPos); this.rootNode = result.getRootNode(); this.generation = generation; + this.updatedQuadsList = result.getUpdatedQuadsList(); + + if (this.updatedQuadsList != null) { + this.indexQuadCount = this.updatedQuadsList.getIndexQuadCount(); + } else { + this.indexQuadCount = inputQuadCount; + } } private class DynamicBSPSorter extends DynamicSorter { @@ -36,14 +48,29 @@ void writeSort(CombinedCameraPos cameraPos, boolean initial) { } } + @Override + public boolean oldDataMatches(TranslucentGeometryCollector collector, SortType sortType, TQuad[] quads) { + // don't reuse data if we need to rewrite the mesh because of quad splitting + return !this.meshesWereModified() && super.oldDataMatches(collector, sortType, quads); + } + + @Override + public int getIndexQuadCount() { + return this.indexQuadCount; + } + @Override public Sorter getSorter() { - return new DynamicBSPSorter(this.getQuadCount()); + return new DynamicBSPSorter(this.getIndexQuadCount()); // index quad count + } + + @Override + public UpdatedQuadsList getUpdatedQuads() { + return this.updatedQuadsList; } - public static DynamicBSPData fromMesh(int vertexCount, - CombinedCameraPos cameraPos, TQuad[] quads, SectionPos sectionPos, - TranslucentData oldData) { + public static DynamicBSPData fromMesh(CombinedCameraPos cameraPos, TQuad[] quads, SectionPos sectionPos, + TranslucentData oldData, QuadSplittingMode quadSplittingMode) { BSPNode oldRoot = null; int generation = 0; boolean prepareNodeReuse = false; @@ -55,9 +82,9 @@ public static DynamicBSPData fromMesh(int vertexCount, // (times the section has been built) prepareNodeReuse = generation >= NODE_REUSE_MIN_GENERATION; } - var result = BSPNode.buildBSP(quads, sectionPos, oldRoot, prepareNodeReuse); + var result = BSPNode.buildBSP(quads, sectionPos, oldRoot, prepareNodeReuse, quadSplittingMode); - var dynamicData = new DynamicBSPData(sectionPos, vertexCount, result, cameraPos.getAbsoluteCameraPos(), quads, generation); + var dynamicData = new DynamicBSPData(sectionPos, quads.length, result, cameraPos.getAbsoluteCameraPos(), generation); // prepare geometry planes for integration into GFNI triggering result.prepareIntegration(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicData.java index 6c3c69080c..033e810db1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicData.java @@ -5,12 +5,12 @@ import net.minecraft.core.SectionPos; import org.joml.Vector3dc; -public abstract class DynamicData extends MixedDirectionData { +public abstract class DynamicData extends PresentTranslucentData { private GeometryPlanes geometryPlanes; private final Vector3dc initialCameraPos; - DynamicData(SectionPos sectionPos, int vertexCount, int quadCount, GeometryPlanes geometryPlanes, Vector3dc initialCameraPos) { - super(sectionPos, vertexCount, quadCount); + DynamicData(SectionPos sectionPos, int inputQuadCount, GeometryPlanes geometryPlanes, Vector3dc initialCameraPos) { + super(sectionPos, inputQuadCount); this.geometryPlanes = geometryPlanes; this.initialCameraPos = initialCameraPos; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java index 68c1bf3a12..ea6f5ce411 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TQuad; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.TQuad; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.GeometryPlanes; import net.caffeinemc.mods.sodium.client.util.sorting.RadixSort; import net.minecraft.core.SectionPos; @@ -44,14 +44,14 @@ public class DynamicTopoData extends DynamicData { private final TQuad[] quads; private final Object2ReferenceMap distancesByNormal; - private DynamicTopoData(SectionPos sectionPos, int vertexCount, TQuad[] quads, + private DynamicTopoData(SectionPos sectionPos, TQuad[] quads, GeometryPlanes geometryPlanes, Vector3dc initialCameraPos, Object2ReferenceMap distancesByNormal) { - super(sectionPos, vertexCount, quads.length, geometryPlanes, initialCameraPos); + super(sectionPos, quads.length, geometryPlanes, initialCameraPos); this.quads = quads; this.distancesByNormal = distancesByNormal; - if (this.getQuadCount() > MAX_TOPO_SORT_QUADS) { + if (this.getInputQuadCount() > MAX_TOPO_SORT_QUADS) { this.directTrigger = true; this.GFNITrigger = false; } @@ -59,7 +59,7 @@ private DynamicTopoData(SectionPos sectionPos, int vertexCount, TQuad[] quads, @Override public Sorter getSorter() { - return new DynamicTopoSorter(this.getQuadCount(), this, this.pendingTriggerIsDirect, this.consecutiveTopoSortFailures, this.GFNITrigger, this.directTrigger); + return new DynamicTopoSorter(this.getInputQuadCount(), this, this.pendingTriggerIsDirect, this.consecutiveTopoSortFailures, this.GFNITrigger, this.directTrigger); } public boolean GFNITriggerEnabled() { @@ -171,7 +171,7 @@ void writeSort(CombinedCameraPos cameraPos, boolean initial) { if (this.GFNITrigger && !this.isDirectTrigger) { this.intBuffer = indexBuffer; var sortStart = initial ? 0 : System.nanoTime(); - var result = TopoGraphSorting.topoGraphSort(this, DynamicTopoData.this.quads, DynamicTopoData.this.distancesByNormal, cameraPos.getRelativeCameraPos()); + var result = TopoGraphSorting.topoGraphSort(this, DynamicTopoData.this.quads, DynamicTopoData.this.distancesByNormal, cameraPos.getRelativeCameraPos(), false); this.intBuffer = null; var sortTime = initial ? 0 : System.nanoTime() - sortStart; @@ -240,12 +240,9 @@ static void distanceSortDirect(IntBuffer indexBuffer, TQuad[] quads, Vector3fc c } } - public static DynamicTopoData fromMesh(int vertexCount, - CombinedCameraPos cameraPos, TQuad[] quads, SectionPos sectionPos, - GeometryPlanes geometryPlanes) { + public static DynamicTopoData fromMesh(CombinedCameraPos cameraPos, TQuad[] quads, SectionPos sectionPos, GeometryPlanes geometryPlanes) { var distancesByNormal = geometryPlanes.prepareAndGetDistances(); - return new DynamicTopoData(sectionPos, vertexCount, quads, geometryPlanes, - cameraPos.getAbsoluteCameraPos(), distancesByNormal); + return new DynamicTopoData(sectionPos, quads, geometryPlanes, cameraPos.getAbsoluteCameraPos(), distancesByNormal); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/MixedDirectionData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/MixedDirectionData.java deleted file mode 100644 index a7ab11d965..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/MixedDirectionData.java +++ /dev/null @@ -1,18 +0,0 @@ -package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; - -import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; -import net.minecraft.core.SectionPos; - -public abstract class MixedDirectionData extends PresentTranslucentData { - private final int[] vertexCounts = new int[ModelQuadFacing.COUNT]; - - MixedDirectionData(SectionPos sectionPos, int vertexCount, int quadCount) { - super(sectionPos, quadCount); - this.vertexCounts[ModelQuadFacing.UNASSIGNED.ordinal()] = vertexCount; - } - - @Override - public int[] getVertexCounts() { - return this.vertexCounts; - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/NoData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/NoData.java index a6f281479c..850e3fe9be 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/NoData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/NoData.java @@ -1,6 +1,8 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortType; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TranslucentGeometryCollector; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.TQuad; import net.minecraft.core.SectionPos; /** @@ -23,6 +25,11 @@ public SortType getSortType() { return this.reason; } + @Override + public boolean oldDataMatches(TranslucentGeometryCollector collector, SortType sortType, TQuad[] quads) { + return false; + } + public static NoData forEmptySection(SectionPos sectionPos) { return new NoData(sectionPos, SortType.EMPTY_SECTION); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/PresentTranslucentData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/PresentTranslucentData.java index d4e219e3a7..220af9f7b3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/PresentTranslucentData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/PresentTranslucentData.java @@ -1,32 +1,43 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortType; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TranslucentGeometryCollector; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.TQuad; import net.minecraft.core.SectionPos; /** * Super class for translucent data that contains an actual buffer. */ public abstract class PresentTranslucentData extends TranslucentData { - protected final int quadCount; + private final int inputQuadCount; private int quadHash; - PresentTranslucentData(SectionPos sectionPos, int quadCount) { + PresentTranslucentData(SectionPos sectionPos, int inputQuadCount) { super(sectionPos); - this.quadCount = quadCount; + this.inputQuadCount = inputQuadCount; } - public abstract int[] getVertexCounts(); - public abstract Sorter getSorter(); + @Override + public boolean oldDataMatches(TranslucentGeometryCollector collector, SortType sortType, TQuad[] quads) { + // for the sort types other than NONE (and the old data being AnyOrderData) the geometry needs to be the same (checked with length and hash) + return this.getInputQuadCount() == quads.length && this.hashMatches(collector); + } + + protected boolean hashMatches(TranslucentGeometryCollector collector) { + return this.quadHash == collector.getQuadHash(); + } + public void setQuadHash(int hash) { this.quadHash = hash; } - public int getQuadHash() { - return this.quadHash; + public int getInputQuadCount() { + return this.inputQuadCount; } - public int getQuadCount() { - return this.quadCount; + public int getIndexQuadCount() { + return this.inputQuadCount; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SplitDirectionData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SplitDirectionData.java deleted file mode 100644 index cc07ffe669..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/SplitDirectionData.java +++ /dev/null @@ -1,22 +0,0 @@ -package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; - -import net.minecraft.core.SectionPos; - -/** - * Super class for translucent data that is rendered separately for each facing. - * (block face culling is possible) It's important that the indices are inserted - * starting at zero for each facing. - */ -public abstract class SplitDirectionData extends PresentTranslucentData { - private final int[] vertexCounts; - - public SplitDirectionData(SectionPos sectionPos, int[] vertexCounts, int quadCount) { - super(sectionPos, quadCount); - this.vertexCounts = vertexCounts; - } - - @Override - public int[] getVertexCounts() { - return this.vertexCounts; - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java index 400db92500..95eb399de2 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortType; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TQuad; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.TQuad; import net.caffeinemc.mods.sodium.client.util.MathUtil; import net.caffeinemc.mods.sodium.client.util.sorting.RadixSort; import net.minecraft.core.SectionPos; @@ -13,11 +13,11 @@ * Unlike sorting by distance, which is descending for translucent rendering to * be correct, sorting by dot product is ascending instead. */ -public class StaticNormalRelativeData extends SplitDirectionData { +public class StaticNormalRelativeData extends PresentTranslucentData { private Sorter sorterOnce; - public StaticNormalRelativeData(SectionPos sectionPos, int[] vertexCounts, int quadCount) { - super(sectionPos, vertexCounts, quadCount); + public StaticNormalRelativeData(SectionPos sectionPos, int inputQuadCount) { + super(sectionPos, inputQuadCount); } @Override @@ -35,8 +35,8 @@ public Sorter getSorter() { return sorter; } - private static StaticNormalRelativeData fromDoubleUnaligned(int[] vertexCounts, TQuad[] quads, SectionPos sectionPos) { - var snrData = new StaticNormalRelativeData(sectionPos, vertexCounts, quads.length); + private static StaticNormalRelativeData fromDoubleUnaligned(TQuad[] quads, SectionPos sectionPos) { + var snrData = new StaticNormalRelativeData(sectionPos, quads.length); var sorter = new StaticSorter(quads.length); snrData.sorterOnce = sorter; var indexBuffer = sorter.getIntBuffer(); @@ -66,18 +66,17 @@ private static StaticNormalRelativeData fromDoubleUnaligned(int[] vertexCounts, /** * Important: The vertex indexes must start at zero for each facing. */ - private static StaticNormalRelativeData fromMixed(int[] vertexCounts, + private static StaticNormalRelativeData fromMixed(int[] meshFacingCounts, TQuad[] quads, SectionPos sectionPos) { - var snrData = new StaticNormalRelativeData(sectionPos, vertexCounts, quads.length); + var snrData = new StaticNormalRelativeData(sectionPos, quads.length); var sorter = new StaticSorter(quads.length); snrData.sorterOnce = sorter; var indexBuffer = sorter.getIntBuffer(); var maxQuadCount = 0; - for (var vertexCount : vertexCounts) { - if (vertexCount != -1) { - var quadCount = TranslucentData.vertexCountToQuadCount(vertexCount); + for (var quadCount : meshFacingCounts) { + if (quadCount != -1) { maxQuadCount = Math.max(maxQuadCount, quadCount); } } @@ -86,28 +85,26 @@ private static StaticNormalRelativeData fromMixed(int[] vertexCounts, // This is necessary because the emitted quad indexes in each facing start at zero, // but the quads are stored in a single continuously indexed array. int quadIndex = 0; - for (var vertexCount : vertexCounts) { - if (vertexCount == -1 || vertexCount == 0) { + for (var quadCount : meshFacingCounts) { + if (quadCount == -1 || quadCount == 0) { continue; } - int count = TranslucentData.vertexCountToQuadCount(vertexCount); - - if (count == 1) { + if (quadCount == 1) { TranslucentData.writeQuadVertexIndexes(indexBuffer, 0); quadIndex++; } else { - final var keys = new int[count]; - final var perm = new int[count]; + final var keys = new int[quadCount]; + final var perm = new int[quadCount]; - for (int idx = 0; idx < count; idx++) { + for (int idx = 0; idx < quadCount; idx++) { keys[idx] = MathUtil.floatToComparableInt(quads[quadIndex++].getAccurateDotProduct()); perm[idx] = idx; } RadixSort.sortIndirect(perm, keys, false); - for (int idx = 0; idx < count; idx++) { + for (int idx = 0; idx < quadCount; idx++) { TranslucentData.writeQuadVertexIndexes(indexBuffer, perm[idx]); } } @@ -116,12 +113,12 @@ private static StaticNormalRelativeData fromMixed(int[] vertexCounts, return snrData; } - public static StaticNormalRelativeData fromMesh(int[] vertexCounts, + public static StaticNormalRelativeData fromMesh(int[] meshFacingCounts, TQuad[] quads, SectionPos sectionPos, boolean isDoubleUnaligned) { if (isDoubleUnaligned) { - return fromDoubleUnaligned(vertexCounts, quads, sectionPos); + return fromDoubleUnaligned(quads, sectionPos); } else { - return fromMixed(vertexCounts, quads, sectionPos); + return fromMixed(meshFacingCounts, quads, sectionPos); } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticTopoData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticTopoData.java index 48d6bb298b..6b9324d236 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticTopoData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticTopoData.java @@ -1,8 +1,7 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; -import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionMeshParts; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortType; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TQuad; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.TQuad; import net.minecraft.core.SectionPos; import java.nio.IntBuffer; @@ -13,11 +12,11 @@ * possible to sort without dynamic triggering, meaning the sort order never * needs to change. */ -public class StaticTopoData extends MixedDirectionData { +public class StaticTopoData extends PresentTranslucentData { private Sorter sorterOnce; - StaticTopoData(SectionPos sectionPos, int vertexCount, int quadCount) { - super(sectionPos, vertexCount, quadCount); + StaticTopoData(SectionPos sectionPos, int inputQuadCount) { + super(sectionPos, inputQuadCount); } @Override @@ -42,16 +41,16 @@ public void accept(int value) { } } - public static StaticTopoData fromMesh(int vertexCount, TQuad[] quads, SectionPos sectionPos) { + public static StaticTopoData fromMesh(TQuad[] quads, SectionPos sectionPos, boolean failOnIntersection) { var sorter = new StaticSorter(quads.length); var indexWriter = new QuadIndexConsumerIntoBuffer(sorter.getIntBuffer()); - if (!TopoGraphSorting.topoGraphSort(indexWriter, quads, null, null)) { + if (!TopoGraphSorting.topoGraphSort(indexWriter, quads, null, null, failOnIntersection)) { sorter.getIndexBuffer().free(); return null; } - var staticTopoData = new StaticTopoData(sectionPos, vertexCount, quads.length); + var staticTopoData = new StaticTopoData(sectionPos, quads.length); staticTopoData.sorterOnce = sorter; return staticTopoData; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TopoGraphSorting.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TopoGraphSorting.java index 478f51362a..d3792db7a1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TopoGraphSorting.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TopoGraphSorting.java @@ -2,7 +2,7 @@ import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TQuad; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.TQuad; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.NormalList; import net.caffeinemc.mods.sodium.client.util.collections.BitArray; import org.joml.Vector3fc; @@ -69,7 +69,7 @@ private static boolean pointOutsideHalfSpaceEpsilon(float planeDistance, Vector3 return planeNormal.dot(x, y, z) - HALF_SPACE_EPSILON > planeDistance; } - public static boolean orthogonalQuadVisibleThrough(TQuad quadA, TQuad quadB) { + public static boolean orthogonalQuadVisibleThrough(TQuad quadA, TQuad quadB, boolean intersectionsVisible) { var aDirection = quadA.getFacing().ordinal(); var aOpposite = quadA.getFacing().getOpposite().ordinal(); var bDirection = quadB.getFacing().ordinal(); @@ -85,8 +85,11 @@ public static boolean orthogonalQuadVisibleThrough(TQuad quadA, TQuad quadB) { var vis = BIntoADescent > 0 && AOutsideBAscent > 0; - // if they're visible and their bounding boxes intersect and apply a heuristic to resolve + // if they're visible and their bounding boxes intersect, apply a heuristic to resolve if allowed if (vis && TQuad.extentsIntersect(aExtents, bExtents)) { + if (intersectionsVisible) { + return true; + } return BIntoADescent + AOutsideBAscent > 1; } return vis; @@ -157,13 +160,15 @@ private static boolean visibilityWithSeparator(TQuad quadA, TQuad quadB, * Checks if one quad is visible through the other quad. This accepts arbitrary * quads, even unaligned ones. * - * @param quad the quad through which the other quad is being tested - * @param other the quad being tested - * @param distancesByNormal a map of normals to sorted arrays of face plane distances for disproving that the quads are visible through each other, null to disable + * @param quad the quad through which the other quad is being tested + * @param other the quad being tested + * @param distancesByNormal a map of normals to sorted arrays of face plane distances for disproving that the quads are visible through each other, null to disable + * @param cameraPos the camera position, or null to disable the visibility check + * @param intersectionsVisible if true, the method will return true if the quads intersect instead of trying to break the tie * @return true if the other quad is visible through the first quad */ private static boolean quadVisibleThrough(TQuad quad, TQuad other, - Object2ReferenceMap distancesByNormal, Vector3fc cameraPos) { + Object2ReferenceMap distancesByNormal, Vector3fc cameraPos, boolean intersectionsVisible) { if (quad == other) { return false; } @@ -186,7 +191,7 @@ private static boolean quadVisibleThrough(TQuad quad, TQuad other, result = sign * quad.getExtents()[direction] > sign * other.getExtents()[direction]; } else { // orthogonal quads - result = orthogonalQuadVisibleThrough(quad, other); + result = orthogonalQuadVisibleThrough(quad, other, intersectionsVisible); } } else { // at least one unaligned quad @@ -241,17 +246,18 @@ private static boolean quadVisibleThrough(TQuad quad, TQuad other, * doing a DFS on the implicit graph. Edges are tested as they are searched for * and if necessary separator planes are used to disprove visibility. * - * @param indexConsumer the consumer to write the topo sort result to - * @param allQuads the quads to sort - * @param distancesByNormal a map of normals to sorted arrays of face plane - * distances, null to disable - * @param cameraPos the camera position, or null to disable the - * visibility check + * @param indexConsumer the consumer to write the topo sort result to + * @param allQuads the quads to sort + * @param distancesByNormal a map of normals to sorted arrays of face plane + * distances, null to disable + * @param cameraPos the camera position, or null to disable the + * visibility check + * @param failOnIntersection if true, intersecting orthogonal quads are treated as visible to each other */ public static boolean topoGraphSort( IntConsumer indexConsumer, TQuad[] allQuads, Object2ReferenceMap distancesByNormal, - Vector3fc cameraPos) { + Vector3fc cameraPos, boolean failOnIntersection) { // if enabled, check for visibility and produce a mapping of indices TQuad[] quads; int[] activeToRealIndex = null; @@ -281,10 +287,10 @@ public static boolean topoGraphSort( quadCount = allQuads.length; } - return topoGraphSort(indexConsumer, quads, quadCount, activeToRealIndex, distancesByNormal, cameraPos); + return topoGraphSort(indexConsumer, quads, quadCount, activeToRealIndex, distancesByNormal, cameraPos, failOnIntersection); } - public static boolean topoGraphSort(IntConsumer indexConsumer, TQuad[] quads, int quadCount, int[] activeToRealIndex, Object2ReferenceMap distancesByNormal, Vector3fc cameraPos) { + public static boolean topoGraphSort(IntConsumer indexConsumer, TQuad[] quads, int quadCount, int[] activeToRealIndex, Object2ReferenceMap distancesByNormal, Vector3fc cameraPos, boolean failOnIntersection) { // special case for 0 to 2 quads if (quadCount == 0) { return true; @@ -302,7 +308,12 @@ public static boolean topoGraphSort(IntConsumer indexConsumer, TQuad[] quads, in if (quadCount == 2) { var a = 0; var b = 1; - if (quadVisibleThrough(quads[a], quads[b], null, null)) { + if (quadVisibleThrough(quads[a], quads[b], null, null, failOnIntersection)) { + // fail on cycle if required by flag + if (failOnIntersection && quadVisibleThrough(quads[b], quads[a], null, null, true)) { + return false; + } + a = 1; b = 0; } @@ -339,7 +350,7 @@ public static boolean topoGraphSort(IntConsumer indexConsumer, TQuad[] quads, in if (currentQuadIndex != nextEdgeTest) { var currentQuad = quads[currentQuadIndex]; var nextQuad = quads[nextEdgeTest]; - if (quadVisibleThrough(currentQuad, nextQuad, distancesByNormal, cameraPos)) { + if (quadVisibleThrough(currentQuad, nextQuad, distancesByNormal, cameraPos, failOnIntersection)) { // if the visible quad is on the stack, there is a cycle if (onStack.getAndSet(nextEdgeTest)) { return false; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TranslucentData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TranslucentData.java index 4a683bc09c..6e1718ce75 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TranslucentData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TranslucentData.java @@ -1,11 +1,13 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; -import java.nio.IntBuffer; - -import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortType; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TranslucentGeometryCollector; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.bsp_tree.UpdatedQuadsList; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.TQuad; import net.minecraft.core.SectionPos; +import java.nio.IntBuffer; + /** * The base class for all types of translucent data. Subclasses are generated by * the geometry collector after the section is built. @@ -24,10 +26,20 @@ public abstract class TranslucentData { public abstract SortType getSortType(); + public abstract boolean oldDataMatches(TranslucentGeometryCollector collector, SortType sortType, TQuad[] quads); + + public UpdatedQuadsList getUpdatedQuads() { + return null; + } + + public boolean meshesWereModified() { + return this.getUpdatedQuads() != null; + } + /** * Prepares the translucent data for triggering of the given type. This is run * on the main thread before a sort task is scheduled. - * + * * @param isAngleTrigger Whether the trigger is an angle trigger */ public void prepareTrigger(boolean isAngleTrigger) { @@ -42,8 +54,8 @@ public static int quadCountToIndexBytes(int quadCount) { return quadCount * BYTES_PER_QUAD; } - public static int indexBytesToQuadCount(int indexBytes) { - return indexBytes / BYTES_PER_QUAD; + public static int quadCountToVertexCount(int quadCount) { + return quadCount * VERTICES_PER_QUAD; } public static void writeQuadVertexIndexes(IntBuffer intBuffer, int quadIndex) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/quad/FullTQuad.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/quad/FullTQuad.java new file mode 100644 index 0000000000..4bc1ed2235 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/quad/FullTQuad.java @@ -0,0 +1,178 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad; + +import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; +import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.DefaultMaterials; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.TranslucentData; +import net.caffeinemc.mods.sodium.client.render.chunk.vertex.builder.ChunkMeshBufferBuilder; +import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexEncoder; +import org.joml.Vector3f; +import org.joml.Vector3fc; + +import java.nio.ByteBuffer; + +public class FullTQuad extends RegularTQuad { + private final ChunkVertexEncoder.Vertex[] vertices = ChunkVertexEncoder.Vertex.uninitializedQuad(); + private int sameVertexMap; + private boolean normalIsVeryAccurate = false; + + private boolean hasUpdatedVertices = false; + + // NO_WRITE means it should not be written (either there is no update or it's getting overwritten) + private static final int NO_WRITE = -1; + private int writeToIndex = NO_WRITE; + + FullTQuad(ModelQuadFacing facing, int packedNormal) { + super(facing, packedNormal); + } + + public static FullTQuad fromVertices(ChunkVertexEncoder.Vertex[] vertices, ModelQuadFacing facing, int packedNormal) { + var quad = new FullTQuad(facing, packedNormal); + quad.sameVertexMap = quad.initExtentsAndCenter(vertices); + if (quad.isInvalid()) { + return null; + } + + quad.initDotProduct(); + quad.initVertices(vertices); + + return quad; + } + + private void initVertices(ChunkVertexEncoder.Vertex[] vertices) { + // deep copy the vertices since the caller may modify them + for (int i = 0; i < 4; i++) { + var newVertex = this.vertices[i]; + var oldVertex = vertices[i]; + ChunkVertexEncoder.Vertex.copyVertexTo(oldVertex, newVertex); + } + } + + public static FullTQuad splittingCopy(FullTQuad quad) { + var newQuad = new FullTQuad(quad.facing, quad.packedNormal); + newQuad.initVertices(quad.vertices); + + newQuad.extents = quad.extents; + newQuad.accurateDotProduct = quad.accurateDotProduct; + newQuad.quantizedDotProduct = quad.quantizedDotProduct; + + newQuad.center = quad.center; + newQuad.quantizedNormal = quad.quantizedNormal; + newQuad.accurateNormal = quad.accurateNormal; + + newQuad.normalIsVeryAccurate = quad.normalIsVeryAccurate; + + return newQuad; + } + + public void updateSplitQuadAfterVertexModification() { + this.sameVertexMap = this.initExtentsAndCenter(this.vertices); + + // invalidate vertex positions after modification of the vertices + this.vertexPositions = null; + + // no need to update dot product since splitting a quad doesn't change its normal or dot product + } + + public boolean isInvalid() { + return isInvalid(this.sameVertexMap); + } + + public int getUniqueVertexMap() { + return (~this.sameVertexMap) & 0b1111; + } + + public int getSameVertexMap() { + return this.sameVertexMap; + } + + public boolean triggerAndSetUpdatedVertices() { + if (this.hasUpdatedVertices) { + return false; + } + + this.hasUpdatedVertices = true; + return true; + } + + public void setWriteToIndex(int writeToIndex) { + this.writeToIndex = writeToIndex; + } + + public void setNoWrite() { + this.writeToIndex = NO_WRITE; + } + + public void writeToBuffer(ChunkMeshBufferBuilder bufferBuilder, ByteBuffer buffer) { + if (this.writeToIndex != NO_WRITE) { + bufferBuilder.writeExternal(buffer, TranslucentData.quadCountToVertexCount(this.writeToIndex), this.vertices, DefaultMaterials.TRANSLUCENT); + } + } + + @Override + public float[] getVertexPositions() { + if (this.vertexPositions == null) { + this.vertexPositions = new float[12]; + + for (int i = 0; i < 4; i++) { + this.vertexPositions[i * 3] = this.vertices[i].x; + this.vertexPositions[i * 3 + 1] = this.vertices[i].y; + this.vertexPositions[i * 3 + 2] = this.vertices[i].z; + } + } + + return this.vertexPositions; + } + + public Vector3fc getVeryAccurateNormal() { + if (this.facing.isAligned()) { + return this.facing.getAlignedNormal(); + } else { + if (!this.normalIsVeryAccurate) { + final float x0 = this.vertices[0].x; + final float y0 = this.vertices[0].y; + final float z0 = this.vertices[0].z; + + final float x1 = this.vertices[1].x; + final float y1 = this.vertices[1].y; + final float z1 = this.vertices[1].z; + + final float x2 = this.vertices[2].x; + final float y2 = this.vertices[2].y; + final float z2 = this.vertices[2].z; + + final float x3 = this.vertices[3].x; + final float y3 = this.vertices[3].y; + final float z3 = this.vertices[3].z; + + final float dx0 = x2 - x0; + final float dy0 = y2 - y0; + final float dz0 = z2 - z0; + final float dx1 = x3 - x1; + final float dy1 = y3 - y1; + final float dz1 = z3 - z1; + + float normX = dy0 * dz1 - dz0 * dy1; + float normY = dz0 * dx1 - dx0 * dz1; + float normZ = dx0 * dy1 - dy0 * dx1; + + // normalize by length for the packed normal + // TODO: normalization necessary? + float length = (float) Math.sqrt(normX * normX + normY * normY + normZ * normZ); + if (length != 0.0 && length != 1.0) { + normX /= length; + normY /= length; + normZ /= length; + } + + this.accurateNormal = new Vector3f(normX, normY, normZ); + this.accurateDotProduct = this.accurateNormal.dot(this.center); + this.normalIsVeryAccurate = true; + } + } + return this.accurateNormal; + } + + public ChunkVertexEncoder.Vertex[] getVertices() { + return this.vertices; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/quad/RegularTQuad.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/quad/RegularTQuad.java new file mode 100644 index 0000000000..032d43bc5f --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/quad/RegularTQuad.java @@ -0,0 +1,85 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad; + +import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; +import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexEncoder; + +public class RegularTQuad extends TQuad { + float[] vertexPositions; + + RegularTQuad(ModelQuadFacing facing, int packedNormal) { + super(facing, packedNormal); + } + + public static RegularTQuad fromVertices(ChunkVertexEncoder.Vertex[] vertices, ModelQuadFacing facing, int packedNormal) { + var quad = new RegularTQuad(facing, packedNormal); + + var sameVertexMap = quad.initExtentsAndCenter(vertices); + if (isInvalid(sameVertexMap)) { + return null; + } + + quad.initVertexPositions(vertices, sameVertexMap); + quad.initDotProduct(); + + return quad; + } + + void initVertexPositions(ChunkVertexEncoder.Vertex[] vertices, int sameVertexMap) { + // check if we need to store vertex positions for this quad, only necessary if it's unaligned or rotated (yet aligned) + var needsVertexPositions = (sameVertexMap != 0 || !this.facing.isAligned()); + if (!needsVertexPositions) { + float posXExtent = this.extents[0]; + float posYExtent = this.extents[1]; + float posZExtent = this.extents[2]; + float negXExtent = this.extents[3]; + float negYExtent = this.extents[4]; + float negZExtent = this.extents[5]; + + for (int i = 0; i < 4; i++) { + var vertex = vertices[i]; + if (vertex.x != posYExtent && vertex.x != negYExtent || + vertex.y != posZExtent && vertex.y != negZExtent || + vertex.z != posXExtent && vertex.z != negXExtent) { + needsVertexPositions = true; + break; + } + } + } + + if (needsVertexPositions) { + var vertexPositions = new float[12]; + this.vertexPositions = vertexPositions; + for (int i = 0, itemIndex = 0; i < 4; i++) { + var vertex = vertices[i]; + vertexPositions[itemIndex++] = vertex.x; + vertexPositions[itemIndex++] = vertex.y; + vertexPositions[itemIndex++] = vertex.z; + } + } + } + + public float[] getVertexPositions() { + // calculate vertex positions from extents if there's no cached value + // (we don't want to be preemptively collecting vertex positions for all aligned quads) + if (this.vertexPositions == null) { + this.vertexPositions = new float[12]; + + var facingAxis = this.facing.getAxis(); + var xRange = facingAxis == 0 ? 0 : 3; + var yRange = facingAxis == 1 ? 0 : 3; + var zRange = facingAxis == 2 ? 0 : 3; + + var itemIndex = 0; + for (int x = 0; x <= xRange; x += 3) { + for (int y = 0; y <= yRange; y += 3) { + for (int z = 0; z <= zRange; z += 3) { + this.vertexPositions[itemIndex++] = this.extents[x]; + this.vertexPositions[itemIndex++] = this.extents[y + 1]; + this.vertexPositions[itemIndex++] = this.extents[z + 2]; + } + } + } + } + return this.vertexPositions; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TQuad.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/quad/TQuad.java similarity index 55% rename from common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TQuad.java rename to common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/quad/TQuad.java index 8d0ae60c73..cfdd7eee94 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TQuad.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/quad/TQuad.java @@ -1,18 +1,27 @@ -package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting; - -import java.util.Arrays; +package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad; +import net.caffeinemc.mods.sodium.api.util.NormI8; +import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline.DefaultFluidRenderer; +import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexEncoder; import org.joml.Vector3f; import org.joml.Vector3fc; -import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; -import net.caffeinemc.mods.sodium.api.util.NormI8; +import java.util.Arrays; /** * Represents a quad for the purposes of translucency sorting. Called TQuad to * avoid confusion with other quad classes. */ -public class TQuad { +public abstract class TQuad { + /** + * If the delta between two vertices is smaller than this value, they are + * considered to be the same vertex. This is also used for checking whether a vertex + * lies on a splitting plane and whether the result of a splitting operation results + * in an empty quad. + */ + public static final float VERTEX_EPSILON = 0.00001f; + /** * The quantization factor with which the normals are quantized such that there * are fewer possible unique normals. The factor describes the number of steps @@ -21,25 +30,129 @@ public class TQuad { * at the origin onto which the normals are projected. The normals are snapped * to the nearest grid point. */ - private static final int QUANTIZATION_FACTOR = 4; - - private ModelQuadFacing facing; - private final float[] extents; - private float[] vertexPositions; - private final int packedNormal; - private final float accurateDotProduct; - private float quantizedDotProduct; - private Vector3fc center; // null on aligned quads - private Vector3fc quantizedNormal; - private Vector3fc accurateNormal; - - private TQuad(ModelQuadFacing facing, float[] extents, float[] vertexPositions, Vector3fc center, int packedNormal) { + static final int NORMAL_QUANTIZATION_STEPS = 4; + + private static final float INV_QUANTIZE_EPSILON = 256f; + public static final float QUANTIZE_EPSILON = 1f / INV_QUANTIZE_EPSILON; + + static { + // ensure it fits with the fluid renderer epsilon and that it's a power-of-two + // fraction + var targetEpsilon = DefaultFluidRenderer.EPSILON * 2.1f; + if (QUANTIZE_EPSILON <= targetEpsilon && Integer.bitCount((int) INV_QUANTIZE_EPSILON) == 1) { + throw new RuntimeException("epsilon is invalid: " + QUANTIZE_EPSILON); + } + } + + ModelQuadFacing facing; + final int packedNormal; + float[] extents; + float accurateDotProduct; + float quantizedDotProduct; + Vector3fc center; // null on aligned quads + Vector3fc quantizedNormal; + Vector3fc accurateNormal; + + TQuad(ModelQuadFacing facing, int packedNormal) { + if (facing.isAligned()) { + packedNormal = ModelQuadFacing.PACKED_ALIGNED_NORMALS[facing.ordinal()]; + } + this.facing = facing; - this.extents = extents; - this.vertexPositions = vertexPositions; - this.center = center; this.packedNormal = packedNormal; + } + + protected static boolean isInvalid(int sameVertexMap) { + return Integer.bitCount(sameVertexMap) > 1; + } + + int initExtentsAndCenter(ChunkVertexEncoder.Vertex[] vertices) { + float xSum = 0; + float ySum = 0; + float zSum = 0; + + // keep track of distinct vertices to compute the center accurately for + // degenerate quads + float lastX = vertices[3].x; + float lastY = vertices[3].y; + float lastZ = vertices[3].z; + int sameVertexMap = 0; + + float posXExtent = Float.NEGATIVE_INFINITY; + float posYExtent = Float.NEGATIVE_INFINITY; + float posZExtent = Float.NEGATIVE_INFINITY; + float negXExtent = Float.POSITIVE_INFINITY; + float negYExtent = Float.POSITIVE_INFINITY; + float negZExtent = Float.POSITIVE_INFINITY; + + for (int i = 0; i < 4; i++) { + float x = vertices[i].x; + float y = vertices[i].y; + float z = vertices[i].z; + + posXExtent = Math.max(posXExtent, x); + posYExtent = Math.max(posYExtent, y); + posZExtent = Math.max(posZExtent, z); + negXExtent = Math.min(negXExtent, x); + negYExtent = Math.min(negYExtent, y); + negZExtent = Math.min(negZExtent, z); + + if (Math.abs(x - lastX) >= VERTEX_EPSILON || + Math.abs(y - lastY) >= VERTEX_EPSILON || + Math.abs(z - lastZ) >= VERTEX_EPSILON) { + xSum += x; + ySum += y; + zSum += z; + } else { + sameVertexMap |= 1 << i; + } + if (i != 3) { + lastX = x; + lastY = y; + lastZ = z; + } + } + + // shrink quad in non-normal directions to prevent intersections caused by + // epsilon offsets applied by FluidRenderer + if (this.facing != ModelQuadFacing.POS_X && this.facing != ModelQuadFacing.NEG_X) { + posXExtent -= QUANTIZE_EPSILON; + negXExtent += QUANTIZE_EPSILON; + if (negXExtent > posXExtent) { + negXExtent = posXExtent; + } + } + if (this.facing != ModelQuadFacing.POS_Y && this.facing != ModelQuadFacing.NEG_Y) { + posYExtent -= QUANTIZE_EPSILON; + negYExtent += QUANTIZE_EPSILON; + if (negYExtent > posYExtent) { + negYExtent = posYExtent; + } + } + if (this.facing != ModelQuadFacing.POS_Z && this.facing != ModelQuadFacing.NEG_Z) { + posZExtent -= QUANTIZE_EPSILON; + negZExtent += QUANTIZE_EPSILON; + if (negZExtent > posZExtent) { + negZExtent = posZExtent; + } + } + + // POS_X, POS_Y, POS_Z, NEG_X, NEG_Y, NEG_Z + this.extents = new float[] { posXExtent, posYExtent, posZExtent, negXExtent, negYExtent, negZExtent }; + var uniqueVertexes = 4 - Integer.bitCount(sameVertexMap); + if ((!this.facing.isAligned() || uniqueVertexes != 4) && uniqueVertexes >= 3) { + var invUniqueVertexes = 1.0f / uniqueVertexes; + var centerX = xSum * invUniqueVertexes; + var centerY = ySum * invUniqueVertexes; + var centerZ = zSum * invUniqueVertexes; + this.center = new Vector3f(centerX, centerY, centerZ); + } + + return sameVertexMap; + } + + void initDotProduct() { if (this.facing.isAligned()) { this.accurateDotProduct = getAlignedDotProduct(this.facing, this.extents); } else { @@ -55,13 +168,7 @@ private static float getAlignedDotProduct(ModelQuadFacing facing, float[] extent return extents[facing.ordinal()] * facing.getSign(); } - static TQuad fromAligned(ModelQuadFacing facing, float[] extents, float[] vertexPositions, Vector3fc center) { - return new TQuad(facing, extents, vertexPositions, center, ModelQuadFacing.PACKED_ALIGNED_NORMALS[facing.ordinal()]); - } - - static TQuad fromUnaligned(ModelQuadFacing facing, float[] extents, float[] vertexPositions, Vector3fc center, int packedNormal) { - return new TQuad(facing, extents, vertexPositions, center, packedNormal); - } + public abstract float[] getVertexPositions(); public ModelQuadFacing getFacing() { return this.facing; @@ -76,7 +183,7 @@ public ModelQuadFacing useQuantizedFacing() { if (!this.facing.isAligned()) { // quantize the normal, get the new facing and get fix the dot product to match this.getQuantizedNormal(); - this.facing = ModelQuadFacing.fromNormal(this.quantizedNormal.x(), this.quantizedNormal.y(), this.quantizedNormal.z()); + this.facing = ModelQuadFacing.fromNormal(this.quantizedNormal); if (this.facing.isAligned()) { this.quantizedDotProduct = getAlignedDotProduct(this.facing, this.extents); } else { @@ -91,31 +198,6 @@ public float[] getExtents() { return this.extents; } - public float[] getVertexPositions() { - // calculate vertex positions from extents if there's no cached value - // (we don't want to be preemptively collecting vertex positions for all aligned quads) - if (this.vertexPositions == null) { - this.vertexPositions = new float[12]; - - var facingAxis = this.facing.getAxis(); - var xRange = facingAxis == 0 ? 0 : 3; - var yRange = facingAxis == 1 ? 0 : 3; - var zRange = facingAxis == 2 ? 0 : 3; - - var itemIndex = 0; - for (int x = 0; x <= xRange; x += 3) { - for (int y = 0; y <= yRange; y += 3) { - for (int z = 0; z <= zRange; z += 3) { - this.vertexPositions[itemIndex++] = this.extents[x]; - this.vertexPositions[itemIndex++] = this.extents[y + 1]; - this.vertexPositions[itemIndex++] = this.extents[z + 2]; - } - } - } - } - return this.vertexPositions; - } - public Vector3fc getCenter() { // calculate aligned quad center on demand if (this.center == null) { @@ -182,14 +264,14 @@ private void computeQuantizedNormal() { // in each axis the number of values is 2 * QUANTIZATION_FACTOR + 1. // the total number of normals is the number of points on that cube's surface. var normal = new Vector3f( - (int) (normX * QUANTIZATION_FACTOR), - (int) (normY * QUANTIZATION_FACTOR), - (int) (normZ * QUANTIZATION_FACTOR)); + (int) (normX * NORMAL_QUANTIZATION_STEPS), + (int) (normY * NORMAL_QUANTIZATION_STEPS), + (int) (normZ * NORMAL_QUANTIZATION_STEPS)); normal.normalize(); this.quantizedNormal = normal; } - int getQuadHash() { + public int getQuadHash() { // the hash code needs to be particularly collision resistant int result = 1; result = 31 * result + Arrays.hashCode(this.extents); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GeometryPlanes.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GeometryPlanes.java index 0076a04e2c..cab8bef130 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GeometryPlanes.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/GeometryPlanes.java @@ -3,7 +3,7 @@ import it.unimi.dsi.fastutil.objects.Object2ReferenceMap; import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFacing; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TQuad; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.quad.TQuad; import net.minecraft.core.SectionPos; import org.joml.Vector3f; import org.joml.Vector3fc; @@ -69,7 +69,7 @@ public void addAlignedPlane(SectionPos sectionPos, int direction, float distance normalPlanes.addPlaneMember(distance); } - public void addDoubleSidedPlane(SectionPos sectionPos, int axis, float distance) { + public void addDoubleSidedAlignedPlane(SectionPos sectionPos, int axis, float distance) { this.addAlignedPlane(sectionPos, axis, distance); this.addAlignedPlane(sectionPos, axis + 3, -distance); } @@ -83,7 +83,8 @@ public void addUnalignedPlane(SectionPos sectionPos, Vector3fc normal, float dis var normalPlanes = unalignedDistances.get(cleanedNormal); if (normalPlanes == null) { - normalPlanes = new NormalPlanes(sectionPos, new Vector3f(normal)); + // construct new normal plane using the cleaned normal to make sure its .normal is zero-cleaned + normalPlanes = new NormalPlanes(sectionPos, new Vector3f(cleanedNormal)); // NOTE: importantly use the cleaned normal here, not the cleanedNormal, which is mutable unalignedDistances.put(normalPlanes.normal, normalPlanes); @@ -107,6 +108,11 @@ private Vector3f cleanNormal(Vector3fc normal) { return cleanedNormal; } + public void addDoubleSidedUnalignedPlane(SectionPos sectionPos, Vector3fc normal, float distance) { + this.addUnalignedPlane(sectionPos, normal, distance); + this.addUnalignedPlane(sectionPos, normal.negate(new Vector3f()), -distance); + } + public void addQuadPlane(SectionPos sectionPos, TQuad quad) { var facing = quad.useQuantizedFacing(); if (facing.isAligned()) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java index bc3e781bed..569cbf8a2f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java @@ -227,8 +227,10 @@ public void addDebugStrings(List list) { if (sortBehavior.getSortMode() == SortBehavior.SortMode.NONE) { list.add("TS OFF"); } else { - list.add("TS (%s) NL=%02d TrN=%02d TrS=G%03d/D%03d".formatted( + var splittingMode = SodiumClientMod.options().performance.quadSplittingMode; + list.add("TS (%s,%s) NL=%02d TrN=%02d TrS=G%03d/D%03d".formatted( sortBehavior.getShortName(), + splittingMode.getShortName(), this.gfni.getUniqueNormalCount(), this.triggeredNormalCount, this.gfniTriggerCount, 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 0b9771d600..a68cb975b1 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 @@ -3,8 +3,8 @@ import net.caffeinemc.mods.sodium.client.render.chunk.terrain.material.Material; import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexEncoder; import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexType; -import org.apache.commons.lang3.Validate; import org.lwjgl.system.MemoryUtil; + import java.nio.ByteBuffer; public class ChunkMeshBufferBuilder { @@ -45,6 +45,11 @@ public void push(ChunkVertexEncoder.Vertex[] vertices, int materialBits) { this.vertexCount += 4; } + public void writeExternal(ByteBuffer buffer, int position, ChunkVertexEncoder.Vertex[] vertices, Material material) { + this.encoder.write(MemoryUtil.memAddress(buffer, position * this.stride), + material.bits(), vertices, this.sectionIndex); + } + private void ensureCapacity(int vertexCount) { if (this.vertexCount + vertexCount >= this.vertexCapacity) { this.grow(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 cac465580c..3f6db6e205 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 @@ -22,5 +22,27 @@ public static Vertex[] uninitializedQuad() { return vertices; } + + public static void copyVertexTo(Vertex from, Vertex to) { + to.x = from.x; + to.y = from.y; + to.z = from.z; + to.color = from.color; + to.ao = from.ao; + to.u = from.u; + to.v = from.v; + to.light = from.light; + } + + public static void writeVertex(ChunkVertexEncoder.Vertex targetA, float newX, float newY, float newZ, int newColor, float newAo, float newU, float newV, int newLight) { + targetA.x = newX; + targetA.y = newY; + targetA.z = newZ; + targetA.color = newColor; + targetA.ao = newAo; + targetA.u = newU; + targetA.v = newV; + targetA.light = newLight; + } } } diff --git a/common/src/main/resources/assets/sodium/lang/en_us.json b/common/src/main/resources/assets/sodium/lang/en_us.json index 04e2d66b27..986a15797e 100644 --- a/common/src/main/resources/assets/sodium/lang/en_us.json +++ b/common/src/main/resources/assets/sodium/lang/en_us.json @@ -54,6 +54,10 @@ "sodium.options.chunk_update_threads.tooltip": "Specifies the number of threads to use for chunk building and sorting. Using more threads can speed up chunk loading and update speed, but may negatively impact frame times. The default value is usually good enough for all situations.", "sodium.options.always_defer_chunk_updates.name": "Always Defer Chunk Updates", "sodium.options.always_defer_chunk_updates.tooltip": "If enabled, rendering will never wait for chunk updates to finish, even if they are important. This can greatly improve frame rates in some scenarios, but it may create significant visual lag where blocks take a while to appear or disappear.", + "sodium.options.quad_splitting.name": "Perfect Translucency", + "sodium.options.quad_splitting.tooltip": "Uses quad splitting to make translucent blocks (not entities or items) look correct through translucency sorting even if they are intersecting or have weird shapes, such as waterlogged stained glass panes. In \"Safe\" mode there's a limit on how much geometry can be generated in extreme cases to prevent crashes or performance degradation. This limit is disabled in \"Unlimited\" mode.", + "sodium.options.quad_splitting.safe": "Safe", + "sodium.options.quad_splitting.unlimited": "Unlimited", "sodium.options.use_no_error_context.name": "Use No Error Context", "sodium.options.use_no_error_context.tooltip": "When enabled, the OpenGL context will be created with error checking disabled. This slightly improves rendering performance, but it can make debugging sudden unexplained crashes much harder.", "sodium.options.buttons.undo": "Undo", From 922ca888cd80ea9ac43cab7a7e7105401dfe54b2 Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 26 Aug 2025 20:51:30 +0200 Subject: [PATCH 109/215] Implementation of task effort estimation and frame-rate independent task scheduling Relates to ticket item #1 of #2887. --- .../arena/staging/FallbackStagingBuffer.java | 7 + .../gl/arena/staging/MappedStagingBuffer.java | 9 +- .../gl/arena/staging/StagingBuffer.java | 2 + .../client/gui/SodiumGameOptionPages.java | 11 +- .../sodium/client/gui/SodiumGameOptions.java | 5 +- .../client/render/SodiumWorldRenderer.java | 8 +- .../client/render/chunk/ChunkUpdateType.java | 43 --- .../client/render/chunk/ChunkUpdateTypes.java | 54 ++++ .../sodium/client/render/chunk/DeferMode.java | 27 ++ .../client/render/chunk/RenderSection.java | 27 +- .../render/chunk/RenderSectionManager.java | 306 +++++++++++------- .../client/render/chunk/TaskQueueType.java | 16 + .../chunk/compile/BuilderTaskOutput.java | 11 + .../chunk/compile/ChunkBuildOutput.java | 13 + .../render/chunk/compile/ChunkSortOutput.java | 12 + .../estimation/Average1DEstimator.java | 89 +++++ .../chunk/compile/estimation/Estimator.java | 91 ++++++ .../estimation/JobDurationEstimator.java | 24 ++ .../chunk/compile/estimation/JobEffort.java | 17 + .../compile/estimation/Linear2DEstimator.java | 143 ++++++++ .../compile/estimation/MeshResultSize.java | 44 +++ .../estimation/MeshTaskSizeEstimator.java | 28 ++ .../chunk/compile/executor/ChunkBuilder.java | 35 +- .../chunk/compile/executor/ChunkJob.java | 4 +- .../compile/executor/ChunkJobCollector.java | 43 +-- .../chunk/compile/executor/ChunkJobQueue.java | 17 +- .../compile/executor/ChunkJobResult.java | 17 +- .../chunk/compile/executor/ChunkJobTyped.java | 13 +- .../tasks/ChunkBuilderMeshingTask.java | 12 +- .../tasks/ChunkBuilderSortingTask.java | 15 +- .../chunk/compile/tasks/ChunkBuilderTask.java | 27 +- .../chunk/lists/VisibleChunkCollector.java | 27 +- .../render/chunk/region/RenderRegion.java | 11 +- .../translucent_sorting/SortBehavior.java | 14 +- .../data/DynamicBSPData.java | 2 +- .../translucent_sorting/data/DynamicData.java | 2 + .../data/DynamicSorter.java | 6 +- .../data/DynamicTopoData.java | 2 +- .../mods/sodium/client/util/MathUtil.java | 9 + .../resources/assets/sodium/lang/en_us.json | 7 +- 40 files changed, 944 insertions(+), 306 deletions(-) delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkUpdateType.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkUpdateTypes.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DeferMode.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/TaskQueueType.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Average1DEstimator.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Estimator.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobDurationEstimator.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobEffort.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Linear2DEstimator.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshResultSize.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshTaskSizeEstimator.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/FallbackStagingBuffer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/FallbackStagingBuffer.java index cfe9aae5a8..0230e6f82b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/FallbackStagingBuffer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/FallbackStagingBuffer.java @@ -8,6 +8,8 @@ import java.nio.ByteBuffer; public class FallbackStagingBuffer implements StagingBuffer { + private static final float BYTES_PER_NANO_LIMIT = 8_000_000.0f / (1_000_000_000.0f / 60.0f); // MB per frame at 60fps + private final GlMutableBuffer fallbackBufferObject; public FallbackStagingBuffer(CommandList commandList) { @@ -39,4 +41,9 @@ public void flip() { public String toString() { return "Fallback"; } + + @Override + public long getUploadSizeLimit(long frameDuration) { + return (long) (frameDuration * BYTES_PER_NANO_LIMIT); + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/MappedStagingBuffer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/MappedStagingBuffer.java index d70d4378dc..645e17012b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/MappedStagingBuffer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/MappedStagingBuffer.java @@ -2,19 +2,21 @@ import it.unimi.dsi.fastutil.PriorityQueue; import it.unimi.dsi.fastutil.objects.ObjectArrayFIFOQueue; +import net.caffeinemc.mods.sodium.client.gl.buffer.*; import net.caffeinemc.mods.sodium.client.gl.device.CommandList; import net.caffeinemc.mods.sodium.client.gl.device.RenderDevice; import net.caffeinemc.mods.sodium.client.gl.functions.BufferStorageFunctions; import net.caffeinemc.mods.sodium.client.gl.sync.GlFence; import net.caffeinemc.mods.sodium.client.gl.util.EnumBitField; import net.caffeinemc.mods.sodium.client.util.MathUtil; -import net.caffeinemc.mods.sodium.client.gl.buffer.*; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; public class MappedStagingBuffer implements StagingBuffer { + private static final float UPLOAD_LIMIT_MARGIN = 0.8f; + private static final EnumBitField STORAGE_FLAGS = EnumBitField.of(GlBufferStorageFlags.PERSISTENT, GlBufferStorageFlags.CLIENT_STORAGE, GlBufferStorageFlags.MAP_WRITE); @@ -163,6 +165,11 @@ public void flip() { } } + @Override + public long getUploadSizeLimit(long frameDuration) { + return (long) (this.capacity * UPLOAD_LIMIT_MARGIN); + } + private static final class CopyCommand { private final GlBuffer buffer; private final long readOffset; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/StagingBuffer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/StagingBuffer.java index d3087e8df5..0fe71170d9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/StagingBuffer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/StagingBuffer.java @@ -13,4 +13,6 @@ public interface StagingBuffer { void delete(CommandList commandList); void flip(); + + long getUploadSizeLimit(long frameDuration); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java index b632b6df45..f10a878e72 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java @@ -15,6 +15,7 @@ import net.caffeinemc.mods.sodium.client.gui.options.control.*; import net.caffeinemc.mods.sodium.client.gui.options.storage.MinecraftOptionsStorage; import net.caffeinemc.mods.sodium.client.gui.options.storage.SodiumOptionsStorage; +import net.caffeinemc.mods.sodium.client.render.chunk.DeferMode; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.QuadSplittingMode; import net.minecraft.client.*; import net.minecraft.network.chat.Component; @@ -281,12 +282,12 @@ public static OptionPage performance() { .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) .build() ) - .add(OptionImpl.createBuilder(boolean.class, sodiumOpts) - .setName(Component.translatable("sodium.options.always_defer_chunk_updates.name")) - .setTooltip(Component.translatable("sodium.options.always_defer_chunk_updates.tooltip")) - .setControl(TickBoxControl::new) + .add(OptionImpl.createBuilder(DeferMode.class, sodiumOpts) + .setName(Component.translatable("sodium.options.defer_chunk_updates.name")) + .setTooltip(Component.translatable("sodium.options.defer_chunk_updates.tooltip")) + .setControl(option -> new CyclingControl<>(option, DeferMode.class)) .setImpact(OptionImpact.HIGH) - .setBinding((opts, value) -> opts.performance.alwaysDeferChunkUpdates = value, opts -> opts.performance.alwaysDeferChunkUpdates) + .setBinding((opts, value) -> opts.performance.chunkBuildDeferMode = value, opts -> opts.performance.chunkBuildDeferMode) .setFlags(OptionFlag.REQUIRES_RENDERER_UPDATE) .build()) .build() diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java index 2c49f38832..37abe7b280 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java @@ -3,8 +3,8 @@ import com.google.gson.FieldNamingPolicy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import com.google.gson.annotations.SerializedName; import net.caffeinemc.mods.sodium.client.gui.options.TextProvider; +import net.caffeinemc.mods.sodium.client.render.chunk.DeferMode; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.QuadSplittingMode; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior; import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; @@ -41,8 +41,7 @@ public static SodiumGameOptions defaults() { public static class PerformanceSettings { public int chunkBuilderThreads = 0; - @SerializedName("always_defer_chunk_updates_v2") // this will reset the option in older configs - public boolean alwaysDeferChunkUpdates = true; + public DeferMode chunkBuildDeferMode = DeferMode.ALWAYS; public boolean animateOnlyVisibleTextures = true; public boolean useEntityCulling = true; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java index 8175fa71d8..6aad2f6d4e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java @@ -189,14 +189,14 @@ public void setupTerrain(Camera camera, float fogDistance = RenderSystem.getShaderFogEnd(); if (this.lastCameraPos == null) { - this.lastCameraPos = new Vector3d(pos); + this.lastCameraPos = pos; } if (this.lastProjectionMatrix == null) { this.lastProjectionMatrix = new Matrix4f(projectionMatrix); } boolean cameraLocationChanged = !pos.equals(this.lastCameraPos); boolean cameraAngleChanged = pitch != this.lastCameraPitch || yaw != this.lastCameraYaw || fogDistance != this.lastFogDistance; - boolean cameraProjectionChanged = !projectionMatrix.equals(this.lastProjectionMatrix); + boolean cameraProjectionChanged = !projectionMatrix.equals(this.lastProjectionMatrix, 0.0001f); this.lastProjectionMatrix = projectionMatrix; @@ -209,13 +209,13 @@ public void setupTerrain(Camera camera, this.lastFogDistance = fogDistance; - this.renderSectionManager.updateCameraState(pos, camera); + this.renderSectionManager.prepareFrame(pos); if (cameraLocationChanged) { profiler.popPush("translucent_triggering"); this.renderSectionManager.processGFNIMovement(new CameraMovement(this.lastCameraPos, pos)); - this.lastCameraPos = new Vector3d(pos); + this.lastCameraPos = pos; } int maxChunkUpdates = updateChunksImmediately ? this.renderDistance : 1; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkUpdateType.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkUpdateType.java deleted file mode 100644 index 2cd7859716..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkUpdateType.java +++ /dev/null @@ -1,43 +0,0 @@ -package net.caffeinemc.mods.sodium.client.render.chunk; - -import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkBuilder; - -public enum ChunkUpdateType { - SORT(Integer.MAX_VALUE, ChunkBuilder.LOW_EFFORT), - INITIAL_BUILD(128, ChunkBuilder.HIGH_EFFORT), - REBUILD(Integer.MAX_VALUE, ChunkBuilder.HIGH_EFFORT), - IMPORTANT_REBUILD(Integer.MAX_VALUE, ChunkBuilder.HIGH_EFFORT), - IMPORTANT_SORT(Integer.MAX_VALUE, ChunkBuilder.LOW_EFFORT); - - private final int maximumQueueSize; - private final int taskEffort; - - ChunkUpdateType(int maximumQueueSize, int taskEffort) { - this.maximumQueueSize = maximumQueueSize; - this.taskEffort = taskEffort; - } - - public static ChunkUpdateType getPromotionUpdateType(ChunkUpdateType prev, ChunkUpdateType next) { - if (prev == null || prev == SORT || prev == next) { - return next; - } - if (next == IMPORTANT_REBUILD - || (prev == IMPORTANT_SORT && next == REBUILD) - || (prev == REBUILD && next == IMPORTANT_SORT)) { - return IMPORTANT_REBUILD; - } - return null; - } - - public int getMaximumQueueSize() { - return this.maximumQueueSize; - } - - public boolean isImportant() { - return this == IMPORTANT_REBUILD || this == IMPORTANT_SORT; - } - - public int getTaskEffort() { - return this.taskEffort; - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkUpdateTypes.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkUpdateTypes.java new file mode 100644 index 0000000000..f5f8f47f9e --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkUpdateTypes.java @@ -0,0 +1,54 @@ +package net.caffeinemc.mods.sodium.client.render.chunk; + +/** + * Important: Whether the task is scheduled immediately after its creation. Otherwise, they're scheduled through + * asynchronous culling that collects non-important tasks. Defer mode: For important tasks, how fast they are going to + * be executed. One or zero frame deferral only allows one or zero frames to pass before the frame blocks on the task. + * Always deferral allows the task to be deferred indefinitely, but if it's important it will still be put to the front + * of the queue. + */ +public class ChunkUpdateTypes { + public static final int SORT = 0b001; + public static final int REBUILD = 0b010; + public static final int IMPORTANT = 0b100; + public static final int INITIAL_BUILD = 0b1000; + + public static int join(int from, int to) { + return from | to; + } + + public static boolean isSort(int type) { + return (type & SORT) != 0; + } + + public static boolean isRebuild(int type) { + return (type & REBUILD) != 0; + } + + public static boolean isImportant(int type) { + return (type & IMPORTANT) != 0; + } + + public static boolean isInitialBuild(int type) { + return (type & INITIAL_BUILD) != 0; + } + + public static boolean isRebuildWithSort(int type) { + return (isRebuild(type) || isInitialBuild(type)) && isSort(type); + } + + public static TaskQueueType getQueueType(int type, TaskQueueType importantRebuildQueueType) { + if (isInitialBuild(type)) { + return TaskQueueType.INITIAL_BUILD; + } + if (isImportant(type)) { + if (isRebuild(type)) { + return importantRebuildQueueType; + } else { // implies important sort task + return TaskQueueType.ZERO_FRAME_DEFER; + } + } else { + return TaskQueueType.ALWAYS_DEFER; + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DeferMode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DeferMode.java new file mode 100644 index 0000000000..608ddcf666 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DeferMode.java @@ -0,0 +1,27 @@ +package net.caffeinemc.mods.sodium.client.render.chunk; + +import net.caffeinemc.mods.sodium.client.gui.options.TextProvider; +import net.minecraft.network.chat.Component; + +public enum DeferMode implements TextProvider { + ALWAYS("sodium.options.defer_chunk_updates.always", TaskQueueType.ALWAYS_DEFER), + ONE_FRAME("sodium.options.defer_chunk_updates.one_frame", TaskQueueType.ONE_FRAME_DEFER), + ZERO_FRAMES("sodium.options.defer_chunk_updates.zero_frames", TaskQueueType.ZERO_FRAME_DEFER); + + private final Component name; + private final TaskQueueType importantRebuildQueueType; + + DeferMode(String name, TaskQueueType importantRebuildQueueType) { + this.name = Component.translatable(name); + this.importantRebuildQueueType = importantRebuildQueueType; + } + + @Override + public Component getLocalizedName() { + return this.name; + } + + public TaskQueueType getImportantRebuildQueueType() { + return this.importantRebuildQueueType; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java index 9aa933474c..4e1feb942c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java @@ -1,5 +1,6 @@ package net.caffeinemc.mods.sodium.client.render.chunk; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshResultSize; import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionInfo; import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.GraphDirection; import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.GraphDirectionSet; @@ -54,9 +55,10 @@ public class RenderSection { // Pending Update State @Nullable private CancellationToken taskCancellationToken = null; + private long lastMeshResultSize = MeshResultSize.NO_DATA; - @Nullable - private ChunkUpdateType pendingUpdateType; + private int pendingUpdateType; + private long pendingUpdateSince; private int lastUploadFrame = -1; private int lastSubmittedFrame = -1; @@ -179,6 +181,14 @@ private boolean clearRenderState() { return wasBuilt; } + public void setLastMeshResultSize(long size) { + this.lastMeshResultSize = size; + } + + public long getLastMeshResultSize() { + return this.lastMeshResultSize; + } + /** * Returns the chunk section position which this render refers to in the level. */ @@ -347,12 +357,21 @@ public void setTaskCancellationToken(@Nullable CancellationToken token) { this.taskCancellationToken = token; } - public @Nullable ChunkUpdateType getPendingUpdate() { + public int getPendingUpdate() { return this.pendingUpdateType; } - public void setPendingUpdate(@Nullable ChunkUpdateType type) { + public long getPendingUpdateSince() { + return this.pendingUpdateSince; + } + + public void setPendingUpdate(int type, long now) { this.pendingUpdateType = type; + this.pendingUpdateSince = now; + } + + public void clearPendingUpdate() { + this.pendingUpdateType = 0; } public void prepareTrigger(boolean isDirectTrigger) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index bb9207197e..f8d05d0d41 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -4,10 +4,7 @@ import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; import it.unimi.dsi.fastutil.longs.Long2ReferenceMaps; import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap; -import it.unimi.dsi.fastutil.objects.Reference2ReferenceLinkedOpenHashMap; -import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -import it.unimi.dsi.fastutil.objects.ReferenceSet; -import it.unimi.dsi.fastutil.objects.ReferenceSets; +import it.unimi.dsi.fastutil.objects.*; import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.caffeinemc.mods.sodium.client.SodiumClientMod; import net.caffeinemc.mods.sodium.client.gl.device.CommandList; @@ -15,6 +12,9 @@ import net.caffeinemc.mods.sodium.client.render.chunk.compile.BuilderTaskOutput; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildOutput; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkSortOutput; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.JobDurationEstimator; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshResultSize; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshTaskSizeEstimator; import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkBuilder; import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkJobCollector; import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkJobResult; @@ -30,7 +30,6 @@ import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegionManager; import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior.DeferMode; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior.PriorityMode; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.DynamicTopoData; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.NoData; @@ -41,6 +40,7 @@ import net.caffeinemc.mods.sodium.client.render.util.RenderAsserts; import net.caffeinemc.mods.sodium.client.render.viewport.CameraTransform; import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; +import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; import net.caffeinemc.mods.sodium.client.util.MathUtil; import net.caffeinemc.mods.sodium.client.world.LevelSlice; import net.caffeinemc.mods.sodium.client.world.cloned.ChunkRenderContext; @@ -63,6 +63,9 @@ import java.util.concurrent.ConcurrentLinkedDeque; public class RenderSectionManager { + private static final float NEARBY_REBUILD_DISTANCE = Mth.square(16.0f); + private static final float NEARBY_SORT_DISTANCE = Mth.square(25.0f); + private final ChunkBuilder builder; private final RenderRegionManager regions; @@ -71,6 +74,12 @@ public class RenderSectionManager { private final Long2ReferenceMap sectionByPosition = new Long2ReferenceOpenHashMap<>(); private final ConcurrentLinkedDeque> buildResults = new ConcurrentLinkedDeque<>(); + private final JobDurationEstimator jobDurationEstimator = new JobDurationEstimator(); + private final MeshTaskSizeEstimator meshTaskSizeEstimator = new MeshTaskSizeEstimator(); + private ChunkJobCollector lastBlockingCollector; + private int thisFrameBlockingTasks; + private int nextFrameBlockingTasks; + private int deferredTasks; private final ChunkRenderer chunkRenderer; @@ -84,19 +93,21 @@ public class RenderSectionManager { private final SortTriggering sortTriggering; - private ChunkJobCollector lastBlockingCollector; - @NotNull private SortedRenderLists renderLists; @NotNull - private Map> taskLists; + private Map> taskLists; - private int lastUpdatedFrame; + private int frame; + private long lastFrameDuration = -1; + private long averageFrameDuration = -1; + private long lastFrameAtTime = System.nanoTime(); + private static final float FRAME_DURATION_UPDATE_RATIO = 0.05f; private boolean needsGraphUpdate; + private int lastUpdatedFrame; - private @Nullable BlockPos cameraBlockPos; private @Nullable Vector3dc cameraPosition; public RenderSectionManager(ClientLevel level, int renderDistance, CommandList commandList) { @@ -116,15 +127,26 @@ public RenderSectionManager(ClientLevel level, int renderDistance, CommandList c this.renderLists = SortedRenderLists.empty(); this.occlusionCuller = new OcclusionCuller(Long2ReferenceMaps.unmodifiable(this.sectionByPosition), this.level); - this.taskLists = new EnumMap<>(ChunkUpdateType.class); + this.taskLists = new EnumMap<>(TaskQueueType.class); - for (var type : ChunkUpdateType.values()) { + for (var type : TaskQueueType.values()) { this.taskLists.put(type, new ArrayDeque<>()); } } - public void updateCameraState(Vector3dc cameraPosition, Camera camera) { - this.cameraBlockPos = camera.getBlockPosition(); + public void prepareFrame(Vector3dc cameraPosition) { + var now = System.nanoTime(); + this.lastFrameDuration = now - this.lastFrameAtTime; + this.lastFrameAtTime = now; + if (this.averageFrameDuration == -1) { + this.averageFrameDuration = this.lastFrameDuration; + } else { + this.averageFrameDuration = MathUtil.exponentialMovingAverage(this.averageFrameDuration, this.lastFrameDuration, FRAME_DURATION_UPDATE_RATIO); + } + this.averageFrameDuration = Mth.clamp(this.averageFrameDuration, 1_000_100, 100_000_000); + + this.frame += 1; + this.cameraPosition = cameraPosition; } @@ -142,7 +164,7 @@ private void createTerrainRenderList(Camera camera, Viewport viewport, int frame final var searchDistance = this.getSearchDistance(); final var useOcclusionCulling = this.shouldUseOcclusionCulling(camera, spectator); - var visitor = new VisibleChunkCollector(frame); + var visitor = new VisibleChunkCollector(frame, SodiumClientMod.options().performance.chunkBuildDeferMode.getImportantRebuildQueueType()); this.occlusionCuller.findVisible(visitor, viewport, searchDistance, useOcclusionCulling, frame); @@ -167,8 +189,7 @@ private boolean shouldUseOcclusionCulling(Camera camera, boolean spectator) { BlockPos origin = camera.getBlockPosition(); if (spectator && this.level.getBlockState(origin) - .isSolidRender(this.level, origin)) - { + .isSolidRender(this.level, origin)) { useOcclusionCulling = false; } else { useOcclusionCulling = Minecraft.getInstance().smartCull; @@ -204,7 +225,7 @@ public void onSectionAdded(int x, int y, int z) { if (section.hasOnlyAir()) { this.updateSectionInfo(renderSection, BuiltSectionInfo.EMPTY); } else { - renderSection.setPendingUpdate(ChunkUpdateType.INITIAL_BUILD); + renderSection.setPendingUpdate(ChunkUpdateTypes.INITIAL_BUILD, this.lastFrameAtTime); } this.connectNeighborNodes(renderSection); @@ -321,6 +342,10 @@ private boolean processChunkBuildResults(ArrayList results) { if (result instanceof ChunkBuildOutput chunkBuildOutput) { touchedSectionInfo |= this.updateSectionInfo(result.render, chunkBuildOutput.info); + var resultSize = chunkBuildOutput.getResultSize(); + result.render.setLastMeshResultSize(resultSize); + this.meshTaskSizeEstimator.addData(MeshResultSize.forSection(result.render, resultSize)); + if (chunkBuildOutput.translucentData != null) { this.sortTriggering.integrateTranslucentData(oldData, chunkBuildOutput.translucentData, this.cameraPosition, this::scheduleSort); @@ -344,6 +369,8 @@ private boolean processChunkBuildResults(ArrayList results) { result.render.setLastUploadFrame(result.submitTime); } + this.meshTaskSizeEstimator.updateModels(); + return touchedSectionInfo; } @@ -379,12 +406,19 @@ private static List filterChunkBuildResults(ArrayList collectChunkBuildResults() { ArrayList results = new ArrayList<>(); + ChunkJobResult result; while ((result = this.buildResults.poll()) != null) { results.add(result.unwrap()); + var jobEffort = result.getJobEffort(); + if (jobEffort != null) { + this.jobDurationEstimator.addData(jobEffort); + } } + this.jobDurationEstimator.updateModels(); + return results; } @@ -394,6 +428,10 @@ public void cleanupAndFlip() { } public void updateChunks(boolean updateImmediately) { + this.thisFrameBlockingTasks = 0; + this.nextFrameBlockingTasks = 0; + this.deferredTasks = 0; + var thisFrameBlockingCollector = this.lastBlockingCollector; this.lastBlockingCollector = null; if (thisFrameBlockingCollector == null) { @@ -403,24 +441,29 @@ public void updateChunks(boolean updateImmediately) { if (updateImmediately) { // for a perfect frame where everything is finished use the last frame's blocking collector // and add all tasks to it so that they're waited on - this.submitSectionTasks(thisFrameBlockingCollector, thisFrameBlockingCollector, thisFrameBlockingCollector); + this.submitSectionTasks(thisFrameBlockingCollector, thisFrameBlockingCollector, thisFrameBlockingCollector, Long.MAX_VALUE); + this.thisFrameBlockingTasks = thisFrameBlockingCollector.getSubmittedTaskCount(); thisFrameBlockingCollector.awaitCompletion(this.builder); } else { + var remainingDuration = this.builder.getTotalRemainingDuration(this.averageFrameDuration); + var remainingUploadSize = this.regions.getStagingBuffer().getUploadSizeLimit(this.averageFrameDuration); + var nextFrameBlockingCollector = new ChunkJobCollector(this.buildResults::add); - var deferredCollector = new ChunkJobCollector( - this.builder.getHighEffortSchedulingBudget(), - this.builder.getLowEffortSchedulingBudget(), - this.buildResults::add); + var deferredCollector = new ChunkJobCollector(remainingDuration, this.buildResults::add); // if zero frame delay is allowed, submit important sorts with the current frame blocking collector. // otherwise submit with the collector that the next frame is blocking on. if (SodiumClientMod.options().debug.getSortBehavior().getDeferMode() == DeferMode.ZERO_FRAMES) { - this.submitSectionTasks(thisFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector); + this.submitSectionTasks(thisFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector, remainingUploadSize); } else { - this.submitSectionTasks(nextFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector); + this.submitSectionTasks(nextFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector, remainingUploadSize); } + this.thisFrameBlockingTasks = thisFrameBlockingCollector.getSubmittedTaskCount(); + this.nextFrameBlockingTasks = nextFrameBlockingCollector.getSubmittedTaskCount(); + this.deferredTasks = deferredCollector.getSubmittedTaskCount(); + // wait on this frame's blocking collector which contains the important tasks from this frame // and semi-important tasks from the last frame thisFrameBlockingCollector.awaitCompletion(this.builder); @@ -431,79 +474,83 @@ public void updateChunks(boolean updateImmediately) { } private void submitSectionTasks( - ChunkJobCollector importantCollector, - ChunkJobCollector semiImportantCollector, - ChunkJobCollector deferredCollector) { - this.submitSectionTasks(importantCollector, ChunkUpdateType.IMPORTANT_SORT, true); - this.submitSectionTasks(semiImportantCollector, ChunkUpdateType.IMPORTANT_REBUILD, true); - - // since the sort tasks are run last, the effort category can be ignored and - // simply fills up the remaining budget. Splitting effort categories is still - // important to prevent high effort tasks from using up the entire budget if it - // happens to divide evenly. - this.submitSectionTasks(deferredCollector, ChunkUpdateType.REBUILD, false); - this.submitSectionTasks(deferredCollector, ChunkUpdateType.INITIAL_BUILD, false); - this.submitSectionTasks(deferredCollector, ChunkUpdateType.SORT, true); + ChunkJobCollector importantCollector, ChunkJobCollector semiImportantCollector, ChunkJobCollector deferredCollector, long remainingUploadSize) { + remainingUploadSize = submitSectionTasks(importantCollector, remainingUploadSize, TaskQueueType.ZERO_FRAME_DEFER); + remainingUploadSize = submitSectionTasks(semiImportantCollector, remainingUploadSize, TaskQueueType.ONE_FRAME_DEFER); + remainingUploadSize = submitSectionTasks(deferredCollector, remainingUploadSize, TaskQueueType.ALWAYS_DEFER); + submitSectionTasks(deferredCollector, remainingUploadSize, TaskQueueType.INITIAL_BUILD); } - private void submitSectionTasks(ChunkJobCollector collector, ChunkUpdateType type, boolean ignoreEffortCategory) { - var queue = this.taskLists.get(type); + private long submitSectionTasks(ChunkJobCollector collector, long remainingUploadSize, TaskQueueType queueType) { + var taskList = this.taskLists.get(queueType); - while (!queue.isEmpty() && collector.hasBudgetFor(type.getTaskEffort(), ignoreEffortCategory)) { - RenderSection section = queue.remove(); + while (!taskList.isEmpty() && (remainingUploadSize > 0 || queueType.allowsUnlimitedUploadSize())) { + RenderSection section = taskList.poll(); - if (section.isDisposed()) { - continue; + if (section == null) { + break; } - // stop if the section is in this list but doesn't have this update type + // don't schedule tasks for sections that don't need it anymore, + // since the pending update it cleared when a task is started, this includes + // sections for which there's a currently running task. var pendingUpdate = section.getPendingUpdate(); - if (pendingUpdate != null && pendingUpdate != type) { - continue; + if (pendingUpdate != 0) { + remainingUploadSize -= submitSectionTask(collector, section, pendingUpdate); } + } - int frame = this.lastUpdatedFrame; - ChunkBuilderTask task; - if (type == ChunkUpdateType.SORT || type == ChunkUpdateType.IMPORTANT_SORT) { - task = this.createSortTask(section, frame); + return remainingUploadSize; + } - if (task == null) { - // when a sort task is null it means the render section has no dynamic data and - // doesn't need to be sorted. Nothing needs to be done. - continue; - } - } else { - task = this.createRebuildTask(section, frame); - - if (task == null) { - // if the section is empty or doesn't exist submit this null-task to set the - // built flag on the render section. - // It's important to use a NoData instead of null translucency data here in - // order for it to clear the old data from the translucency sorting system. - // This doesn't apply to sorting tasks as that would result in the section being - // marked as empty just because it was scheduled to be sorted and its dynamic - // data has since been removed. In that case simply nothing is done as the - // rebuild that must have happened in the meantime includes new non-dynamic - // index data. - var result = ChunkJobResult.successfully(new ChunkBuildOutput( - section, frame, NoData.forEmptySection(section.getPosition()), - BuiltSectionInfo.EMPTY, Collections.emptyMap())); - this.buildResults.add(result); - - section.setTaskCancellationToken(null); - } - } + private long submitSectionTask(ChunkJobCollector collector, @NotNull RenderSection section, int type) { + if (section.isDisposed()) { + return 0; + } + + ChunkBuilderTask task; + if (ChunkUpdateTypes.isInitialBuild(type) || ChunkUpdateTypes.isRebuild(type)) { + task = this.createRebuildTask(section, this.frame); - if (task != null) { - var job = this.builder.scheduleTask(task, type.isImportant(), collector::onJobFinished); - collector.addSubmittedJob(job); + if (task == null) { + // if the section is empty or doesn't exist submit this null-task to set the + // built flag on the render section. + // It's important to use a NoData instead of null translucency data here in + // order for it to clear the old data from the translucency sorting system. + // This doesn't apply to sorting tasks as that would result in the section being + // marked as empty just because it was scheduled to be sorted and its dynamic + // data has since been removed. In that case simply nothing is done as the + // rebuild that must have happened in the meantime includes new non-dynamic + // index data. + var result = ChunkJobResult.successfully(new ChunkBuildOutput( + section, this.frame, NoData.forEmptySection(section.getPosition()), + BuiltSectionInfo.EMPTY, Collections.emptyMap())); + this.buildResults.add(result); + + section.setTaskCancellationToken(null); + } + } else { // implies it's a type of sort task + task = this.createSortTask(section, this.frame); - section.setTaskCancellationToken(job); + if (task == null) { + // when a sort task is null it means the render section has no dynamic data and + // doesn't need to be sorted. Nothing needs to be done. + return 0; } + } + + var estimatedTaskSize = 0L; + if (task != null) { + var job = this.builder.scheduleTask(task, ChunkUpdateTypes.isImportant(type), collector::onJobFinished); + collector.addSubmittedJob(job); + estimatedTaskSize = job.getEstimatedSize(); - section.setLastSubmittedFrame(frame); - section.setPendingUpdate(null); + section.setTaskCancellationToken(job); } + + section.setLastSubmittedFrame(this.frame); + section.clearPendingUpdate(); + return estimatedTaskSize; } public @Nullable ChunkBuilderMeshingTask createRebuildTask(RenderSection render, int frame) { @@ -513,11 +560,17 @@ private void submitSectionTasks(ChunkJobCollector collector, ChunkUpdateType typ return null; } - return new ChunkBuilderMeshingTask(render, frame, this.cameraPosition, context); + var task = new ChunkBuilderMeshingTask(render, frame, this.cameraPosition, context, ChunkUpdateTypes.isRebuildWithSort(render.getPendingUpdate())); + task.calculateEstimations(this.jobDurationEstimator, this.meshTaskSizeEstimator); + return task; } public ChunkBuilderSortingTask createSortTask(RenderSection render, int frame) { - return ChunkBuilderSortingTask.createTask(render, frame, this.cameraPosition); + var task = ChunkBuilderSortingTask.createTask(render, frame, this.cameraPosition); + if (task != null) { + task.calculateEstimations(this.jobDurationEstimator, this.meshTaskSizeEstimator); + } + return task; } public void processGFNIMovement(CameraMovement movement) { @@ -572,25 +625,40 @@ public int getVisibleChunkCount() { return sections; } + private boolean upgradePendingUpdate(RenderSection section, int updateType) { + if (updateType == 0) { + return false; + } + + var current = section.getPendingUpdate(); + var joined = ChunkUpdateTypes.join(current, updateType); + + if (joined == current) { + return false; + } + + section.setPendingUpdate(joined, this.lastFrameAtTime); + + return true; + } + public void scheduleSort(long sectionPos, boolean isDirectTrigger) { RenderSection section = this.sectionByPosition.get(sectionPos); if (section != null) { - var pendingUpdate = ChunkUpdateType.SORT; + int pendingUpdate = ChunkUpdateTypes.SORT; var priorityMode = SodiumClientMod.options().debug.getSortBehavior().getPriorityMode(); - if (priorityMode == PriorityMode.ALL - || priorityMode == PriorityMode.NEARBY && this.shouldPrioritizeTask(section, NEARBY_SORT_DISTANCE)) { - pendingUpdate = ChunkUpdateType.IMPORTANT_SORT; + if (priorityMode == PriorityMode.NEARBY && this.shouldPrioritizeTask(section, NEARBY_SORT_DISTANCE) || priorityMode == PriorityMode.ALL) { + pendingUpdate = ChunkUpdateTypes.join(pendingUpdate, ChunkUpdateTypes.IMPORTANT); } - pendingUpdate = ChunkUpdateType.getPromotionUpdateType(section.getPendingUpdate(), pendingUpdate); - if (pendingUpdate != null) { - section.setPendingUpdate(pendingUpdate); + + if (this.upgradePendingUpdate(section, pendingUpdate)) { section.prepareTrigger(isDirectTrigger); } } } - public void scheduleRebuild(int x, int y, int z, boolean important) { + public void scheduleRebuild(int x, int y, int z, boolean playerChanged) { RenderAsserts.validateCurrentThread(); this.sectionCache.invalidate(x, y, z); @@ -598,33 +666,24 @@ public void scheduleRebuild(int x, int y, int z, boolean important) { RenderSection section = this.sectionByPosition.get(SectionPos.asLong(x, y, z)); if (section != null && section.isBuilt()) { - ChunkUpdateType pendingUpdate; + int pendingUpdate; - if (allowImportantRebuilds() && (important || this.shouldPrioritizeTask(section, NEARBY_REBUILD_DISTANCE))) { - pendingUpdate = ChunkUpdateType.IMPORTANT_REBUILD; + if (playerChanged && this.shouldPrioritizeTask(section, NEARBY_REBUILD_DISTANCE)) { + pendingUpdate = ChunkUpdateTypes.join(ChunkUpdateTypes.REBUILD, ChunkUpdateTypes.IMPORTANT); } else { - pendingUpdate = ChunkUpdateType.REBUILD; + pendingUpdate = ChunkUpdateTypes.REBUILD; } - pendingUpdate = ChunkUpdateType.getPromotionUpdateType(section.getPendingUpdate(), pendingUpdate); - if (pendingUpdate != null) { - section.setPendingUpdate(pendingUpdate); - - // force update to schedule rebuild task on this section - this.needsGraphUpdate = true; - } + this.upgradePendingUpdate(section, pendingUpdate); } } - private static final float NEARBY_REBUILD_DISTANCE = Mth.square(16.0f); - private static final float NEARBY_SORT_DISTANCE = Mth.square(25.0f); - private boolean shouldPrioritizeTask(RenderSection section, float distance) { - return this.cameraBlockPos != null && section.getSquaredDistance(this.cameraBlockPos) < distance; - } - - private static boolean allowImportantRebuilds() { - return !SodiumClientMod.options().performance.alwaysDeferChunkUpdates; + return this.cameraPosition != null && section.getSquaredDistance( + (float) this.cameraPosition.x(), + (float) this.cameraPosition.y(), + (float) this.cameraPosition.z() + ) < distance; } private float getEffectiveRenderDistance() { @@ -706,17 +765,26 @@ public Collection getDebugStrings() { MathUtil.toMib(indexDeviceUsed), MathUtil.toMib(indexDeviceAllocated), count)); list.add(String.format("Transfer Queue: %s", this.regions.getStagingBuffer().toString())); - list.add(String.format("Chunk Builder: Permits=%02d (E %03d) | Busy=%02d | Total=%02d", - this.builder.getScheduledJobCount(), this.builder.getScheduledEffort(), this.builder.getBusyThreadCount(), this.builder.getTotalThreadCount()) + list.add(String.format("Chunk Builder: Schd=%02d | Busy=%02d (%04d%%) | Total=%02d", + this.builder.getScheduledJobCount(), this.builder.getBusyThreadCount(), (int) (this.builder.getBusyFraction(this.lastFrameDuration) * 100), this.builder.getTotalThreadCount()) ); - list.add(String.format("Chunk Queues: U=%02d (P0=%03d | P1=%03d | P2=%03d)", - this.buildResults.size(), - this.taskLists.get(ChunkUpdateType.IMPORTANT_REBUILD).size() + this.taskLists.get(ChunkUpdateType.IMPORTANT_SORT).size(), - this.taskLists.get(ChunkUpdateType.REBUILD).size() + this.taskLists.get(ChunkUpdateType.SORT).size(), - this.taskLists.get(ChunkUpdateType.INITIAL_BUILD).size()) + list.add(String.format("Tasks: N0=%03d | N1=%03d | Def=%03d, Recv=%03d", + this.thisFrameBlockingTasks, this.nextFrameBlockingTasks, this.deferredTasks, this.buildResults.size()) ); + if (PlatformRuntimeInformation.getInstance().isDevelopmentEnvironment()) { + var meshTaskParameters = this.jobDurationEstimator.toString(ChunkBuilderMeshingTask.class); + var sortTaskParameters = this.jobDurationEstimator.toString(ChunkBuilderSortingTask.class); + list.add(String.format("Duration: Mesh %s, Sort %s", meshTaskParameters, sortTaskParameters)); + + var sizeEstimates = new ReferenceArrayList(); + for (var type : MeshResultSize.SectionCategory.values()) { + sizeEstimates.add(String.format("%s=%s", type, this.meshTaskSizeEstimator.toString(type))); + } + list.add(String.format("Size: %s", String.join(", ", sizeEstimates))); + } + this.sortTriggering.addDebugStrings(list); return list; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/TaskQueueType.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/TaskQueueType.java new file mode 100644 index 0000000000..0dddde8fa1 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/TaskQueueType.java @@ -0,0 +1,16 @@ +package net.caffeinemc.mods.sodium.client.render.chunk; + +public enum TaskQueueType { + ZERO_FRAME_DEFER, + ONE_FRAME_DEFER, + ALWAYS_DEFER, + INITIAL_BUILD; + + public boolean allowsUnlimitedUploadSize() { + return this == ZERO_FRAME_DEFER; + } + + public int queueSizeLimit() { + return this == INITIAL_BUILD ? 128 : Integer.MAX_VALUE; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/BuilderTaskOutput.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/BuilderTaskOutput.java index 102f93727a..89fcc25d5f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/BuilderTaskOutput.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/BuilderTaskOutput.java @@ -1,10 +1,12 @@ package net.caffeinemc.mods.sodium.client.render.chunk.compile; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshResultSize; public abstract class BuilderTaskOutput { public final RenderSection render; public final int submitTime; + private long resultSize = MeshResultSize.NO_DATA; public BuilderTaskOutput(RenderSection render, int buildTime) { this.render = render; @@ -13,4 +15,13 @@ public BuilderTaskOutput(RenderSection render, int buildTime) { public void destroy() { } + + protected abstract long calculateResultSize(); + + public long getResultSize() { + if (this.resultSize == MeshResultSize.NO_DATA) { + this.resultSize = this.calculateResultSize(); + } + return this.resultSize; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildOutput.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildOutput.java index 88102d1d16..20f075681d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildOutput.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkBuildOutput.java @@ -40,4 +40,17 @@ public void destroy() { data.getVertexData().free(); } } + + private long getMeshSize() { + long size = 0; + for (var data : this.meshes.values()) { + size += data.getVertexData().getLength(); + } + return size; + } + + @Override + public long calculateResultSize() { + return super.calculateResultSize() + this.getMeshSize(); + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java index 1e17585537..8c9607f0d4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/ChunkSortOutput.java @@ -44,4 +44,16 @@ public void destroy() { this.sorter.destroy(); } } + + @Override + protected long calculateResultSize() { + if (this.sorter == null) { + return 0; + } + var indexBuffer = this.sorter.getIndexBuffer(); + if (indexBuffer == null) { + return 0; + } + return indexBuffer.getLength(); + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Average1DEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Average1DEstimator.java new file mode 100644 index 0000000000..244f9a5fe4 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Average1DEstimator.java @@ -0,0 +1,89 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation; + +import net.caffeinemc.mods.sodium.client.util.MathUtil; + +import java.util.Locale; + +public abstract class Average1DEstimator extends Estimator, Average1DEstimator.ValueBatch, Void, Long, Average1DEstimator.Average> { + private final float newDataRatio; + private final long initialEstimate; + + public Average1DEstimator(float newDataRatio, long initialEstimate) { + this.newDataRatio = newDataRatio; + this.initialEstimate = initialEstimate; + } + + public interface Value extends DataPoint { + long value(); + } + + protected static class ValueBatch implements Estimator.DataBatch> { + private long valueSum; + private long count; + + @Override + public void addDataPoint(Value input) { + this.valueSum += input.value(); + this.count++; + } + + @Override + public void reset() { + this.valueSum = 0; + this.count = 0; + } + + public float getAverage() { + return ((float) this.valueSum) / this.count; + } + } + + @Override + protected ValueBatch createNewDataBatch() { + return new ValueBatch<>(); + } + + protected static class Average implements Estimator.Model, Average> { + private final float newDataRatio; + private boolean hasRealData = false; + private float average; + + public Average(float newDataRatio, float initialValue) { + this.average = initialValue; + this.newDataRatio = newDataRatio; + } + + @Override + public Average update(ValueBatch batch) { + if (batch.count > 0) { + if (this.hasRealData) { + this.average = MathUtil.exponentialMovingAverage(this.average, batch.getAverage(), this.newDataRatio); + } else { + this.average = batch.getAverage(); + this.hasRealData = true; + } + } + + return this; + } + + @Override + public Long predict(Void input) { + return (long) this.average; + } + + @Override + public String toString() { + return String.format(Locale.US, "%.0f", this.average); + } + } + + @Override + protected Average createNewModel() { + return new Average<>(this.newDataRatio, this.initialEstimate); + } + + public Long predict(C category) { + return super.predict(category, null); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Estimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Estimator.java new file mode 100644 index 0000000000..04c275c09d --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Estimator.java @@ -0,0 +1,91 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation; + +import java.util.Map; + +/** + * This generic model learning class that can be used to estimate values based on a set of data points. It performs batch-wise model updates. The actual data aggregation and model updates are delegated to the implementing classes. The estimator stores multiple models in a map, one for each category. + * + * @param The type of the category key + * @param A data point contains a category and one piece of data + * @param A data batch contains multiple data points + * @param The input to the model + * @param The output of the model + * @param The model that is used to predict values + */ +public abstract class Estimator< + TCategory, + TPoint extends Estimator.DataPoint, + TBatch extends Estimator.DataBatch, + TInput, + TOutput, + TModel extends Estimator.Model> { + protected final Map models = createMap(); + protected final Map batches = createMap(); + + protected interface DataBatch { + void addDataPoint(TBatchPoint input); + + void reset(); + } + + protected interface DataPoint { + TPointCategory category(); + } + + protected interface Model> { + TModelSelf update(TModelBatch batch); + + TModelOutput predict(TModelInput input); + } + + protected abstract TBatch createNewDataBatch(); + + protected abstract TModel createNewModel(); + + protected abstract Map createMap(); + + public void addData(TPoint data) { + var category = data.category(); + var batch = this.batches.get(category); + if (batch == null) { + batch = this.createNewDataBatch(); + this.batches.put(category, batch); + } + batch.addDataPoint(data); + } + + private TModel ensureModel(TCategory category) { + var model = this.models.get(category); + if (model == null) { + model = this.createNewModel(); + this.models.put(category, model); + } + return model; + } + + public void updateModels() { + this.batches.forEach((category, aggregator) -> { + var oldModel = this.ensureModel(category); + + // update the model and store it back if it returned a new model + var newModel = oldModel.update(aggregator); + if (newModel != oldModel) { + this.models.put(category, newModel); + } + + aggregator.reset(); + }); + } + + public TOutput predict(TCategory category, TInput input) { + return this.ensureModel(category).predict(input); + } + + public String toString(TCategory category) { + var model = this.models.get(category); + if (model == null) { + return "-"; + } + return model.toString(); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobDurationEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobDurationEstimator.java new file mode 100644 index 0000000000..0f83a902c9 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobDurationEstimator.java @@ -0,0 +1,24 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation; + +import it.unimi.dsi.fastutil.objects.Reference2ReferenceArrayMap; + +import java.util.Map; + +public class JobDurationEstimator extends Linear2DEstimator> { + public static final int INITIAL_SAMPLE_TARGET = 100; + public static final float NEW_DATA_RATIO = 0.05f; + private static final long INITIAL_JOB_DURATION_ESTIMATE = 5_000_000L; // 5ms + + public JobDurationEstimator() { + super(NEW_DATA_RATIO, INITIAL_SAMPLE_TARGET, INITIAL_JOB_DURATION_ESTIMATE); + } + + public long estimateJobDuration(Class jobType, long effort) { + return this.predict(jobType, effort); + } + + @Override + protected Map, T> createMap() { + return new Reference2ReferenceArrayMap<>(); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobEffort.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobEffort.java new file mode 100644 index 0000000000..2a35dda343 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobEffort.java @@ -0,0 +1,17 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation; + +public record JobEffort(Class category, long duration, long effort) implements Linear2DEstimator.DataPair> { + public static JobEffort untilNowWithEffort(Class effortType, long start, long effort) { + return new JobEffort(effortType,System.nanoTime() - start, effort); + } + + @Override + public long x() { + return this.effort; + } + + @Override + public long y() { + return this.duration; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Linear2DEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Linear2DEstimator.java new file mode 100644 index 0000000000..159d08f067 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Linear2DEstimator.java @@ -0,0 +1,143 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +import java.util.Locale; + +public abstract class Linear2DEstimator extends Estimator, Linear2DEstimator.LinearRegressionBatch, Long, Long, Linear2DEstimator.LinearFunction> { + private final float newDataRatio; + private final int initialSampleTarget; + private final long initialOutput; + + public Linear2DEstimator(float newDataRatio, int initialSampleTarget, long initialOutput) { + this.newDataRatio = newDataRatio; + this.initialSampleTarget = initialSampleTarget; + this.initialOutput = initialOutput; + } + + public interface DataPair extends DataPoint { + long x(); + + long y(); + } + + protected static class LinearRegressionBatch extends ObjectArrayList> implements Estimator.DataBatch> { + @Override + public void addDataPoint(DataPair input) { + this.add(input); + } + + @Override + public void reset() { + this.clear(); + } + } + + @Override + protected LinearRegressionBatch createNewDataBatch() { + return new LinearRegressionBatch<>(); + } + + protected static class LinearFunction implements Model, LinearFunction> { + // the maximum fraction of the total weight that new data can have + private final float newDataRatioInv; + // how many samples we want to have at least before we start diminishing the new data's weight + private final int initialSampleTarget; + private final long initialOutput; + + private float yIntercept; + private float slope; + + private int gatheredSamples = 0; + private float xMeanOld = 0; + private float yMeanOld = 0; + private float covarianceOld = 0; + private float varianceOld = 0; + + public LinearFunction(float newDataRatio, int initialSampleTarget, long initialOutput) { + this.newDataRatioInv = 1.0f / newDataRatio; + this.initialSampleTarget = initialSampleTarget; + this.initialOutput = initialOutput; + } + + @Override + public LinearFunction update(LinearRegressionBatch batch) { + if (batch.isEmpty()) { + return this; + } + + // condition the weight to gather at least the initial sample target, and then weight the new data with a ratio + var newDataSize = batch.size(); + var totalSamples = this.gatheredSamples + newDataSize; + float oldDataWeight; + float totalWeight; + if (totalSamples <= this.initialSampleTarget) { + totalWeight = totalSamples; + oldDataWeight = this.gatheredSamples; + this.gatheredSamples = totalSamples; + } else { + oldDataWeight = newDataSize * this.newDataRatioInv - newDataSize; + totalWeight = oldDataWeight + newDataSize; + } + + var totalWeightInv = 1.0f / totalWeight; + + // calculate the weighted mean along both axes + long xSum = 0; + long ySum = 0; + for (var data : batch) { + xSum += data.x(); + ySum += data.y(); + } + var xMean = (this.xMeanOld * oldDataWeight + xSum) * totalWeightInv; + var yMean = (this.yMeanOld * oldDataWeight + ySum) * totalWeightInv; + + // the covariance and variance are calculated from the differences to the mean + var covarianceSum = 0.0f; + var varianceSum = 0.0f; + for (var data : batch) { + var xDelta = data.x() - xMean; + var yDelta = data.y() - yMean; + covarianceSum += xDelta * yDelta; + varianceSum += xDelta * xDelta; + } + + if (varianceSum == 0) { + return this; + } + + covarianceSum += this.covarianceOld * oldDataWeight; + varianceSum += this.varianceOld * oldDataWeight; + + // negative slopes are clamped to produce a flat line if necessary + this.slope = Math.max(0, covarianceSum / varianceSum); + this.yIntercept = yMean - this.slope * xMean; + + this.xMeanOld = xMean; + this.yMeanOld = yMean; + this.covarianceOld = covarianceSum * totalWeightInv; + this.varianceOld = varianceSum * totalWeightInv; + + return this; + } + + @Override + public Long predict(Long input) { + if (this.gatheredSamples == 0) { + return this.initialOutput; + } + + return (long) (this.yIntercept + this.slope * input); + } + + @Override + public String toString() { + return String.format(Locale.US, "s=%.2f,y=%.0f", this.slope, this.yIntercept); + } + } + + @Override + protected LinearFunction createNewModel() { + return new LinearFunction<>(this.newDataRatio, this.initialSampleTarget, this.initialOutput); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshResultSize.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshResultSize.java new file mode 100644 index 0000000000..e4d976bac0 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshResultSize.java @@ -0,0 +1,44 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation; + +import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; + +public record MeshResultSize(SectionCategory category, long resultSize) implements Average1DEstimator.Value { + public static long NO_DATA = -1; + + public enum SectionCategory { + LOW, + UNDERGROUND, + WATER_LEVEL, + SURFACE, + HIGH; + + public static SectionCategory forSection(RenderSection section) { + var sectionY = section.getChunkY(); + if (sectionY < 0) { + return LOW; + } else if (sectionY < 3) { + return UNDERGROUND; + } else if (sectionY == 3) { + return WATER_LEVEL; + } else if (sectionY < 7) { + return SURFACE; + } else { + return HIGH; + } + } + } + + public static MeshResultSize forSection(RenderSection section, long resultSize) { + return new MeshResultSize(SectionCategory.forSection(section), resultSize); + } + + @Override + public SectionCategory category() { + return this.category; + } + + @Override + public long value() { + return this.resultSize; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshTaskSizeEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshTaskSizeEstimator.java new file mode 100644 index 0000000000..67809d61d8 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshTaskSizeEstimator.java @@ -0,0 +1,28 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation; + +import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; +import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; + +import java.util.EnumMap; +import java.util.Map; + +public class MeshTaskSizeEstimator extends Average1DEstimator { + public static final float NEW_DATA_RATIO = 0.02f; + + public MeshTaskSizeEstimator() { + super(NEW_DATA_RATIO, RenderRegion.SECTION_BUFFER_ESTIMATE); + } + + public long estimateSize(RenderSection section) { + var lastResultSize = section.getLastMeshResultSize(); + if (lastResultSize != MeshResultSize.NO_DATA) { + return lastResultSize; + } + return this.predict(MeshResultSize.SectionCategory.forSection(section)); + } + + @Override + protected Map createMap() { + return new EnumMap<>(MeshResultSize.SectionCategory.class); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkBuilder.java index 792903a5b5..7e08e5032f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkBuilder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkBuilder.java @@ -17,30 +17,11 @@ import java.util.function.Consumer; public class ChunkBuilder { - /** - * The low and high efforts given to the sorting and meshing tasks, - * respectively. This split into two separate effort categories means more - * sorting tasks, which are faster, can be scheduled compared to mesh tasks. - * These values need to capture that there's a limit to how much data can be - * uploaded per frame. Since sort tasks generate index data, which is smaller - * per quad and (on average) per section, more of their results can be uploaded - * in one frame. This number should essentially be a conservative estimate of - * min((mesh task upload size) / (sort task upload size), (mesh task time) / - * (sort task time)). - */ - public static final int HIGH_EFFORT = 10; - public static final int LOW_EFFORT = 1; - public static final int EFFORT_PER_THREAD_PER_FRAME = HIGH_EFFORT + LOW_EFFORT; - private static final float HIGH_EFFORT_BUDGET_FACTOR = (float)HIGH_EFFORT / EFFORT_PER_THREAD_PER_FRAME; - static final Logger LOGGER = LogManager.getLogger("ChunkBuilder"); private final ChunkJobQueue queue = new ChunkJobQueue(); - private final List threads = new ArrayList<>(); - private final AtomicInteger busyThreadCount = new AtomicInteger(); - private final ChunkBuildContext localContext; public ChunkBuilder(ClientLevel level, ChunkVertexType vertexType) { @@ -66,16 +47,8 @@ public ChunkBuilder(ClientLevel level, ChunkVertexType vertexType) { * Returns the remaining effort for tasks which should be scheduled this frame. If an attempt is made to * spawn more tasks than the budget allows, it will block until resources become available. */ - private int getTotalRemainingBudget() { - return Math.max(0, this.threads.size() * EFFORT_PER_THREAD_PER_FRAME - this.queue.getEffortSum()); - } - - public int getHighEffortSchedulingBudget() { - return Math.max(HIGH_EFFORT, (int) (this.getTotalRemainingBudget() * HIGH_EFFORT_BUDGET_FACTOR)); - } - - public int getLowEffortSchedulingBudget() { - return Math.max(LOW_EFFORT, this.getTotalRemainingBudget() - this.getHighEffortSchedulingBudget()); + public long getTotalRemainingDuration(long durationPerThread) { + return Math.max(0, this.threads.size() * durationPerThread - this.queue.getJobDurationSum()); } /** @@ -169,8 +142,8 @@ public int getScheduledJobCount() { return this.queue.size(); } - public int getScheduledEffort() { - return this.queue.getEffortSum(); + public float getBusyFraction(long frameDuration) { + return (float) this.queue.getJobDurationSum() / (frameDuration * this.threads.size()); } public int getBusyThreadCount() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJob.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJob.java index a0bcb33505..65dd30a5fb 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJob.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJob.java @@ -8,5 +8,7 @@ public interface ChunkJob extends CancellationToken { boolean isStarted(); - int getEffort(); + long getEstimatedSize(); + + long getEstimatedDuration(); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobCollector.java index 69701b0a26..5566a97e06 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobCollector.java @@ -11,25 +11,16 @@ public class ChunkJobCollector { private final Semaphore semaphore = new Semaphore(0); private final Consumer> collector; private final List submitted = new ArrayList<>(); - private int submittedHighEffort = 0; - private int submittedLowEffort = 0; - private final int highEffortBudget; - private final int lowEffortBudget; - private final boolean unlimitedBudget; + private long duration; public ChunkJobCollector(Consumer> collector) { - this.unlimitedBudget = true; - this.highEffortBudget = 0; - this.lowEffortBudget = 0; + this.duration = Long.MAX_VALUE; this.collector = collector; } - public ChunkJobCollector(int highEffortBudget, int lowEffortBudget, - Consumer> collector) { - this.unlimitedBudget = false; - this.highEffortBudget = highEffortBudget; - this.lowEffortBudget = lowEffortBudget; + public ChunkJobCollector(long duration, Consumer> collector) { + this.duration = duration; this.collector = collector; } @@ -56,28 +47,14 @@ public void awaitCompletion(ChunkBuilder builder) { public void addSubmittedJob(ChunkJob job) { this.submitted.add(job); + this.duration -= job.getEstimatedDuration(); + } - if (this.unlimitedBudget) { - return; - } - var effort = job.getEffort(); - if (effort <= ChunkBuilder.LOW_EFFORT) { - this.submittedLowEffort += effort; - } else { - this.submittedHighEffort += effort; - } + public boolean hasBudgetRemaining() { + return this.duration > 0; } - public boolean hasBudgetFor(int effort, boolean ignoreEffortCategory) { - if (this.unlimitedBudget) { - return true; - } - if (ignoreEffortCategory) { - return this.submittedLowEffort + this.submittedHighEffort + effort - <= this.highEffortBudget + this.lowEffortBudget; - } - return effort <= ChunkBuilder.LOW_EFFORT - ? this.submittedLowEffort + effort <= this.lowEffortBudget - : this.submittedHighEffort + effort <= this.highEffortBudget; + public int getSubmittedTaskCount() { + return this.submitted.size(); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobQueue.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobQueue.java index 0e6c2b9aa2..373f478045 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobQueue.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobQueue.java @@ -2,17 +2,18 @@ import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.Nullable; + import java.util.ArrayDeque; import java.util.Collection; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; class ChunkJobQueue { private final ConcurrentLinkedDeque jobs = new ConcurrentLinkedDeque<>(); - private final AtomicInteger jobEffortSum = new AtomicInteger(); + private final AtomicLong jobDurationSum = new AtomicLong(); private final Semaphore semaphore = new Semaphore(0); @@ -30,7 +31,7 @@ public void add(ChunkJob job, boolean important) { } else { this.jobs.addLast(job); } - this.jobEffortSum.addAndGet(job.getEffort()); + this.jobDurationSum.addAndGet(job.getEstimatedDuration()); this.semaphore.release(1); } @@ -45,7 +46,7 @@ public ChunkJob waitForNextJob() throws InterruptedException { var job = this.getNextTask(); if (job != null) { - this.jobEffortSum.addAndGet(-job.getEffort()); + this.jobDurationSum.addAndGet(-job.getEstimatedDuration()); } return job; } @@ -58,7 +59,7 @@ public boolean stealJob(ChunkJob job) { var success = this.jobs.remove(job); if (success) { - this.jobEffortSum.addAndGet(-job.getEffort()); + this.jobDurationSum.addAndGet(-job.getEstimatedDuration()); } else { // If we didn't manage to actually steal the task, then we need to release the permit which we did steal this.semaphore.release(1); @@ -89,7 +90,7 @@ public Collection shutdown() { // force the worker threads to wake up and exit this.semaphore.release(Runtime.getRuntime().availableProcessors()); - this.jobEffortSum.set(0); + this.jobDurationSum.set(0); return list; } @@ -98,8 +99,8 @@ public int size() { return this.semaphore.availablePermits(); } - public int getEffortSum() { - return this.jobEffortSum.get(); + public long getJobDurationSum() { + return this.jobDurationSum.get(); } public boolean isEmpty() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobResult.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobResult.java index 5619855494..2a1b98ac54 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobResult.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobResult.java @@ -1,22 +1,29 @@ package net.caffeinemc.mods.sodium.client.render.chunk.compile.executor; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.JobEffort; import net.minecraft.ReportedException; public class ChunkJobResult { private final OUTPUT output; private final Throwable throwable; + private final JobEffort jobEffort; - private ChunkJobResult(OUTPUT output, Throwable throwable) { + private ChunkJobResult(OUTPUT output, Throwable throwable, JobEffort jobEffort) { this.output = output; this.throwable = throwable; + this.jobEffort = jobEffort; } public static ChunkJobResult exceptionally(Throwable throwable) { - return new ChunkJobResult<>(null, throwable); + return new ChunkJobResult<>(null, throwable, null); + } + + public static ChunkJobResult successfully(OUTPUT output, JobEffort jobEffort) { + return new ChunkJobResult<>(output, null, jobEffort); } public static ChunkJobResult successfully(OUTPUT output) { - return new ChunkJobResult<>(output, null); + return new ChunkJobResult<>(output, null, null); } public OUTPUT unwrap() { @@ -29,4 +36,8 @@ public OUTPUT unwrap() { return this.output; } + + public JobEffort getJobEffort() { + return this.jobEffort; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java index 78e1242803..97f877fba4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java @@ -2,6 +2,7 @@ import net.caffeinemc.mods.sodium.client.render.chunk.compile.BuilderTaskOutput; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildContext; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.JobEffort; import net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderTask; import java.util.function.Consumer; @@ -42,6 +43,7 @@ public void execute(ChunkBuildContext context) { ChunkJobResult result; try { + var start = System.nanoTime(); var output = this.task.execute(context, this); // Task was cancelled while executing @@ -49,7 +51,7 @@ public void execute(ChunkBuildContext context) { return; } - result = ChunkJobResult.successfully(output); + result = ChunkJobResult.successfully(output, JobEffort.untilNowWithEffort(this.task.getClass(), start, output.getResultSize())); } catch (Throwable throwable) { result = ChunkJobResult.exceptionally(throwable); ChunkBuilder.LOGGER.error("Chunk build failed", throwable); @@ -68,7 +70,12 @@ public boolean isStarted() { } @Override - public int getEffort() { - return this.task.getEffort(); + public long getEstimatedSize() { + return this.task.getEstimatedSize(); + } + + @Override + public long getEstimatedDuration() { + return this.task.getEstimatedDuration(); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java index e6a1855c4c..71a256f7f6 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java @@ -8,7 +8,7 @@ import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildBuffers; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildContext; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildOutput; -import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkBuilder; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshTaskSizeEstimator; import net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline.BlockRenderCache; import net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline.BlockRenderer; import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionInfo; @@ -50,10 +50,12 @@ */ public class ChunkBuilderMeshingTask extends ChunkBuilderTask { private final ChunkRenderContext renderContext; + private final boolean forceSort; - public ChunkBuilderMeshingTask(RenderSection render, int buildTime, Vector3dc absoluteCameraPos, ChunkRenderContext renderContext) { + public ChunkBuilderMeshingTask(RenderSection render, int buildTime, Vector3dc absoluteCameraPos, ChunkRenderContext renderContext, boolean forceSort) { super(render, buildTime, absoluteCameraPos); this.renderContext = renderContext; + this.forceSort = forceSort; } @Override @@ -165,7 +167,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke if (collector != null) { var oldData = this.render.getTranslucentData(); translucentData = collector.getTranslucentData(oldData, this); - reuseUploadedData = translucentData == oldData; + reuseUploadedData = !this.forceSort && translucentData == oldData; } Map meshes = new Reference2ReferenceOpenHashMap<>(); @@ -229,7 +231,7 @@ private ReportedException fillCrashInfo(CrashReport report, LevelSlice slice, Bl } @Override - public int getEffort() { - return ChunkBuilder.HIGH_EFFORT; + public long estimateTaskSizeWith(MeshTaskSizeEstimator estimator) { + return estimator.estimateSize(this.render); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderSortingTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderSortingTask.java index c4efd4ddc1..c405c91b37 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderSortingTask.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderSortingTask.java @@ -1,19 +1,18 @@ package net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.Sorter; -import org.joml.Vector3dc; - import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildContext; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkSortOutput; -import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkBuilder; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshTaskSizeEstimator; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.DynamicData; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.DynamicSorter; import net.caffeinemc.mods.sodium.client.util.task.CancellationToken; +import org.joml.Vector3dc; public class ChunkBuilderSortingTask extends ChunkBuilderTask { - private final Sorter sorter; + private final DynamicSorter sorter; - public ChunkBuilderSortingTask(RenderSection render, int frame, Vector3dc absoluteCameraPos, Sorter sorter) { + public ChunkBuilderSortingTask(RenderSection render, int frame, Vector3dc absoluteCameraPos, DynamicSorter sorter) { super(render, frame, absoluteCameraPos); this.sorter = sorter; } @@ -35,7 +34,7 @@ public static ChunkBuilderSortingTask createTask(RenderSection render, int frame } @Override - public int getEffort() { - return ChunkBuilder.LOW_EFFORT; + public long estimateTaskSizeWith(MeshTaskSizeEstimator estimator) { + return this.sorter.getQuadCount(); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java index 1735c23bb6..0dce316ebf 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java @@ -1,14 +1,15 @@ package net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks; -import org.joml.Vector3dc; -import org.joml.Vector3f; -import org.joml.Vector3fc; - import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.compile.BuilderTaskOutput; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildContext; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.JobDurationEstimator; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshTaskSizeEstimator; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.CombinedCameraPos; import net.caffeinemc.mods.sodium.client.util.task.CancellationToken; +import org.joml.Vector3dc; +import org.joml.Vector3f; +import org.joml.Vector3fc; /** * Build tasks are immutable jobs (with optional prioritization) which contain all the necessary state to perform @@ -26,6 +27,9 @@ public abstract class ChunkBuilderTask impleme protected final Vector3dc absoluteCameraPos; protected final Vector3fc cameraPos; + private long estimatedSize; + private long estimatedDuration; + /** * Constructs a new build task for the given chunk and converts the absolute camera position to a relative position. While the absolute position is stored as a double vector, the relative position is stored as a float vector. * @@ -54,7 +58,20 @@ public ChunkBuilderTask(RenderSection render, int time, Vector3dc absoluteCamera */ public abstract OUTPUT execute(ChunkBuildContext context, CancellationToken cancellationToken); - public abstract int getEffort(); + public abstract long estimateTaskSizeWith(MeshTaskSizeEstimator estimator); + + public void calculateEstimations(JobDurationEstimator jobEstimator, MeshTaskSizeEstimator sizeEstimator) { + this.estimatedSize = this.estimateTaskSizeWith(sizeEstimator); + this.estimatedDuration = jobEstimator.estimateJobDuration(this.getClass(), this.estimatedSize); + } + + public long getEstimatedSize() { + return this.estimatedSize; + } + + public long getEstimatedDuration() { + return this.estimatedDuration; + } @Override public Vector3fc getRelativeCameraPos() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java index 74c022fd03..4acb25e70d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java @@ -2,8 +2,9 @@ import it.unimi.dsi.fastutil.ints.IntArrays; import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import net.caffeinemc.mods.sodium.client.render.chunk.ChunkUpdateType; +import net.caffeinemc.mods.sodium.client.render.chunk.ChunkUpdateTypes; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; +import net.caffeinemc.mods.sodium.client.render.chunk.TaskQueueType; import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.OcclusionCuller; import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; @@ -14,22 +15,23 @@ import java.util.Queue; /** - * The visible chunk collector is passed to the occlusion graph search culler to - * collect the visible chunks. + * The visible chunk collector is passed to the occlusion graph search culler to collect the visible chunks. */ public class VisibleChunkCollector implements OcclusionCuller.Visitor { private final ObjectArrayList sortedRenderLists; - private final EnumMap> sortedRebuildLists; + private final EnumMap> sortedRebuildLists; private final int frame; + private final TaskQueueType importantRebuildQueueType; - public VisibleChunkCollector(int frame) { + public VisibleChunkCollector(int frame, TaskQueueType importantRebuildQueueType) { this.frame = frame; + this.importantRebuildQueueType = importantRebuildQueueType; this.sortedRenderLists = new ObjectArrayList<>(); - this.sortedRebuildLists = new EnumMap<>(ChunkUpdateType.class); + this.sortedRebuildLists = new EnumMap<>(TaskQueueType.class); - for (var type : ChunkUpdateType.values()) { + for (var type : TaskQueueType.values()) { this.sortedRebuildLists.put(type, new ArrayDeque<>()); } } @@ -55,12 +57,13 @@ public void visit(RenderSection section) { } private void addToRebuildLists(RenderSection section) { - ChunkUpdateType type = section.getPendingUpdate(); + var pendingUpdate = section.getPendingUpdate(); - if (type != null && section.getTaskCancellationToken() == null) { - Queue queue = this.sortedRebuildLists.get(type); + if (pendingUpdate != 0 && section.getTaskCancellationToken() == null) { + var queueType = ChunkUpdateTypes.getQueueType(pendingUpdate, this.importantRebuildQueueType); + Queue queue = this.sortedRebuildLists.get(queueType); - if (queue.size() < type.getMaximumQueueSize()) { + if (queue.size() < queueType.queueSizeLimit()) { queue.add(section); } } @@ -105,7 +108,7 @@ public SortedRenderLists createRenderLists(Viewport viewport) { return new SortedRenderLists(sorted); } - public Map> getRebuildLists() { + public Map> getRebuildLists() { return this.sortedRebuildLists; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java index 0b619bf96d..ce484e6f86 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java @@ -22,6 +22,10 @@ import java.util.Map; public class RenderRegion { + public static final int SECTION_VERTEX_COUNT_ESTIMATE = 756; + public static final int SECTION_INDEX_COUNT_ESTIMATE = (SECTION_VERTEX_COUNT_ESTIMATE / 4) * 6; + public static final int SECTION_BUFFER_ESTIMATE = SECTION_VERTEX_COUNT_ESTIMATE * ChunkMeshFormats.COMPACT.getVertexFormat().getStride() + SECTION_INDEX_COUNT_ESTIMATE * Integer.BYTES; + public static final int REGION_WIDTH = 8; public static final int REGION_HEIGHT = 4; public static final int REGION_LENGTH = 8; @@ -260,11 +264,8 @@ public static class DeviceResources { public DeviceResources(CommandList commandList, StagingBuffer stagingBuffer) { int stride = ChunkMeshFormats.COMPACT.getVertexFormat().getStride(); - // the magic number 756 for the initial size is arbitrary, it was made up. - var initialVertices = 756; - this.geometryArena = new GlBufferArena(commandList, REGION_SIZE * initialVertices, stride, stagingBuffer); - var initialIndices = (initialVertices / 4) * 6; - this.indexArena = new GlBufferArena(commandList, REGION_SIZE * initialIndices, Integer.BYTES, stagingBuffer); + this.geometryArena = new GlBufferArena(commandList, REGION_SIZE * SECTION_VERTEX_COUNT_ESTIMATE, stride, stagingBuffer); + this.indexArena = new GlBufferArena(commandList, REGION_SIZE * SECTION_INDEX_COUNT_ESTIMATE, Integer.BYTES, stagingBuffer); } public void updateTessellation(CommandList commandList, GlTessellation tessellation) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortBehavior.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortBehavior.java index 055270f7bf..c6b2cd5466 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortBehavior.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortBehavior.java @@ -1,5 +1,7 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting; +import net.caffeinemc.mods.sodium.client.render.chunk.DeferMode; + public enum SortBehavior { OFF("OFF", SortMode.NONE), STATIC("S", SortMode.STATIC), @@ -12,10 +14,10 @@ public enum SortBehavior { private final String shortName; private final SortBehavior.SortMode sortMode; private final SortBehavior.PriorityMode priorityMode; - private final SortBehavior.DeferMode deferMode; + private final DeferMode deferMode; SortBehavior(String shortName, SortBehavior.SortMode sortMode, SortBehavior.PriorityMode priorityMode, - SortBehavior.DeferMode deferMode) { + DeferMode deferMode) { this.shortName = shortName; this.sortMode = sortMode; this.priorityMode = priorityMode; @@ -26,7 +28,7 @@ public enum SortBehavior { this(shortName, sortMode, null, null); } - SortBehavior(String shortName, SortBehavior.PriorityMode priorityMode, SortBehavior.DeferMode deferMode) { + SortBehavior(String shortName, SortBehavior.PriorityMode priorityMode, DeferMode deferMode) { this(shortName, SortMode.DYNAMIC, priorityMode, deferMode); } @@ -42,7 +44,7 @@ public SortBehavior.PriorityMode getPriorityMode() { return this.priorityMode; } - public SortBehavior.DeferMode getDeferMode() { + public DeferMode getDeferMode() { return this.deferMode; } @@ -53,8 +55,4 @@ public enum SortMode { public enum PriorityMode { NONE, NEARBY, ALL } - - public enum DeferMode { - ALWAYS, ONE_FRAME, ZERO_FRAMES - } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java index a22d7b6357..51aa594387 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java @@ -60,7 +60,7 @@ public int getIndexQuadCount() { } @Override - public Sorter getSorter() { + public DynamicSorter getSorter() { return new DynamicBSPSorter(this.getIndexQuadCount()); // index quad count } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicData.java index 033e810db1..bc140658d5 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicData.java @@ -20,6 +20,8 @@ public SortType getSortType() { return SortType.DYNAMIC; } + public abstract DynamicSorter getSorter(); + public GeometryPlanes getGeometryPlanes() { return this.geometryPlanes; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java index d184fb4ad3..33988489e4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data; -abstract class DynamicSorter extends PresentSorter { +public abstract class DynamicSorter extends PresentSorter { private final int quadCount; DynamicSorter(int quadCount) { @@ -14,4 +14,8 @@ public void writeIndexBuffer(CombinedCameraPos cameraPos, boolean initial) { this.initBufferWithQuadLength(this.quadCount); this.writeSort(cameraPos, initial); } + + public int getQuadCount() { + return this.quadCount; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java index ea6f5ce411..204fa0b7a9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java @@ -58,7 +58,7 @@ private DynamicTopoData(SectionPos sectionPos, TQuad[] quads, } @Override - public Sorter getSorter() { + public DynamicSorter getSorter() { return new DynamicTopoSorter(this.getInputQuadCount(), this, this.pendingTriggerIsDirect, this.consecutiveTopoSortFailures, this.GFNITrigger, this.directTrigger); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java index a01fe12a2e..bf399bc2b3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java @@ -1,5 +1,6 @@ package net.caffeinemc.mods.sodium.client.util; + import org.joml.Vector3dc; import org.joml.Vector3fc; @@ -49,6 +50,14 @@ public static float comparableIntToFloat(int i) { return Float.intBitsToFloat(i ^ ((i >> 31) & 0x7FFFFFFF)); } + public static float exponentialMovingAverage(float oldValue, float newValue, float newValueContribution) { + return newValueContribution * newValue + (1 - newValueContribution) * oldValue; + } + + public static long exponentialMovingAverage(long oldValue, long newValue, float newValueContribution) { + return (long) (newValueContribution * newValue) + (long) ((1 - newValueContribution) * oldValue); + } + public static double floatDoubleDot(Vector3fc a, Vector3dc b) { return fma(a.x(), b.x(), fma(a.y(), b.y(), a.z() * b.z())); } diff --git a/common/src/main/resources/assets/sodium/lang/en_us.json b/common/src/main/resources/assets/sodium/lang/en_us.json index 986a15797e..635443e50b 100644 --- a/common/src/main/resources/assets/sodium/lang/en_us.json +++ b/common/src/main/resources/assets/sodium/lang/en_us.json @@ -52,8 +52,11 @@ "sodium.options.use_persistent_mapping.tooltip": "For debugging only. If enabled, persistent memory mappings will be used for the staging buffer so that unnecessary memory copies can be avoided. Disabling this can be useful for narrowing down the cause of graphical corruption.\n\nRequires OpenGL 4.4 or ARB_buffer_storage.", "sodium.options.chunk_update_threads.name": "Chunk Update Threads", "sodium.options.chunk_update_threads.tooltip": "Specifies the number of threads to use for chunk building and sorting. Using more threads can speed up chunk loading and update speed, but may negatively impact frame times. The default value is usually good enough for all situations.", - "sodium.options.always_defer_chunk_updates.name": "Always Defer Chunk Updates", - "sodium.options.always_defer_chunk_updates.tooltip": "If enabled, rendering will never wait for chunk updates to finish, even if they are important. This can greatly improve frame rates in some scenarios, but it may create significant visual lag where blocks take a while to appear or disappear.", + "sodium.options.defer_chunk_updates.name": "Chunk Updates", + "sodium.options.defer_chunk_updates.tooltip": "If set to \"Deferred\", rendering will never wait for nearby chunk updates to finish, even if they are important. This can greatly improve frame rates in some scenarios, but it may create significant visual lag where blocks take a while to appear or disappear. \"Immediate\" eliminates visual lag by blocking the frame until chunk updates are complete while \"Soon\" allows at most one frame of visual lag.", + "sodium.options.defer_chunk_updates.always": "Deferred", + "sodium.options.defer_chunk_updates.one_frame": "Soon", + "sodium.options.defer_chunk_updates.zero_frames": "Immediate", "sodium.options.quad_splitting.name": "Perfect Translucency", "sodium.options.quad_splitting.tooltip": "Uses quad splitting to make translucent blocks (not entities or items) look correct through translucency sorting even if they are intersecting or have weird shapes, such as waterlogged stained glass panes. In \"Safe\" mode there's a limit on how much geometry can be generated in extreme cases to prevent crashes or performance degradation. This limit is disabled in \"Unlimited\" mode.", "sodium.options.quad_splitting.safe": "Safe", From 11e3e4a495523b374a1e2c26ba8202d6d8efa077 Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 20 Feb 2025 16:20:44 +0100 Subject: [PATCH 110/215] Implementation of bitmap-based trees for improved out-of-bounds rendering Relates to ticket item #2 of #2887. --- .../client/render/SodiumWorldRenderer.java | 1 + .../render/chunk/RenderSectionFlags.java | 9 + .../render/chunk/RenderSectionManager.java | 52 ++- .../render/chunk/lists/ChunkRenderList.java | 17 +- .../chunk/lists/CoordinateSectionVisitor.java | 5 + .../lists/OcclusionSectionCollector.java | 18 ++ .../chunk/lists/RenderListProvider.java | 57 ++++ .../chunk/lists/RenderSectionVisitor.java | 7 + .../render/chunk/lists/SectionCollector.java | 82 +++++ .../render/chunk/lists/SortItemsProvider.java | 16 + .../chunk/lists/TreeSectionCollector.java | 32 ++ .../chunk/lists/VisibleChunkCollector.java | 114 ------- .../chunk/occlusion/OcclusionCuller.java | 19 +- .../tree/AbstractTraversableBiForest.java | 27 ++ .../tree/AbstractTraversableMultiForest.java | 53 +++ .../render/chunk/tree/BaseBiForest.java | 57 ++++ .../client/render/chunk/tree/BaseForest.java | 15 + .../render/chunk/tree/BaseMultiForest.java | 91 ++++++ .../client/render/chunk/tree/Forest.java | 17 + .../render/chunk/tree/RemovableForest.java | 5 + .../chunk/tree/RemovableMultiForest.java | 139 ++++++++ .../render/chunk/tree/RemovableTree.java | 69 ++++ .../chunk/tree/TraversableBiForest.java | 12 + .../render/chunk/tree/TraversableForest.java | 19 ++ .../chunk/tree/TraversableMultiForest.java | 17 + .../render/chunk/tree/TraversableTree.java | 302 ++++++++++++++++++ .../sodium/client/render/chunk/tree/Tree.java | 54 ++++ .../client/render/viewport/Viewport.java | 24 ++ .../render/viewport/frustum/Frustum.java | 2 + .../viewport/frustum/SimpleFrustum.java | 5 + 30 files changed, 1200 insertions(+), 137 deletions(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/CoordinateSectionVisitor.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/OcclusionSectionCollector.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/RenderListProvider.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/RenderSectionVisitor.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortItemsProvider.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/TreeSectionCollector.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/AbstractTraversableBiForest.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/AbstractTraversableMultiForest.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseBiForest.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseForest.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseMultiForest.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/Forest.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/RemovableForest.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/RemovableMultiForest.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/RemovableTree.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableBiForest.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableForest.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableMultiForest.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableTree.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/Tree.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java index 6aad2f6d4e..b3037bd125 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java @@ -251,6 +251,7 @@ public void setupTerrain(Camera camera, } private void processChunkEvents() { + this.renderSectionManager.beforeSectionUpdates(); var tracker = ChunkTrackerHolder.get(this.level); tracker.forEachEvent(this.renderSectionManager::onChunkAdded, this.renderSectionManager::onChunkRemoved); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionFlags.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionFlags.java index cb19504069..30379fdc91 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionFlags.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionFlags.java @@ -5,5 +5,14 @@ public class RenderSectionFlags { public static final int HAS_BLOCK_ENTITIES = 1; public static final int HAS_ANIMATED_SPRITES = 2; + public static final int MASK_HAS_BLOCK_GEOMETRY = 1 << HAS_BLOCK_GEOMETRY; + public static final int MASK_HAS_BLOCK_ENTITIES = 1 << HAS_BLOCK_ENTITIES; + public static final int MASK_HAS_ANIMATED_SPRITES = 1 << HAS_ANIMATED_SPRITES; + public static final int MASK_NEEDS_RENDER = MASK_HAS_BLOCK_GEOMETRY | MASK_HAS_BLOCK_ENTITIES | MASK_HAS_ANIMATED_SPRITES; + public static final int NONE = 0; + + public static boolean needsRender(int flags) { + return (flags & MASK_NEEDS_RENDER) != 0; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index f8d05d0d41..22bd959245 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -22,9 +22,7 @@ import net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderSortingTask; import net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks.ChunkBuilderTask; import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionInfo; -import net.caffeinemc.mods.sodium.client.render.chunk.lists.ChunkRenderList; -import net.caffeinemc.mods.sodium.client.render.chunk.lists.SortedRenderLists; -import net.caffeinemc.mods.sodium.client.render.chunk.lists.VisibleChunkCollector; +import net.caffeinemc.mods.sodium.client.render.chunk.lists.*; import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.GraphDirection; import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.OcclusionCuller; import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; @@ -36,6 +34,7 @@ import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.TranslucentData; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.CameraMovement; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.SortTriggering; +import net.caffeinemc.mods.sodium.client.render.chunk.tree.RemovableMultiForest; import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkMeshFormats; import net.caffeinemc.mods.sodium.client.render.util.RenderAsserts; import net.caffeinemc.mods.sodium.client.render.viewport.CameraTransform; @@ -110,6 +109,8 @@ public class RenderSectionManager { private @Nullable Vector3dc cameraPosition; + private final RemovableMultiForest renderableSectionTree; + public RenderSectionManager(ClientLevel level, int renderDistance, CommandList commandList) { this.chunkRenderer = new DefaultChunkRenderer(RenderDevice.INSTANCE, ChunkMeshFormats.COMPACT); @@ -127,6 +128,8 @@ public RenderSectionManager(ClientLevel level, int renderDistance, CommandList c this.renderLists = SortedRenderLists.empty(); this.occlusionCuller = new OcclusionCuller(Long2ReferenceMaps.unmodifiable(this.sectionByPosition), this.level); + this.renderableSectionTree = new RemovableMultiForest(renderDistance); + this.taskLists = new EnumMap<>(TaskQueueType.class); for (var type : TaskQueueType.values()) { @@ -164,12 +167,30 @@ private void createTerrainRenderList(Camera camera, Viewport viewport, int frame final var searchDistance = this.getSearchDistance(); final var useOcclusionCulling = this.shouldUseOcclusionCulling(camera, spectator); - var visitor = new VisibleChunkCollector(frame, SodiumClientMod.options().performance.chunkBuildDeferMode.getImportantRebuildQueueType()); + RenderListProvider renderListProvider; + var importantRebuildQueueType = SodiumClientMod.options().performance.chunkBuildDeferMode.getImportantRebuildQueueType(); + if (this.isOutOfGraph(viewport.getChunkCoord())) { + var visitor = new TreeSectionCollector(frame, importantRebuildQueueType, this.sectionByPosition); + this.renderableSectionTree.prepareForTraversal(); + this.renderableSectionTree.traverse(visitor, viewport, searchDistance); + + renderListProvider = visitor; + } else { + var visitor = new OcclusionSectionCollector(frame, importantRebuildQueueType); + this.occlusionCuller.findVisible(visitor, viewport, searchDistance, useOcclusionCulling, frame); + + renderListProvider = visitor; + } - this.occlusionCuller.findVisible(visitor, viewport, searchDistance, useOcclusionCulling, frame); + this.renderLists = renderListProvider.createRenderLists(viewport); + this.taskLists = renderListProvider.getTaskLists(); + } - this.renderLists = visitor.createRenderLists(viewport); - this.taskLists = visitor.getRebuildLists(); + private boolean isOutOfGraph(SectionPos pos) { + var sectionY = pos.getY(); + // 1.21.1 has no getMinSectionY/getMaxSectionY. + // in 1.21.1 we have: getMinSection/getMaxSection, which are half-open [min, max). + return this.level.getMinSection() <= sectionY && sectionY < this.level.getMaxSection() && !this.sectionByPosition.containsKey(pos.asLong()); } private float getSearchDistance() { @@ -197,6 +218,10 @@ private boolean shouldUseOcclusionCulling(Camera camera, boolean spectator) { return useOcclusionCulling; } + public void beforeSectionUpdates() { + this.renderableSectionTree.ensureCapacity(this.getRenderDistance()); + } + private void resetRenderLists() { this.renderLists = SortedRenderLists.empty(); @@ -225,13 +250,14 @@ public void onSectionAdded(int x, int y, int z) { if (section.hasOnlyAir()) { this.updateSectionInfo(renderSection, BuiltSectionInfo.EMPTY); } else { + this.renderableSectionTree.add(renderSection); renderSection.setPendingUpdate(ChunkUpdateTypes.INITIAL_BUILD, this.lastFrameAtTime); } this.connectNeighborNodes(renderSection); // force update to schedule build task - this.needsGraphUpdate = true; + this.markGraphDirty(); } public void onSectionRemoved(int x, int y, int z) { @@ -242,6 +268,8 @@ public void onSectionRemoved(int x, int y, int z) { return; } + this.renderableSectionTree.remove(x, y, z); + if (section.getTranslucentData() != null) { this.sortTriggering.removeSection(section.getTranslucentData(), sectionPos); } @@ -258,7 +286,7 @@ public void onSectionRemoved(int x, int y, int z) { section.delete(); // force update to remove section from render lists - this.needsGraphUpdate = true; + this.markGraphDirty(); } public void renderLayer(ChunkRenderMatrices matrices, TerrainRenderPass pass, double x, double y, double z) { @@ -375,6 +403,12 @@ private boolean processChunkBuildResults(ArrayList results) { } private boolean updateSectionInfo(RenderSection render, BuiltSectionInfo info) { + if (info == null || !RenderSectionFlags.needsRender(info.flags)) { + this.renderableSectionTree.remove(render); + } else { + this.renderableSectionTree.add(render); + } + var infoChanged = render.setInfo(info); if (info == null || ArrayUtils.isEmpty(info.globalBlockEntities)) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java index 4d6779c576..eb65bae46c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java @@ -24,6 +24,7 @@ public class ChunkRenderList { private int lastRelativeCameraSectionX; private int lastRelativeCameraSectionY; private int lastRelativeCameraSectionZ; + private boolean addedSectionsAreSorted = false; private final byte[] sectionsWithSprites = new byte[RenderRegion.REGION_SIZE]; private int sectionsWithSpritesCount = 0; @@ -39,7 +40,7 @@ public ChunkRenderList(RenderRegion region) { this.region = region; } - public void reset(int frame) { + public void reset(int frame, boolean addedSectionsAreSorted) { this.prevSectionsWithGeometryCount = this.sectionsWithGeometryCount; Arrays.fill(this.sectionsWithGeometryMap, 0L); @@ -49,12 +50,13 @@ public void reset(int frame) { this.size = 0; this.lastVisibleFrame = frame; + this.addedSectionsAreSorted = addedSectionsAreSorted; } // clamping the relative camera position to the region bounds means there can only be very few different distances private static final int SORTING_HISTOGRAM_SIZE = RenderRegion.REGION_WIDTH + RenderRegion.REGION_HEIGHT + RenderRegion.REGION_LENGTH - 2; - public void prepareForRender(SectionPos cameraPos, int[] sortItems) { + public void prepareForRender(SectionPos cameraPos, SortItemsProvider sortItemsProvider) { // The relative coordinates are clamped to one section larger than the region bounds to also capture cache invalidation that happens // when the camera moves from outside the region to inside the region (when seen on all axes independently). // This type of cache invalidation stems from different facings of sections being rendered if the camera is aligned with them on an axis. @@ -78,16 +80,20 @@ public void prepareForRender(SectionPos cameraPos, int[] sortItems) { this.lastRelativeCameraSectionY = relativeCameraSectionY; this.lastRelativeCameraSectionZ = relativeCameraSectionZ; - this.sortSections(relativeCameraSectionX, relativeCameraSectionY, relativeCameraSectionZ, sortItems); + // only sort sections if necessary, read directly from bitmap instead of no sorting is required + if (!this.addedSectionsAreSorted) { + this.sortSections(relativeCameraSectionX, relativeCameraSectionY, relativeCameraSectionZ, sortItemsProvider); + } } } - public void sortSections(int relativeCameraSectionX, int relativeCameraSectionY, int relativeCameraSectionZ, int[] sortItems) { + private void sortSections(int relativeCameraSectionX, int relativeCameraSectionY, int relativeCameraSectionZ, SortItemsProvider sortItemsProvider) { relativeCameraSectionX = Mth.clamp(relativeCameraSectionX, 0, RenderRegion.REGION_WIDTH - 1); relativeCameraSectionY = Mth.clamp(relativeCameraSectionY, 0, RenderRegion.REGION_HEIGHT - 1); relativeCameraSectionZ = Mth.clamp(relativeCameraSectionZ, 0, RenderRegion.REGION_LENGTH - 1); int[] histogram = new int[SORTING_HISTOGRAM_SIZE]; + var sortItems = sortItemsProvider.ensureSortItemsOfLength(this.sectionsWithGeometryCount); this.sectionsWithGeometryCount = 0; for (int mapIndex = 0; mapIndex < this.sectionsWithGeometryMap.length; mapIndex++) { @@ -132,6 +138,9 @@ public void add(RenderSection render) { if (((flags >>> RenderSectionFlags.HAS_BLOCK_GEOMETRY) & 1) == 1) { this.sectionsWithGeometryMap[index >> 6] |= 1L << (index & 0b111111); + if (this.addedSectionsAreSorted) { + this.sectionsWithGeometry[this.sectionsWithGeometryCount] = (byte) index; + } this.sectionsWithGeometryCount++; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/CoordinateSectionVisitor.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/CoordinateSectionVisitor.java new file mode 100644 index 0000000000..62af2d4294 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/CoordinateSectionVisitor.java @@ -0,0 +1,5 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.lists; + +public interface CoordinateSectionVisitor { + void visit(int x, int y, int z); +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/OcclusionSectionCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/OcclusionSectionCollector.java new file mode 100644 index 0000000000..51477a283d --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/OcclusionSectionCollector.java @@ -0,0 +1,18 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.lists; + +import net.caffeinemc.mods.sodium.client.render.chunk.TaskQueueType; + +/** + * The occlusion section collector is passed to the occlusion graph search culler to + * collect the visible chunks. + */ +public class OcclusionSectionCollector extends SectionCollector { + public OcclusionSectionCollector(int frame, TaskQueueType importantRebuildQueueType) { + super(frame, importantRebuildQueueType); + } + + @Override + public boolean orderIsSorted() { + return false; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/RenderListProvider.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/RenderListProvider.java new file mode 100644 index 0000000000..4c0a428ecc --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/RenderListProvider.java @@ -0,0 +1,57 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.lists; + +import it.unimi.dsi.fastutil.ints.IntArrays; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; +import net.caffeinemc.mods.sodium.client.render.chunk.TaskQueueType; +import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; +import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; + +import java.util.ArrayDeque; +import java.util.Map; + +public interface RenderListProvider extends SortItemsProvider { + ObjectArrayList getUnsortedRenderLists(); + + Map> getTaskLists(); + + boolean orderIsSorted(); + + default SortedRenderLists createRenderLists(Viewport viewport) { + var sectionPos = viewport.getChunkCoord(); + var unsorted = this.getUnsortedRenderLists(); + + // sort the regions by distance to fix rare region ordering problems. + // regions need to be sorted even when the order is generated by a correctly traversed section tree + // but sections don't need to be sorted within regions + var cameraX = sectionPos.getX() >> RenderRegion.REGION_WIDTH_SH; + var cameraY = sectionPos.getY() >> RenderRegion.REGION_HEIGHT_SH; + var cameraZ = sectionPos.getZ() >> RenderRegion.REGION_LENGTH_SH; + + var size = unsorted.size(); + var sortItems = this.ensureSortItemsOfLength(size); + + for (var i = 0; i < size; i++) { + var region = unsorted.get(i).getRegion(); + var x = Math.abs(region.getX() - cameraX); + var y = Math.abs(region.getY() - cameraY); + var z = Math.abs(region.getZ() - cameraZ); + sortItems[i] = (x + y + z) << 16 | i; + } + + IntArrays.unstableSort(sortItems, 0, size); + + var sorted = new ObjectArrayList(size); + for (var i = 0; i < size; i++) { + var key = sortItems[i]; + var renderList = unsorted.get(key & 0xFFFF); + sorted.add(renderList); + } + + for (var list : sorted) { + list.prepareForRender(sectionPos, this); + } + + return new SortedRenderLists(sorted); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/RenderSectionVisitor.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/RenderSectionVisitor.java new file mode 100644 index 0000000000..2be54f4e56 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/RenderSectionVisitor.java @@ -0,0 +1,7 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.lists; + +import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; + +public interface RenderSectionVisitor { + void visit(RenderSection section); +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java new file mode 100644 index 0000000000..1a60d01e73 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java @@ -0,0 +1,82 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.lists; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.caffeinemc.mods.sodium.client.render.chunk.ChunkUpdateTypes; +import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; +import net.caffeinemc.mods.sodium.client.render.chunk.TaskQueueType; +import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; + +import java.util.ArrayDeque; +import java.util.EnumMap; +import java.util.Map; +import java.util.Queue; + +public abstract class SectionCollector implements RenderListProvider, RenderSectionVisitor { + private final int frame; + private final TaskQueueType importantRebuildQueueType; + private final ObjectArrayList renderLists; + private final EnumMap> sortedTaskLists; + + private static int[] sortItems = new int[RenderRegion.REGION_SIZE]; + + public SectionCollector(int frame, TaskQueueType importantRebuildQueueType) { + this.frame = frame; + this.importantRebuildQueueType = importantRebuildQueueType; + + this.renderLists = new ObjectArrayList<>(); + this.sortedTaskLists = new EnumMap<>(TaskQueueType.class); + + for (var type : TaskQueueType.values()) { + this.sortedTaskLists.put(type, new ArrayDeque<>()); + } + } + + @Override + public void visit(RenderSection section) { + // only process section (and associated render list) if it has content that needs rendering + if (section.getFlags() != 0) { + RenderRegion region = section.getRegion(); + ChunkRenderList renderList = region.getRenderList(); + + if (renderList.getLastVisibleFrame() != this.frame) { + renderList.reset(this.frame, this.orderIsSorted()); + + this.renderLists.add(renderList); + } + + renderList.add(section); + } + + // always add to rebuild lists though, because it might just not be built yet + var pendingUpdate = section.getPendingUpdate(); + + if (pendingUpdate != 0 && section.getTaskCancellationToken() == null) { + var queueType = ChunkUpdateTypes.getQueueType(pendingUpdate, this.importantRebuildQueueType); + Queue queue = this.sortedTaskLists.get(queueType); + + if (queue.size() < queueType.queueSizeLimit()) { + queue.add(section); + } + } + } + + @Override + public ObjectArrayList getUnsortedRenderLists() { + return this.renderLists; + } + + @Override + public Map> getTaskLists() { + return this.sortedTaskLists; + } + + @Override + public void setCachedSortItems(int[] sortItems) { + SectionCollector.sortItems = sortItems; + } + + @Override + public int[] getCachedSortItems() { + return SectionCollector.sortItems; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortItemsProvider.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortItemsProvider.java new file mode 100644 index 0000000000..3ce542af1e --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SortItemsProvider.java @@ -0,0 +1,16 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.lists; + +public interface SortItemsProvider { + int[] getCachedSortItems(); + + void setCachedSortItems(int[] sortItems); + + default int[] ensureSortItemsOfLength(int length) { + var sortItems = this.getCachedSortItems(); + if (sortItems == null || sortItems.length < length) { + sortItems = new int[length]; + this.setCachedSortItems(sortItems); + } + return sortItems; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/TreeSectionCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/TreeSectionCollector.java new file mode 100644 index 0000000000..282ab1a8d1 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/TreeSectionCollector.java @@ -0,0 +1,32 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.lists; + +import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; +import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; +import net.caffeinemc.mods.sodium.client.render.chunk.TaskQueueType; +import net.minecraft.core.SectionPos; + +/** + * Collects sections from a tree traversal. It needs to turn coordinates into section objects because the section collector is not capable of handling raw section indexes yet. + */ +public class TreeSectionCollector extends SectionCollector implements CoordinateSectionVisitor { + private final Long2ReferenceMap sections; + + public TreeSectionCollector(int frame, TaskQueueType importantRebuildQueueType, Long2ReferenceMap sections) { + super(frame, importantRebuildQueueType); + this.sections = sections; + } + + @Override + public void visit(int x, int y, int z) { + var section = this.sections.get(SectionPos.asLong(x, y, z)); + + if (section != null) { + this.visit(section); + } + } + + @Override + public boolean orderIsSorted() { + return true; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java index 4acb25e70d..e69de29bb2 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/VisibleChunkCollector.java @@ -1,114 +0,0 @@ -package net.caffeinemc.mods.sodium.client.render.chunk.lists; - -import it.unimi.dsi.fastutil.ints.IntArrays; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import net.caffeinemc.mods.sodium.client.render.chunk.ChunkUpdateTypes; -import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; -import net.caffeinemc.mods.sodium.client.render.chunk.TaskQueueType; -import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.OcclusionCuller; -import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; -import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; - -import java.util.ArrayDeque; -import java.util.EnumMap; -import java.util.Map; -import java.util.Queue; - -/** - * The visible chunk collector is passed to the occlusion graph search culler to collect the visible chunks. - */ -public class VisibleChunkCollector implements OcclusionCuller.Visitor { - private final ObjectArrayList sortedRenderLists; - private final EnumMap> sortedRebuildLists; - - private final int frame; - private final TaskQueueType importantRebuildQueueType; - - public VisibleChunkCollector(int frame, TaskQueueType importantRebuildQueueType) { - this.frame = frame; - this.importantRebuildQueueType = importantRebuildQueueType; - - this.sortedRenderLists = new ObjectArrayList<>(); - this.sortedRebuildLists = new EnumMap<>(TaskQueueType.class); - - for (var type : TaskQueueType.values()) { - this.sortedRebuildLists.put(type, new ArrayDeque<>()); - } - } - - @Override - public void visit(RenderSection section) { - // only process section (and associated render list) if it has content that needs rendering - if (section.getFlags() != 0) { - RenderRegion region = section.getRegion(); - ChunkRenderList renderList = region.getRenderList(); - - if (renderList.getLastVisibleFrame() != this.frame) { - renderList.reset(this.frame); - - this.sortedRenderLists.add(renderList); - } - - renderList.add(section); - } - - // always add to rebuild lists though, because it might just not be built yet - this.addToRebuildLists(section); - } - - private void addToRebuildLists(RenderSection section) { - var pendingUpdate = section.getPendingUpdate(); - - if (pendingUpdate != 0 && section.getTaskCancellationToken() == null) { - var queueType = ChunkUpdateTypes.getQueueType(pendingUpdate, this.importantRebuildQueueType); - Queue queue = this.sortedRebuildLists.get(queueType); - - if (queue.size() < queueType.queueSizeLimit()) { - queue.add(section); - } - } - } - - private static int[] sortItems = new int[RenderRegion.REGION_SIZE]; - - public SortedRenderLists createRenderLists(Viewport viewport) { - // sort the regions by distance to fix rare region ordering bugs - var sectionPos = viewport.getChunkCoord(); - var cameraX = sectionPos.getX() >> RenderRegion.REGION_WIDTH_SH; - var cameraY = sectionPos.getY() >> RenderRegion.REGION_HEIGHT_SH; - var cameraZ = sectionPos.getZ() >> RenderRegion.REGION_LENGTH_SH; - var size = this.sortedRenderLists.size(); - - if (sortItems.length < size) { - sortItems = new int[size]; - } - - for (var i = 0; i < size; i++) { - var region = this.sortedRenderLists.get(i).getRegion(); - var x = Math.abs(region.getX() - cameraX); - var y = Math.abs(region.getY() - cameraY); - var z = Math.abs(region.getZ() - cameraZ); - sortItems[i] = (x + y + z) << 16 | i; - } - - IntArrays.unstableSort(sortItems, 0, size); - - var sorted = new ObjectArrayList(size); - for (var i = 0; i < size; i++) { - var key = sortItems[i]; - var renderList = this.sortedRenderLists.get(key & 0xFFFF); - sorted.add(renderList); - } - - // sort sections and invalidate batch caches if the render lists changed - for (var list : sorted) { - list.prepareForRender(sectionPos, sortItems); - } - - return new SortedRenderLists(sorted); - } - - public Map> getRebuildLists() { - return this.sortedRebuildLists; - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java index ed03ebef43..87593ccd4a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java @@ -2,6 +2,7 @@ import it.unimi.dsi.fastutil.longs.Long2ReferenceMap; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; +import net.caffeinemc.mods.sodium.client.render.chunk.lists.RenderSectionVisitor; import net.caffeinemc.mods.sodium.client.render.viewport.CameraTransform; import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; import net.caffeinemc.mods.sodium.client.util.collections.DoubleBufferedQueue; @@ -23,7 +24,7 @@ public OcclusionCuller(Long2ReferenceMap sections, Level level) { this.level = level; } - public void findVisible(Visitor visitor, + public void findVisible(RenderSectionVisitor visitor, Viewport viewport, float searchDistance, boolean useOcclusionCulling, @@ -41,7 +42,7 @@ public void findVisible(Visitor visitor, this.addNearbySections(visitor, viewport, searchDistance, frame); } - private static void processQueue(Visitor visitor, + private static void processQueue(RenderSectionVisitor visitor, Viewport viewport, float searchDistance, boolean useOcclusionCulling, @@ -211,8 +212,9 @@ private static int nearestToZero(int min, int max) { // The bounding box of a chunk section must be large enough to contain all possible geometry within it. Block models // can extend outside a block volume by +/- 1.0 blocks on all axis. Additionally, we make use of a small epsilon // to deal with floating point imprecision during a frustum check (see GH#2132). - private static final float CHUNK_SECTION_RADIUS = 8.0f /* chunk bounds */; - private static final float CHUNK_SECTION_SIZE = CHUNK_SECTION_RADIUS + 1.0f /* maximum model extent */ + 0.125f /* epsilon */; + public static final float CHUNK_SECTION_RADIUS = 8.0f /* chunk bounds */; + public static final float CHUNK_SECTION_MARGIN = 1.0f /* maximum model extent */ + 0.125f /* epsilon */; + public static final float CHUNK_SECTION_SIZE = CHUNK_SECTION_RADIUS + CHUNK_SECTION_MARGIN; public static boolean isWithinFrustum(Viewport viewport, RenderSection section) { return viewport.isBoxVisible(section.getCenterX(), section.getCenterY(), section.getCenterZ(), @@ -232,7 +234,7 @@ public static boolean isWithinNearbySectionFrustum(Viewport viewport, RenderSect // for all neighboring, even diagonally neighboring, sections around the origin to render them // if their extended bounding box is visible, and they may render large models that extend // outside the 16x16x16 base volume of the section. - private void addNearbySections(Visitor visitor, Viewport viewport, float searchDistance, int frame) { + private void addNearbySections(RenderSectionVisitor visitor, Viewport viewport, float searchDistance, int frame) { var origin = viewport.getChunkCoord(); var originX = origin.getX(); var originY = origin.getY(); @@ -259,7 +261,7 @@ private void addNearbySections(Visitor visitor, Viewport viewport, float searchD } } - private void init(Visitor visitor, + private void init(RenderSectionVisitor visitor, WriteQueue queue, Viewport viewport, float searchDistance, @@ -281,7 +283,7 @@ private void init(Visitor visitor, } } - private void initWithinWorld(Visitor visitor, WriteQueue queue, Viewport viewport, boolean useOcclusionCulling, int frame) { + private void initWithinWorld(RenderSectionVisitor visitor, WriteQueue queue, Viewport viewport, boolean useOcclusionCulling, int frame) { var origin = viewport.getChunkCoord(); var section = this.getRenderSection(origin.getX(), origin.getY(), origin.getZ()); @@ -377,7 +379,4 @@ private RenderSection getRenderSection(int x, int y, int z) { return this.sections.get(SectionPos.asLong(x, y, z)); } - public interface Visitor { - void visit(RenderSection section); - } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/AbstractTraversableBiForest.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/AbstractTraversableBiForest.java new file mode 100644 index 0000000000..e276f6d6e8 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/AbstractTraversableBiForest.java @@ -0,0 +1,27 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.tree; + +import net.caffeinemc.mods.sodium.client.render.chunk.lists.CoordinateSectionVisitor; +import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; + +public abstract class AbstractTraversableBiForest extends BaseBiForest implements TraversableForest { + public AbstractTraversableBiForest(int baseOffsetX, int baseOffsetY, int baseOffsetZ, float buildDistance) { + super(baseOffsetX, baseOffsetY, baseOffsetZ, buildDistance); + } + + @Override + public void prepareForTraversal() { + this.mainTree.prepareForTraversal(); + if (this.secondaryTree != null) { + this.secondaryTree.prepareForTraversal(); + } + } + + @Override + public void traverse(CoordinateSectionVisitor visitor, Viewport viewport, float distanceLimit) { + // no sorting is necessary because we assume the camera will never be closer to the secondary tree than the main tree + this.mainTree.traverse(visitor, viewport, distanceLimit, this.buildDistance); + if (this.secondaryTree != null) { + this.secondaryTree.traverse(visitor, viewport, distanceLimit, this.buildDistance); + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/AbstractTraversableMultiForest.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/AbstractTraversableMultiForest.java new file mode 100644 index 0000000000..e6b5b406df --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/AbstractTraversableMultiForest.java @@ -0,0 +1,53 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.tree; + +import it.unimi.dsi.fastutil.ints.IntArrays; +import net.caffeinemc.mods.sodium.client.render.chunk.lists.CoordinateSectionVisitor; +import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; + +public abstract class AbstractTraversableMultiForest extends BaseMultiForest implements TraversableForest { + public AbstractTraversableMultiForest(int baseOffsetX, int baseOffsetY, int baseOffsetZ, float buildDistance) { + super(baseOffsetX, baseOffsetY, baseOffsetZ, buildDistance); + } + + @Override + public void prepareForTraversal() { + for (var tree : this.trees) { + if (tree != null) { + tree.prepareForTraversal(); + } + } + } + + @Override + public void traverse(CoordinateSectionVisitor visitor, Viewport viewport, float distanceLimit) { + var cameraPos = viewport.getChunkCoord(); + var cameraSectionX = cameraPos.getX(); + var cameraSectionY = cameraPos.getY(); + var cameraSectionZ = cameraPos.getZ(); + + // sort the trees by distance from the camera by sorting a packed index array. + var items = new int[this.trees.length]; + for (int i = 0; i < this.trees.length; i++) { + var tree = this.trees[i]; + if (tree != null) { + var deltaX = Math.abs(tree.offsetX + 32 - cameraSectionX); + var deltaY = Math.abs(tree.offsetY + 32 - cameraSectionY); + var deltaZ = Math.abs(tree.offsetZ + 32 - cameraSectionZ); + items[i] = (deltaX + deltaY + deltaZ + 1) << 16 | i; + } + } + + IntArrays.unstableSort(items); + + // traverse in sorted front-to-back order for correct render order + for (var item : items) { + if (item == 0) { + continue; + } + var tree = this.trees[item & 0xFFFF]; + if (tree != null) { + tree.traverse(visitor, viewport, distanceLimit, this.buildDistance); + } + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseBiForest.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseBiForest.java new file mode 100644 index 0000000000..ddbfe1ae05 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseBiForest.java @@ -0,0 +1,57 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.tree; + +import net.minecraft.world.level.Level; + +public abstract class BaseBiForest extends BaseForest { + private static final int SECONDARY_TREE_OFFSET_XZ = 4; + + protected final T mainTree; + protected T secondaryTree; + + public BaseBiForest(int baseOffsetX,int baseOffsetY, int baseOffsetZ, float buildDistance) { + super(baseOffsetX, baseOffsetY, baseOffsetZ, buildDistance); + + this.mainTree = this.makeTree(this.baseOffsetX, this.baseOffsetY, this.baseOffsetZ); + } + + protected T makeSecondaryTree() { + // offset diagonally to fully encompass the required 65x65 area + return this.makeTree( + this.baseOffsetX + SECONDARY_TREE_OFFSET_XZ, + this.baseOffsetY, + this.baseOffsetZ + SECONDARY_TREE_OFFSET_XZ); + } + + @Override + public void add(int x, int y, int z) { + if (this.mainTree.add(x, y, z)) { + return; + } + + if (this.secondaryTree == null) { + this.secondaryTree = this.makeSecondaryTree(); + } + this.secondaryTree.add(x, y, z); + } + + @Override + public int getPresence(int x, int y, int z) { + var result = this.mainTree.getPresence(x, y, z); + if (result != Tree.OUT_OF_BOUNDS) { + return result; + } + + if (this.secondaryTree != null) { + return this.secondaryTree.getPresence(x, y, z); + } + return Tree.OUT_OF_BOUNDS; + } + + public static boolean checkApplicable(float buildDistance, Level level) { + if (buildDistance / 16.0f > 64.0f) { + return false; + } + + return level.getHeight() >> 4 <= 64; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseForest.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseForest.java new file mode 100644 index 0000000000..2501fbd68e --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseForest.java @@ -0,0 +1,15 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.tree; + +public abstract class BaseForest implements Forest { + protected final int baseOffsetX, baseOffsetY, baseOffsetZ; + final float buildDistance; + + protected BaseForest(int baseOffsetX, int baseOffsetY, int baseOffsetZ, float buildDistance) { + this.baseOffsetX = baseOffsetX; + this.baseOffsetY = baseOffsetY; + this.baseOffsetZ = baseOffsetZ; + this.buildDistance = buildDistance; + } + + protected abstract T makeTree(int offsetX, int offsetY, int offsetZ); +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseMultiForest.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseMultiForest.java new file mode 100644 index 0000000000..ccf62c58f9 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseMultiForest.java @@ -0,0 +1,91 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.tree; + +public abstract class BaseMultiForest extends BaseForest { + protected final T[] trees; + protected final int forestDim; + + protected T lastTree; + + public BaseMultiForest(int baseOffsetX, int baseOffsetY, int baseOffsetZ, float buildDistance) { + super(baseOffsetX, baseOffsetY, baseOffsetZ, buildDistance); + + this.forestDim = forestDimFromBuildDistance(buildDistance); + this.trees = this.makeTrees(this.forestDim * this.forestDim * this.forestDim); + } + + public static int forestDimFromBuildDistance(float buildDistance) { + // / 16 (block to chunk) * 2 (radius to diameter) + 1 (center chunk) / 64 (chunks per tree) + return (int) Math.ceil((buildDistance / 8.0 + 1) / 64.0); + } + + protected int getTreeIndex(int localX, int localY, int localZ) { + var treeX = localX >> 6; + var treeY = localY >> 6; + var treeZ = localZ >> 6; + + if (treeX < 0 || treeX >= this.forestDim || + treeY < 0 || treeY >= this.forestDim || + treeZ < 0 || treeZ >= this.forestDim) { + return Tree.OUT_OF_BOUNDS; + } + + return treeX + (treeZ * this.forestDim + treeY) * this.forestDim; + } + + @Override + public void add(int x, int y, int z) { + if (this.lastTree != null && this.lastTree.add(x, y, z)) { + return; + } + + var localX = x - this.baseOffsetX; + var localY = y - this.baseOffsetY; + var localZ = z - this.baseOffsetZ; + + var treeIndex = this.getTreeIndex(localX, localY, localZ); + if (treeIndex == Tree.OUT_OF_BOUNDS) { + return; + } + + var tree = this.trees[treeIndex]; + + if (tree == null) { + var treeOffsetX = this.baseOffsetX + (localX & ~0b111111); + var treeOffsetY = this.baseOffsetY + (localY & ~0b111111); + var treeOffsetZ = this.baseOffsetZ + (localZ & ~0b111111); + tree = this.makeTree(treeOffsetX, treeOffsetY, treeOffsetZ); + this.trees[treeIndex] = tree; + } + + tree.add(x, y, z); + this.lastTree = tree; + } + + @Override + public int getPresence(int x, int y, int z) { + if (this.lastTree != null) { + var result = this.lastTree.getPresence(x, y, z); + if (result != Tree.OUT_OF_BOUNDS) { + return result; + } + } + + var localX = x - this.baseOffsetX; + var localY = y - this.baseOffsetY; + var localZ = z - this.baseOffsetZ; + + var treeIndex = this.getTreeIndex(localX, localY, localZ); + if (treeIndex == Tree.OUT_OF_BOUNDS) { + return Tree.OUT_OF_BOUNDS; + } + + var tree = this.trees[treeIndex]; + if (tree != null) { + this.lastTree = tree; + return tree.getPresence(x, y, z); + } + return Tree.OUT_OF_BOUNDS; + } + + protected abstract T[] makeTrees(int length); +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/Forest.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/Forest.java new file mode 100644 index 0000000000..9fbbd3b471 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/Forest.java @@ -0,0 +1,17 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.tree; + +import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; + +public interface Forest { + void add(int x, int y, int z); + + default void add(RenderSection section) { + add(section.getChunkX(), section.getChunkY(), section.getChunkZ()); + } + + int getPresence(int x, int y, int z); + + default boolean isSectionPresent(int x, int y, int z) { + return this.getPresence(x, y, z) == Tree.PRESENT; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/RemovableForest.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/RemovableForest.java new file mode 100644 index 0000000000..4729c43b26 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/RemovableForest.java @@ -0,0 +1,5 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.tree; + +public interface RemovableForest extends TraversableForest { + void remove(int x, int y, int z); +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/RemovableMultiForest.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/RemovableMultiForest.java new file mode 100644 index 0000000000..a81c219117 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/RemovableMultiForest.java @@ -0,0 +1,139 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.tree; + +import it.unimi.dsi.fastutil.longs.Long2ReferenceLinkedOpenHashMap; +import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; +import net.caffeinemc.mods.sodium.client.render.chunk.lists.CoordinateSectionVisitor; +import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; +import net.minecraft.core.SectionPos; + +import java.util.Comparator; + +public class RemovableMultiForest implements RemovableForest { + private final Long2ReferenceLinkedOpenHashMap trees; + private final ReferenceArrayList treeSortList = new ReferenceArrayList<>(); + private RemovableTree lastTree; + + // the removable tree separately tracks if it needs to prepared for traversal because it's not just built once, prepared, and then traversed. Since it can receive updates, it needs to be prepared for traversal again and to avoid unnecessary preparation, it tracks whether it's ready. + private boolean treesAreReady = true; + + public RemovableMultiForest(float buildDistance) { + this.trees = new Long2ReferenceLinkedOpenHashMap<>(getCapacity(buildDistance)); + } + + private static int getCapacity(float buildDistance) { + var forestDim = BaseMultiForest.forestDimFromBuildDistance(buildDistance) + 1; + return forestDim * forestDim * forestDim; + } + + public void ensureCapacity(float buildDistance) { + this.trees.ensureCapacity(getCapacity(buildDistance)); + } + + @Override + public void prepareForTraversal() { + if (this.treesAreReady) { + return; + } + + var it = this.trees.values().iterator(); + while (it.hasNext()) { + var tree = it.next(); + tree.prepareForTraversal(); + if (tree.isEmpty()) { + it.remove(); + if (this.lastTree == tree) { + this.lastTree = null; + } + } + } + + this.treesAreReady = true; + } + + @Override + public void traverse(CoordinateSectionVisitor visitor, Viewport viewport, float distanceLimit) { + var transform = viewport.getTransform(); + var cameraSectionX = transform.intX >> 4; + var cameraSectionY = transform.intY >> 4; + var cameraSectionZ = transform.intZ >> 4; + + // sort the trees by distance from the camera by sorting a packed index array. + this.treeSortList.clear(); + this.treeSortList.ensureCapacity(this.trees.size()); + this.treeSortList.addAll(this.trees.values()); + for (var tree : this.treeSortList) { + tree.updateSortKeyFor(cameraSectionX, cameraSectionY, cameraSectionZ); + } + + this.treeSortList.unstableSort(Comparator.comparingInt(RemovableTree::getSortKey)); + + // traverse in sorted front-to-back order for correct render order + for (var tree : this.treeSortList) { + // disable distance test in traversal because we don't use it here + tree.traverse(visitor, viewport, 0, 0); + } + } + + @Override + public void add(int x, int y, int z) { + this.treesAreReady = false; + + if (this.lastTree != null && this.lastTree.add(x, y, z)) { + return; + } + + // get the tree coordinate by dividing by 64 + var treeX = x >> 6; + var treeY = y >> 6; + var treeZ = z >> 6; + + var treeKey = SectionPos.asLong(treeX, treeY, treeZ); + var tree = this.trees.get(treeKey); + + if (tree == null) { + var treeOffsetX = treeX << 6; + var treeOffsetY = treeY << 6; + var treeOffsetZ = treeZ << 6; + tree = new RemovableTree(treeOffsetX, treeOffsetY, treeOffsetZ); + this.trees.put(treeKey, tree); + } + + tree.add(x, y, z); + this.lastTree = tree; + } + + public void remove(int x, int y, int z) { + this.treesAreReady = false; + + if (this.lastTree != null && this.lastTree.remove(x, y, z)) { + return; + } + + // get the tree coordinate by dividing by 64 + var treeX = x >> 6; + var treeY = y >> 6; + var treeZ = z >> 6; + + var treeKey = SectionPos.asLong(treeX, treeY, treeZ); + var tree = this.trees.get(treeKey); + + if (tree == null) { + return; + } + + tree.remove(x, y, z); + + this.lastTree = tree; + } + + public void remove(RenderSection section) { + this.remove(section.getChunkX(), section.getChunkY(), section.getChunkZ()); + } + + @Override + public int getPresence(int x, int y, int z) { + // unused operation on removable trees + throw new UnsupportedOperationException("Not implemented"); + } +} \ No newline at end of file diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/RemovableTree.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/RemovableTree.java new file mode 100644 index 0000000000..4b7cea63e4 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/RemovableTree.java @@ -0,0 +1,69 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.tree; + +import net.minecraft.core.SectionPos; + +public class RemovableTree extends TraversableTree { + private boolean reducedIsValid = true; + private int sortKey; + + public RemovableTree(int offsetX, int offsetY, int offsetZ) { + super(offsetX, offsetY, offsetZ); + } + + public boolean remove(int x, int y, int z) { + x -= this.offsetX; + y -= this.offsetY; + z -= this.offsetZ; + if (Tree.isOutOfBounds(x, y, z)) { + return false; + } + + var bitIndex = Tree.interleave6x3(x, y, z); + this.tree[bitIndex >> 6] &= ~(1L << (bitIndex & 0b111111)); + + this.reducedIsValid = false; + + return true; + } + + @Override + public void prepareForTraversal() { + if (!this.reducedIsValid) { + super.prepareForTraversal(); + this.reducedIsValid = true; + } + } + + @Override + public boolean add(int x, int y, int z) { + var result = super.add(x, y, z); + if (result) { + this.reducedIsValid = false; + } + return result; + } + + public boolean isEmpty() { + return this.treeDoubleReduced == 0; + } + + public long getTreeKey() { + return SectionPos.asLong(this.offsetX, this.offsetY, this.offsetZ); + } + + public void updateSortKeyFor(int cameraSectionX, int cameraSectionY, int cameraSectionZ) { + var deltaX = Math.abs(this.offsetX + 32 - cameraSectionX); + var deltaY = Math.abs(this.offsetY + 32 - cameraSectionY); + var deltaZ = Math.abs(this.offsetZ + 32 - cameraSectionZ); + this.sortKey = deltaX + deltaY + deltaZ + 1; + } + + public int getSortKey() { + return this.sortKey; + } + + @Override + public int getPresence(int i, int i1, int i2) { + throw new UnsupportedOperationException("Not implemented"); + } +} \ No newline at end of file diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableBiForest.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableBiForest.java new file mode 100644 index 0000000000..ae75cc441b --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableBiForest.java @@ -0,0 +1,12 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.tree; + +public class TraversableBiForest extends AbstractTraversableBiForest { + public TraversableBiForest(int baseOffsetX, int baseOffsetY, int baseOffsetZ, float buildDistance) { + super(baseOffsetX, baseOffsetY, baseOffsetZ, buildDistance); + } + + @Override + protected TraversableTree makeTree(int offsetX, int offsetY, int offsetZ) { + return new TraversableTree(offsetX, offsetY, offsetZ); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableForest.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableForest.java new file mode 100644 index 0000000000..bcd38e8595 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableForest.java @@ -0,0 +1,19 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.tree; + +import net.caffeinemc.mods.sodium.client.render.chunk.lists.CoordinateSectionVisitor; +import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; +import net.minecraft.world.level.Level; + +public interface TraversableForest extends Forest { + void prepareForTraversal(); + + void traverse(CoordinateSectionVisitor visitor, Viewport viewport, float distanceLimit); + + static TraversableForest createTraversableForest(int baseOffsetX, int baseOffsetY, int baseOffsetZ, float buildDistance, Level level) { + if (BaseBiForest.checkApplicable(buildDistance, level)) { + return new TraversableBiForest(baseOffsetX, baseOffsetY, baseOffsetZ, buildDistance); + } + + return new TraversableMultiForest(baseOffsetX, baseOffsetY, baseOffsetZ, buildDistance); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableMultiForest.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableMultiForest.java new file mode 100644 index 0000000000..d547655de8 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableMultiForest.java @@ -0,0 +1,17 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.tree; + +public class TraversableMultiForest extends AbstractTraversableMultiForest { + public TraversableMultiForest(int baseOffsetX, int baseOffsetY, int baseOffsetZ, float buildDistance) { + super(baseOffsetX, baseOffsetY, baseOffsetZ, buildDistance); + } + + @Override + protected TraversableTree makeTree(int offsetX, int offsetY, int offsetZ) { + return new TraversableTree(offsetX, offsetY, offsetZ); + } + + @Override + protected TraversableTree[] makeTrees(int length) { + return new TraversableTree[length]; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableTree.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableTree.java new file mode 100644 index 0000000000..841e206376 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableTree.java @@ -0,0 +1,302 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.tree; + +import net.caffeinemc.mods.sodium.client.render.chunk.lists.CoordinateSectionVisitor; +import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.OcclusionCuller; +import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; +import org.joml.FrustumIntersection; + +/** + * A traversable tree is a tree of sections that can be traversed with a distance limit and a frustum. It traverses the sections in visual front-to-back order, so that they can be directly put into a render list. Note however that ordering regions by adding them to the list the first time one of their sections is visited does not yield the correct order. This is because the sections are traversed in visual order, not ordered by distance from the camera. + */ +public class TraversableTree extends Tree { + private static final int INSIDE_FRUSTUM = 0b01; + private static final int INSIDE_DISTANCE = 0b10; + private static final int FULLY_INSIDE = INSIDE_FRUSTUM | INSIDE_DISTANCE; + + protected final long[] treeReduced = new long[64]; + public long treeDoubleReduced = 0L; + + // set temporarily during traversal + private int cameraOffsetX, cameraOffsetY, cameraOffsetZ; + private CoordinateSectionVisitor visitor; + protected Viewport viewport; + private float distanceLimit; + + public TraversableTree(int offsetX, int offsetY, int offsetZ) { + super(offsetX, offsetY, offsetZ); + } + + public void prepareForTraversal() { + long doubleReduced = 0; + for (int i = 0; i < 64; i++) { + long reduced = 0; + var reducedOffset = i << 6; + for (int j = 0; j < 64; j++) { + reduced |= this.tree[reducedOffset + j] == 0 ? 0L : 1L << j; + } + this.treeReduced[i] = reduced; + doubleReduced |= reduced == 0 ? 0L : 1L << i; + } + this.treeDoubleReduced = doubleReduced; + } + + @Override + public int getPresence(int x, int y, int z) { + x -= this.offsetX; + y -= this.offsetY; + z -= this.offsetZ; + if (isOutOfBounds(x, y, z)) { + return OUT_OF_BOUNDS; + } + + var bitIndex = interleave6x3(x, y, z); + int doubleReducedBitIndex = bitIndex >> 12; + if ((this.treeDoubleReduced & (1L << doubleReducedBitIndex)) == 0) { + return NOT_PRESENT; + } + + int reducedBitIndex = bitIndex >> 6; + return (this.tree[reducedBitIndex] & (1L << (bitIndex & 0b111111))) != 0 ? PRESENT : NOT_PRESENT; + } + + public void traverse(CoordinateSectionVisitor visitor, Viewport viewport, float distanceLimit, float buildDistance) { + this.visitor = visitor; + this.viewport = viewport; + this.distanceLimit = distanceLimit; + + // + 1 to offset section position to compensate for shifted global offset + // adjust camera block position to account for fractional part of camera position + var sectionPos = viewport.getChunkCoord(); + this.cameraOffsetX = sectionPos.getX() - this.offsetX + 1; + this.cameraOffsetY = sectionPos.getY() - this.offsetY + 1; + this.cameraOffsetZ = sectionPos.getZ() - this.offsetZ + 1; + + // everything is already inside the distance limit if the build distance is smaller + var initialInside = this.distanceLimit >= buildDistance ? INSIDE_DISTANCE : 0; + this.traverse(getChildOrderModulator(0, 0, 0, 1 << 5), 0, 5, initialInside); + + this.visitor = null; + this.viewport = null; + } + + void traverse(int orderModulator, int nodeOrigin, int level, int inside) { + // half of the dimension of a child of this node, in blocks + int childHalfDim = 1 << (level + 3); // * 16 / 2 + + // odd levels (the higher levels of each reduction) need to modulate indexes that are multiples of 8 + if ((level & 1) == 1) { + orderModulator <<= 3; + } + + if (level <= 1) { + // check using the full bitmap + int childOriginBase = nodeOrigin & 0b111111_111111_000000; + long map = this.tree[nodeOrigin >> 6]; + + if (level == 0) { + int startBit = nodeOrigin & 0b111111; + int endBit = startBit + 8; + + for (int bitIndex = startBit; bitIndex < endBit; bitIndex++) { + int childIndex = bitIndex ^ orderModulator; + if ((map & (1L << childIndex)) != 0) { + int sectionOrigin = childOriginBase | childIndex; + int x = deinterleave6(sectionOrigin) + this.offsetX; + int y = deinterleave6(sectionOrigin >> 1) + this.offsetY; + int z = deinterleave6(sectionOrigin >> 2) + this.offsetZ; + + if (inside == FULLY_INSIDE || testLeafNode(x, y, z, inside)) { + this.visitor.visit(x, y, z); + } + } + } + } else { + for (int bitIndex = 0; bitIndex < 64; bitIndex += 8) { + int childIndex = bitIndex ^ orderModulator; + if ((map & (0xFFL << childIndex)) != 0) { + this.testChild(childOriginBase | childIndex, childHalfDim, level, inside); + } + } + } + } else if (level <= 3) { + int childOriginBase = nodeOrigin & 0b111111_000000_000000; + long map = this.treeReduced[nodeOrigin >> 12]; + + if (level == 2) { + int startBit = (nodeOrigin >> 6) & 0b111111; + int endBit = startBit + 8; + + for (int bitIndex = startBit; bitIndex < endBit; bitIndex++) { + int childIndex = bitIndex ^ orderModulator; + if ((map & (1L << childIndex)) != 0) { + this.testChild(childOriginBase | (childIndex << 6), childHalfDim, level, inside); + } + } + } else { + for (int bitIndex = 0; bitIndex < 64; bitIndex += 8) { + int childIndex = bitIndex ^ orderModulator; + if ((map & (0xFFL << childIndex)) != 0) { + this.testChild(childOriginBase | (childIndex << 6), childHalfDim, level, inside); + } + } + } + } else { + if (level == 4) { + int startBit = nodeOrigin >> 12; + int endBit = startBit + 8; + + for (int bitIndex = startBit; bitIndex < endBit; bitIndex++) { + int childIndex = bitIndex ^ orderModulator; + if ((this.treeDoubleReduced & (1L << childIndex)) != 0) { + this.testChild(childIndex << 12, childHalfDim, level, inside); + } + } + } else { + for (int bitIndex = 0; bitIndex < 64; bitIndex += 8) { + int childIndex = bitIndex ^ orderModulator; + if ((this.treeDoubleReduced & (0xFFL << childIndex)) != 0) { + this.testChild(childIndex << 12, childHalfDim, level, inside); + } + } + } + } + } + + void testChild(int childOrigin, int childHalfDim, int level, int inside) { + // calculate section coordinates in tree-space + int x = deinterleave6(childOrigin); + int y = deinterleave6(childOrigin >> 1); + int z = deinterleave6(childOrigin >> 2); + + // immediately traverse if fully inside + if (inside == FULLY_INSIDE) { + level--; + this.traverse(getChildOrderModulator(x, y, z, 1 << level), childOrigin, level, inside); + return; + } + + // convert to world-space section origin in blocks, then to camera space + var transform = this.viewport.getTransform(); + int worldX = ((x + this.offsetX) << 4) - transform.intX; + int worldY = ((y + this.offsetY) << 4) - transform.intY; + int worldZ = ((z + this.offsetZ) << 4) - transform.intZ; + + boolean visible = true; + + if ((inside & INSIDE_FRUSTUM) == 0) { + var intersectionResult = this.viewport.getBoxIntersectionDirect( + (worldX + childHalfDim) - transform.fracX, + (worldY + childHalfDim) - transform.fracY, + (worldZ + childHalfDim) - transform.fracZ, + childHalfDim + OcclusionCuller.CHUNK_SECTION_MARGIN); + if (intersectionResult == FrustumIntersection.INSIDE) { + inside |= INSIDE_FRUSTUM; + } else { + visible = intersectionResult == FrustumIntersection.INTERSECT; + } + } + + if ((inside & INSIDE_DISTANCE) == 0) { + // calculate the point of the node closest to the camera + int childFullDim = childHalfDim << 1; + float dx = nearestToZero(worldX, worldX + childFullDim) - transform.fracX; + float dy = nearestToZero(worldY, worldY + childFullDim) - transform.fracY; + float dz = nearestToZero(worldZ, worldZ + childFullDim) - transform.fracZ; + + // check if closest point inside the cylinder + visible = cylindricalDistanceTest(dx, dy, dz, this.distanceLimit); + if (visible) { + // if the farthest point is also visible, the node is fully inside + dx = farthestFromZero(worldX, worldX + childFullDim) - transform.fracX; + dy = farthestFromZero(worldY, worldY + childFullDim) - transform.fracY; + dz = farthestFromZero(worldZ, worldZ + childFullDim) - transform.fracZ; + + if (cylindricalDistanceTest(dx, dy, dz, this.distanceLimit)) { + inside |= INSIDE_DISTANCE; + } + } + } + + if (visible) { + level--; + this.traverse(getChildOrderModulator(x, y, z, 1 << level), childOrigin, level, inside); + } + } + + boolean testLeafNode(int x, int y, int z, int inside) { + // input coordinates are section coordinates in world-space + + var transform = this.viewport.getTransform(); + + // convert to blocks and move into integer camera space + x = (x << 4) - transform.intX; + y = (y << 4) - transform.intY; + z = (z << 4) - transform.intZ; + + // test frustum if not already inside frustum + if ((inside & INSIDE_FRUSTUM) == 0 && !this.viewport.isBoxVisibleDirect( + (x + 8) - transform.fracX, + (y + 8) - transform.fracY, + (z + 8) - transform.fracZ, + OcclusionCuller.CHUNK_SECTION_RADIUS)) { + return false; + } + + // test distance if not already inside distance + if ((inside & INSIDE_DISTANCE) == 0) { + // coordinates of the point to compare (in view space) + // this is the closest point within the bounding box to the center (0, 0, 0) + float dx = nearestToZero(x - 1, x + 17) - transform.fracX; + float dy = nearestToZero(y - 1, y + 17) - transform.fracY; + float dz = nearestToZero(z - 1, z + 17) - transform.fracZ; + + return cylindricalDistanceTest(dx, dy, dz, this.distanceLimit); + } + + return true; + } + + static boolean cylindricalDistanceTest(float dx, float dy, float dz, float distanceLimit) { + // vanilla's "cylindrical fog" algorithm + // max(length(distance.xz), abs(distance.y)) + return (((dx * dx) + (dz * dz)) < (distanceLimit * distanceLimit)) && + (Math.abs(dy) < distanceLimit); + } + + @SuppressWarnings("ManualMinMaxCalculation") // we know what we are doing. + private static int nearestToZero(int min, int max) { + // this compiles to slightly better code than Math.min(Math.max(0, min), max) + int clamped = 0; + if (min > 0) { + clamped = min; + } + if (max < 0) { + clamped = max; + } + return clamped; + } + + private static int farthestFromZero(int min, int max) { + int clamped = 0; + if (min > 0) { + clamped = max; + } + if (max < 0) { + clamped = min; + } + if (clamped == 0) { + if (Math.abs(min) > Math.abs(max)) { + clamped = min; + } else { + clamped = max; + } + } + return clamped; + } + + int getChildOrderModulator(int x, int y, int z, int childFullSectionDim) { + return (x + childFullSectionDim - this.cameraOffsetX) >>> 31 + | ((y + childFullSectionDim - this.cameraOffsetY) >>> 31) << 1 + | ((z + childFullSectionDim - this.cameraOffsetZ) >>> 31) << 2; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/Tree.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/Tree.java new file mode 100644 index 0000000000..9128db2721 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/Tree.java @@ -0,0 +1,54 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.tree; + +public abstract class Tree { + public static final int OUT_OF_BOUNDS = -1; + public static final int NOT_PRESENT = 0; + public static final int PRESENT = 1; + + protected final long[] tree = new long[64 * 64]; + protected final int offsetX, offsetY, offsetZ; + + public Tree(int offsetX, int offsetY, int offsetZ) { + this.offsetX = offsetX; + this.offsetY = offsetY; + this.offsetZ = offsetZ; + } + + public static boolean isOutOfBounds(int x, int y, int z) { + return x > 63 || y > 63 || z > 63 || x < 0 || y < 0 || z < 0; + } + + protected static int interleave6x3(int x, int y, int z) { + return Tree.interleave6(x) | Tree.interleave6(y) << 1 | Tree.interleave6(z) << 2; + } + + private static int interleave6(int n) { + n &= 0b000000000000111111; + n = (n | n << 4 | n << 8) & 0b000011000011000011; + n = (n | n << 2) & 0b001001001001001001; + return n; + } + + protected static int deinterleave6(int n) { + n &= 0b001001001001001001; + n = (n | n >> 2) & 0b000011000011000011; + n = (n | n >> 4 | n >> 8) & 0b000000000000111111; + return n; + } + + public boolean add(int x, int y, int z) { + x -= this.offsetX; + y -= this.offsetY; + z -= this.offsetZ; + if (Tree.isOutOfBounds(x, y, z)) { + return false; + } + + var bitIndex = Tree.interleave6x3(x, y, z); + this.tree[bitIndex >> 6] |= 1L << (bitIndex & 0b111111); + + return true; + } + + public abstract int getPresence(int x, int y, int z); +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java index be9521ebad..999310be3c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java @@ -41,6 +41,30 @@ public boolean isBoxVisible(int intOriginX, int intOriginY, int intOriginZ, floa ); } + public boolean isBoxVisibleDirect(float floatOriginX, float floatOriginY, float floatOriginZ, float floatSize) { + return this.frustum.testAab( + floatOriginX - floatSize, + floatOriginY - floatSize, + floatOriginZ - floatSize, + + floatOriginX + floatSize, + floatOriginY + floatSize, + floatOriginZ + floatSize + ); + } + + public int getBoxIntersectionDirect(float floatOriginX, float floatOriginY, float floatOriginZ, float floatSize) { + return this.frustum.intersectAab( + floatOriginX - floatSize, + floatOriginY - floatSize, + floatOriginZ - floatSize, + + floatOriginX + floatSize, + floatOriginY + floatSize, + floatOriginZ + floatSize + ); + } + public CameraTransform getTransform() { return this.transform; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/Frustum.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/Frustum.java index 3ec8d16aa6..c7495555dd 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/Frustum.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/Frustum.java @@ -2,4 +2,6 @@ public interface Frustum { boolean testAab(float minX, float minY, float minZ, float maxX, float maxY, float maxZ); + + int intersectAab(float minX, float minY, float minZ, float maxX, float maxY, float maxZ); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java index 88ff3b7738..2ada093971 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java @@ -13,4 +13,9 @@ public SimpleFrustum(FrustumIntersection frustumIntersection) { public boolean testAab(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) { return this.frustum.testAab(minX, minY, minZ, maxX, maxY, maxZ); } + + @Override + public int intersectAab(float minX, float minY, float minZ, float maxX, float maxY, float maxZ) { + return this.frustum.intersectAab(minX, minY, minZ, maxX, maxY, maxZ); + } } From cf2b2ed7f4c48c9315b4b6632ca3182f4cd8b3b9 Mon Sep 17 00:00:00 2001 From: douira Date: Sat, 31 May 2025 02:56:46 +0200 Subject: [PATCH 111/215] Remove check for cancellation token when starting a section task We don't want to wait for the currently running task to complete before scheduling another task for this section, because it has been marked as needing another update since the currently running task on it was dispatched. Also, clear the pending updates from empty sections which are scheduled for sorting. --- .../mods/sodium/client/render/chunk/RenderSectionManager.java | 1 + .../mods/sodium/client/render/chunk/lists/SectionCollector.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index 22bd959245..4dd0135d5e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -569,6 +569,7 @@ private long submitSectionTask(ChunkJobCollector collector, @NotNull RenderSecti if (task == null) { // when a sort task is null it means the render section has no dynamic data and // doesn't need to be sorted. Nothing needs to be done. + section.clearPendingUpdate(); return 0; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java index 1a60d01e73..2a53d43ac9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java @@ -50,7 +50,7 @@ public void visit(RenderSection section) { // always add to rebuild lists though, because it might just not be built yet var pendingUpdate = section.getPendingUpdate(); - if (pendingUpdate != 0 && section.getTaskCancellationToken() == null) { + if (pendingUpdate != 0) { var queueType = ChunkUpdateTypes.getQueueType(pendingUpdate, this.importantRebuildQueueType); Queue queue = this.sortedTaskLists.get(queueType); From f918e7dcc791301ee7b9aa66b992f6da607f2520 Mon Sep 17 00:00:00 2001 From: douira Date: Wed, 11 Jun 2025 00:04:54 +0200 Subject: [PATCH 112/215] Ensure render graph invalidation when a section's scheduled task type changes --- .../mods/sodium/client/render/chunk/RenderSectionManager.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index 4dd0135d5e..24f1a2e546 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -673,6 +673,9 @@ private boolean upgradePendingUpdate(RenderSection section, int updateType) { } section.setPendingUpdate(joined, this.lastFrameAtTime); + + // mark graph as dirty so that it picks up the section's pending task + this.markGraphDirty(); return true; } From 2ef0a8481ec0b16f3bfc39f76135b8a9f580b701 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 22 Jun 2025 16:41:09 +0200 Subject: [PATCH 113/215] Fix some ancient race conditions with task scheduling Tasks could end up not being picked up if a task is scheduled for a section while another one is already running, but the camera is stationary as to not trigger a graph update. --- .../client/render/chunk/RenderSectionManager.java | 15 +++++++++------ .../render/chunk/lists/RenderListProvider.java | 2 ++ .../render/chunk/lists/SectionCollector.java | 14 ++++++++++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index 24f1a2e546..d5defba437 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -104,7 +104,7 @@ public class RenderSectionManager { private long lastFrameAtTime = System.nanoTime(); private static final float FRAME_DURATION_UPDATE_RATIO = 0.05f; - private boolean needsGraphUpdate; + private boolean needsGraphUpdate = true; private int lastUpdatedFrame; private @Nullable Vector3dc cameraPosition; @@ -117,7 +117,6 @@ public RenderSectionManager(ClientLevel level, int renderDistance, CommandList c this.level = level; this.builder = new ChunkBuilder(level, ChunkMeshFormats.COMPACT); - this.needsGraphUpdate = true; this.renderDistance = renderDistance; this.sortTriggering = new SortTriggering(); @@ -156,12 +155,10 @@ public void prepareFrame(Vector3dc cameraPosition) { public void update(Camera camera, Viewport viewport, boolean spectator) { this.lastUpdatedFrame += 1; - this.createTerrainRenderList(camera, viewport, this.lastUpdatedFrame, spectator); - - this.needsGraphUpdate = false; + this.needsGraphUpdate = this.createTerrainRenderList(camera, viewport, this.lastUpdatedFrame, spectator); } - private void createTerrainRenderList(Camera camera, Viewport viewport, int frame, boolean spectator) { + private boolean createTerrainRenderList(Camera camera, Viewport viewport, int frame, boolean spectator) { this.resetRenderLists(); final var searchDistance = this.getSearchDistance(); @@ -184,6 +181,12 @@ private void createTerrainRenderList(Camera camera, Viewport viewport, int frame this.renderLists = renderListProvider.createRenderLists(viewport); this.taskLists = renderListProvider.getTaskLists(); + + // when there were sections with pending updates that were skipped because they already had a task running, + // it needs to revisit them to schedule the remaining pending updates. + // since not all tasks necessarily change the section info to trigger a graph update, + // without this pending updates might be missed when the camera is stationary + return renderListProvider.needsRevisitForPendingUpdates(); } private boolean isOutOfGraph(SectionPos pos) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/RenderListProvider.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/RenderListProvider.java index 4c0a428ecc..243fe21ef1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/RenderListProvider.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/RenderListProvider.java @@ -14,6 +14,8 @@ public interface RenderListProvider extends SortItemsProvider { ObjectArrayList getUnsortedRenderLists(); Map> getTaskLists(); + + boolean needsRevisitForPendingUpdates(); boolean orderIsSorted(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java index 2a53d43ac9..759032759a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java @@ -16,6 +16,7 @@ public abstract class SectionCollector implements RenderListProvider, RenderSect private final TaskQueueType importantRebuildQueueType; private final ObjectArrayList renderLists; private final EnumMap> sortedTaskLists; + private boolean needsRevisitForPendingUpdates = false; private static int[] sortItems = new int[RenderRegion.REGION_SIZE]; @@ -51,6 +52,14 @@ public void visit(RenderSection section) { var pendingUpdate = section.getPendingUpdate(); if (pendingUpdate != 0) { + // if the section has a pending update but a task is already running for it, + // don't add it to the task list again because starting a new task when there's already one running is invalid. + // (for example, it would become impossible to cancel the earlier task) + if (section.getTaskCancellationToken() != null) { + this.needsRevisitForPendingUpdates = true; + return; + } + var queueType = ChunkUpdateTypes.getQueueType(pendingUpdate, this.importantRebuildQueueType); Queue queue = this.sortedTaskLists.get(queueType); @@ -70,6 +79,11 @@ public Map> getTaskLists() { return this.sortedTaskLists; } + @Override + public boolean needsRevisitForPendingUpdates() { + return this.needsRevisitForPendingUpdates; + } + @Override public void setCachedSortItems(int[] sortItems) { SectionCollector.sortItems = sortItems; From 8909a9d34d2b5324e7f30863fa08b0757fab2183 Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 23 Jun 2025 04:09:26 +0200 Subject: [PATCH 114/215] Use sort task result size for task size estimation for consistency --- .../chunk/compile/estimation/Linear2DEstimator.java | 8 +++++++- .../chunk/compile/tasks/ChunkBuilderSortingTask.java | 2 +- .../chunk/translucent_sorting/data/DynamicSorter.java | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Linear2DEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Linear2DEstimator.java index 159d08f067..5a6ba9b31e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Linear2DEstimator.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Linear2DEstimator.java @@ -4,7 +4,13 @@ import java.util.Locale; -public abstract class Linear2DEstimator extends Estimator, Linear2DEstimator.LinearRegressionBatch, Long, Long, Linear2DEstimator.LinearFunction> { +public abstract class Linear2DEstimator extends Estimator< + C, + Linear2DEstimator.DataPair, + Linear2DEstimator.LinearRegressionBatch, + Long, + Long, + Linear2DEstimator.LinearFunction> { private final float newDataRatio; private final int initialSampleTarget; private final long initialOutput; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderSortingTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderSortingTask.java index c405c91b37..ae58d4e33d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderSortingTask.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderSortingTask.java @@ -35,6 +35,6 @@ public static ChunkBuilderSortingTask createTask(RenderSection render, int frame @Override public long estimateTaskSizeWith(MeshTaskSizeEstimator estimator) { - return this.sorter.getQuadCount(); + return this.sorter.getResultSize(); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java index 33988489e4..f58bdf14cb 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicSorter.java @@ -18,4 +18,8 @@ public void writeIndexBuffer(CombinedCameraPos cameraPos, boolean initial) { public int getQuadCount() { return this.quadCount; } + + public int getResultSize() { + return TranslucentData.quadCountToIndexBytes(this.quadCount); + } } From 4caf5eca828912a67a686edb1b095300198278d0 Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 23 Jun 2025 20:40:25 +0200 Subject: [PATCH 115/215] Implement task scheduling estimation Estimate the cost of uploading chunks and limit task scheduling rates using both the "duration" budget (max time to be spent uploading chunks each frame) and "size" budget (max bytes to be uploaded per frame) to improve frame time consistency. --- .../arena/staging/FallbackStagingBuffer.java | 4 +- .../render/chunk/RenderSectionManager.java | 76 +++++++----- .../client/render/chunk/TaskQueueType.java | 2 +- .../estimation/Abstract2DLinearEstimator.java | 61 ++++++++++ .../estimation/Average1DEstimator.java | 6 +- .../chunk/compile/estimation/Estimator.java | 15 +-- ...or.java => ExpDecayLinear2DEstimator.java} | 95 ++++++--------- .../estimation/JobDurationEstimator.java | 5 +- .../chunk/compile/estimation/JobEffort.java | 2 +- .../estimation/LimitedResourceBudget.java | 22 ++++ .../estimation/UnlimitedResourceBudget.java | 15 +++ .../compile/estimation/UploadDuration.java | 18 +++ .../estimation/UploadDurationEstimator.java | 112 ++++++++++++++++++ .../estimation/UploadResourceBudget.java | 7 ++ .../chunk/compile/executor/ChunkJob.java | 2 + .../chunk/compile/executor/ChunkJobTyped.java | 5 + .../chunk/compile/tasks/ChunkBuilderTask.java | 9 +- 17 files changed, 347 insertions(+), 109 deletions(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Abstract2DLinearEstimator.java rename common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/{Linear2DEstimator.java => ExpDecayLinear2DEstimator.java} (59%) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/LimitedResourceBudget.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UnlimitedResourceBudget.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadDuration.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadDurationEstimator.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadResourceBudget.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/FallbackStagingBuffer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/FallbackStagingBuffer.java index 0230e6f82b..28d0ea26fe 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/FallbackStagingBuffer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/staging/FallbackStagingBuffer.java @@ -8,8 +8,6 @@ import java.nio.ByteBuffer; public class FallbackStagingBuffer implements StagingBuffer { - private static final float BYTES_PER_NANO_LIMIT = 8_000_000.0f / (1_000_000_000.0f / 60.0f); // MB per frame at 60fps - private final GlMutableBuffer fallbackBufferObject; public FallbackStagingBuffer(CommandList commandList) { @@ -44,6 +42,6 @@ public String toString() { @Override public long getUploadSizeLimit(long frameDuration) { - return (long) (frameDuration * BYTES_PER_NANO_LIMIT); + return Long.MAX_VALUE; // No limit for fallback buffer since time-liming takes care of it } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index d5defba437..84383348af 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -12,9 +12,7 @@ import net.caffeinemc.mods.sodium.client.render.chunk.compile.BuilderTaskOutput; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildOutput; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkSortOutput; -import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.JobDurationEstimator; -import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshResultSize; -import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshTaskSizeEstimator; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.*; import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkBuilder; import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkJobCollector; import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkJobResult; @@ -64,6 +62,9 @@ public class RenderSectionManager { private static final float NEARBY_REBUILD_DISTANCE = Mth.square(16.0f); private static final float NEARBY_SORT_DISTANCE = Mth.square(25.0f); + + private static final float FRAME_DURATION_UPLOAD_FRACTION = 0.1f; + private static final long MIN_UPLOAD_DURATION_BUDGET = 2_000_000L; // 2ms private final ChunkBuilder builder; @@ -75,6 +76,7 @@ public class RenderSectionManager { private final ConcurrentLinkedDeque> buildResults = new ConcurrentLinkedDeque<>(); private final JobDurationEstimator jobDurationEstimator = new JobDurationEstimator(); private final MeshTaskSizeEstimator meshTaskSizeEstimator = new MeshTaskSizeEstimator(); + private final UploadDurationEstimator jobUploadDurationEstimator = new UploadDurationEstimator(); private ChunkJobCollector lastBlockingCollector; private int thisFrameBlockingTasks; private int nextFrameBlockingTasks; @@ -365,15 +367,19 @@ public void uploadChunks() { private boolean processChunkBuildResults(ArrayList results) { var filtered = filterChunkBuildResults(results); + var start = System.nanoTime(); this.regions.uploadResults(RenderDevice.INSTANCE.createCommandList(), filtered); + var uploadDuration = System.nanoTime() - start; boolean touchedSectionInfo = false; + long totalUploadSize = 0; for (var result : filtered) { + var resultSize = result.getResultSize(); + TranslucentData oldData = result.render.getTranslucentData(); if (result instanceof ChunkBuildOutput chunkBuildOutput) { touchedSectionInfo |= this.updateSectionInfo(result.render, chunkBuildOutput.info); - var resultSize = chunkBuildOutput.getResultSize(); result.render.setLastMeshResultSize(resultSize); this.meshTaskSizeEstimator.addData(MeshResultSize.forSection(result.render, resultSize)); @@ -398,10 +404,20 @@ private boolean processChunkBuildResults(ArrayList results) { } result.render.setLastUploadFrame(result.submitTime); + + totalUploadSize += resultSize; } this.meshTaskSizeEstimator.updateModels(); + // insert and update the upload duration estimator with the total upload size, + // since we don't know which task took how long and the time it takes to upload is not independent between tasks + // we take the average size and duration + if (!filtered.isEmpty()) { + this.jobUploadDurationEstimator.addData(new UploadDuration(uploadDuration / filtered.size(), totalUploadSize / filtered.size())); + this.jobUploadDurationEstimator.updateModels(); + } + return touchedSectionInfo; } @@ -478,13 +494,18 @@ public void updateChunks(boolean updateImmediately) { if (updateImmediately) { // for a perfect frame where everything is finished use the last frame's blocking collector // and add all tasks to it so that they're waited on - this.submitSectionTasks(thisFrameBlockingCollector, thisFrameBlockingCollector, thisFrameBlockingCollector, Long.MAX_VALUE); + this.submitSectionTasks(thisFrameBlockingCollector, thisFrameBlockingCollector, thisFrameBlockingCollector, UnlimitedResourceBudget.INSTANCE); this.thisFrameBlockingTasks = thisFrameBlockingCollector.getSubmittedTaskCount(); thisFrameBlockingCollector.awaitCompletion(this.builder); } else { var remainingDuration = this.builder.getTotalRemainingDuration(this.averageFrameDuration); - var remainingUploadSize = this.regions.getStagingBuffer().getUploadSizeLimit(this.averageFrameDuration); + + // an estimator is used estimate task duration and limit the execution time to the available worker capacity. + // separately, tasks are limited by their estimated upload size and duration. + var uploadBudget = new LimitedResourceBudget( + Math.max((long)(this.averageFrameDuration * FRAME_DURATION_UPLOAD_FRACTION), MIN_UPLOAD_DURATION_BUDGET), + this.regions.getStagingBuffer().getUploadSizeLimit(this.averageFrameDuration)); var nextFrameBlockingCollector = new ChunkJobCollector(this.buildResults::add); var deferredCollector = new ChunkJobCollector(remainingDuration, this.buildResults::add); @@ -492,9 +513,9 @@ public void updateChunks(boolean updateImmediately) { // if zero frame delay is allowed, submit important sorts with the current frame blocking collector. // otherwise submit with the collector that the next frame is blocking on. if (SodiumClientMod.options().debug.getSortBehavior().getDeferMode() == DeferMode.ZERO_FRAMES) { - this.submitSectionTasks(thisFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector, remainingUploadSize); + this.submitSectionTasks(thisFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector, uploadBudget); } else { - this.submitSectionTasks(nextFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector, remainingUploadSize); + this.submitSectionTasks(nextFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector, uploadBudget); } this.thisFrameBlockingTasks = thisFrameBlockingCollector.getSubmittedTaskCount(); @@ -511,17 +532,17 @@ public void updateChunks(boolean updateImmediately) { } private void submitSectionTasks( - ChunkJobCollector importantCollector, ChunkJobCollector semiImportantCollector, ChunkJobCollector deferredCollector, long remainingUploadSize) { - remainingUploadSize = submitSectionTasks(importantCollector, remainingUploadSize, TaskQueueType.ZERO_FRAME_DEFER); - remainingUploadSize = submitSectionTasks(semiImportantCollector, remainingUploadSize, TaskQueueType.ONE_FRAME_DEFER); - remainingUploadSize = submitSectionTasks(deferredCollector, remainingUploadSize, TaskQueueType.ALWAYS_DEFER); - submitSectionTasks(deferredCollector, remainingUploadSize, TaskQueueType.INITIAL_BUILD); + ChunkJobCollector importantCollector, ChunkJobCollector semiImportantCollector, ChunkJobCollector deferredCollector, UploadResourceBudget uploadBudget) { + submitSectionTasks(importantCollector, uploadBudget, TaskQueueType.ZERO_FRAME_DEFER); + submitSectionTasks(semiImportantCollector, uploadBudget, TaskQueueType.ONE_FRAME_DEFER); + submitSectionTasks(deferredCollector, uploadBudget, TaskQueueType.ALWAYS_DEFER); + submitSectionTasks(deferredCollector, uploadBudget, TaskQueueType.INITIAL_BUILD); } - private long submitSectionTasks(ChunkJobCollector collector, long remainingUploadSize, TaskQueueType queueType) { + private void submitSectionTasks(ChunkJobCollector collector, UploadResourceBudget uploadBudget, TaskQueueType queueType) { var taskList = this.taskLists.get(queueType); - while (!taskList.isEmpty() && (remainingUploadSize > 0 || queueType.allowsUnlimitedUploadSize())) { + while (!taskList.isEmpty() && (uploadBudget.isAvailable() || queueType.allowsUnlimitedUploadDuration())) { RenderSection section = taskList.poll(); if (section == null) { @@ -533,16 +554,14 @@ private long submitSectionTasks(ChunkJobCollector collector, long remainingUploa // sections for which there's a currently running task. var pendingUpdate = section.getPendingUpdate(); if (pendingUpdate != 0) { - remainingUploadSize -= submitSectionTask(collector, section, pendingUpdate); + submitSectionTask(collector, section, pendingUpdate, uploadBudget); } } - - return remainingUploadSize; } - private long submitSectionTask(ChunkJobCollector collector, @NotNull RenderSection section, int type) { + private void submitSectionTask(ChunkJobCollector collector, @NotNull RenderSection section, int type, UploadResourceBudget uploadBudget) { if (section.isDisposed()) { - return 0; + return; } ChunkBuilderTask task; @@ -573,22 +592,22 @@ private long submitSectionTask(ChunkJobCollector collector, @NotNull RenderSecti // when a sort task is null it means the render section has no dynamic data and // doesn't need to be sorted. Nothing needs to be done. section.clearPendingUpdate(); - return 0; + return; } } - var estimatedTaskSize = 0L; if (task != null) { var job = this.builder.scheduleTask(task, ChunkUpdateTypes.isImportant(type), collector::onJobFinished); collector.addSubmittedJob(job); - estimatedTaskSize = job.getEstimatedSize(); + + // consume upload budget in size and duration using estimates + uploadBudget.consume(job.getEstimatedUploadDuration(), job.getEstimatedSize()); section.setTaskCancellationToken(job); } section.setLastSubmittedFrame(this.frame); section.clearPendingUpdate(); - return estimatedTaskSize; } public @Nullable ChunkBuilderMeshingTask createRebuildTask(RenderSection render, int frame) { @@ -599,14 +618,14 @@ private long submitSectionTask(ChunkJobCollector collector, @NotNull RenderSecti } var task = new ChunkBuilderMeshingTask(render, frame, this.cameraPosition, context, ChunkUpdateTypes.isRebuildWithSort(render.getPendingUpdate())); - task.calculateEstimations(this.jobDurationEstimator, this.meshTaskSizeEstimator); + task.calculateEstimations(this.jobDurationEstimator, this.meshTaskSizeEstimator, this.jobUploadDurationEstimator); return task; } public ChunkBuilderSortingTask createSortTask(RenderSection render, int frame) { var task = ChunkBuilderSortingTask.createTask(render, frame, this.cameraPosition); if (task != null) { - task.calculateEstimations(this.jobDurationEstimator, this.meshTaskSizeEstimator); + task.calculateEstimations(this.jobDurationEstimator, this.meshTaskSizeEstimator, this.jobUploadDurationEstimator); } return task; } @@ -676,7 +695,7 @@ private boolean upgradePendingUpdate(RenderSection section, int updateType) { } section.setPendingUpdate(joined, this.lastFrameAtTime); - + // mark graph as dirty so that it picks up the section's pending task this.markGraphDirty(); @@ -817,7 +836,8 @@ public Collection getDebugStrings() { if (PlatformRuntimeInformation.getInstance().isDevelopmentEnvironment()) { var meshTaskParameters = this.jobDurationEstimator.toString(ChunkBuilderMeshingTask.class); var sortTaskParameters = this.jobDurationEstimator.toString(ChunkBuilderSortingTask.class); - list.add(String.format("Duration: Mesh %s, Sort %s", meshTaskParameters, sortTaskParameters)); + var uploadDurationParameters = this.jobUploadDurationEstimator.toString(null); + list.add(String.format("Duration: Mesh %s, Sort %s, Upload %s", meshTaskParameters, sortTaskParameters, uploadDurationParameters)); var sizeEstimates = new ReferenceArrayList(); for (var type : MeshResultSize.SectionCategory.values()) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/TaskQueueType.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/TaskQueueType.java index 0dddde8fa1..648bf05b5d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/TaskQueueType.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/TaskQueueType.java @@ -6,7 +6,7 @@ public enum TaskQueueType { ALWAYS_DEFER, INITIAL_BUILD; - public boolean allowsUnlimitedUploadSize() { + public boolean allowsUnlimitedUploadDuration() { return this == ZERO_FRAME_DEFER; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Abstract2DLinearEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Abstract2DLinearEstimator.java new file mode 100644 index 0000000000..2ec1bdeebc --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Abstract2DLinearEstimator.java @@ -0,0 +1,61 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +import java.util.Locale; + +public abstract class Abstract2DLinearEstimator< + C, + TBatch extends Estimator.DataBatch>, + TModel extends Abstract2DLinearEstimator.LinearFunction + > extends Estimator< + C, + Abstract2DLinearEstimator.DataPair, + TBatch, + Long, + Long, + TModel> { + protected final long initialOutput; + + public Abstract2DLinearEstimator(long initialOutput) { + this.initialOutput = initialOutput; + } + + public interface DataPair extends DataPoint { + long x(); + + long y(); + } + + protected abstract static class LinearRegressionBatch extends ObjectArrayList> implements DataBatch> { + @Override + public void addDataPoint(DataPair input) { + this.add(input); + } + } + + protected abstract static class LinearFunction>> implements Model { + protected final long initialOutput; + protected float yIntercept; + protected float slope; + protected int gatheredSamples = 0; + + public LinearFunction(long initialOutput) { + this.initialOutput = initialOutput; + } + + @Override + public Long predict(Long input) { + if (this.gatheredSamples == 0) { + return this.initialOutput; + } + + return (long) (this.yIntercept + this.slope * input); + } + + @Override + public String toString() { + return String.format(Locale.US, "s=%.2f,y=%.0f", this.slope, this.yIntercept); + } + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Average1DEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Average1DEstimator.java index 244f9a5fe4..7816e180a1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Average1DEstimator.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Average1DEstimator.java @@ -43,7 +43,7 @@ protected ValueBatch createNewDataBatch() { return new ValueBatch<>(); } - protected static class Average implements Estimator.Model, Average> { + protected static class Average implements Estimator.Model> { private final float newDataRatio; private boolean hasRealData = false; private float average; @@ -54,7 +54,7 @@ public Average(float newDataRatio, float initialValue) { } @Override - public Average update(ValueBatch batch) { + public void update(ValueBatch batch) { if (batch.count > 0) { if (this.hasRealData) { this.average = MathUtil.exponentialMovingAverage(this.average, batch.getAverage(), this.newDataRatio); @@ -63,8 +63,6 @@ public Average update(ValueBatch batch) { this.hasRealData = true; } } - - return this; } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Estimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Estimator.java index 04c275c09d..c7a475075d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Estimator.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Estimator.java @@ -18,7 +18,7 @@ public abstract class Estimator< TBatch extends Estimator.DataBatch, TInput, TOutput, - TModel extends Estimator.Model> { + TModel extends Estimator.Model> { protected final Map models = createMap(); protected final Map batches = createMap(); @@ -32,8 +32,8 @@ protected interface DataPoint { TPointCategory category(); } - protected interface Model> { - TModelSelf update(TModelBatch batch); + protected interface Model { + void update(TModelBatch batch); TModelOutput predict(TModelInput input); } @@ -65,14 +65,7 @@ private TModel ensureModel(TCategory category) { public void updateModels() { this.batches.forEach((category, aggregator) -> { - var oldModel = this.ensureModel(category); - - // update the model and store it back if it returned a new model - var newModel = oldModel.update(aggregator); - if (newModel != oldModel) { - this.models.put(category, newModel); - } - + this.ensureModel(category).update(aggregator); aggregator.reset(); }); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Linear2DEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/ExpDecayLinear2DEstimator.java similarity index 59% rename from common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Linear2DEstimator.java rename to common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/ExpDecayLinear2DEstimator.java index 5a6ba9b31e..c78a22a7a4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Linear2DEstimator.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/ExpDecayLinear2DEstimator.java @@ -1,75 +1,70 @@ package net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; - -import java.util.Locale; - -public abstract class Linear2DEstimator extends Estimator< +public abstract class ExpDecayLinear2DEstimator extends Abstract2DLinearEstimator< C, - Linear2DEstimator.DataPair, - Linear2DEstimator.LinearRegressionBatch, - Long, - Long, - Linear2DEstimator.LinearFunction> { + ExpDecayLinear2DEstimator.ClearingLinearRegressionBatch, + ExpDecayLinear2DEstimator.ExpDecayLinearFunction> { private final float newDataRatio; private final int initialSampleTarget; - private final long initialOutput; + private final int minBatchSize; - public Linear2DEstimator(float newDataRatio, int initialSampleTarget, long initialOutput) { + public ExpDecayLinear2DEstimator(float newDataRatio, int initialSampleTarget, int minBatchSize, long initialOutput) { + super(initialOutput); this.newDataRatio = newDataRatio; this.initialSampleTarget = initialSampleTarget; - this.initialOutput = initialOutput; + this.minBatchSize = minBatchSize; } - public interface DataPair extends DataPoint { - long x(); - - long y(); - } - - protected static class LinearRegressionBatch extends ObjectArrayList> implements Estimator.DataBatch> { - @Override - public void addDataPoint(DataPair input) { - this.add(input); - } + protected static class ClearingLinearRegressionBatch extends Abstract2DLinearEstimator.LinearRegressionBatch { + boolean deferredClear = false; @Override public void reset() { - this.clear(); + if (!this.deferredClear) { + this.clear(); + } + this.deferredClear = false; + } + + private boolean checkUpdateDefer(int minBatchSize) { + if (this.size() < minBatchSize) { + this.deferredClear = true; + return true; + } + return false; } } @Override - protected LinearRegressionBatch createNewDataBatch() { - return new LinearRegressionBatch<>(); + protected ClearingLinearRegressionBatch createNewDataBatch() { + return new ClearingLinearRegressionBatch<>(); } - protected static class LinearFunction implements Model, LinearFunction> { + protected static class ExpDecayLinearFunction extends Abstract2DLinearEstimator.LinearFunction< + C, + ExpDecayLinear2DEstimator.ClearingLinearRegressionBatch> { // the maximum fraction of the total weight that new data can have private final float newDataRatioInv; // how many samples we want to have at least before we start diminishing the new data's weight private final int initialSampleTarget; - private final long initialOutput; - - private float yIntercept; - private float slope; + private final int minBatchSize; - private int gatheredSamples = 0; private float xMeanOld = 0; private float yMeanOld = 0; private float covarianceOld = 0; private float varianceOld = 0; - public LinearFunction(float newDataRatio, int initialSampleTarget, long initialOutput) { + public ExpDecayLinearFunction(float newDataRatio, int initialSampleTarget, int minBatchSize, long initialOutput) { + super(initialOutput); this.newDataRatioInv = 1.0f / newDataRatio; this.initialSampleTarget = initialSampleTarget; - this.initialOutput = initialOutput; + this.minBatchSize = minBatchSize; } @Override - public LinearFunction update(LinearRegressionBatch batch) { - if (batch.isEmpty()) { - return this; + public void update(ClearingLinearRegressionBatch batch) { + if (batch.isEmpty() || batch.checkUpdateDefer(this.minBatchSize)) { + return; } // condition the weight to gather at least the initial sample target, and then weight the new data with a ratio @@ -109,7 +104,7 @@ public LinearFunction update(LinearRegressionBatch batch) { } if (varianceSum == 0) { - return this; + return; } covarianceSum += this.covarianceOld * oldDataWeight; @@ -118,32 +113,16 @@ public LinearFunction update(LinearRegressionBatch batch) { // negative slopes are clamped to produce a flat line if necessary this.slope = Math.max(0, covarianceSum / varianceSum); this.yIntercept = yMean - this.slope * xMean; - + this.xMeanOld = xMean; this.yMeanOld = yMean; this.covarianceOld = covarianceSum * totalWeightInv; this.varianceOld = varianceSum * totalWeightInv; - - return this; - } - - @Override - public Long predict(Long input) { - if (this.gatheredSamples == 0) { - return this.initialOutput; - } - - return (long) (this.yIntercept + this.slope * input); - } - - @Override - public String toString() { - return String.format(Locale.US, "s=%.2f,y=%.0f", this.slope, this.yIntercept); } } @Override - protected LinearFunction createNewModel() { - return new LinearFunction<>(this.newDataRatio, this.initialSampleTarget, this.initialOutput); + protected ExpDecayLinearFunction createNewModel() { + return new ExpDecayLinearFunction<>(this.newDataRatio, this.initialSampleTarget, this.minBatchSize, this.initialOutput); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobDurationEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobDurationEstimator.java index 0f83a902c9..013551ddc5 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobDurationEstimator.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobDurationEstimator.java @@ -4,13 +4,14 @@ import java.util.Map; -public class JobDurationEstimator extends Linear2DEstimator> { +public class JobDurationEstimator extends ExpDecayLinear2DEstimator> { public static final int INITIAL_SAMPLE_TARGET = 100; public static final float NEW_DATA_RATIO = 0.05f; + private static final int MIN_BATCH_SIZE = 40; private static final long INITIAL_JOB_DURATION_ESTIMATE = 5_000_000L; // 5ms public JobDurationEstimator() { - super(NEW_DATA_RATIO, INITIAL_SAMPLE_TARGET, INITIAL_JOB_DURATION_ESTIMATE); + super(NEW_DATA_RATIO, INITIAL_SAMPLE_TARGET, MIN_BATCH_SIZE, INITIAL_JOB_DURATION_ESTIMATE); } public long estimateJobDuration(Class jobType, long effort) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobEffort.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobEffort.java index 2a35dda343..91da3a8ed1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobEffort.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobEffort.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation; -public record JobEffort(Class category, long duration, long effort) implements Linear2DEstimator.DataPair> { +public record JobEffort(Class category, long duration, long effort) implements ExpDecayLinear2DEstimator.DataPair> { public static JobEffort untilNowWithEffort(Class effortType, long start, long effort) { return new JobEffort(effortType,System.nanoTime() - start, effort); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/LimitedResourceBudget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/LimitedResourceBudget.java new file mode 100644 index 0000000000..f00b6cbfea --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/LimitedResourceBudget.java @@ -0,0 +1,22 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation; + +public class LimitedResourceBudget implements UploadResourceBudget { + private long duration; + private long size; + + public LimitedResourceBudget(long duration, long size) { + this.duration = duration; + this.size = size; + } + + @Override + public boolean isAvailable() { + return this.duration > 0 && this.size > 0; + } + + @Override + public void consume(long duration, long size) { + this.duration -= duration; + this.size -= size; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UnlimitedResourceBudget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UnlimitedResourceBudget.java new file mode 100644 index 0000000000..988916b435 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UnlimitedResourceBudget.java @@ -0,0 +1,15 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation; + +public class UnlimitedResourceBudget implements UploadResourceBudget { + public static final UnlimitedResourceBudget INSTANCE = new UnlimitedResourceBudget(); + + @Override + public boolean isAvailable() { + return true; // always available + } + + @Override + public void consume(long duration, long size) { + // no-op, unlimited budget means no consumption + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadDuration.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadDuration.java new file mode 100644 index 0000000000..36ee55ed91 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadDuration.java @@ -0,0 +1,18 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation; + +public record UploadDuration(long uploadDuration, long size) implements ExpDecayLinear2DEstimator.DataPair { + @Override + public long x() { + return this.size; + } + + @Override + public long y() { + return this.uploadDuration; + } + + @Override + public Void category() { + return null; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadDurationEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadDurationEstimator.java new file mode 100644 index 0000000000..d0e5a938e5 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadDurationEstimator.java @@ -0,0 +1,112 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class UploadDurationEstimator extends ExpDecayLinear2DEstimator { + public static final float NEW_DATA_RATIO = 0.05f; + public static final int INITIAL_SAMPLE_TARGET = 100; + public static final int MIN_BATCH_SIZE = 100; + private static final long INITIAL_UPLOAD_TIME_ESTIMATE = 100_000L; // 100µs + + public UploadDurationEstimator() { + super(NEW_DATA_RATIO, INITIAL_SAMPLE_TARGET, MIN_BATCH_SIZE, INITIAL_UPLOAD_TIME_ESTIMATE); + } + + public long estimateUploadDuration(long size) { + return this.predict(null, size); + } + + // special map that can contain one key: null and a generic value type + private static class VoidKeyMap implements Map { + private T value; + + @Override + public int size() { + return this.value == null ? 0 : 1; + } + + @Override + public boolean isEmpty() { + return this.value == null; + } + + @Override + public boolean containsKey(Object o) { + return o == null; + } + + @Override + public boolean containsValue(Object o) { + return this.value != null && this.value.equals(o); + } + + @Override + public T get(Object o) { + if (o == null) { + return this.value; + } + return null; + } + + @Override + public @Nullable T put(Void unused, T t) { + T oldValue = this.value; + this.value = t; + return oldValue; + } + + @Override + public T remove(Object o) { + if (o == null) { + T oldValue = this.value; + this.value = null; + return oldValue; + } + return null; + } + + @Override + public void putAll(@NotNull Map map) { + if (map.containsKey(null)) { + this.value = map.get(null); + } + } + + @Override + public void clear() { + this.value = null; + } + + @Override + public @NotNull Set keySet() { + if (this.value != null) { + return Collections.singleton(null); + } + return Set.of(); + } + + @Override + public @NotNull Collection values() { + if (this.value != null) { + return Collections.singleton(this.value); + } + return Collections.emptyList(); + } + + @Override + public @NotNull Set> entrySet() { + if (this.value != null) { + return Collections.singleton(new AbstractMap.SimpleEntry<>(null, this.value)); + } + return Collections.emptySet(); + } + } + + @Override + protected Map createMap() { + return new VoidKeyMap<>(); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadResourceBudget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadResourceBudget.java new file mode 100644 index 0000000000..894f82192d --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadResourceBudget.java @@ -0,0 +1,7 @@ +package net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation; + +public interface UploadResourceBudget { + boolean isAvailable(); + + void consume(long duration, long size); +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJob.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJob.java index 65dd30a5fb..7e7b4b15b4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJob.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJob.java @@ -11,4 +11,6 @@ public interface ChunkJob extends CancellationToken { long getEstimatedSize(); long getEstimatedDuration(); + + long getEstimatedUploadDuration(); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java index 97f877fba4..51c8830525 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java @@ -78,4 +78,9 @@ public long getEstimatedSize() { public long getEstimatedDuration() { return this.task.getEstimatedDuration(); } + + @Override + public long getEstimatedUploadDuration() { + return this.task.getEstimatedUploadDuration(); + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java index 0dce316ebf..975ce4c818 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java @@ -5,6 +5,7 @@ import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildContext; import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.JobDurationEstimator; import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshTaskSizeEstimator; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.UploadDurationEstimator; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.CombinedCameraPos; import net.caffeinemc.mods.sodium.client.util.task.CancellationToken; import org.joml.Vector3dc; @@ -29,6 +30,7 @@ public abstract class ChunkBuilderTask impleme private long estimatedSize; private long estimatedDuration; + private long estimatedUploadDuration; /** * Constructs a new build task for the given chunk and converts the absolute camera position to a relative position. While the absolute position is stored as a double vector, the relative position is stored as a float vector. @@ -60,9 +62,10 @@ public ChunkBuilderTask(RenderSection render, int time, Vector3dc absoluteCamera public abstract long estimateTaskSizeWith(MeshTaskSizeEstimator estimator); - public void calculateEstimations(JobDurationEstimator jobEstimator, MeshTaskSizeEstimator sizeEstimator) { + public void calculateEstimations(JobDurationEstimator jobEstimator, MeshTaskSizeEstimator sizeEstimator, UploadDurationEstimator uploadEstimator) { this.estimatedSize = this.estimateTaskSizeWith(sizeEstimator); this.estimatedDuration = jobEstimator.estimateJobDuration(this.getClass(), this.estimatedSize); + this.estimatedUploadDuration = uploadEstimator.estimateUploadDuration(this.estimatedSize); } public long getEstimatedSize() { @@ -73,6 +76,10 @@ public long getEstimatedDuration() { return this.estimatedDuration; } + public long getEstimatedUploadDuration() { + return this.estimatedUploadDuration; + } + @Override public Vector3fc getRelativeCameraPos() { return this.cameraPos; From afbfc491954dde59d5d0b67966253e356f15cdd8 Mon Sep 17 00:00:00 2001 From: douira Date: Wed, 27 Aug 2025 01:05:15 +0200 Subject: [PATCH 116/215] Remove usage of deprecated #getSortBehavior() --- .../sodium/client/gui/SodiumGameOptions.java | 11 ------ .../client/render/SodiumWorldRenderer.java | 12 ++++++- .../client/render/chunk/ChunkRenderer.java | 13 +++---- .../render/chunk/DefaultChunkRenderer.java | 10 ++---- .../render/chunk/RenderSectionManager.java | 35 ++++++++++++++----- .../tasks/ChunkBuilderMeshingTask.java | 20 ++++++----- .../TranslucentGeometryCollector.java | 14 ++++---- .../trigger/SortTriggering.java | 35 ++++++++----------- 8 files changed, 80 insertions(+), 70 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java index 37abe7b280..e79c9f437f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptions.java @@ -6,7 +6,6 @@ import net.caffeinemc.mods.sodium.client.gui.options.TextProvider; import net.caffeinemc.mods.sodium.client.render.chunk.DeferMode; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.QuadSplittingMode; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior; import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; import net.caffeinemc.mods.sodium.client.util.FileUtil; import net.minecraft.client.GraphicsStatus; @@ -61,16 +60,6 @@ public static class AdvancedSettings { public static class DebugSettings { public boolean terrainSortingEnabled = true; - - @Deprecated(forRemoval = true) - public SortBehavior getSortBehavior() { - // TODO: This logic should not exist here, we need to move it into renderer initialization - if (PlatformRuntimeInformation.getInstance().isDevelopmentEnvironment()) { - return this.terrainSortingEnabled ? SortBehavior.DYNAMIC_DEFER_NEARBY_ZERO_FRAMES : SortBehavior.OFF; - } - - return SortBehavior.DYNAMIC_DEFER_NEARBY_ZERO_FRAMES; - } } public static class QualitySettings { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java index b3037bd125..86c33c464d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java @@ -17,9 +17,11 @@ import net.caffeinemc.mods.sodium.client.render.chunk.map.ChunkTracker; import net.caffeinemc.mods.sodium.client.render.chunk.map.ChunkTrackerHolder; import net.caffeinemc.mods.sodium.client.render.chunk.terrain.DefaultTerrainRenderPasses; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.CameraMovement; import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; import net.caffeinemc.mods.sodium.client.services.PlatformBlockAccess; +import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; import net.caffeinemc.mods.sodium.client.util.NativeBuffer; import net.caffeinemc.mods.sodium.client.world.LevelRendererExtension; import net.minecraft.client.Camera; @@ -284,9 +286,17 @@ private void initRenderer(CommandList commandList) { this.renderSectionManager = null; } + // translucency sorting can be disabled in development environments by setting the debug option in the config file + var sortBehavior = SortBehavior.DYNAMIC_DEFER_NEARBY_ZERO_FRAMES; + + if (PlatformRuntimeInformation.getInstance().isDevelopmentEnvironment() + && !SodiumClientMod.options().debug.terrainSortingEnabled) { + sortBehavior = SortBehavior.OFF; + } + this.renderDistance = this.client.options.getEffectiveRenderDistance(); - this.renderSectionManager = new RenderSectionManager(this.level, this.renderDistance, commandList); + this.renderSectionManager = new RenderSectionManager(this.level, this.renderDistance, sortBehavior, commandList); var tracker = ChunkTrackerHolder.get(this.level); ChunkTracker.forEachChunk(tracker.getReadyChunks(), this.renderSectionManager::onChunkAdded); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkRenderer.java index 2db8a0bc9c..9db3ac0d85 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkRenderer.java @@ -13,13 +13,14 @@ public interface ChunkRenderer { /** * Renders the given chunk render list to the active framebuffer. * - * @param matrices The camera matrices to use for rendering - * @param commandList The command list which OpenGL commands should be serialized to - * @param renderLists The collection of render lists - * @param pass The block render pass to execute - * @param camera The camera context containing chunk offsets for the current render + * @param matrices The camera matrices to use for rendering + * @param commandList The command list which OpenGL commands should be serialized to + * @param renderLists The collection of render lists + * @param pass The block render pass to execute + * @param camera The camera context containing chunk offsets for the current render + * @param indexedRenderingEnabled Whether indexed rendering is enabled */ - void render(ChunkRenderMatrices matrices, CommandList commandList, ChunkRenderListIterable renderLists, TerrainRenderPass pass, CameraTransform camera); + void render(ChunkRenderMatrices matrices, CommandList commandList, ChunkRenderListIterable renderLists, TerrainRenderPass pass, CameraTransform camera, boolean indexedRenderingEnabled); /** * Deletes this render backend and any resources attached to it. diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java index 9f6f25aa08..1b629f7e3f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java @@ -17,7 +17,6 @@ import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; import net.caffeinemc.mods.sodium.client.render.chunk.shader.ChunkShaderInterface; import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior; import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexType; import net.caffeinemc.mods.sodium.client.render.viewport.CameraTransform; import net.caffeinemc.mods.sodium.client.util.BitwiseMath; @@ -46,11 +45,12 @@ public void render(ChunkRenderMatrices matrices, CommandList commandList, ChunkRenderListIterable renderLists, TerrainRenderPass renderPass, - CameraTransform camera) { + CameraTransform camera, + boolean indexedRenderingEnabled) { super.begin(renderPass); final boolean useBlockFaceCulling = SodiumClientMod.options().performance.useBlockFaceCulling; - final boolean useIndexedTessellation = isTranslucentRenderPass(renderPass); + final boolean useIndexedTessellation = renderPass.isTranslucent() && indexedRenderingEnabled; ChunkShaderInterface shader = this.activeProgram.getInterface(); shader.setProjectionMatrix(matrices.projection()); @@ -98,10 +98,6 @@ public void render(ChunkRenderMatrices matrices, super.end(renderPass); } - private static boolean isTranslucentRenderPass(TerrainRenderPass renderPass) { - return renderPass.isTranslucent() && SodiumClientMod.options().debug.getSortBehavior() != SortBehavior.OFF; - } - private static void fillCommandBuffer(MultiDrawBatch batch, RenderRegion renderRegion, SectionRenderDataStorage renderDataStorage, diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index 84383348af..7c580f19d9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -26,6 +26,7 @@ import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegionManager; import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior.PriorityMode; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.DynamicTopoData; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.NoData; @@ -91,6 +92,7 @@ public class RenderSectionManager { private final OcclusionCuller occlusionCuller; private final int renderDistance; + private final SortBehavior sortBehavior; private final SortTriggering sortTriggering; @@ -113,15 +115,20 @@ public class RenderSectionManager { private final RemovableMultiForest renderableSectionTree; - public RenderSectionManager(ClientLevel level, int renderDistance, CommandList commandList) { + public RenderSectionManager(ClientLevel level, int renderDistance, SortBehavior sortBehavior, CommandList commandList) { this.chunkRenderer = new DefaultChunkRenderer(RenderDevice.INSTANCE, ChunkMeshFormats.COMPACT); this.level = level; this.builder = new ChunkBuilder(level, ChunkMeshFormats.COMPACT); this.renderDistance = renderDistance; + this.sortBehavior = sortBehavior; - this.sortTriggering = new SortTriggering(); + if (this.sortBehavior != SortBehavior.OFF) { + this.sortTriggering = new SortTriggering(); + } else { + this.sortTriggering = null; + } this.regions = new RenderRegionManager(commandList); this.sectionCache = new ClonedChunkSectionCache(this.level); @@ -298,7 +305,7 @@ public void renderLayer(ChunkRenderMatrices matrices, TerrainRenderPass pass, do RenderDevice device = RenderDevice.INSTANCE; CommandList commandList = device.createCommandList(); - this.chunkRenderer.render(matrices, commandList, this.renderLists, pass, new CameraTransform(x, y, z)); + this.chunkRenderer.render(matrices, commandList, this.renderLists, pass, new CameraTransform(x, y, z), this.sortBehavior != SortBehavior.OFF); commandList.flush(); } @@ -512,7 +519,7 @@ public void updateChunks(boolean updateImmediately) { // if zero frame delay is allowed, submit important sorts with the current frame blocking collector. // otherwise submit with the collector that the next frame is blocking on. - if (SodiumClientMod.options().debug.getSortBehavior().getDeferMode() == DeferMode.ZERO_FRAMES) { + if (this.sortBehavior.getDeferMode() == DeferMode.ZERO_FRAMES) { this.submitSectionTasks(thisFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector, uploadBudget); } else { this.submitSectionTasks(nextFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector, uploadBudget); @@ -578,8 +585,12 @@ private void submitSectionTask(ChunkJobCollector collector, @NotNull RenderSecti // data has since been removed. In that case simply nothing is done as the // rebuild that must have happened in the meantime includes new non-dynamic // index data. + TranslucentData translucentData = null; + if (this.sortBehavior != SortBehavior.OFF) { + translucentData = NoData.forEmptySection(section.getPosition()); + } var result = ChunkJobResult.successfully(new ChunkBuildOutput( - section, this.frame, NoData.forEmptySection(section.getPosition()), + section, this.frame, translucentData, BuiltSectionInfo.EMPTY, Collections.emptyMap())); this.buildResults.add(result); @@ -617,7 +628,7 @@ private void submitSectionTask(ChunkJobCollector collector, @NotNull RenderSecti return null; } - var task = new ChunkBuilderMeshingTask(render, frame, this.cameraPosition, context, ChunkUpdateTypes.isRebuildWithSort(render.getPendingUpdate())); + var task = new ChunkBuilderMeshingTask(render, frame, this.cameraPosition, context, this.sortBehavior, ChunkUpdateTypes.isRebuildWithSort(render.getPendingUpdate())); task.calculateEstimations(this.jobDurationEstimator, this.meshTaskSizeEstimator, this.jobUploadDurationEstimator); return task; } @@ -631,7 +642,9 @@ public ChunkBuilderSortingTask createSortTask(RenderSection render, int frame) { } public void processGFNIMovement(CameraMovement movement) { - this.sortTriggering.triggerSections(this::scheduleSort, movement); + if (this.sortTriggering != null) { + this.sortTriggering.triggerSections(this::scheduleSort, movement); + } } public void markGraphDirty() { @@ -707,7 +720,7 @@ public void scheduleSort(long sectionPos, boolean isDirectTrigger) { if (section != null) { int pendingUpdate = ChunkUpdateTypes.SORT; - var priorityMode = SodiumClientMod.options().debug.getSortBehavior().getPriorityMode(); + var priorityMode = this.sortBehavior.getPriorityMode(); if (priorityMode == PriorityMode.NEARBY && this.shouldPrioritizeTask(section, NEARBY_SORT_DISTANCE) || priorityMode == PriorityMode.ALL) { pendingUpdate = ChunkUpdateTypes.join(pendingUpdate, ChunkUpdateTypes.IMPORTANT); } @@ -846,7 +859,11 @@ public Collection getDebugStrings() { list.add(String.format("Size: %s", String.join(", ", sizeEstimates))); } - this.sortTriggering.addDebugStrings(list); + if (this.sortBehavior != SortBehavior.OFF) { + this.sortTriggering.addDebugStrings(list, this.sortBehavior); + } else { + list.add("TS OFF"); + } return list; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java index 71a256f7f6..ad65947ac8 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java @@ -1,7 +1,6 @@ package net.caffeinemc.mods.sodium.client.render.chunk.compile.tasks; import it.unimi.dsi.fastutil.objects.Reference2ReferenceOpenHashMap; -import net.caffeinemc.mods.sodium.client.SodiumClientMod; import net.caffeinemc.mods.sodium.client.render.chunk.DefaultChunkRenderer; import net.caffeinemc.mods.sodium.client.render.chunk.ExtendedBlockEntityType; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; @@ -50,11 +49,13 @@ */ public class ChunkBuilderMeshingTask extends ChunkBuilderTask { private final ChunkRenderContext renderContext; + private final SortBehavior sortBehavior; private final boolean forceSort; - public ChunkBuilderMeshingTask(RenderSection render, int buildTime, Vector3dc absoluteCameraPos, ChunkRenderContext renderContext, boolean forceSort) { + public ChunkBuilderMeshingTask(RenderSection render, int buildTime, Vector3dc absoluteCameraPos, ChunkRenderContext renderContext, SortBehavior sortBehavior, boolean forceSort) { super(render, buildTime, absoluteCameraPos); this.renderContext = renderContext; + this.sortBehavior = sortBehavior; this.forceSort = forceSort; } @@ -83,9 +84,10 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos(minX, minY, minZ); BlockPos.MutableBlockPos modelOffset = new BlockPos.MutableBlockPos(); + boolean sortEnabled = this.sortBehavior != SortBehavior.OFF; TranslucentGeometryCollector collector; - if (SodiumClientMod.options().debug.getSortBehavior() != SortBehavior.OFF) { - collector = new TranslucentGeometryCollector(this.render.getPosition()); + if (sortEnabled) { + collector = new TranslucentGeometryCollector(this.render.getPosition(), this.sortBehavior); } else { collector = null; } @@ -153,7 +155,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke blockRenderer.release(); SortType sortType = SortType.NONE; - if (collector != null) { + if (sortEnabled) { sortType = collector.finishRendering(); } @@ -164,10 +166,10 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke boolean reuseUploadedData = false; TranslucentData translucentData = null; - if (collector != null) { + if (sortEnabled) { var oldData = this.render.getTranslucentData(); translucentData = collector.getTranslucentData(oldData, this); - reuseUploadedData = !this.forceSort && translucentData == oldData; + reuseUploadedData = !this.forceSort && translucentData == oldData; } Map meshes = new Reference2ReferenceOpenHashMap<>(); @@ -187,7 +189,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke // if the translucent geometry needs to share an index buffer between the directions, // consolidate all translucent geometry into UNASSIGNED - boolean translucentBehavior = collector != null && pass.isTranslucent(); + boolean translucentBehavior = sortEnabled && pass.isTranslucent(); boolean forceUnassigned = translucentBehavior && sortType.needsDirectionMixing; boolean sliceReordering = !translucentBehavior || sortType.allowSliceReordering; BuiltSectionMeshParts mesh = buffers.createMesh(pass, visibleSlices, forceUnassigned, sliceReordering); @@ -200,7 +202,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke renderData.setOcclusionData(occluder.resolve()); var output = new ChunkBuildOutput(this.render, this.submitTime, translucentData, renderData.build(), meshes); - if (collector != null) { + if (sortEnabled) { if (reuseUploadedData) { output.markAsReusingUploadedData(); } else if (translucentData instanceof PresentTranslucentData present) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java index 77de852f19..c4865c7212 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java @@ -47,6 +47,7 @@ public class TranslucentGeometryCollector { private final SectionPos sectionPos; private final QuadSplittingMode quadSplittingMode = SodiumClientMod.options().performance.quadSplittingMode; + private final SortBehavior sortBehavior; // true if there are any unaligned quads private boolean hasUnaligned = false; @@ -90,8 +91,9 @@ public class TranslucentGeometryCollector { private boolean quadHashPresent = false; private int quadHash = 0; - public TranslucentGeometryCollector(SectionPos sectionPos) { + public TranslucentGeometryCollector(SectionPos sectionPos, SortBehavior sortBehavior) { this.sectionPos = sectionPos; + this.sortBehavior = sortBehavior; } /** @@ -190,8 +192,7 @@ public boolean isSplittingQuads() { * * @param sortType the sort type to filter */ - private static SortType filterSortType(SortType sortType) { - SortBehavior sortBehavior = SodiumClientMod.options().debug.getSortBehavior(); + private static SortType filterSortType(SortType sortType, SortBehavior sortBehavior) { switch (sortBehavior) { case OFF: return SortType.NONE; @@ -255,8 +256,7 @@ private SortType sortTypeHeuristic() { return SortType.NONE; } - SortBehavior sortBehavior = SodiumClientMod.options().debug.getSortBehavior(); - if (sortBehavior.getSortMode() == SortBehavior.SortMode.NONE) { + if (this.sortBehavior.getSortMode() == SortBehavior.SortMode.NONE) { return SortType.NONE; } @@ -390,7 +390,7 @@ public SortType finishRendering() { } this.quadLists = null; // they're not needed anymore - this.sortType = filterSortType(sortTypeHeuristic()); + this.sortType = filterSortType(sortTypeHeuristic(), this.sortBehavior); return this.sortType; } @@ -416,7 +416,7 @@ private TranslucentData makeNewTranslucentData(CombinedCameraPos cameraPos, } // filter the sort type with the user setting and re-evaluate - this.sortType = filterSortType(this.sortType); + this.sortType = filterSortType(this.sortType, this.sortBehavior); if (this.sortType == SortType.NONE) { return AnyOrderData.fromMesh(this.quads, this.sectionPos); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java index 569cbf8a2f..b9c8d54f0a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java @@ -222,25 +222,20 @@ public void integrateTranslucentData(TranslucentData oldData, TranslucentData ne } } - public void addDebugStrings(List list) { - var sortBehavior = SodiumClientMod.options().debug.getSortBehavior(); - if (sortBehavior.getSortMode() == SortBehavior.SortMode.NONE) { - list.add("TS OFF"); - } else { - var splittingMode = SodiumClientMod.options().performance.quadSplittingMode; - list.add("TS (%s,%s) NL=%02d TrN=%02d TrS=G%03d/D%03d".formatted( - sortBehavior.getShortName(), - splittingMode.getShortName(), - this.gfni.getUniqueNormalCount(), - this.triggeredNormalCount, - this.gfniTriggerCount, - this.directTriggerCount)); - list.add("N=%05d SNR=%05d STA=%05d DYN=%05d (DIR=%02d)".formatted( - this.sortTypeCounters[SortType.NONE.ordinal()], - this.sortTypeCounters[SortType.STATIC_NORMAL_RELATIVE.ordinal()], - this.sortTypeCounters[SortType.STATIC_TOPO.ordinal()], - this.sortTypeCounters[SortType.DYNAMIC.ordinal()], - this.direct.getDirectTriggerCount())); - } + public void addDebugStrings(List list, SortBehavior sortBehavior) { + var splittingMode = SodiumClientMod.options().performance.quadSplittingMode; + list.add("TS (%s,%s) NL=%02d TrN=%02d TrS=G%03d/D%03d".formatted( + sortBehavior.getShortName(), + splittingMode.getShortName(), + this.gfni.getUniqueNormalCount(), + this.triggeredNormalCount, + this.gfniTriggerCount, + this.directTriggerCount)); + list.add("N=%05d SNR=%05d STA=%05d DYN=%05d (DIR=%02d)".formatted( + this.sortTypeCounters[SortType.NONE.ordinal()], + this.sortTypeCounters[SortType.STATIC_NORMAL_RELATIVE.ordinal()], + this.sortTypeCounters[SortType.STATIC_TOPO.ordinal()], + this.sortTypeCounters[SortType.DYNAMIC.ordinal()], + this.direct.getDirectTriggerCount())); } } From c528092b9aa32284989ea6492139e79e5f231e57 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 7 Sep 2025 21:52:49 +0200 Subject: [PATCH 117/215] Use level's configured sea height for chunk mesh estimation heuristics (#3241) Previously the code assumed the sea height was always y=62. --- .../render/chunk/RenderSectionManager.java | 6 +++-- .../compile/estimation/MeshResultSize.java | 27 ++++++++++--------- .../estimation/MeshTaskSizeEstimator.java | 13 +++++++-- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index 7c580f19d9..08b61c9f8c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -76,7 +76,7 @@ public class RenderSectionManager { private final ConcurrentLinkedDeque> buildResults = new ConcurrentLinkedDeque<>(); private final JobDurationEstimator jobDurationEstimator = new JobDurationEstimator(); - private final MeshTaskSizeEstimator meshTaskSizeEstimator = new MeshTaskSizeEstimator(); + private final MeshTaskSizeEstimator meshTaskSizeEstimator; private final UploadDurationEstimator jobUploadDurationEstimator = new UploadDurationEstimator(); private ChunkJobCollector lastBlockingCollector; private int thisFrameBlockingTasks; @@ -116,6 +116,8 @@ public class RenderSectionManager { private final RemovableMultiForest renderableSectionTree; public RenderSectionManager(ClientLevel level, int renderDistance, SortBehavior sortBehavior, CommandList commandList) { + this.meshTaskSizeEstimator = new MeshTaskSizeEstimator(level); + this.chunkRenderer = new DefaultChunkRenderer(RenderDevice.INSTANCE, ChunkMeshFormats.COMPACT); this.level = level; @@ -388,7 +390,7 @@ private boolean processChunkBuildResults(ArrayList results) { touchedSectionInfo |= this.updateSectionInfo(result.render, chunkBuildOutput.info); result.render.setLastMeshResultSize(resultSize); - this.meshTaskSizeEstimator.addData(MeshResultSize.forSection(result.render, resultSize)); + this.meshTaskSizeEstimator.addData(this.meshTaskSizeEstimator.resultForSection(result.render, resultSize)); if (chunkBuildOutput.translucentData != null) { this.sortTriggering.integrateTranslucentData(oldData, chunkBuildOutput.translucentData, this.cameraPosition, this::scheduleSort); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshResultSize.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshResultSize.java index e4d976bac0..833a2a338b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshResultSize.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshResultSize.java @@ -12,26 +12,29 @@ public enum SectionCategory { SURFACE, HIGH; - public static SectionCategory forSection(RenderSection section) { + public static SectionCategory forSection(RenderSection section, int seaLevelChunk) { var sectionY = section.getChunkY(); - if (sectionY < 0) { + + // Roughly classify type of chunk based on Y level relative to sea level: + // Water level chunks are likely to have different meshes from those below and those above. + // Very low chunks are again different because they aren't going to include the underwater terrain (just caves). + // Very high chunks are (at least locally) different because they are likely to be mostly air with some terrain poking through, or just buildings/jungle tree tops. + if (sectionY == seaLevelChunk) { + return WATER_LEVEL; + } + if (sectionY < seaLevelChunk - 4) { return LOW; - } else if (sectionY < 3) { + } + if (sectionY < seaLevelChunk) { return UNDERGROUND; - } else if (sectionY == 3) { - return WATER_LEVEL; - } else if (sectionY < 7) { + } + if (sectionY < seaLevelChunk + 3) { return SURFACE; - } else { - return HIGH; } + return HIGH; } } - public static MeshResultSize forSection(RenderSection section, long resultSize) { - return new MeshResultSize(SectionCategory.forSection(section), resultSize); - } - @Override public SectionCategory category() { return this.category; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshTaskSizeEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshTaskSizeEstimator.java index 67809d61d8..82aac38a10 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshTaskSizeEstimator.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshTaskSizeEstimator.java @@ -2,15 +2,20 @@ import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; import java.util.EnumMap; import java.util.Map; public class MeshTaskSizeEstimator extends Average1DEstimator { public static final float NEW_DATA_RATIO = 0.02f; + + private final int seaLevelChunk; - public MeshTaskSizeEstimator() { + public MeshTaskSizeEstimator(ClientLevel level) { super(NEW_DATA_RATIO, RenderRegion.SECTION_BUFFER_ESTIMATE); + this.seaLevelChunk = level.getSeaLevel() >> 4; } public long estimateSize(RenderSection section) { @@ -18,7 +23,11 @@ public long estimateSize(RenderSection section) { if (lastResultSize != MeshResultSize.NO_DATA) { return lastResultSize; } - return this.predict(MeshResultSize.SectionCategory.forSection(section)); + return this.predict(MeshResultSize.SectionCategory.forSection(section, this.seaLevelChunk)); + } + + public MeshResultSize resultForSection(RenderSection section, long resultSize) { + return new MeshResultSize(MeshResultSize.SectionCategory.forSection(section, this.seaLevelChunk), resultSize); } @Override From 105315376651e0b20a192145b16ca589987aec87 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 28 Sep 2025 01:15:02 +0200 Subject: [PATCH 118/215] Fix an oversight in RSM that wasn't taking the available worker budget into account when scheduling tasks. (#3257) This caused too many tasks to be submitted, causing weird chunk loading patterns and unresponsive loading of chunks when moving rapidly. --- .../mods/sodium/client/render/chunk/RenderSectionManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index 08b61c9f8c..a0da4843da 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -551,7 +551,8 @@ private void submitSectionTasks( private void submitSectionTasks(ChunkJobCollector collector, UploadResourceBudget uploadBudget, TaskQueueType queueType) { var taskList = this.taskLists.get(queueType); - while (!taskList.isEmpty() && (uploadBudget.isAvailable() || queueType.allowsUnlimitedUploadDuration())) { + // submit tasks as long as there's tasks available, the collector has worker thread budget, and there's enough upload budget left + while (!taskList.isEmpty() && collector.hasBudgetRemaining() && (uploadBudget.isAvailable() || queueType.allowsUnlimitedUploadDuration())) { RenderSection section = taskList.poll(); if (section == null) { From 304c6fef0897656a183770f05ac2ef24c367436d Mon Sep 17 00:00:00 2001 From: IMS Date: Mon, 29 Sep 2025 21:57:08 +0200 Subject: [PATCH 119/215] Disable color smoothing when biome blending is off (#3252) --- .../client/model/color/ColorProvider.java | 3 ++- .../model/color/DefaultColorProviders.java | 4 ++-- .../quad/blender/BlendedColorProvider.java | 17 ++++++++++++----- .../chunk/compile/pipeline/BlockRenderer.java | 2 +- .../compile/pipeline/DefaultFluidRenderer.java | 2 +- .../mods/sodium/client/world/LevelSlice.java | 10 ++++++++-- .../client/world/biome/LevelColorCache.java | 4 ++++ .../fabric/render/FabricColorProviders.java | 2 +- .../neoforge/render/ForgeColorProviders.java | 2 +- 9 files changed, 32 insertions(+), 14 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/ColorProvider.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/ColorProvider.java index f416a134af..6dd0a5ce8a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/ColorProvider.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/ColorProvider.java @@ -15,6 +15,7 @@ public interface ColorProvider { * @param state The state of the object being colorized * @param quad The quad geometry which should be colorized * @param output The output array of vertex colors (in ABGR format) + * @param smooth Whether the colors should be blended across vertices */ - void getColors(LevelSlice slice, BlockPos pos, BlockPos.MutableBlockPos scratchPos, T state, ModelQuadView quad, int[] output); + void getColors(LevelSlice slice, BlockPos pos, BlockPos.MutableBlockPos scratchPos, T state, ModelQuadView quad, int[] output, boolean smooth); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/DefaultColorProviders.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/DefaultColorProviders.java index 848896141b..15b210e138 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/DefaultColorProviders.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/color/DefaultColorProviders.java @@ -7,7 +7,7 @@ import net.minecraft.client.renderer.BiomeColors; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.FluidState; + import java.util.Arrays; public class DefaultColorProviders { @@ -49,7 +49,7 @@ private VanillaAdapter(BlockColor color) { } @Override - public void getColors(LevelSlice slice, BlockPos pos, BlockPos.MutableBlockPos scratchPos, BlockState state, ModelQuadView quad, int[] output) { + public void getColors(LevelSlice slice, BlockPos pos, BlockPos.MutableBlockPos scratchPos, BlockState state, ModelQuadView quad, int[] output, boolean smooth) { Arrays.fill(output, 0xFF000000 | this.color.getColor(state, slice, pos, quad.getColorIndex())); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/blender/BlendedColorProvider.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/blender/BlendedColorProvider.java index 24014df9b9..bf151359c9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/blender/BlendedColorProvider.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/blender/BlendedColorProvider.java @@ -1,17 +1,24 @@ package net.caffeinemc.mods.sodium.client.model.quad.blender; -import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadView; +import net.caffeinemc.mods.sodium.api.util.ColorMixer; import net.caffeinemc.mods.sodium.client.model.color.ColorProvider; +import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadView; import net.caffeinemc.mods.sodium.client.world.LevelSlice; -import net.caffeinemc.mods.sodium.api.util.ColorMixer; import net.minecraft.core.BlockPos; import net.minecraft.util.Mth; public abstract class BlendedColorProvider implements ColorProvider { @Override - public void getColors(LevelSlice slice, BlockPos pos, BlockPos.MutableBlockPos scratchPos, T state, ModelQuadView quad, int[] output) { - for (int vertexIndex = 0; vertexIndex < 4; vertexIndex++) { - output[vertexIndex] = this.getVertexColor(slice, pos, scratchPos, quad, state, vertexIndex); + public void getColors(LevelSlice slice, BlockPos pos, BlockPos.MutableBlockPos scratchPos, T state, ModelQuadView quad, int[] output, boolean smooth) { + if (smooth) { + for (int vertexIndex = 0; vertexIndex < 4; vertexIndex++) { + output[vertexIndex] = this.getVertexColor(slice, pos, scratchPos, quad, state, vertexIndex); + } + } else { + int color = this.getColor(slice, state, pos); + for (int vertexIndex = 0; vertexIndex < 4; vertexIndex++) { + output[vertexIndex] = color; + } } } 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 07ff4dd682..243d3a29b7 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,7 +160,7 @@ private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) { if (colorProvider != null) { int[] vertexColors = this.vertexColors; - colorProvider.getColors(this.slice, this.pos, this.scratchPos, this.state, quad, vertexColors); + colorProvider.getColors(this.slice, this.pos, this.scratchPos, this.state, quad, vertexColors, slice.hasBiomeBlend()); for (int i = 0; i < 4; i++) { quad.color(i, ColorMixer.mulComponentWise(vertexColors[i], quad.color(i))); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java index 13e1b818a7..6df1c9ebca 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java @@ -377,7 +377,7 @@ private void updateQuad(ModelQuadViewMutable quad, LevelSlice level, BlockPos po lighter.calculate(quad, pos, light, null, dir, false, false); - colorProvider.getColors(level, pos, scratchPos, fluidState, quad, this.quadColors); + colorProvider.getColors(level, pos, scratchPos, fluidState, quad, this.quadColors, level.hasBiomeBlend()); // multiply the per-vertex color against the combined brightness // the combined brightness is the per-vertex brightness multiplied by the block's brightness diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/LevelSlice.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/LevelSlice.java index c6b18eac52..969258eefc 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/LevelSlice.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/LevelSlice.java @@ -1,9 +1,11 @@ package net.caffeinemc.mods.sodium.client.world; import it.unimi.dsi.fastutil.ints.Int2ReferenceMap; -import net.caffeinemc.mods.sodium.client.services.*; -import net.caffeinemc.mods.sodium.client.world.biome.LevelColorCache; +import net.caffeinemc.mods.sodium.client.services.PlatformLevelRenderHooks; +import net.caffeinemc.mods.sodium.client.services.SodiumModelData; +import net.caffeinemc.mods.sodium.client.services.SodiumModelDataContainer; import net.caffeinemc.mods.sodium.client.world.biome.LevelBiomeSlice; +import net.caffeinemc.mods.sodium.client.world.biome.LevelColorCache; import net.caffeinemc.mods.sodium.client.world.cloned.ChunkRenderContext; import net.caffeinemc.mods.sodium.client.world.cloned.ClonedChunkSection; import net.caffeinemc.mods.sodium.client.world.cloned.ClonedChunkSectionCache; @@ -353,6 +355,10 @@ public int getBlockTint(BlockPos pos, ColorResolver resolver) { return this.biomeColors.getColor(resolver, pos.getX(), pos.getY(), pos.getZ()); } + public boolean hasBiomeBlend() { + return this.biomeColors.getBlendRadius() > 0; + } + @Override public int getHeight() { return this.level.getHeight(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java index 370635f67e..a245f6f14d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java @@ -109,6 +109,10 @@ private void updateColorBuffers(int relY, ColorResolver resolver, Slice slice) { slice.lastPopulateStamp = this.populateStamp; } + public int getBlendRadius() { + return blendRadius; + } + private static class Slice { private final ColorBuffer buffer; private long lastPopulateStamp; diff --git a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FabricColorProviders.java b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FabricColorProviders.java index 28dec3befe..03dbb341ea 100644 --- a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FabricColorProviders.java +++ b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FabricColorProviders.java @@ -22,7 +22,7 @@ public FabricFluidAdapter(FluidRenderHandler handler) { } @Override - public void getColors(LevelSlice slice, BlockPos pos, BlockPos.MutableBlockPos scratchPos, FluidState state, ModelQuadView quad, int[] output) { + public void getColors(LevelSlice slice, BlockPos pos, BlockPos.MutableBlockPos scratchPos, FluidState state, ModelQuadView quad, int[] output, boolean smooth) { Arrays.fill(output, 0xFF000000 | this.handler.getFluidColor(slice, pos, state)); } } diff --git a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/ForgeColorProviders.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/ForgeColorProviders.java index 7e91918760..c606013d7c 100644 --- a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/ForgeColorProviders.java +++ b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/ForgeColorProviders.java @@ -22,7 +22,7 @@ public ForgeFluidAdapter(IClientFluidTypeExtensions handler) { } @Override - public void getColors(LevelSlice slice, BlockPos pos, BlockPos.MutableBlockPos scratchPos, FluidState state, ModelQuadView quad, int[] output) { + public void getColors(LevelSlice slice, BlockPos pos, BlockPos.MutableBlockPos scratchPos, FluidState state, ModelQuadView quad, int[] output, boolean smooth) { Arrays.fill(output,this.handler.getTintColor(state, slice, pos)); } } From 14de0b9a3cf1a898df5de4d75bf4c2227bb49a50 Mon Sep 17 00:00:00 2001 From: 2190303755 <68867706+2190303755@users.noreply.github.com> Date: Mon, 6 Oct 2025 23:52:51 +0800 Subject: [PATCH 120/215] Swap face indexes to achieve parity with vanilla (#3254) --- .../sodium/client/render/immediate/model/ModelCuboid.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/ModelCuboid.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/ModelCuboid.java index 4309d8bb8e..bd77b30850 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/ModelCuboid.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/immediate/model/ModelCuboid.java @@ -27,9 +27,9 @@ public class ModelCuboid { public static final int FACE_NEG_Y = 0, // DOWN FACE_POS_Y = 1, // UP - FACE_NEG_X = 2, // WEST + FACE_NEG_X = 2, // EAST FACE_NEG_Z = 3, // NORTH - FACE_POS_X = 4, // EAST + FACE_POS_X = 4, // WEST FACE_POS_Z = 5; // SOUTH public final float originX, originY, originZ; @@ -183,8 +183,8 @@ private static int getFaceIndex(@NotNull Direction dir) { case UP -> FACE_POS_Y; case NORTH -> FACE_NEG_Z; case SOUTH -> FACE_POS_Z; - case WEST -> FACE_NEG_X; - case EAST -> FACE_POS_X; + case WEST -> FACE_POS_X; + case EAST -> FACE_NEG_X; }; } } From 03bedc7357c33f00499723797fe6664bc4953fe2 Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 7 Oct 2025 06:08:40 +0200 Subject: [PATCH 121/215] Do not reuse sorting data when sorting is forced and data is not dynamic (#3288) that was recently introduced to fix a different issue. --- .../chunk/compile/tasks/ChunkBuilderMeshingTask.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java index ad65947ac8..253fd5ae73 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java @@ -18,6 +18,7 @@ import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortBehavior; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.SortType; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TranslucentGeometryCollector; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.DynamicData; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.PresentTranslucentData; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.TranslucentData; import net.caffeinemc.mods.sodium.client.services.PlatformLevelRenderHooks; @@ -167,7 +168,15 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke boolean reuseUploadedData = false; TranslucentData translucentData = null; if (sortEnabled) { - var oldData = this.render.getTranslucentData(); + TranslucentData oldData = this.render.getTranslucentData(); + + // Reusing non-dynamic data leads to attempting to sort with it again, + // which throws an exception since it can only generate a sorter once. + // To prevent this, reusing data is prevented when forceSort is enabled and the data is not dynamic. + if (this.forceSort && !(oldData instanceof DynamicData)) { + oldData = null; + } + translucentData = collector.getTranslucentData(oldData, this); reuseUploadedData = !this.forceSort && translucentData == oldData; } From 64fd42956bfb8577810aa3b9722aa4b51e636c0b Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 9 Oct 2025 18:34:20 +0200 Subject: [PATCH 122/215] Improved the presentation of immediate chunk updates (#3255) Previously even when meshes were generated in the blocking defer mode "Immediate" (zero frame) they would not become visible if they were part of a previously invisible section and thus not in a render list already. This change makes it so that sections that receive results from blocking rebuilds are added to the render list. Notably, this fixes a problem where block entities would take one frame to show up after the upload for their containing section has been completed. This change only performs unconditional rendering of sections with newly added block entities if the section was updated with a blocking (immediate) update or if the section is close to the camera. There was previously an implementation for controlling sort task deferring as part of the sorting code. A different implementation of essentially the same concept but for rebuild tasks was added later. Now they both use the newer approach, which simplifies the task submit code. --- .../client/render/SodiumWorldRenderer.java | 4 + .../client/render/chunk/ChunkUpdateTypes.java | 4 +- .../client/render/chunk/RenderSection.java | 18 +-- .../render/chunk/RenderSectionFlags.java | 5 + .../render/chunk/RenderSectionManager.java | 103 +++++++++++++----- .../chunk/compile/executor/ChunkBuilder.java | 5 +- .../chunk/compile/executor/ChunkJob.java | 2 + .../chunk/compile/executor/ChunkJobTyped.java | 9 +- .../lists/OcclusionSectionCollector.java | 4 +- .../render/chunk/lists/SectionCollector.java | 10 +- .../chunk/lists/TreeSectionCollector.java | 4 +- 11 files changed, 117 insertions(+), 51 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java index 86c33c464d..527296e79d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/SodiumWorldRenderer.java @@ -243,6 +243,10 @@ public void setupTerrain(Camera camera, } } + profiler.popPush("chunk_render_lists"); + + this.renderSectionManager.finalizeRenderLists(viewport); + profiler.popPush("chunk_render_tick"); this.renderSectionManager.tickVisibleRenders(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkUpdateTypes.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkUpdateTypes.java index f5f8f47f9e..b7ae6df533 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkUpdateTypes.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ChunkUpdateTypes.java @@ -37,7 +37,7 @@ public static boolean isRebuildWithSort(int type) { return (isRebuild(type) || isInitialBuild(type)) && isSort(type); } - public static TaskQueueType getQueueType(int type, TaskQueueType importantRebuildQueueType) { + public static TaskQueueType getQueueType(int type, TaskQueueType importantRebuildQueueType, TaskQueueType importantSortQueueType) { if (isInitialBuild(type)) { return TaskQueueType.INITIAL_BUILD; } @@ -45,7 +45,7 @@ public static TaskQueueType getQueueType(int type, TaskQueueType importantRebuil if (isRebuild(type)) { return importantRebuildQueueType; } else { // implies important sort task - return TaskQueueType.ZERO_FRAME_DEFER; + return importantSortQueueType; } } else { return TaskQueueType.ALWAYS_DEFER; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java index 4e1feb942c..c16c371249 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSection.java @@ -1,13 +1,13 @@ package net.caffeinemc.mods.sodium.client.render.chunk; import net.caffeinemc.mods.sodium.client.render.chunk.compile.estimation.MeshResultSize; +import net.caffeinemc.mods.sodium.client.render.chunk.compile.executor.ChunkJob; import net.caffeinemc.mods.sodium.client.render.chunk.data.BuiltSectionInfo; import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.GraphDirection; import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.GraphDirectionSet; import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.VisibilityEncoding; import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.TranslucentData; -import net.caffeinemc.mods.sodium.client.util.task.CancellationToken; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; @@ -54,7 +54,7 @@ public class RenderSection { // Pending Update State @Nullable - private CancellationToken taskCancellationToken = null; + private ChunkJob runningJob = null; private long lastMeshResultSize = MeshResultSize.NO_DATA; private int pendingUpdateType; @@ -132,9 +132,9 @@ public void setTranslucentData(TranslucentData translucentData) { * be used. */ public void delete() { - if (this.taskCancellationToken != null) { - this.taskCancellationToken.setCancelled(); - this.taskCancellationToken = null; + if (this.runningJob != null) { + this.runningJob.setCancelled(); + this.runningJob = null; } this.clearRenderState(); @@ -349,12 +349,12 @@ public long getVisibilityData() { return this.globalBlockEntities; } - public @Nullable CancellationToken getTaskCancellationToken() { - return this.taskCancellationToken; + public @Nullable ChunkJob getRunningJob() { + return this.runningJob; } - public void setTaskCancellationToken(@Nullable CancellationToken token) { - this.taskCancellationToken = token; + public void setRunningJob(@Nullable ChunkJob token) { + this.runningJob = token; } public int getPendingUpdate() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionFlags.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionFlags.java index 30379fdc91..c544947de5 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionFlags.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionFlags.java @@ -15,4 +15,9 @@ public class RenderSectionFlags { public static boolean needsRender(int flags) { return (flags & MASK_NEEDS_RENDER) != 0; } + + public static boolean renderingMoreTypesNow(int prevFlags, int newFlags) { + // true if there is some bit that is set now and was not set previously + return ((newFlags & MASK_NEEDS_RENDER) & ~(prevFlags & MASK_NEEDS_RENDER)) != 0; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index a0da4843da..f910579001 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -62,8 +62,9 @@ public class RenderSectionManager { private static final float NEARBY_REBUILD_DISTANCE = Mth.square(16.0f); + private static final float IMMEDIATE_PRESENT_DISTANCE = Mth.square(64.0f); private static final float NEARBY_SORT_DISTANCE = Mth.square(25.0f); - + private static final float FRAME_DURATION_UPLOAD_FRACTION = 0.1f; private static final long MIN_UPLOAD_DURATION_BUDGET = 2_000_000L; // 2ms @@ -98,6 +99,8 @@ public class RenderSectionManager { @NotNull private SortedRenderLists renderLists; + private SectionCollector sectionCollector; + private SectionCollector lastSectionCollector; @NotNull private Map> taskLists; @@ -117,7 +120,7 @@ public class RenderSectionManager { public RenderSectionManager(ClientLevel level, int renderDistance, SortBehavior sortBehavior, CommandList commandList) { this.meshTaskSizeEstimator = new MeshTaskSizeEstimator(level); - + this.chunkRenderer = new DefaultChunkRenderer(RenderDevice.INSTANCE, ChunkMeshFormats.COMPACT); this.level = level; @@ -175,29 +178,37 @@ private boolean createTerrainRenderList(Camera camera, Viewport viewport, int fr final var searchDistance = this.getSearchDistance(); final var useOcclusionCulling = this.shouldUseOcclusionCulling(camera, spectator); - RenderListProvider renderListProvider; var importantRebuildQueueType = SodiumClientMod.options().performance.chunkBuildDeferMode.getImportantRebuildQueueType(); + var importantSortQueueType = this.sortBehavior.getDeferMode().getImportantRebuildQueueType(); if (this.isOutOfGraph(viewport.getChunkCoord())) { - var visitor = new TreeSectionCollector(frame, importantRebuildQueueType, this.sectionByPosition); + var visitor = new TreeSectionCollector(frame, importantRebuildQueueType, importantSortQueueType, this.sectionByPosition); this.renderableSectionTree.prepareForTraversal(); this.renderableSectionTree.traverse(visitor, viewport, searchDistance); - renderListProvider = visitor; + this.sectionCollector = visitor; } else { - var visitor = new OcclusionSectionCollector(frame, importantRebuildQueueType); + var visitor = new OcclusionSectionCollector(frame, importantRebuildQueueType, importantSortQueueType); this.occlusionCuller.findVisible(visitor, viewport, searchDistance, useOcclusionCulling, frame); - renderListProvider = visitor; + this.sectionCollector = visitor; } + this.lastSectionCollector = null; - this.renderLists = renderListProvider.createRenderLists(viewport); - this.taskLists = renderListProvider.getTaskLists(); + this.taskLists = this.sectionCollector.getTaskLists(); // when there were sections with pending updates that were skipped because they already had a task running, // it needs to revisit them to schedule the remaining pending updates. // since not all tasks necessarily change the section info to trigger a graph update, // without this pending updates might be missed when the camera is stationary - return renderListProvider.needsRevisitForPendingUpdates(); + return this.sectionCollector.needsRevisitForPendingUpdates(); + } + + public void finalizeRenderLists(Viewport viewport) { + if (this.sectionCollector != null) { + this.renderLists = this.sectionCollector.createRenderLists(viewport); + this.lastSectionCollector = this.sectionCollector; + this.sectionCollector = null; + } } private boolean isOutOfGraph(SectionPos pos) { @@ -373,6 +384,35 @@ public void uploadChunks() { } } + private boolean sectionVisible(RenderSection section) { + return section.getLastVisibleFrame() == this.lastUpdatedFrame; + } + + private boolean isSectionImmediatePresentationCandidate(RenderSection section) { + if (this.cameraPosition == null) { + return false; + } + var distanceSquared = section.getSquaredDistance( + (float) this.cameraPosition.x(), + (float) this.cameraPosition.y(), + (float) this.cameraPosition.z() + ); + + if (distanceSquared < NEARBY_REBUILD_DISTANCE) { + return true; + } + + return distanceSquared < IMMEDIATE_PRESENT_DISTANCE && + // check that visible or adjacent to a visible section + (this.sectionVisible(section) + || this.sectionVisible(section.adjacentDown) + || this.sectionVisible(section.adjacentUp) + || this.sectionVisible(section.adjacentNorth) + || this.sectionVisible(section.adjacentSouth) + || this.sectionVisible(section.adjacentWest) + || this.sectionVisible(section.adjacentEast)); + } + private boolean processChunkBuildResults(ArrayList results) { var filtered = filterChunkBuildResults(results); @@ -384,11 +424,27 @@ private boolean processChunkBuildResults(ArrayList results) { long totalUploadSize = 0; for (var result : filtered) { var resultSize = result.getResultSize(); + var job = result.render.getRunningJob(); TranslucentData oldData = result.render.getTranslucentData(); if (result instanceof ChunkBuildOutput chunkBuildOutput) { + var prevFlags = result.render.getFlags(); + touchedSectionInfo |= this.updateSectionInfo(result.render, chunkBuildOutput.info); + // if result was blocking (or is approximately visible) and section is now newly renderable, force render it since it's probably a newly uncovered chunk. + // This also fixes flickering issues with pistons moving blocks and switching between being a mesh and a BE. + if (job != null + && (job.isBlocking() || this.isSectionImmediatePresentationCandidate(result.render)) + && RenderSectionFlags.renderingMoreTypesNow(prevFlags, chunkBuildOutput.info.flags)) { + // if there is currently no section collector since there was no graph traversal, + // reuse the previous section collector and use it to generate new extended render lists + if (this.sectionCollector == null) { + this.sectionCollector = this.lastSectionCollector; + } + this.sectionCollector.visit(result.render); + } + result.render.setLastMeshResultSize(resultSize); this.meshTaskSizeEstimator.addData(this.meshTaskSizeEstimator.resultForSection(result.render, resultSize)); @@ -404,12 +460,9 @@ private boolean processChunkBuildResults(ArrayList results) { this.sortTriggering.applyTriggerChanges(data, sortOutput.getDynamicSorter(), result.render.getPosition(), this.cameraPosition); } - var job = result.render.getTaskCancellationToken(); - - // clear the cancellation token (thereby marking the section as not having an - // active task) if this job is the most recent submitted job for this section + // clear the running job if this job is the most recent submitted job for this section if (job != null && result.submitTime >= result.render.getLastSubmittedFrame()) { - result.render.setTaskCancellationToken(null); + result.render.setRunningJob(null); } result.render.setLastUploadFrame(result.submitTime); @@ -513,19 +566,13 @@ public void updateChunks(boolean updateImmediately) { // an estimator is used estimate task duration and limit the execution time to the available worker capacity. // separately, tasks are limited by their estimated upload size and duration. var uploadBudget = new LimitedResourceBudget( - Math.max((long)(this.averageFrameDuration * FRAME_DURATION_UPLOAD_FRACTION), MIN_UPLOAD_DURATION_BUDGET), + Math.max((long) (this.averageFrameDuration * FRAME_DURATION_UPLOAD_FRACTION), MIN_UPLOAD_DURATION_BUDGET), this.regions.getStagingBuffer().getUploadSizeLimit(this.averageFrameDuration)); var nextFrameBlockingCollector = new ChunkJobCollector(this.buildResults::add); var deferredCollector = new ChunkJobCollector(remainingDuration, this.buildResults::add); - // if zero frame delay is allowed, submit important sorts with the current frame blocking collector. - // otherwise submit with the collector that the next frame is blocking on. - if (this.sortBehavior.getDeferMode() == DeferMode.ZERO_FRAMES) { - this.submitSectionTasks(thisFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector, uploadBudget); - } else { - this.submitSectionTasks(nextFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector, uploadBudget); - } + this.submitSectionTasks(thisFrameBlockingCollector, nextFrameBlockingCollector, deferredCollector, uploadBudget); this.thisFrameBlockingTasks = thisFrameBlockingCollector.getSubmittedTaskCount(); this.nextFrameBlockingTasks = nextFrameBlockingCollector.getSubmittedTaskCount(); @@ -564,12 +611,12 @@ private void submitSectionTasks(ChunkJobCollector collector, UploadResourceBudge // sections for which there's a currently running task. var pendingUpdate = section.getPendingUpdate(); if (pendingUpdate != 0) { - submitSectionTask(collector, section, pendingUpdate, uploadBudget); + submitSectionTask(collector, section, pendingUpdate, uploadBudget, queueType == TaskQueueType.ZERO_FRAME_DEFER); } } } - private void submitSectionTask(ChunkJobCollector collector, @NotNull RenderSection section, int type, UploadResourceBudget uploadBudget) { + private void submitSectionTask(ChunkJobCollector collector, @NotNull RenderSection section, int type, UploadResourceBudget uploadBudget, boolean blocking) { if (section.isDisposed()) { return; } @@ -597,7 +644,7 @@ private void submitSectionTask(ChunkJobCollector collector, @NotNull RenderSecti BuiltSectionInfo.EMPTY, Collections.emptyMap())); this.buildResults.add(result); - section.setTaskCancellationToken(null); + section.setRunningJob(null); } } else { // implies it's a type of sort task task = this.createSortTask(section, this.frame); @@ -611,13 +658,13 @@ private void submitSectionTask(ChunkJobCollector collector, @NotNull RenderSecti } if (task != null) { - var job = this.builder.scheduleTask(task, ChunkUpdateTypes.isImportant(type), collector::onJobFinished); + var job = this.builder.scheduleTask(task, ChunkUpdateTypes.isImportant(type), collector::onJobFinished, blocking); collector.addSubmittedJob(job); // consume upload budget in size and duration using estimates uploadBudget.consume(job.getEstimatedUploadDuration(), job.getEstimatedSize()); - section.setTaskCancellationToken(job); + section.setRunningJob(job); } section.setLastSubmittedFrame(this.frame); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkBuilder.java index 7e08e5032f..76edae02c0 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkBuilder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkBuilder.java @@ -87,8 +87,7 @@ private void shutdownThreads() { this.threads.clear(); } - public , OUTPUT extends BuilderTaskOutput> ChunkJobTyped scheduleTask(TASK task, boolean important, - Consumer> consumer) + public , OUTPUT extends BuilderTaskOutput> ChunkJobTyped scheduleTask(TASK task, boolean important, Consumer> consumer, boolean blocking) { Validate.notNull(task, "Task must be non-null"); @@ -96,7 +95,7 @@ public , OUTPUT extends BuilderTaskOutput> throw new IllegalStateException("Executor is stopped"); } - var job = new ChunkJobTyped<>(task, consumer); + var job = new ChunkJobTyped<>(task, consumer, blocking); this.queue.add(job, important); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJob.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJob.java index 7e7b4b15b4..e010e29170 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJob.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJob.java @@ -7,6 +7,8 @@ public interface ChunkJob extends CancellationToken { void execute(ChunkBuildContext context); boolean isStarted(); + + boolean isBlocking(); long getEstimatedSize(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java index 51c8830525..201918f880 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/executor/ChunkJobTyped.java @@ -12,13 +12,15 @@ public class ChunkJobTyped, OUTPUT extends { private final TASK task; private final Consumer> consumer; + private final boolean blocking; private volatile boolean cancelled; private volatile boolean started; - ChunkJobTyped(TASK task, Consumer> consumer) { + ChunkJobTyped(TASK task, Consumer> consumer, boolean blocking) { this.task = task; this.consumer = consumer; + this.blocking = blocking; } @Override @@ -69,6 +71,11 @@ public boolean isStarted() { return this.started; } + @Override + public boolean isBlocking() { + return this.blocking; + } + @Override public long getEstimatedSize() { return this.task.getEstimatedSize(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/OcclusionSectionCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/OcclusionSectionCollector.java index 51477a283d..b627ae41ca 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/OcclusionSectionCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/OcclusionSectionCollector.java @@ -7,8 +7,8 @@ * collect the visible chunks. */ public class OcclusionSectionCollector extends SectionCollector { - public OcclusionSectionCollector(int frame, TaskQueueType importantRebuildQueueType) { - super(frame, importantRebuildQueueType); + public OcclusionSectionCollector(int frame, TaskQueueType importantRebuildQueueType, TaskQueueType importantSortQueueType) { + super(frame, importantRebuildQueueType, importantSortQueueType); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java index 759032759a..891590d18e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java @@ -14,15 +14,17 @@ public abstract class SectionCollector implements RenderListProvider, RenderSectionVisitor { private final int frame; private final TaskQueueType importantRebuildQueueType; + private final TaskQueueType importantSortQueueType; private final ObjectArrayList renderLists; private final EnumMap> sortedTaskLists; private boolean needsRevisitForPendingUpdates = false; private static int[] sortItems = new int[RenderRegion.REGION_SIZE]; - public SectionCollector(int frame, TaskQueueType importantRebuildQueueType) { + public SectionCollector(int frame, TaskQueueType importantRebuildQueueType, TaskQueueType importantSortQueueType) { this.frame = frame; this.importantRebuildQueueType = importantRebuildQueueType; + this.importantSortQueueType = importantSortQueueType; this.renderLists = new ObjectArrayList<>(); this.sortedTaskLists = new EnumMap<>(TaskQueueType.class); @@ -55,12 +57,12 @@ public void visit(RenderSection section) { // if the section has a pending update but a task is already running for it, // don't add it to the task list again because starting a new task when there's already one running is invalid. // (for example, it would become impossible to cancel the earlier task) - if (section.getTaskCancellationToken() != null) { + if (section.getRunningJob() != null) { this.needsRevisitForPendingUpdates = true; return; } - - var queueType = ChunkUpdateTypes.getQueueType(pendingUpdate, this.importantRebuildQueueType); + + var queueType = ChunkUpdateTypes.getQueueType(pendingUpdate, this.importantRebuildQueueType, this.importantSortQueueType); Queue queue = this.sortedTaskLists.get(queueType); if (queue.size() < queueType.queueSizeLimit()) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/TreeSectionCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/TreeSectionCollector.java index 282ab1a8d1..33edb33720 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/TreeSectionCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/TreeSectionCollector.java @@ -11,8 +11,8 @@ public class TreeSectionCollector extends SectionCollector implements CoordinateSectionVisitor { private final Long2ReferenceMap sections; - public TreeSectionCollector(int frame, TaskQueueType importantRebuildQueueType, Long2ReferenceMap sections) { - super(frame, importantRebuildQueueType); + public TreeSectionCollector(int frame, TaskQueueType importantRebuildQueueType, TaskQueueType importantSortQueueType, Long2ReferenceMap sections) { + super(frame, importantRebuildQueueType, importantSortQueueType); this.sections = sections; } From 430a643155d4b9b402187b6537aef7546aa6d5e1 Mon Sep 17 00:00:00 2001 From: MCRcortex <18544518+MCRcortex@users.noreply.github.com> Date: Fri, 10 Oct 2025 02:37:11 +1000 Subject: [PATCH 123/215] Use doubles instead of floats in estimators (#3277) This has no performance cost and improves numerical precision. --- .../estimation/Abstract2DLinearEstimator.java | 4 +- .../estimation/Average1DEstimator.java | 14 +++---- .../estimation/ExpDecayLinear2DEstimator.java | 38 +++++++++---------- .../estimation/JobDurationEstimator.java | 2 +- .../estimation/UploadDurationEstimator.java | 2 +- .../mods/sodium/client/util/MathUtil.java | 2 +- 6 files changed, 31 insertions(+), 31 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Abstract2DLinearEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Abstract2DLinearEstimator.java index 2ec1bdeebc..2baf9c829b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Abstract2DLinearEstimator.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Abstract2DLinearEstimator.java @@ -36,8 +36,8 @@ public void addDataPoint(DataPair input) { protected abstract static class LinearFunction>> implements Model { protected final long initialOutput; - protected float yIntercept; - protected float slope; + protected double yIntercept; + protected double slope; protected int gatheredSamples = 0; public LinearFunction(long initialOutput) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Average1DEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Average1DEstimator.java index 7816e180a1..f9f10c5c09 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Average1DEstimator.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Average1DEstimator.java @@ -5,10 +5,10 @@ import java.util.Locale; public abstract class Average1DEstimator extends Estimator, Average1DEstimator.ValueBatch, Void, Long, Average1DEstimator.Average> { - private final float newDataRatio; + private final double newDataRatio; private final long initialEstimate; - public Average1DEstimator(float newDataRatio, long initialEstimate) { + public Average1DEstimator(double newDataRatio, long initialEstimate) { this.newDataRatio = newDataRatio; this.initialEstimate = initialEstimate; } @@ -33,8 +33,8 @@ public void reset() { this.count = 0; } - public float getAverage() { - return ((float) this.valueSum) / this.count; + public double getAverage() { + return ((double) this.valueSum) / this.count; } } @@ -44,11 +44,11 @@ protected ValueBatch createNewDataBatch() { } protected static class Average implements Estimator.Model> { - private final float newDataRatio; + private final double newDataRatio; private boolean hasRealData = false; - private float average; + private double average; - public Average(float newDataRatio, float initialValue) { + public Average(double newDataRatio, double initialValue) { this.average = initialValue; this.newDataRatio = newDataRatio; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/ExpDecayLinear2DEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/ExpDecayLinear2DEstimator.java index c78a22a7a4..4579bfcb74 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/ExpDecayLinear2DEstimator.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/ExpDecayLinear2DEstimator.java @@ -4,11 +4,11 @@ public abstract class ExpDecayLinear2DEstimator extends Abstract2DLinearEstim C, ExpDecayLinear2DEstimator.ClearingLinearRegressionBatch, ExpDecayLinear2DEstimator.ExpDecayLinearFunction> { - private final float newDataRatio; + private final double newDataRatio; private final int initialSampleTarget; private final int minBatchSize; - public ExpDecayLinear2DEstimator(float newDataRatio, int initialSampleTarget, int minBatchSize, long initialOutput) { + public ExpDecayLinear2DEstimator(double newDataRatio, int initialSampleTarget, int minBatchSize, long initialOutput) { super(initialOutput); this.newDataRatio = newDataRatio; this.initialSampleTarget = initialSampleTarget; @@ -44,19 +44,19 @@ protected static class ExpDecayLinearFunction extends Abstract2DLinearEstimat C, ExpDecayLinear2DEstimator.ClearingLinearRegressionBatch> { // the maximum fraction of the total weight that new data can have - private final float newDataRatioInv; + private final double newDataRatioInv; // how many samples we want to have at least before we start diminishing the new data's weight private final int initialSampleTarget; private final int minBatchSize; - private float xMeanOld = 0; - private float yMeanOld = 0; - private float covarianceOld = 0; - private float varianceOld = 0; + private double xMeanOld = 0; + private double yMeanOld = 0; + private double covarianceOld = 0; + private double varianceOld = 0; - public ExpDecayLinearFunction(float newDataRatio, int initialSampleTarget, int minBatchSize, long initialOutput) { + public ExpDecayLinearFunction(double newDataRatio, int initialSampleTarget, int minBatchSize, long initialOutput) { super(initialOutput); - this.newDataRatioInv = 1.0f / newDataRatio; + this.newDataRatioInv = 1.0 / newDataRatio; this.initialSampleTarget = initialSampleTarget; this.minBatchSize = minBatchSize; } @@ -70,8 +70,8 @@ public void update(ClearingLinearRegressionBatch batch) { // condition the weight to gather at least the initial sample target, and then weight the new data with a ratio var newDataSize = batch.size(); var totalSamples = this.gatheredSamples + newDataSize; - float oldDataWeight; - float totalWeight; + double oldDataWeight; + double totalWeight; if (totalSamples <= this.initialSampleTarget) { totalWeight = totalSamples; oldDataWeight = this.gatheredSamples; @@ -81,7 +81,7 @@ public void update(ClearingLinearRegressionBatch batch) { totalWeight = oldDataWeight + newDataSize; } - var totalWeightInv = 1.0f / totalWeight; + double totalWeightInv = 1.0 / totalWeight; // calculate the weighted mean along both axes long xSum = 0; @@ -90,20 +90,20 @@ public void update(ClearingLinearRegressionBatch batch) { xSum += data.x(); ySum += data.y(); } - var xMean = (this.xMeanOld * oldDataWeight + xSum) * totalWeightInv; - var yMean = (this.yMeanOld * oldDataWeight + ySum) * totalWeightInv; + double xMean = (this.xMeanOld * oldDataWeight + xSum) * totalWeightInv; + double yMean = (this.yMeanOld * oldDataWeight + ySum) * totalWeightInv; // the covariance and variance are calculated from the differences to the mean - var covarianceSum = 0.0f; - var varianceSum = 0.0f; + double covarianceSum = 0.0; + double varianceSum = 0.0; for (var data : batch) { - var xDelta = data.x() - xMean; - var yDelta = data.y() - yMean; + double xDelta = data.x() - xMean; + double yDelta = data.y() - yMean; covarianceSum += xDelta * yDelta; varianceSum += xDelta * xDelta; } - if (varianceSum == 0) { + if (Math.abs(varianceSum) <= Double.MIN_NORMAL) { return; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobDurationEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobDurationEstimator.java index 013551ddc5..715cb13ac7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobDurationEstimator.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/JobDurationEstimator.java @@ -6,7 +6,7 @@ public class JobDurationEstimator extends ExpDecayLinear2DEstimator> { public static final int INITIAL_SAMPLE_TARGET = 100; - public static final float NEW_DATA_RATIO = 0.05f; + public static final double NEW_DATA_RATIO = 0.05; private static final int MIN_BATCH_SIZE = 40; private static final long INITIAL_JOB_DURATION_ESTIMATE = 5_000_000L; // 5ms diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadDurationEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadDurationEstimator.java index d0e5a938e5..352f67242d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadDurationEstimator.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/UploadDurationEstimator.java @@ -6,7 +6,7 @@ import java.util.*; public class UploadDurationEstimator extends ExpDecayLinear2DEstimator { - public static final float NEW_DATA_RATIO = 0.05f; + public static final double NEW_DATA_RATIO = 0.05; public static final int INITIAL_SAMPLE_TARGET = 100; public static final int MIN_BATCH_SIZE = 100; private static final long INITIAL_UPLOAD_TIME_ESTIMATE = 100_000L; // 100µs diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java index bf399bc2b3..c0d2e25d9f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/MathUtil.java @@ -50,7 +50,7 @@ public static float comparableIntToFloat(int i) { return Float.intBitsToFloat(i ^ ((i >> 31) & 0x7FFFFFFF)); } - public static float exponentialMovingAverage(float oldValue, float newValue, float newValueContribution) { + public static double exponentialMovingAverage(double oldValue, double newValue, double newValueContribution) { return newValueContribution * newValue + (1 - newValueContribution) * oldValue; } From 2d868b3e2462625920277331df283e8dd4aa4f3f Mon Sep 17 00:00:00 2001 From: JellySquid Date: Thu, 9 Oct 2025 15:21:23 -0500 Subject: [PATCH 124/215] Fix crash when a section on the edge of the world is tested for immediate presentation --- .../mods/sodium/client/render/chunk/RenderSectionManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index f910579001..4eb95fdfee 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -385,7 +385,8 @@ public void uploadChunks() { } private boolean sectionVisible(RenderSection section) { - return section.getLastVisibleFrame() == this.lastUpdatedFrame; + // unloaded sections are considered visible as to not be an impossible requirement for immediate presentation + return section == null || section.getLastVisibleFrame() == this.lastUpdatedFrame; } private boolean isSectionImmediatePresentationCandidate(RenderSection section) { From ffb2cc6cad818df98bc76780ff279333ec9182a4 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 9 Nov 2025 17:52:05 +0100 Subject: [PATCH 125/215] Improved buffer size estimation leading to far fewer buffer allocations and much less resizing (#3313) --- .../sodium/client/gl/arena/GlBufferArena.java | 176 ++++++++++++++---- .../chunk/data/SectionRenderDataStorage.java | 4 +- .../render/chunk/region/RenderRegion.java | 6 +- .../chunk/region/RenderRegionManager.java | 12 +- 4 files changed, 147 insertions(+), 51 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java index 0820ae0a9f..9e38fd2f60 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java @@ -11,15 +11,21 @@ import java.util.Collection; import java.util.LinkedList; import java.util.List; -import java.util.stream.Collectors; import java.util.stream.Stream; public class GlBufferArena { static final boolean CHECK_ASSERTIONS = false; - private static final GlBufferUsage BUFFER_USAGE = GlBufferUsage.STATIC_DRAW; + // how many segments we require to be present before we calculate an average size + public static final int MIN_SEGMENTS_FOR_AVG = 16; + // growth factor to use when we have too few segments present + public static final float FEW_SEGMENTS_GROWTH_FACTOR = 1.5f; + // factor to use when we are allocating with an expected size + public static final float EXPECTED_SIZE_TARGET_FACTOR = 1.5f; + // how much bigger than requested a buffer can be to be considered for reuse + public static final float MAX_BUFFER_REUSE_SIZE_FACTOR = 1.4f; - private final int resizeIncrement; + private static final GlBufferUsage BUFFER_USAGE = GlBufferUsage.STATIC_DRAW; private final StagingBuffer stagingBuffer; private GlMutableBuffer arenaBuffer; @@ -28,20 +34,23 @@ public class GlBufferArena { private long capacity; private long used; + private int segmentCount; private final int stride; + private static final GlMutableBuffer[] freeBuffers = new GlMutableBuffer[8]; + private static int freeBufferCount = 0; + public GlBufferArena(CommandList commands, int initialCapacity, int stride, StagingBuffer stagingBuffer) { this.capacity = initialCapacity; - this.resizeIncrement = initialCapacity / 16; this.stride = stride; - this.head = new GlBufferSegment(this, 0, initialCapacity); + this.head = new GlBufferSegment(this, 0, this.capacity); this.head.setFree(true); - this.arenaBuffer = commands.createMutableBuffer(); - commands.allocateStorage(this.arenaBuffer, this.capacity * stride, BUFFER_USAGE); + this.arenaBuffer = getBufferOfSizeAtLeast(commands, this.capacity * stride); + this.capacity = this.arenaBuffer.getSize() / stride; this.stagingBuffer = stagingBuffer; } @@ -117,6 +126,57 @@ private List buildTransferList(List u return pendingCopies; } + private static GlMutableBuffer getBufferOfSizeAtLeast(CommandList commandList, long size) { + GlMutableBuffer buffer = null; + + if (freeBufferCount > 0) { + // get any buffer of at least the requested size but at most MAX_BUFFER_REUSE_SIZE_FACTOR larger + long maxAcceptableSize = (long) (size * MAX_BUFFER_REUSE_SIZE_FACTOR); + + // iterate buffers to get the smallest acceptable one + int candidateIndex = -1; + for (int i = 0; i < freeBuffers.length; i++) { + GlMutableBuffer freeBuffer = freeBuffers[i]; + if (freeBuffer != null) { + long testSize = freeBuffer.getSize(); + if (testSize >= size && testSize <= maxAcceptableSize && + (buffer == null || testSize < buffer.getSize())) { + candidateIndex = i; + buffer = freeBuffer; + } + } + } + if (buffer != null) { + freeBuffers[candidateIndex] = null; + freeBufferCount--; + } + } + + if (buffer == null) { + buffer = commandList.createMutableBuffer(); + commandList.allocateStorage(buffer, size, BUFFER_USAGE); + } + return buffer; + } + + private static void releaseBufferForReuse(CommandList commandList, GlMutableBuffer buffer) { + // find an empty slot if there is one + if (freeBufferCount < freeBuffers.length) { + for (int i = 0; i < freeBuffers.length; i++) { + if (freeBuffers[i] == null) { + freeBuffers[i] = buffer; + freeBufferCount++; + return; + } + } + } + + // evict randomly if no empty slot available + int evictIndex = (int) (Math.random() * freeBuffers.length); + commandList.deleteBuffer(freeBuffers[evictIndex]); + freeBuffers[evictIndex] = buffer; + } + private void transferSegments(CommandList commandList, Collection list, long capacity) { long bufferSize = capacity * this.stride; if (bufferSize >= (1L << 32)) { @@ -124,9 +184,7 @@ private void transferSegments(CommandList commandList, Collection getUsedSegments() { @@ -166,6 +226,11 @@ public long getDeviceAllocatedMemory() { return this.capacity * this.stride; } + private void updateUsed(long deltaUsed) { + this.used += deltaUsed; + this.segmentCount += Long.signum(deltaUsed); + } + private GlBufferSegment alloc(int size) { GlBufferSegment a = this.findFree(size); @@ -195,7 +260,7 @@ private GlBufferSegment alloc(int size) { result = b; } - this.used += result.getLength(); + this.updateUsed(result.getLength()); this.checkAssertions(); return result; @@ -229,7 +294,7 @@ public void free(GlBufferSegment entry) { entry.setFree(true); - this.used -= entry.getLength(); + this.updateUsed(-entry.getLength()); GlBufferSegment next = entry.getNext(); @@ -258,32 +323,29 @@ public GlBuffer getBufferObject() { return this.arenaBuffer; } - public boolean upload(CommandList commandList, Stream stream) { + public boolean upload(CommandList commandList, Stream stream, float regionFillFractionInv) { // Record the buffer object before we start any work // If the arena needs to re-allocate a buffer, this will allow us to check and return an appropriate flag GlBuffer buffer = this.arenaBuffer; // A linked list is used as we'll be randomly removing elements and want O(1) performance - List queue = stream.collect(Collectors.toCollection(LinkedList::new)); + long totalUploadSize = 0; + List queue = new LinkedList<>(); + for (var upload : (Iterable) stream::iterator) { + totalUploadSize += upload.getDataBuffer().getLength(); + queue.add(upload); + } - // Try to upload all the data into free segments first - this.tryUploads(commandList, queue); + // Try to upload all the data into free segments first, + // but only attempt this if there is enough free space assuming no fragmentation + if (totalUploadSize < (this.capacity - this.used) * this.stride) { + this.tryUploads(commandList, queue); + } // If we weren't able to upload some buffers, they will have been left behind in the queue if (!queue.isEmpty()) { - // Calculate the amount of memory needed for the remaining uploads - int remainingUploadSize = queue.stream() - .mapToInt(upload -> upload.getDataBuffer().getLength()) - .sum(); - - // Convert size to elements by dividing by the stride. - // This doesn't need a ceil since the upload buffers will be at least as big as required and have the same stride. - long remainingElements = remainingUploadSize / this.stride; - - // Ask the arena to grow to accommodate the remaining uploads - // This will force a re-allocation and compaction, which will leave us a continuous free segment - // for the remaining uploads - this.ensureCapacity(commandList, remainingElements); + // resize to the new estimated capacity + this.resize(commandList, estimateNewCapacity(regionFillFractionInv, queue)); // Try again to upload any buffers that failed last time this.tryUploads(commandList, queue); @@ -297,6 +359,48 @@ public boolean upload(CommandList commandList, Stream stream) { return this.arenaBuffer != buffer; } + private long estimateNewCapacity(float regionFillFractionInv, List queue) { + // Calculate the amount of memory needed for the remaining uploads + long requiredTotalSize = getRequiredTotalSize(queue); + + int newSegmentCount = this.segmentCount + queue.size(); + + // the base estimation is to use a growth factor applied to the new required size + long newCapacity; + + // use average segment size if we have enough segments to make it an accurate value + if (newSegmentCount >= MIN_SEGMENTS_FOR_AVG) { + // find the average segment size after the remaining uploads are allocated + long averageNewSegmentSize = (requiredTotalSize / newSegmentCount) + 1; // +1 to round up + + // use the average segment size to determine a new capacity, with some overshoot applied for safety + var expectedSegmentCount = newSegmentCount * regionFillFractionInv; + newCapacity = (long) (averageNewSegmentSize * expectedSegmentCount * EXPECTED_SIZE_TARGET_FACTOR); + } else { + newCapacity = (long) (requiredTotalSize * FEW_SEGMENTS_GROWTH_FACTOR); + } + return newCapacity; + } + + private long getRequiredTotalSize(List queue) { + long remainingUploadSize = 0; + for (var upload : queue) { + remainingUploadSize += upload.getDataBuffer().getLength(); + } + + // Convert size to elements by dividing by the stride. + // This doesn't need a ceil since the upload buffers will be at least as big as required and have the same stride. + long remainingElements = remainingUploadSize / this.stride; + + // Ask the arena to grow to accommodate the remaining uploads + // This will force a re-allocation and compaction, which will leave us a continuous free segment + // for the remaining uploads + + // Re-sizing the arena results in a compaction, so any free space in the arena will be + // made into one contiguous segment, joined with the new segment of free space we're asking for + return remainingElements + this.used; + } + private void tryUploads(CommandList commandList, List queue) { queue.removeIf(upload -> this.tryUpload(commandList, upload)); this.stagingBuffer.flush(commandList); @@ -322,16 +426,6 @@ private boolean tryUpload(CommandList commandList, PendingUpload upload) { return true; } - public void ensureCapacity(CommandList commandList, long elementCount) { - // Re-sizing the arena results in a compaction, so any free space in the arena will be - // made into one contiguous segment, joined with the new segment of free space we're asking for - // We calculate the number of free elements in our arena and then subtract that from the total requested - long elementsNeeded = elementCount - (this.capacity - this.used); - - // Try to allocate some extra buffer space unless this is an unusually large allocation - this.resize(commandList, Math.max(this.capacity + this.resizeIncrement, this.capacity + elementsNeeded)); - } - private void checkAssertions() { if (CHECK_ASSERTIONS) { this.checkAssertions0(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java index 17ae86a7fb..68c01fc829 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataStorage.java @@ -145,7 +145,7 @@ public boolean needsSharedIndexUpdate() { * @param arena The buffer arena to allocate the new buffer from * @return true if the arena resized itself */ - public boolean updateSharedIndexData(CommandList commandList, GlBufferArena arena) { + public boolean updateSharedIndexData(CommandList commandList, GlBufferArena arena, float regionFillFractionInv) { // assumes this.needsSharedIndexUpdate is true when this is called this.needsSharedIndexUpdate = false; @@ -177,7 +177,7 @@ public boolean updateSharedIndexData(CommandList commandList, GlBufferArena aren // create and upload a new shared index buffer var buffer = SharedQuadIndexBuffer.createIndexBuffer(SharedQuadIndexBuffer.IndexType.INTEGER, this.sharedIndexCapacity); var pendingUpload = new PendingUpload(buffer); - var bufferChanged = arena.upload(commandList, Stream.of(pendingUpload)); + var bufferChanged = arena.upload(commandList, Stream.of(pendingUpload), regionFillFractionInv); this.sharedIndexAllocation = pendingUpload.getResult(); buffer.free(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java index ce484e6f86..12da495bd1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java @@ -23,7 +23,7 @@ public class RenderRegion { public static final int SECTION_VERTEX_COUNT_ESTIMATE = 756; - public static final int SECTION_INDEX_COUNT_ESTIMATE = (SECTION_VERTEX_COUNT_ESTIMATE / 4) * 6; + public static final int SECTION_INDEX_COUNT_ESTIMATE = (SECTION_VERTEX_COUNT_ESTIMATE / DefaultTerrainRenderPasses.ALL.length / 4) * 6; public static final int SECTION_BUFFER_ESTIMATE = SECTION_VERTEX_COUNT_ESTIMATE * ChunkMeshFormats.COMPACT.getVertexFormat().getStride() + SECTION_INDEX_COUNT_ESTIMATE * Integer.BYTES; public static final int REGION_WIDTH = 8; @@ -219,6 +219,10 @@ public void removeSection(RenderSection section) { this.sections[sectionIndex] = null; this.sectionCount--; } + + public float getFillFractionInv() { + return (float) RenderRegion.REGION_SIZE / (float) this.sectionCount; + } public RenderSection getSection(int id) { return this.sections[id]; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java index efcd429126..0b8213cddf 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegionManager.java @@ -20,10 +20,7 @@ import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.SharedIndexSorter; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; +import java.util.*; public class RenderRegionManager { private final Long2ReferenceOpenHashMap regions = new Long2ReferenceOpenHashMap<>(); @@ -132,11 +129,12 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect } var resources = region.createResources(commandList); + var regionFillFractionInv = region.getFillFractionInv(); if (!uploads.isEmpty()) { var arena = resources.getGeometryArena(); boolean bufferChanged = arena.upload(commandList, uploads.stream() - .map(upload -> upload.vertexUpload)); + .map(upload -> upload.vertexUpload), regionFillFractionInv); // If any of the buffers changed, the tessellation will need to be updated // Once invalidated the tessellation will be re-created on the next attempted use @@ -158,7 +156,7 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect if (!indexUploads.isEmpty()) { var arena = resources.getIndexArena(); indexBufferChanged = arena.upload(commandList, indexUploads.stream() - .map(upload -> upload.indexBufferUpload)); + .map(upload -> upload.indexBufferUpload), regionFillFractionInv); for (PendingSectionIndexBufferUpload upload : indexUploads) { var storage = region.createStorage(DefaultTerrainRenderPasses.TRANSLUCENT); @@ -167,7 +165,7 @@ private void uploadResults(CommandList commandList, RenderRegion region, Collect } if (needsSharedIndexUpdate) { - indexBufferChanged |= translucentStorage.updateSharedIndexData(commandList, resources.getIndexArena()); + indexBufferChanged |= translucentStorage.updateSharedIndexData(commandList, resources.getIndexArena(), regionFillFractionInv); } if (indexBufferChanged) { From 57da1974abbd1884e0637669b740bd2734843962 Mon Sep 17 00:00:00 2001 From: jade <96878261+jadeposting@users.noreply.github.com> Date: Sun, 9 Nov 2025 11:53:26 -0500 Subject: [PATCH 126/215] Update texture light coords math to match Vanilla (#3311) --- .../render/chunk/vertex/format/impl/CompactChunkVertex.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ab191b9934..15aee0854c 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 @@ -107,8 +107,8 @@ private static int encodeTexture(float center, float x) { } private static int encodeLight(int light) { - int sky = Mth.clamp((light >>> 16) & 0xFF, 8, 248); - int block = Mth.clamp((light >>> 0) & 0xFF, 8, 248); + int sky = Mth.clamp(((light >>> 16) & 0xFF) + 8, 8, 248); + int block = Mth.clamp(((light >>> 0) & 0xFF) + 8, 8, 248); return (block << 0) | (sky << 8); } From 0ffb2341bb805482e745fb8f7464c0e822426b3e Mon Sep 17 00:00:00 2001 From: douira Date: Fri, 21 Nov 2025 00:17:52 +0100 Subject: [PATCH 127/215] Config API and Options Screen Redesign (#2837) * Squashed implementation of Config API and Options Screen redesign with initial work on UX/UI refactor Curently broken: DonationButtonWidget and ScrollableTooltip need updating to the latest state of Minecraft GUI rendering fix: correct condition and use setAllowedValuesProvider fix: don't show fabulous on unsupported systems Tackle page headers and Apply/Done on bottom don't open vanilla options when doing text input in search mode move scroll code into separate file fix option list scroll bars not working because of wrong dimensions that fail the hit test reset options to binding value on initial config screen open to fetch changes from changed vanilla options improve top margin in scissor of search results add new vanilla option to options screen fix focus behavior on search bar fix compact donation button functionality fix merge issues fix some focus issues with the search box add search results with page list and clickable page list items use abstract option list instead of concrete option list where possible use option page instead of just page as the metadata on text sources from options make the search results actually ordered make page list highlight more visible remove search result class and move score into text source, this simplifies a number of operations around the search result processing. also added more context to the text source objects add search widget that turns search button into a text box with a close button, other refactoring around page and option list layouts move search button to top of page list add search index interface and bigram search index implementation add page list width constant move builder impls into separate package update todos add arrow to toolbar at hovered element, moved tooltip logic into own class, moved donation button logic into own class, refactored layout misc cleanup and refactor scrollbar widget to extend abstract widget consolidate more colors into the color class new: make tooltips scrollable allow changing fullscreen resolution on macOS too fixes #2831 Fix cycling controls not playing a sound when activated using the keyboard fixes #2309 add annotation-based entrypoint loading on neoforge, refactored how mod metadata is retrieved, updated documentation implement dynamic value constraints in control elements, cleanup control elements cleanup add ability for mods to override other mods' options with new options allow gui scale to be changed by holding down control and scrolling, refactor option applying, move flag processing into Config, add api methods to add button that redirects to an external page add usage documentation, update todos truncate mod header subtitles if too long add default implementation to early register entrypoint so users don't need to implement both entrypoints even if they only need the late ("main") one improve config failure crash message fix tooltip after rebase constrain tooltip to screen move layout constants into separate class, fix gradient behind sidebar, change layout of buttons and options panes, fix overlapping ui elements, fix scissor areas, make spacings and arrangement consistent fix crash not working correctly add api method for formatting version instead of setting a new one fix: move sliders and tickboxes when sccrolling Co-authored-by: contaria new: scrollable option list Co-authored-by: contaria refactor: store dimensions in AbstractWidget Co-authored-by: contaria move important members of option builder subinterfaces more useful add api to add custom screens, use a color theme builder instead of a direct method allow users to set their own theme colors, fix some issues when text is too long scrollabe page list authored by @KingContaria Add WIP config api with IMS' redesign of the options page and contaria's forge mod entrypoint loader code New game GUI experiment Co-authored-by: Ennui Langeweile <85590273+ennuil@users.noreply.github.com> Co-authored-by: contaria Co-authored-by: haykam821 <24855774+haykam821@users.noreply.github.com> Co-authored-by: IMS212 * Fix config api * remove unused default color theme * add option to have dependent values update based on external changes and not just internal dynamic value dependencies * remove wrong option group headers * Implement single-list options UI missing from this implementation: auto-scrolling things into view, suggested design improvements * change colors to make options lighter than the headings and fix scroll-based page list highlighting * layout improvements with indentation, better spacing * move theme field into shared abstract control class * adjust layout and presentation of mod headers and the general options list * add ability to provide the config api with icons and render them in the options list * improve icon rendering code, render monochrome tinted, update icons * show icon in page list * formatting * better page list spacing * fix initial page list focus * fix navigation using the page list * simplify selection handling to use option pages directly * synchronized scrolling between page list and options list * improve tooltip scrollbar behavior: - the scrollbar doesn't overlap with the tooltip content - the scrollbar is permanently displayed in the tooltip * adaptively layout and position tooltip based on available space and minimum size requirements * update documentation * Fix shift+p shortcut to open vanilla settings triggering even when entering text * Fix cloud distance setting * Use correct default distance for clouds * Fix exception being thrown when there's no monitor available to select screen resolutions for because of a Windows bug. * fix focus events not activating the search bar text box * open search widget with T * remove debug and demonstration code, remove TODOs * Update to 1.21.10 * Improve search results and avoid overlap with scrollbar * fix toolbox positioning * Make capitalization in value formatting consistent * Change display name of Sodium to "Sodium" instead of "Sodium Renderer" * Use glyph instead of bold text * extract text offset into its own layout parameter * make search spacing consistent with non-search spacing to prevent jump when search starts * update docs --------- Co-authored-by: IMS212 Co-authored-by: Ennui Langeweile <85590273+ennuil@users.noreply.github.com> Co-authored-by: contaria Co-authored-by: haykam821 <24855774+haykam821@users.noreply.github.com> --- .../sodium/api/config/ConfigEntryPoint.java | 10 + .../api/config/ConfigEntryPointForge.java | 17 + .../mods/sodium/api/config/ConfigState.java | 13 + .../api/config/StorageEventHandler.java | 6 + .../mods/sodium/api/config/USAGE.md | 209 +++++++ .../config/option/ControlValueFormatter.java | 7 + .../api/config/option/NameProvider.java | 7 + .../api/config/option/OptionBinding.java | 7 + .../sodium/api/config/option}/OptionFlag.java | 2 +- .../api/config/option}/OptionImpact.java | 6 +- .../mods/sodium/api/config/option/Range.java | 20 + .../structure/BooleanOptionBuilder.java | 51 ++ .../config/structure/ColorThemeBuilder.java | 7 + .../api/config/structure/ConfigBuilder.java | 29 + .../config/structure/EnumOptionBuilder.java | 62 ++ .../ExternalButtonOptionBuilder.java | 25 + .../config/structure/ExternalPageBuilder.java | 12 + .../structure/IntegerOptionBuilder.java | 57 ++ .../config/structure/ModOptionsBuilder.java | 21 + .../api/config/structure/OptionBuilder.java | 17 + .../config/structure/OptionGroupBuilder.java | 9 + .../structure/OptionOverrideBuilder.java | 9 + .../config/structure/OptionPageBuilder.java | 9 + .../api/config/structure/PageBuilder.java | 4 + .../structure/StatefulOptionBuilder.java | 31 + .../mods/sodium/client/SodiumClientMod.java | 31 +- .../client/config/AnonymousOptionBinding.java | 26 + .../sodium/client/config/ConfigManager.java | 134 +++++ .../builder/BooleanOptionBuilderImpl.java | 99 ++++ .../config/builder/ColorThemeBuilderImpl.java | 36 ++ .../config/builder/ConfigBuilderImpl.java | 94 +++ .../config/builder/EnumOptionBuilderImpl.java | 149 +++++ .../ExternalButtonOptionBuilderImpl.java | 65 ++ .../builder/ExternalPageBuilderImpl.java | 38 ++ .../builder/IntegerOptionBuilderImpl.java | 139 +++++ .../config/builder/ModOptionsBuilderImpl.java | 91 +++ .../config/builder/OptionBuilderImpl.java | 64 ++ .../builder/OptionGroupBuilderImpl.java | 34 ++ .../builder/OptionOverrideBuilderImpl.java | 32 + .../config/builder/OptionPageBuilderImpl.java | 41 ++ .../config/builder/PageBuilderImpl.java | 20 + .../builder/StatefulOptionBuilderImpl.java | 140 +++++ .../builder/StaticOptionBuilderImpl.java | 29 + .../config/search/BigramSearchIndex.java | 179 ++++++ .../client/config/search/SearchIndex.java | 34 ++ .../config/search/SearchQuerySession.java | 7 + .../client/config/search/Searchable.java | 5 + .../config/search/SourceStoringIndex.java | 23 + .../client/config/search/TextSource.java | 37 ++ .../config/structure/BooleanOption.java | 26 + .../client/config/structure/Config.java | 298 ++++++++++ .../client/config/structure/EnumOption.java | 55 ++ .../structure/ExternalButtonOption.java | 25 + .../client/config/structure/ExternalPage.java | 20 + .../config/structure/IntegerOption.java | 51 ++ .../client/config/structure/ModOptions.java | 18 + .../client/config/structure/Option.java | 126 ++++ .../client/config/structure/OptionGroup.java | 14 + .../config/structure/OptionOverride.java | 6 + .../client/config/structure/OptionPage.java | 14 + .../sodium/client/config/structure/Page.java | 13 + .../config/structure/StatefulOption.java | 130 ++++ .../client/config/structure/StaticOption.java | 21 + .../client/config/value/ConstantValue.java | 16 + .../client/config/value/DependentValue.java | 15 + .../client/config/value/DynamicValue.java | 67 +++ .../mods/sodium/client/gui/ButtonTheme.java | 18 + .../mods/sodium/client/gui/ColorTheme.java | 23 + .../mods/sodium/client/gui/Colors.java | 44 ++ .../mods/sodium/client/gui/GuiTint.java | 24 + .../mods/sodium/client/gui/Layout.java | 21 + .../client/gui/SodiumConfigBuilder.java | 556 ++++++++++++++++++ .../client/gui/SodiumGameOptionPages.java | 393 ------------- ...iumGameOptions.java => SodiumOptions.java} | 19 +- .../sodium/client/gui/SodiumOptionsGUI.java | 457 -------------- .../client/gui/VideoSettingsScreen.java | 443 ++++++++++++++ .../sodium/client/gui/options/Option.java | 32 - .../client/gui/options/OptionGroup.java | 39 -- .../sodium/client/gui/options/OptionImpl.java | 204 ------- .../sodium/client/gui/options/OptionPage.java | 36 -- .../gui/options/binding/GenericBinding.java | 24 - .../gui/options/binding/OptionBinding.java | 7 - .../compat/VanillaBooleanOptionBinding.java | 23 - .../options/control/AbstractOptionList.java | 18 + .../options/control/AbstractScrollable.java | 23 + .../client/gui/options/control/Control.java | 10 +- .../gui/options/control/ControlElement.java | 91 ++- ...r.java => ControlValueFormatterImpls.java} | 37 +- .../gui/options/control/CyclingControl.java | 115 ++-- .../control/DynamicMaxSliderControl.java | 49 -- .../control/ExternalButtonControl.java | 114 ++++ .../gui/options/control/SliderControl.java | 153 ++--- .../gui/options/control/TickBoxControl.java | 61 +- .../storage/MinecraftOptionsStorage.java | 25 - .../gui/options/storage/OptionStorage.java | 7 - .../options/storage/SodiumOptionsStorage.java | 30 - .../client/gui/prompt/ScreenPrompt.java | 42 +- .../gui/widgets/AbstractParentWidget.java | 93 +++ .../client/gui/widgets/AbstractWidget.java | 81 ++- .../gui/widgets/CenteredFlatWidget.java | 143 +++++ .../gui/widgets/DonationButtonWidget.java | 38 ++ .../client/gui/widgets/FlatButtonWidget.java | 85 +-- .../client/gui/widgets/OptionListWidget.java | 322 ++++++++++ .../client/gui/widgets/PageListWidget.java | 201 +++++++ .../client/gui/widgets/ScrollableTooltip.java | 269 +++++++++ .../client/gui/widgets/ScrollbarWidget.java | 178 ++++++ .../client/gui/widgets/SearchWidget.java | 187 ++++++ .../mods/sodium/client/util/Dim2i.java | 28 + .../sodium/mixin/core/MinecraftMixin.java | 4 + .../hooks/settings/OptionsScreenMixin.java | 4 +- .../resources/assets/sodium/lang/en_us.json | 10 +- .../assets/sodium/textures/gui/arrows.png | Bin 119 -> 0 bytes .../assets/sodium/textures/gui/coffee_cup.png | Bin 0 -> 2740 bytes .../assets/sodium/textures/gui/icon.png | Bin 0 -> 6331 bytes .../sodium/textures/gui/tooltip_arrows.png | Bin 0 -> 3343 bytes .../mods/sodium/fabric/SodiumFabricMod.java | 3 + .../fabric/config/ConfigLoaderFabric.java | 25 + .../platform/neoforge/EntrypointMixin.java | 2 + .../mods/sodium/neoforge/SodiumForgeMod.java | 4 +- .../neoforge/config/ConfigLoaderForge.java | 71 +++ 120 files changed, 6333 insertions(+), 1662 deletions(-) create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigEntryPoint.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigEntryPointForge.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigState.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/StorageEventHandler.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/ControlValueFormatter.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/NameProvider.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionBinding.java rename common/src/{main/java/net/caffeinemc/mods/sodium/client/gui/options => api/java/net/caffeinemc/mods/sodium/api/config/option}/OptionFlag.java (75%) rename common/src/{main/java/net/caffeinemc/mods/sodium/client/gui/options => api/java/net/caffeinemc/mods/sodium/api/config/option}/OptionImpact.java (79%) create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/Range.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/BooleanOptionBuilder.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ColorThemeBuilder.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ConfigBuilder.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/EnumOptionBuilder.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalButtonOptionBuilder.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalPageBuilder.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionBuilder.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionGroupBuilder.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionOverrideBuilder.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionPageBuilder.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/PageBuilder.java create mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/StatefulOptionBuilder.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/AnonymousOptionBinding.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/ConfigManager.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/BooleanOptionBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ColorThemeBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ConfigBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalPageBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionGroupBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionOverrideBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionPageBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/PageBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StaticOptionBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/BigramSearchIndex.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/SearchIndex.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/SearchQuerySession.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/Searchable.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/SourceStoringIndex.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/TextSource.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/BooleanOption.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/EnumOption.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ExternalButtonOption.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ExternalPage.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/IntegerOption.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Option.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionGroup.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionOverride.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionPage.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Page.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/StatefulOption.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/StaticOption.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/value/ConstantValue.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/value/DependentValue.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/value/DynamicValue.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/ButtonTheme.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/ColorTheme.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/Colors.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/GuiTint.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/Layout.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumGameOptionPages.java rename common/src/main/java/net/caffeinemc/mods/sodium/client/gui/{SodiumGameOptions.java => SodiumOptions.java} (91%) delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptionsGUI.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/Option.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionGroup.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionImpl.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionPage.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/binding/GenericBinding.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/binding/OptionBinding.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/binding/compat/VanillaBooleanOptionBinding.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/AbstractOptionList.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/AbstractScrollable.java rename common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/{ControlValueFormatter.java => ControlValueFormatterImpls.java} (66%) delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/DynamicMaxSliderControl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ExternalButtonControl.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/storage/MinecraftOptionsStorage.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/storage/OptionStorage.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/storage/SodiumOptionsStorage.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/AbstractParentWidget.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/CenteredFlatWidget.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/DonationButtonWidget.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ScrollableTooltip.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ScrollbarWidget.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/SearchWidget.java delete mode 100644 common/src/main/resources/assets/sodium/textures/gui/arrows.png create mode 100644 common/src/main/resources/assets/sodium/textures/gui/coffee_cup.png create mode 100644 common/src/main/resources/assets/sodium/textures/gui/icon.png create mode 100644 common/src/main/resources/assets/sodium/textures/gui/tooltip_arrows.png create mode 100644 fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/config/ConfigLoaderFabric.java create mode 100644 neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/config/ConfigLoaderForge.java diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigEntryPoint.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigEntryPoint.java new file mode 100644 index 0000000000..ed44ef21d4 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigEntryPoint.java @@ -0,0 +1,10 @@ +package net.caffeinemc.mods.sodium.api.config; + +import net.caffeinemc.mods.sodium.api.config.structure.ConfigBuilder; + +public interface ConfigEntryPoint { + default void registerConfigEarly(ConfigBuilder builder) { + }; + + void registerConfigLate(ConfigBuilder builder); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigEntryPointForge.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigEntryPointForge.java new file mode 100644 index 0000000000..6c3432fc1b --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigEntryPointForge.java @@ -0,0 +1,17 @@ +package net.caffeinemc.mods.sodium.api.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface ConfigEntryPointForge { + /** + * The mod id to associate this config entrypoint's "owner" with. + * + * @return the mod id + */ + String value(); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigState.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigState.java new file mode 100644 index 0000000000..daad859ac5 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigState.java @@ -0,0 +1,13 @@ +package net.caffeinemc.mods.sodium.api.config; + +import net.minecraft.resources.ResourceLocation; + +public interface ConfigState { + ResourceLocation UPDATE_ON_REBUILD = ResourceLocation.parse("__meta__:update_on_rebuild"); + + boolean readBooleanOption(ResourceLocation id); + + int readIntOption(ResourceLocation id); + + > E readEnumOption(ResourceLocation id, Class enumClass); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/StorageEventHandler.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/StorageEventHandler.java new file mode 100644 index 0000000000..98e0a14cb4 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/StorageEventHandler.java @@ -0,0 +1,6 @@ +package net.caffeinemc.mods.sodium.api.config; + +@FunctionalInterface +public interface StorageEventHandler { + void afterSave(); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md new file mode 100644 index 0000000000..9ef2d26e0c --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md @@ -0,0 +1,209 @@ +# Usage of the Sodium Config API + +The Sodium Config API lets mods add their own pages to the Video Settings screen, which Sodium replaces with its own screen. + +If you encounter difficulties using the API, find bugs in it, or it's missing features you need, don't hesitate to contact us or make a contribution directly. + +## Scope + +The Sodium Config API is intended for mods that add video settings, not as a general purpose config API. For general purpose configuration, use the platform's appropriate mod list and a config library. + +As a presentation API, it does not handle loading, parsing, or saving configuration data to files. It is up to your mod to handle that on its own, or use a third-party library dedicated to that task. + +## Overview + +Sodium redirects Minecraft's "Video Settings" screen to its own screen. Historically, third-party mods have mixed into Sodium to add buttons to their own settings pages or additional options to Sodium's pages. + +With this API, these mods will not need to touch Sodium's internals anymore and should be able to operate independently of the GUI's implementation details. The API may not be able to cover all use cases where mods mixed into Sodium's options code, but it should cover most of the common ones. + +Registration of options happens in two stages: Early and late. Early registration happens when Sodium initializes its own early options before the window is created. Late registration happens after the game launched. Most mods will only need to use late registration. These stages are independent and only options that are registered in the late stage will show up in the GUI. + +### Features of the API + +Here's a summary of the features this config API provides: + +- a list of option pages per mod + - ID, name, and version + - a theme color + - optionally a square monochrome icon +- a list of option groups per page + - optionally named option groups + - external pages that simply open a mod-defined `Screen` +- a list of options per group +- options of various types + - `ResourceIdentifiers` as IDs + - types: integer slider, enum, boolean, external (opens a new `Screen`) + - value bindings through callbacks + - dynamic values for enablement, default value, allowed values + - dynamic values may depend on other option's values + - cycle and declared dependency consistency checking + - value formatters for presentation + - value-dependent tooltips + - flags for renderer reload and other graphics events +- all user facing strings are translatable +- storage providers are flushed after changes are applied +- early registration for mods that need to have their options available before the window is created +- entrypoint-based config registration +- declarative builder-based style + +## Getting Started + +### Dependency on Sodium's API + +Sodium publishes its api package on a maven repository that you can depend on in your buildscript. + +Fabric: + +```groovy +dependencies { + // ... other dependencies + + modImplementation "net.caffeinemc.mods:sodium-fabric:0.8.0+mc1.21.11" +} +``` + +NeoForge: + +```groovy +dependencies { + // ... other dependencies + + implementation "net.caffeinemc.mods:sodium-neoforge:0.8.0+mc1.21.11" +} +``` + +### Creating an Entrypoint + +Entrypoint classes that Sodium calls to run your options registration code can be declared either in your mod's metadata file, or on NeoForge with a special annotation. + +#### With a Metadata Entry + +Metadata-based entrypoints use the key `sodium:config_api_user` and the value is the full reference to a class that implements the `net.caffeinemc.mods.sodium.api.config.ConfigEntryPoint` interface. + +Fabric `fabric.mod.json`: + +```json5 +{ + "entrypoints": { + // ... other entrypoints + + "sodium:config_api_user": [ + "com.example.examplemod.ExampleModConfigBuilder" + ] + } +} +``` + +NeoForge `neoforge.mods.toml`: +```toml +[modproperties.examplemod] +"sodium:config_api_user" = "com.example.examplemod.ExampleModConfigBuilder" +``` + +The implementation of the entrypoint can look something like this: + +```java +package com.example.examplemod; + +import net.caffeinemc.mods.sodium.api.config.ConfigEntryPoint; +import net.caffeinemc.mods.sodium.api.config.structure.ConfigBuilder; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +public class ExampleConfigUser implements ConfigEntryPoint { + private final OptionStorage storage = new OptionStorage(); + private final Runnable handler = this.storage::flush; // typically gets referenced many times + + @Override + public void registerConfigLate(ConfigBuilder builder) { + builder.registerOwnModOptions() + .setIcon(ResourceLocation.parse("examplemod:textures/gui/icon.png")) + .addPage(builder.createOptionPage() + .setName(Component.literal("Example Page")) + .addOptionGroup(builder.createOptionGroup() + .setName(Component.literal("Example Group")) // only if necessary for clarity + .addOption(builder.createBooleanOption(ResourceLocation.parse("examplemod:example_option")) + .setName(Component.literal("Example Option")) // use translation keys here + .setTooltip(Component.literal("Example tooltip")) + .setStorageHandler(this.handler) + .setBinding(this.storage::setExampleOption, this.storage::getExampleOption) + .setDefaultValue(true) + ) + ) + ); + } +} + +// OptionStorage.java +class OptionStorage { + private boolean exampleOption = true; + + public boolean getExampleOption() { + return this.exampleOption; + } + + public void setExampleOption(boolean value) { + this.exampleOption = value; + } + + public void flush() { + // flush options to config file + } +} +``` + +#### NeoForge: With an Annotation + +Since NeoForge has the convention of using annotations for entrypoints, this option is provided as an alternative. Any classes annotated with `@ConfigEntryPointForge("examplemod")` will be loaded as config entrypoints too. Note that the annotation must be given the mod id that should be associated as the default mod for which a config is registered with `ConfigBuilder.registerOwnModOptions`. This is necessary as it's otherwise impossible to uniquely determine which mod a class is associated with on NeoForge. + +```java +import net.caffeinemc.mods.sodium.api.config.ConfigEntryPoint; +import net.caffeinemc.mods.sodium.api.config.ConfigEntryPointForge; + +@ConfigEntryPointForge("examplemod") +public class ExampleConfigUser implements ConfigEntryPoint { + // class body identical to the above +} +``` + +### Registering Your Options + +Each mod adds a page for its options, within each page there are groups of options, and each group contains a list of options. Each option has an id, a name, a tooltip, a storage handler, a binding, and a default value. There are three types of options: boolean (tickbox), integer (slider), and enum. Optionally, all types of options can be disabled, while integer and enum options can have their allowed values restricted. Those two types also require you to set a function that assigns a label to each selected value. + +Some attributes of an option can be provided dynamically, meaning the returned value can depend on the state of another option. The default value, option enablement, and the allowed values can be computed dynamically. The methods for setting a dynamic value provider also require you to specify a list of dependencies, which are the resource locations of the options that the dynamic value provider reads from. Since dynamically evaluated attributes may change the state of an option, cyclic dependencies will lead to option registration failing and the game crashing. + +Sodium constructs one instance of the entrypoint class, and then calls the early and late registration methods at the right time. + +## API Notes + +The API is largely self-explanatory and an example is provided above. Also see Sodium's own options registration for a more in-depth example of the API's usage. + +### Using `ConfigBuilder` and `ModOptions` + +The `ConfigBuilder` instance passed to the registration method allows quick and easy registration of a mod's own options using `ConfigBuilder.registerOwnModOptions`. The mod's id, name, version or a formatter for the existing version, and the color theme can be configured on the returned `ModOptionsBuilder`. It's also possible to register options for additional mods using `ConfigBuilder.registerModOptions`. Which mod is the "own" mod for `registerOwnModOptions` is determined by the mod that owns the metadata-based entrypoint or the mod id passed to the `@ConfigEntryPointForge("examplemod")` annotation. + +Each registered mod gets its own header in the page list. The color of the header and the corresponding entries is randomly selected from a predefined list by default, but can be customized using `ModOptionsBuilder.setColorTheme`. A color theme is created either by specifying three RGB colors or a single base color with the lighter and darker colors getting derived automatically. A mod can also specify an icon with `ModOptionsBuilder.setIcon`, which takes a `ResourceLocation` pointing to a texture, which will be tinted in the theme color and rendered in its entirety as a square. + +To simply switch to a new `Screen` when an entry in the video settings screen's page list is clicked, use `ConfigBuilder.createExternalPage` and add the returned page normally after configuring it with a name and a `Consumer` that receives the current screen and switches to your custom screen. + +### Using `OptionBuilder` + +The storage handler set with `OptionBuilder.setStorageHandler` is called after changes have been made to the options through the bindings. This lets you flush the changes to the config file once, instead of every time an option is changed. + +The tooltip set with `OptionBuilder.setTooltip` can optionally be a function that generates a tooltip depending on the option's current value. This is useful for enum options for which the description would be too long otherwise. + +Optionally a performance impact can be specified with `OptionBuilder.setImpact` where the impact ranges from low to high (or "varies"). + +Flags set with `OptionBuilder.setFlags` control what things are reset when this option is applied. They include reloading chunks or reloading resource packs. See `OptionFlag` for the available values. + +The default value set with `OptionBuilder.setDefaultValue`, or dynamically with `OptionBuilder.setDefaultProvider`, is used if the value returned by the binding does not fulfill the option's value constraint (in the case of a integer or enum option). + +Disabling an option with `OptionBuilder.setEnabled(false)` shows the option as strikethrough and makes it non-interactive. Otherwise, especially with regard to value constraints, it will behave as usual. + +The binding configured with `OptionBuilder.setBinding` is called when changes to the options have been made and are applied, or when the value no longer fulfills the option's constraints and is reset to the default value. It's also used to initially load a value during initialization. + +### Using `? extends OptionBuilder` + +Some of the attributes of an option are required and you must set them, or registration will fail. The concrete extensions of `OptionBuilder` for each of the option types have some additional methods for configuring type-specific things, some of which are also required. Notably, `EnumOptionBuilder.setElementNameProvider` and `IntegerOptionBuilder.setValueFormatter` are required in order to display these types of options. The method `setValueFormatter` for integer options takes a `ControlValueFormatter`, which simply formats a number as a `Component`. Many standard value formatters are provided in `ControlValueFormatterImpls` (not part of the API package). + +Integer and enum options can have value constraints that restrict the set of allowed values the user can select. For integer options, a `Range` must be configured with `IntegerOptionBuilder.setRange` so that the slider's start, end, and step positions are well-defined. Enum options may be configured to only allow the selection of certain elements with `EnumOptionBuilder.setAllowedValues`. \ No newline at end of file diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/ControlValueFormatter.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/ControlValueFormatter.java new file mode 100644 index 0000000000..674e077dcf --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/ControlValueFormatter.java @@ -0,0 +1,7 @@ +package net.caffeinemc.mods.sodium.api.config.option; + +import net.minecraft.network.chat.Component; + +public interface ControlValueFormatter { + Component format(int value); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/NameProvider.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/NameProvider.java new file mode 100644 index 0000000000..01186c0384 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/NameProvider.java @@ -0,0 +1,7 @@ +package net.caffeinemc.mods.sodium.api.config.option; + +import net.minecraft.network.chat.Component; + +public interface NameProvider { + Component getName(); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionBinding.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionBinding.java new file mode 100644 index 0000000000..db2072e59b --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionBinding.java @@ -0,0 +1,7 @@ +package net.caffeinemc.mods.sodium.api.config.option; + +public interface OptionBinding { + void save(V value); + + V load(); +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionFlag.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionFlag.java similarity index 75% rename from common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionFlag.java rename to common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionFlag.java index 17568af5a5..7f71f462b1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionFlag.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionFlag.java @@ -1,4 +1,4 @@ -package net.caffeinemc.mods.sodium.client.gui.options; +package net.caffeinemc.mods.sodium.api.config.option; public enum OptionFlag { REQUIRES_RENDERER_RELOAD, diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionImpact.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionImpact.java similarity index 79% rename from common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionImpact.java rename to common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionImpact.java index 2e274854c8..3c69beb0bc 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/OptionImpact.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionImpact.java @@ -1,9 +1,9 @@ -package net.caffeinemc.mods.sodium.client.gui.options; +package net.caffeinemc.mods.sodium.api.config.option; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; -public enum OptionImpact implements TextProvider { +public enum OptionImpact implements NameProvider { LOW(ChatFormatting.GREEN, "sodium.option_impact.low"), MEDIUM(ChatFormatting.YELLOW, "sodium.option_impact.medium"), HIGH(ChatFormatting.GOLD, "sodium.option_impact.high"), @@ -17,7 +17,7 @@ public enum OptionImpact implements TextProvider { } @Override - public Component getLocalizedName() { + public Component getName() { return this.text; } } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/Range.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/Range.java new file mode 100644 index 0000000000..5bac75b144 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/Range.java @@ -0,0 +1,20 @@ +package net.caffeinemc.mods.sodium.api.config.option; + +public record Range(int min, int max, int step) { + public Range { + if (min > max) { + throw new IllegalArgumentException("Min must be less than or equal to max"); + } + if (step <= 0) { + throw new IllegalArgumentException("Step must be greater than 0"); + } + } + + public boolean isValueValid(int value) { + return value >= this.min && value <= this.max && (value - this.min) % this.step == 0; + } + + public int getSpread() { + return this.max - this.min; + } +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/BooleanOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/BooleanOptionBuilder.java new file mode 100644 index 0000000000..d1b57294a8 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/BooleanOptionBuilder.java @@ -0,0 +1,51 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.caffeinemc.mods.sodium.api.config.ConfigState; +import net.caffeinemc.mods.sodium.api.config.StorageEventHandler; +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public interface BooleanOptionBuilder extends StatefulOptionBuilder { + @Override + BooleanOptionBuilder setName(Component name); + + @Override + BooleanOptionBuilder setStorageHandler(StorageEventHandler storage); + + @Override + BooleanOptionBuilder setTooltip(Component tooltip); + + @Override + BooleanOptionBuilder setTooltip(Function tooltip); + + @Override + BooleanOptionBuilder setImpact(OptionImpact impact); + + @Override + BooleanOptionBuilder setFlags(OptionFlag... flags); + + @Override + BooleanOptionBuilder setDefaultValue(Boolean value); + + @Override + BooleanOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies); + + @Override + BooleanOptionBuilder setEnabled(boolean available); + + @Override + BooleanOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies); + + @Override + BooleanOptionBuilder setBinding(Consumer save, Supplier load); + + @Override + BooleanOptionBuilder setBinding(OptionBinding binding); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ColorThemeBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ColorThemeBuilder.java new file mode 100644 index 0000000000..20a52a933b --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ColorThemeBuilder.java @@ -0,0 +1,7 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +public interface ColorThemeBuilder { + ColorThemeBuilder setBaseThemeRGB(int theme); + + ColorThemeBuilder setFullThemeRGB(int theme, int themeHighlight, int themeDisabled); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ConfigBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ConfigBuilder.java new file mode 100644 index 0000000000..25c975e8e2 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ConfigBuilder.java @@ -0,0 +1,29 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.minecraft.resources.ResourceLocation; + +public interface ConfigBuilder { + ModOptionsBuilder registerModOptions(String namespace, String name, String version); + + ModOptionsBuilder registerModOptions(String namespace); + + ModOptionsBuilder registerOwnModOptions(); + + OptionOverrideBuilder createOptionOverride(); + + ColorThemeBuilder createColorTheme(); + + OptionPageBuilder createOptionPage(); + + ExternalPageBuilder createExternalPage(); + + OptionGroupBuilder createOptionGroup(); + + BooleanOptionBuilder createBooleanOption(ResourceLocation id); + + IntegerOptionBuilder createIntegerOption(ResourceLocation id); + + > EnumOptionBuilder createEnumOption(ResourceLocation id, Class enumClass); + + ExternalButtonOptionBuilder createExternalButtonOption(ResourceLocation id); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/EnumOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/EnumOptionBuilder.java new file mode 100644 index 0000000000..164fba4d9c --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/EnumOptionBuilder.java @@ -0,0 +1,62 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.caffeinemc.mods.sodium.api.config.ConfigState; +import net.caffeinemc.mods.sodium.api.config.StorageEventHandler; +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public interface EnumOptionBuilder> extends StatefulOptionBuilder { + static > Function nameProviderFrom(Component... names) { + return e -> names[e.ordinal()]; + } + + EnumOptionBuilder setAllowedValues(Set allowedValues); + + EnumOptionBuilder setAllowedValuesProvider(Function> provider, ResourceLocation... dependencies); + + EnumOptionBuilder setElementNameProvider(Function provider); + + @Override + EnumOptionBuilder setName(Component name); + + @Override + EnumOptionBuilder setStorageHandler(StorageEventHandler storage); + + @Override + EnumOptionBuilder setTooltip(Component tooltip); + + @Override + EnumOptionBuilder setTooltip(Function tooltip); + + @Override + EnumOptionBuilder setImpact(OptionImpact impact); + + @Override + EnumOptionBuilder setFlags(OptionFlag... flags); + + @Override + EnumOptionBuilder setDefaultValue(E value); + + @Override + EnumOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies); + + @Override + EnumOptionBuilder setEnabled(boolean available); + + @Override + EnumOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies); + + @Override + EnumOptionBuilder setBinding(Consumer save, Supplier load); + + @Override + EnumOptionBuilder setBinding(OptionBinding binding); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalButtonOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalButtonOptionBuilder.java new file mode 100644 index 0000000000..a7b127b346 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalButtonOptionBuilder.java @@ -0,0 +1,25 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.caffeinemc.mods.sodium.api.config.ConfigState; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Consumer; +import java.util.function.Function; + +public interface ExternalButtonOptionBuilder extends OptionBuilder { + ExternalButtonOptionBuilder setScreenProvider(Consumer currentScreenConsumer); + + @Override + ExternalButtonOptionBuilder setName(Component name); + + @Override + ExternalButtonOptionBuilder setTooltip(Component tooltip); + + @Override + ExternalButtonOptionBuilder setEnabled(boolean available); + + @Override + ExternalButtonOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalPageBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalPageBuilder.java new file mode 100644 index 0000000000..e10bc40fee --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalPageBuilder.java @@ -0,0 +1,12 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +import java.util.function.Consumer; + +public interface ExternalPageBuilder extends PageBuilder { + ExternalPageBuilder setName(Component name); + + ExternalPageBuilder setScreenProvider(Consumer currentScreenConsumer); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java new file mode 100644 index 0000000000..fc161d3dc0 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java @@ -0,0 +1,57 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.caffeinemc.mods.sodium.api.config.ConfigState; +import net.caffeinemc.mods.sodium.api.config.StorageEventHandler; +import net.caffeinemc.mods.sodium.api.config.option.*; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public interface IntegerOptionBuilder extends StatefulOptionBuilder { + IntegerOptionBuilder setRange(int min, int max, int step); + + IntegerOptionBuilder setRange(Range range); + + IntegerOptionBuilder setRangeProvider(Function provider, ResourceLocation... dependencies); + + IntegerOptionBuilder setValueFormatter(ControlValueFormatter formatter); + + @Override + IntegerOptionBuilder setName(Component name); + + @Override + IntegerOptionBuilder setStorageHandler(StorageEventHandler storage); + + @Override + IntegerOptionBuilder setTooltip(Component tooltip); + + @Override + IntegerOptionBuilder setTooltip(Function tooltip); + + @Override + IntegerOptionBuilder setImpact(OptionImpact impact); + + @Override + IntegerOptionBuilder setFlags(OptionFlag... flags); + + @Override + IntegerOptionBuilder setDefaultValue(Integer value); + + @Override + IntegerOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies); + + @Override + IntegerOptionBuilder setEnabled(boolean available); + + @Override + IntegerOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies); + + @Override + IntegerOptionBuilder setBinding(Consumer save, Supplier load); + + @Override + IntegerOptionBuilder setBinding(OptionBinding binding); +} \ No newline at end of file diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java new file mode 100644 index 0000000000..806a6c2274 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java @@ -0,0 +1,21 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Function; + +public interface ModOptionsBuilder { + ModOptionsBuilder setName(String name); + + ModOptionsBuilder setVersion(String version); + + ModOptionsBuilder formatVersion(Function versionFormatter); + + ModOptionsBuilder setColorTheme(ColorThemeBuilder colorTheme); + + ModOptionsBuilder setIcon(ResourceLocation texture); + + ModOptionsBuilder addPage(PageBuilder page); + + ModOptionsBuilder registerOptionOverride(OptionOverrideBuilder override); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionBuilder.java new file mode 100644 index 0000000000..59b05ccb5c --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionBuilder.java @@ -0,0 +1,17 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.caffeinemc.mods.sodium.api.config.ConfigState; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Function; + +public interface OptionBuilder { + OptionBuilder setName(Component name); + + OptionBuilder setTooltip(Component tooltip); + + OptionBuilder setEnabled(boolean available); + + OptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionGroupBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionGroupBuilder.java new file mode 100644 index 0000000000..9e136000aa --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionGroupBuilder.java @@ -0,0 +1,9 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.minecraft.network.chat.Component; + +public interface OptionGroupBuilder { + OptionGroupBuilder setName(Component name); + + OptionGroupBuilder addOption(OptionBuilder option); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionOverrideBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionOverrideBuilder.java new file mode 100644 index 0000000000..06cab5eb3d --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionOverrideBuilder.java @@ -0,0 +1,9 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.minecraft.resources.ResourceLocation; + +public interface OptionOverrideBuilder { + OptionOverrideBuilder setTarget(ResourceLocation target); + + OptionOverrideBuilder setReplacement(OptionBuilder option); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionPageBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionPageBuilder.java new file mode 100644 index 0000000000..75e00d1b35 --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionPageBuilder.java @@ -0,0 +1,9 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.minecraft.network.chat.Component; + +public interface OptionPageBuilder extends PageBuilder { + OptionPageBuilder setName(Component name); + + OptionPageBuilder addOptionGroup(OptionGroupBuilder group); +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/PageBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/PageBuilder.java new file mode 100644 index 0000000000..73783d433b --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/PageBuilder.java @@ -0,0 +1,4 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +public interface PageBuilder { +} diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/StatefulOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/StatefulOptionBuilder.java new file mode 100644 index 0000000000..a81afdc03f --- /dev/null +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/StatefulOptionBuilder.java @@ -0,0 +1,31 @@ +package net.caffeinemc.mods.sodium.api.config.structure; + +import net.caffeinemc.mods.sodium.api.config.ConfigState; +import net.caffeinemc.mods.sodium.api.config.StorageEventHandler; +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public interface StatefulOptionBuilder extends OptionBuilder { + StatefulOptionBuilder setStorageHandler(StorageEventHandler storage); + + StatefulOptionBuilder setTooltip(Function tooltip); + + StatefulOptionBuilder setImpact(OptionImpact impact); + + StatefulOptionBuilder setFlags(OptionFlag... flags); + + StatefulOptionBuilder setDefaultValue(V value); + + StatefulOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies); + + StatefulOptionBuilder setBinding(Consumer save, Supplier load); + + StatefulOptionBuilder setBinding(OptionBinding binding); +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java index 3731c70020..9882546b14 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java @@ -1,10 +1,11 @@ package net.caffeinemc.mods.sodium.client; +import net.caffeinemc.mods.sodium.client.config.ConfigManager; import net.caffeinemc.mods.sodium.client.console.Console; import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; import net.caffeinemc.mods.sodium.client.data.fingerprint.FingerprintMeasure; import net.caffeinemc.mods.sodium.client.data.fingerprint.HashedFingerprint; -import net.caffeinemc.mods.sodium.client.gui.SodiumGameOptions; +import net.caffeinemc.mods.sodium.client.gui.SodiumOptions; import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,7 +13,7 @@ import java.io.IOException; public class SodiumClientMod { - private static SodiumGameOptions CONFIG; + private static SodiumOptions OPTIONS; private static final Logger LOGGER = LoggerFactory.getLogger("Sodium"); private static String MOD_VERSION; @@ -20,7 +21,9 @@ public class SodiumClientMod { public static void onInitialization(String version) { MOD_VERSION = version; - CONFIG = loadConfig(); + OPTIONS = loadConfig(); + + ConfigManager.registerConfigsEarly(); try { updateFingerprint(); @@ -29,12 +32,12 @@ public static void onInitialization(String version) { } } - public static SodiumGameOptions options() { - if (CONFIG == null) { + public static SodiumOptions options() { + if (OPTIONS == null) { throw new IllegalStateException("Config not yet available"); } - return CONFIG; + return OPTIONS; } public static Logger logger() { @@ -45,16 +48,16 @@ public static Logger logger() { return LOGGER; } - private static SodiumGameOptions loadConfig() { + private static SodiumOptions loadConfig() { try { - return SodiumGameOptions.loadFromDisk(); + return SodiumOptions.loadFromDisk(); } catch (Exception e) { LOGGER.error("Failed to load configuration file", e); LOGGER.error("Using default configuration file in read-only mode"); Console.instance().logMessage(MessageLevel.SEVERE, "sodium.console.config_not_loaded", true, 12.5); - var config = SodiumGameOptions.defaults(); + var config = SodiumOptions.defaults(); config.setReadOnly(); return config; @@ -62,10 +65,10 @@ private static SodiumGameOptions loadConfig() { } public static void restoreDefaultOptions() { - CONFIG = SodiumGameOptions.defaults(); + OPTIONS = SodiumOptions.defaults(); try { - SodiumGameOptions.writeToDisk(CONFIG); + SodiumOptions.writeToDisk(OPTIONS); } catch (IOException e) { throw new RuntimeException("Failed to write config file", e); } @@ -97,11 +100,11 @@ private static void updateFingerprint() { if (saved == null || !current.looselyMatches(saved)) { HashedFingerprint.writeToDisk(current.hashed()); - CONFIG.notifications.hasSeenDonationPrompt = false; - CONFIG.notifications.hasClearedDonationButton = false; + OPTIONS.notifications.hasSeenDonationPrompt = false; + OPTIONS.notifications.hasClearedDonationButton = false; try { - SodiumGameOptions.writeToDisk(CONFIG); + SodiumOptions.writeToDisk(OPTIONS); } catch (IOException e) { LOGGER.error("Failed to update config file", e); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/AnonymousOptionBinding.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/AnonymousOptionBinding.java new file mode 100644 index 0000000000..e4f057cf6e --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/AnonymousOptionBinding.java @@ -0,0 +1,26 @@ +package net.caffeinemc.mods.sodium.client.config; + +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class AnonymousOptionBinding implements OptionBinding { + private final Consumer save; + private final Supplier load; + + public AnonymousOptionBinding(Consumer save, Supplier load) { + this.save = save; + this.load = load; + } + + @Override + public void save(V value) { + this.save.accept(value); + } + + @Override + public V load() { + return this.load.get(); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/ConfigManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/ConfigManager.java new file mode 100644 index 0000000000..6f904c0944 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/ConfigManager.java @@ -0,0 +1,134 @@ +package net.caffeinemc.mods.sodium.client.config; + + +import com.google.common.collect.ImmutableList; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import net.caffeinemc.mods.sodium.api.config.ConfigEntryPoint; +import net.caffeinemc.mods.sodium.api.config.structure.ConfigBuilder; +import net.caffeinemc.mods.sodium.client.SodiumClientMod; +import net.caffeinemc.mods.sodium.client.config.builder.ConfigBuilderImpl; +import net.caffeinemc.mods.sodium.client.config.structure.Config; +import net.caffeinemc.mods.sodium.client.config.structure.ModOptions; +import net.minecraft.CrashReport; +import net.minecraft.client.Minecraft; + +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +public class ConfigManager { + public static final String CONFIG_ENTRY_POINT_KEY = "sodium:config_api_user"; + + private record ConfigUser( + Supplier configEntrypoint, + String modId) { + } + public record ModMetadata(String modName, String modVersion) { + } + + private static final Collection configUsers = new ArrayList<>(); + + public static Config CONFIG; + private static Function modInfoFunction; + + public static void setModInfoFunction(Function modInfoFunction) { + ConfigManager.modInfoFunction = modInfoFunction; + } + + public static void registerConfigEntryPoint(String className, String modId) { + Class entryPointClass; + try { + entryPointClass = Class.forName(className); + } catch (ClassNotFoundException e) { + SodiumClientMod.logger().warn("Mod '{}' provided a custom config integration but the class is missing: {}", modId, className); + return; + } + if (!ConfigEntryPoint.class.isAssignableFrom(entryPointClass)) { + SodiumClientMod.logger().warn("Mod '{}' provided a custom config integration but the class is of the wrong type: {}", modId, entryPointClass); + return; + } + + registerConfigEntryPoint(() -> { + try { + Constructor constructor = entryPointClass.getDeclaredConstructor(); + constructor.setAccessible(true); + return (ConfigEntryPoint) constructor.newInstance(); + } catch (ReflectiveOperationException e) { + SodiumClientMod.logger().warn("Mod '{}' provided a custom config integration but the class could not be constructed: {}", modId, entryPointClass); + } + return null; + }, modId); + } + + public static void registerConfigEntryPoint(Supplier entryPoint, String modId) { + configUsers.add(new ConfigUser(entryPoint, modId)); + } + + public static void registerConfigsEarly() { + registerConfigs(ConfigEntryPoint::registerConfigEarly); + } + + public static void registerConfigsLate() { + registerConfigs(ConfigEntryPoint::registerConfigLate); + } + + private static void registerConfigs(BiConsumer registerMethod) { + var namespaces = new ObjectOpenHashSet<>(); + ModOptions sodiumModOptions = null; + var modConfigs = new ObjectArrayList(); + + for (ConfigUser configUser : configUsers) { + var entryPoint = configUser.configEntrypoint.get(); + if (entryPoint == null) { + continue; + } + + var builder = new ConfigBuilderImpl(modInfoFunction, configUser.modId); + Collection builtConfigs; + try { + registerMethod.accept(entryPoint, builder); + builtConfigs = builder.build(); + + for (var modConfig : builtConfigs) { + var namespace = modConfig.namespace(); + if (namespaces.contains(namespace)) { + throw new IllegalArgumentException("Mod '" + configUser.modId + "' provided a duplicate mod id: " + namespace); + } + + namespaces.add(namespace); + + if (namespace.equals("sodium")) { + sodiumModOptions = modConfig; + } else { + modConfigs.add(modConfig); + } + } + } catch (Exception e) { + crashWithMessage("Mod '" + configUser.modId + "' failed while registering config options.", e); + return; + } + } + + modConfigs.sort(Comparator.comparing(ModOptions::name)); + + if (sodiumModOptions == null) { + throw new RuntimeException("Sodium mod config not found"); + } + modConfigs.add(0, sodiumModOptions); + + try { + CONFIG = new Config(ImmutableList.copyOf(modConfigs)); + } catch (Exception e) { + crashWithMessage("Failed to build config options", e); + } + } + + private static void crashWithMessage(String message, Exception e) { + Minecraft.crash(null, Minecraft.getInstance().gameDirectory, new CrashReport(message, e)); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/BooleanOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/BooleanOptionBuilderImpl.java new file mode 100644 index 0000000000..e5e96f2541 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/BooleanOptionBuilderImpl.java @@ -0,0 +1,99 @@ +package net.caffeinemc.mods.sodium.client.config.builder; + +import net.caffeinemc.mods.sodium.api.config.ConfigState; +import net.caffeinemc.mods.sodium.api.config.StorageEventHandler; +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; +import net.caffeinemc.mods.sodium.api.config.structure.BooleanOptionBuilder; +import net.caffeinemc.mods.sodium.client.config.structure.BooleanOption; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; + +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +class BooleanOptionBuilderImpl extends StatefulOptionBuilderImpl implements BooleanOptionBuilder { + BooleanOptionBuilderImpl(ResourceLocation id) { + super(id); + } + + @Override + BooleanOption build() { + this.prepareBuild(); + return new BooleanOption(this.id, this.getDependencies(), this.name, this.enabled, this.storage, this.tooltipProvider, this.impact, this.flags, this.defaultValue, this.binding); + } + + @Override + public BooleanOptionBuilder setName(Component name) { + super.setName(name); + return this; + } + + @Override + public BooleanOptionBuilder setStorageHandler(StorageEventHandler storage) { + super.setStorageHandler(storage); + return this; + } + + @Override + public BooleanOptionBuilder setTooltip(Component tooltip) { + super.setTooltip(tooltip); + return this; + } + + @Override + public BooleanOptionBuilder setTooltip(Function tooltip) { + super.setTooltip(tooltip); + return this; + } + + @Override + public BooleanOptionBuilder setImpact(OptionImpact impact) { + super.setImpact(impact); + return this; + } + + @Override + public BooleanOptionBuilder setFlags(OptionFlag... flags) { + super.setFlags(flags); + return this; + } + + @Override + public BooleanOptionBuilder setDefaultValue(Boolean value) { + super.setDefaultValue(value); + return this; + } + + @Override + public BooleanOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies) { + super.setDefaultProvider(provider, dependencies); + return this; + } + + @Override + public BooleanOptionBuilder setEnabled(boolean available) { + super.setEnabled(available); + return this; + } + + @Override + public BooleanOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies) { + super.setEnabledProvider(provider, dependencies); + return this; + } + + @Override + public BooleanOptionBuilder setBinding(Consumer save, Supplier load) { + super.setBinding(save, load); + return this; + } + + @Override + public BooleanOptionBuilder setBinding(OptionBinding binding) { + super.setBinding(binding); + return this; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ColorThemeBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ColorThemeBuilderImpl.java new file mode 100644 index 0000000000..0f0f14bcbf --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ColorThemeBuilderImpl.java @@ -0,0 +1,36 @@ +package net.caffeinemc.mods.sodium.client.config.builder; + +import net.caffeinemc.mods.sodium.api.config.structure.ColorThemeBuilder; +import net.caffeinemc.mods.sodium.client.gui.ColorTheme; + +public class ColorThemeBuilderImpl implements ColorThemeBuilder { + private int baseTheme; + private int themeHighlight; + private int themeDisabled; + + ColorTheme build() { + if (this.baseTheme == 0) { + throw new IllegalStateException("Base theme must be set"); + } + + if (this.themeHighlight == 0 || this.themeDisabled == 0) { + return new ColorTheme(this.baseTheme); + } else { + return new ColorTheme(this.baseTheme, this.themeHighlight, this.themeDisabled); + } + } + + @Override + public ColorThemeBuilder setBaseThemeRGB(int theme) { + this.baseTheme = theme; + return this; + } + + @Override + public ColorThemeBuilder setFullThemeRGB(int theme, int themeHighlight, int themeDisabled) { + this.baseTheme = theme; + this.themeHighlight = themeHighlight; + this.themeDisabled = themeDisabled; + return this; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ConfigBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ConfigBuilderImpl.java new file mode 100644 index 0000000000..7687e76e49 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ConfigBuilderImpl.java @@ -0,0 +1,94 @@ +package net.caffeinemc.mods.sodium.client.config.builder; + +import net.caffeinemc.mods.sodium.api.config.structure.*; +import net.caffeinemc.mods.sodium.client.config.ConfigManager; +import net.caffeinemc.mods.sodium.client.config.structure.ModOptions; +import net.minecraft.resources.ResourceLocation; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; + +public class ConfigBuilderImpl implements ConfigBuilder { + private final List pendingModConfigBuilders = new ArrayList<>(1); + + private final Function modInfoFunction; + private final String defaultNamespace; + + public ConfigBuilderImpl(Function modInfoFunction, String defaultNamespace) { + this.modInfoFunction = modInfoFunction; + this.defaultNamespace = defaultNamespace; + } + + public Collection build() { + var configs = new ArrayList(this.pendingModConfigBuilders.size()); + for (var builder : this.pendingModConfigBuilders) { + configs.add(builder.build()); + } + return configs; + } + + @Override + public ModOptionsBuilder registerModOptions(String namespace, String name, String version) { + var builder = new ModOptionsBuilderImpl(namespace, name, version); + this.pendingModConfigBuilders.add(builder); + return builder; + } + + @Override + public ModOptionsBuilder registerModOptions(String namespace) { + var metadata = this.modInfoFunction.apply(namespace); + return this.registerModOptions(namespace, metadata.modName(), metadata.modVersion()); + } + + @Override + public ModOptionsBuilder registerOwnModOptions() { + return this.registerModOptions(this.defaultNamespace); + } + + @Override + public OptionOverrideBuilder createOptionOverride() { + return new OptionOverrideBuilderImpl(); + } + + @Override + public ColorThemeBuilder createColorTheme() { + return new ColorThemeBuilderImpl(); + } + + @Override + public OptionPageBuilder createOptionPage() { + return new OptionPageBuilderImpl(); + } + + @Override + public ExternalPageBuilder createExternalPage() { + return new ExternalPageBuilderImpl(); + } + + @Override + public OptionGroupBuilder createOptionGroup() { + return new OptionGroupBuilderImpl(); + } + + @Override + public BooleanOptionBuilder createBooleanOption(ResourceLocation id) { + return new BooleanOptionBuilderImpl(id); + } + + @Override + public IntegerOptionBuilder createIntegerOption(ResourceLocation id) { + return new IntegerOptionBuilderImpl(id); + } + + @Override + public > EnumOptionBuilder createEnumOption(ResourceLocation id, Class enumClass) { + return new EnumOptionBuilderImpl<>(id, enumClass); + } + + @Override + public ExternalButtonOptionBuilder createExternalButtonOption(ResourceLocation id) { + return new ExternalButtonOptionBuilderImpl(id); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java new file mode 100644 index 0000000000..a71b5f070a --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java @@ -0,0 +1,149 @@ +package net.caffeinemc.mods.sodium.client.config.builder; + +import net.caffeinemc.mods.sodium.api.config.ConfigState; +import net.caffeinemc.mods.sodium.api.config.StorageEventHandler; +import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; +import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; +import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; +import net.caffeinemc.mods.sodium.api.config.structure.EnumOptionBuilder; +import net.caffeinemc.mods.sodium.client.config.structure.EnumOption; +import net.caffeinemc.mods.sodium.client.config.structure.Option; +import net.caffeinemc.mods.sodium.client.config.value.ConstantValue; +import net.caffeinemc.mods.sodium.client.config.value.DependentValue; +import net.caffeinemc.mods.sodium.client.config.value.DynamicValue; +import net.caffeinemc.mods.sodium.client.gui.options.TextProvider; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.Validate; + +import java.util.Collection; +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +class EnumOptionBuilderImpl> extends StatefulOptionBuilderImpl implements EnumOptionBuilder { + final Class enumClass; + + DependentValue> allowedValues; + Function elementNameProvider; + + EnumOptionBuilderImpl(ResourceLocation id, Class enumClass) { + super(id); + this.enumClass = enumClass; + } + + @Override + Option build() { + this.prepareBuild(); + + if (this.allowedValues == null) { + this.allowedValues = new ConstantValue<>(Set.of(this.enumClass.getEnumConstants())); + } + + if (this.elementNameProvider == null && TextProvider.class.isAssignableFrom(this.enumClass)) { + this.elementNameProvider = e -> ((TextProvider) e).getLocalizedName(); + } + + Validate.notNull(this.elementNameProvider, "Element name provider must be set or enum class must implement TextProvider"); + + return new EnumOption<>(this.id, this.getDependencies(), this.name, this.enabled, this.storage, this.tooltipProvider, this.impact, this.flags, this.defaultValue, this.binding, this.enumClass, this.allowedValues, this.elementNameProvider); + } + + @Override + Collection getDependencies() { + var deps = super.getDependencies(); + deps.addAll(this.allowedValues.getDependencies()); + return deps; + } + + @Override + public EnumOptionBuilder setAllowedValues(Set allowedValues) { + this.allowedValues = new ConstantValue<>(allowedValues); + return this; + } + + @Override + public EnumOptionBuilder setAllowedValuesProvider(Function> provider, ResourceLocation... dependencies) { + this.allowedValues = new DynamicValue<>(provider, dependencies); + return this; + } + + @Override + public EnumOptionBuilder setElementNameProvider(Function provider) { + this.elementNameProvider = provider; + return this; + } + + @Override + public EnumOptionBuilder setName(Component name) { + super.setName(name); + return this; + } + + @Override + public EnumOptionBuilder setStorageHandler(StorageEventHandler storage) { + super.setStorageHandler(storage); + return this; + } + + @Override + public EnumOptionBuilder setTooltip(Component tooltip) { + super.setTooltip(tooltip); + return this; + } + + @Override + public EnumOptionBuilder setTooltip(Function tooltip) { + super.setTooltip(tooltip); + return this; + } + + @Override + public EnumOptionBuilder setImpact(OptionImpact impact) { + super.setImpact(impact); + return this; + } + + @Override + public EnumOptionBuilder setFlags(OptionFlag... flags) { + super.setFlags(flags); + return this; + } + + @Override + public EnumOptionBuilder setDefaultValue(E value) { + super.setDefaultValue(value); + return this; + } + + @Override + public EnumOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies) { + super.setDefaultProvider(provider, dependencies); + return this; + } + + @Override + public EnumOptionBuilder setEnabled(boolean available) { + super.setEnabled(available); + return this; + } + + @Override + public EnumOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies) { + super.setEnabledProvider(provider, dependencies); + return this; + } + + @Override + public EnumOptionBuilder setBinding(Consumer save, Supplier load) { + super.setBinding(save, load); + return this; + } + + @Override + public EnumOptionBuilder setBinding(OptionBinding binding) { + super.setBinding(binding); + return this; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java new file mode 100644 index 0000000000..1d27f4446c --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java @@ -0,0 +1,65 @@ +package net.caffeinemc.mods.sodium.client.config.builder; + +import net.caffeinemc.mods.sodium.api.config.ConfigState; +import net.caffeinemc.mods.sodium.api.config.structure.ExternalButtonOptionBuilder; +import net.caffeinemc.mods.sodium.client.config.structure.ExternalButtonOption; +import net.caffeinemc.mods.sodium.client.config.structure.Option; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.Validate; + +import java.util.function.Consumer; +import java.util.function.Function; + +class ExternalButtonOptionBuilderImpl extends StaticOptionBuilderImpl implements ExternalButtonOptionBuilder { + private Consumer currentScreenConsumer; + + ExternalButtonOptionBuilderImpl(ResourceLocation id) { + super(id); + } + + @Override + void prepareBuild() { + super.prepareBuild(); + + Validate.notNull(this.currentScreenConsumer, "Screen provider must be set"); + } + + @Override + Option build() { + this.prepareBuild(); + + return new ExternalButtonOption(this.id, this.getDependencies(), this.name, this.enabled, this.tooltip, this.currentScreenConsumer); + } + + @Override + public ExternalButtonOptionBuilder setScreenProvider(Consumer currentScreenConsumer) { + this.currentScreenConsumer = currentScreenConsumer; + return this; + } + + @Override + public ExternalButtonOptionBuilder setName(Component name) { + super.setName(name); + return this; + } + + @Override + public ExternalButtonOptionBuilder setEnabled(boolean available) { + super.setEnabled(available); + return this; + } + + @Override + public ExternalButtonOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies) { + super.setEnabledProvider(provider, dependencies); + return this; + } + + @Override + public ExternalButtonOptionBuilder setTooltip(Component tooltip) { + super.setTooltip(tooltip); + return this; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalPageBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalPageBuilderImpl.java new file mode 100644 index 0000000000..fd60faaf8d --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalPageBuilderImpl.java @@ -0,0 +1,38 @@ +package net.caffeinemc.mods.sodium.client.config.builder; + +import net.caffeinemc.mods.sodium.api.config.structure.ExternalPageBuilder; +import net.caffeinemc.mods.sodium.client.config.structure.ExternalPage; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import org.apache.commons.lang3.Validate; + +import java.util.function.Consumer; + +public class ExternalPageBuilderImpl extends PageBuilderImpl implements ExternalPageBuilder { + private Consumer currentScreenConsumer; + + @Override + void prepareBuild() { + super.prepareBuild(); + + Validate.notNull(this.currentScreenConsumer, "Screen consumer must not be null"); + } + + @Override + ExternalPage build() { + this.prepareBuild(); + return new ExternalPage(this.name, this.currentScreenConsumer); + } + + @Override + public ExternalPageBuilder setScreenProvider(Consumer currentScreenConsumer) { + this.currentScreenConsumer = currentScreenConsumer; + return this; + } + + @Override + public ExternalPageBuilderImpl setName(Component name) { + super.setName(name); + return this; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java new file mode 100644 index 0000000000..3653e6b8d3 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java @@ -0,0 +1,139 @@ +package net.caffeinemc.mods.sodium.client.config.builder; + +import net.caffeinemc.mods.sodium.api.config.ConfigState; +import net.caffeinemc.mods.sodium.api.config.StorageEventHandler; +import net.caffeinemc.mods.sodium.api.config.option.*; +import net.caffeinemc.mods.sodium.api.config.structure.IntegerOptionBuilder; +import net.caffeinemc.mods.sodium.client.config.structure.IntegerOption; +import net.caffeinemc.mods.sodium.client.config.value.ConstantValue; +import net.caffeinemc.mods.sodium.client.config.value.DependentValue; +import net.caffeinemc.mods.sodium.client.config.value.DynamicValue; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.Validate; + +import java.util.Collection; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +class IntegerOptionBuilderImpl extends StatefulOptionBuilderImpl implements IntegerOptionBuilder { + DependentValue rangeProvider; + ControlValueFormatter valueFormatter; + + IntegerOptionBuilderImpl(ResourceLocation id) { + super(id); + } + + @Override + IntegerOption build() { + this.prepareBuild(); + + Validate.notNull(this.rangeProvider, "Range provider must be set"); + Validate.notNull(this.valueFormatter, "Value formatter must be set"); + + return new IntegerOption(this.id, this.getDependencies(), this.name, this.enabled, this.storage, this.tooltipProvider, this.impact, this.flags, this.defaultValue, this.binding, this.rangeProvider, this.valueFormatter); + } + + @Override + Collection getDependencies() { + var deps = super.getDependencies(); + deps.addAll(this.rangeProvider.getDependencies()); + return deps; + } + + @Override + public IntegerOptionBuilder setRange(int min, int max, int step) { + return this.setRange(new Range(min, max, step)); + } + + @Override + public IntegerOptionBuilder setRange(Range range) { + this.rangeProvider = new ConstantValue<>(range); + return this; + } + + @Override + public IntegerOptionBuilder setRangeProvider(Function provider, ResourceLocation... dependencies) { + this.rangeProvider = new DynamicValue<>(provider, dependencies); + return this; + } + + @Override + public IntegerOptionBuilder setValueFormatter(ControlValueFormatter formatter) { + this.valueFormatter = formatter; + return this; + } + + @Override + public IntegerOptionBuilder setName(Component name) { + super.setName(name); + return this; + } + + @Override + public IntegerOptionBuilder setStorageHandler(StorageEventHandler storage) { + super.setStorageHandler(storage); + return this; + } + + @Override + public IntegerOptionBuilder setTooltip(Component tooltip) { + super.setTooltip(tooltip); + return this; + } + + @Override + public IntegerOptionBuilder setTooltip(Function tooltip) { + super.setTooltip(tooltip); + return this; + } + + @Override + public IntegerOptionBuilder setImpact(OptionImpact impact) { + super.setImpact(impact); + return this; + } + + @Override + public IntegerOptionBuilder setFlags(OptionFlag... flags) { + super.setFlags(flags); + return this; + } + + @Override + public IntegerOptionBuilder setDefaultValue(Integer value) { + super.setDefaultValue(value); + return this; + } + + @Override + public IntegerOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies) { + super.setDefaultProvider(provider, dependencies); + return this; + } + + @Override + public IntegerOptionBuilder setEnabled(boolean available) { + super.setEnabled(available); + return this; + } + + @Override + public IntegerOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies) { + super.setEnabledProvider(provider, dependencies); + return this; + } + + @Override + public IntegerOptionBuilder setBinding(Consumer save, Supplier load) { + super.setBinding(save, load); + return this; + } + + @Override + public IntegerOptionBuilder setBinding(OptionBinding binding) { + super.setBinding(binding); + return this; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java new file mode 100644 index 0000000000..23beaf9671 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java @@ -0,0 +1,91 @@ +package net.caffeinemc.mods.sodium.client.config.builder; + +import com.google.common.collect.ImmutableList; +import net.caffeinemc.mods.sodium.api.config.structure.ColorThemeBuilder; +import net.caffeinemc.mods.sodium.api.config.structure.ModOptionsBuilder; +import net.caffeinemc.mods.sodium.api.config.structure.OptionOverrideBuilder; +import net.caffeinemc.mods.sodium.api.config.structure.PageBuilder; +import net.caffeinemc.mods.sodium.client.config.structure.ModOptions; +import net.caffeinemc.mods.sodium.client.config.structure.OptionOverride; +import net.caffeinemc.mods.sodium.client.config.structure.Page; +import net.caffeinemc.mods.sodium.client.gui.ColorTheme; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.Validate; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +class ModOptionsBuilderImpl implements ModOptionsBuilder { + private final String namespace; + private String name; + private String version; + private ColorTheme theme; + private ResourceLocation icon; + private final List pages = new ArrayList<>(); + private final List optionOverrides = new ArrayList<>(0); + + ModOptionsBuilderImpl(String namespace, String name, String version) { + this.namespace = namespace; + this.name = name; + this.version = version; + } + + ModOptions build() { + Validate.notEmpty(this.name, "Name must not be empty"); + Validate.notEmpty(this.version, "Version must not be empty"); + + if (this.optionOverrides.isEmpty() && this.pages.isEmpty()) { + throw new IllegalStateException("At least one page or option override must be added"); + } + + // if no theme is specified, pick one pseudo-randomly based on the namespace + if (this.theme == null) { + this.theme = ColorTheme.PRESETS[Math.abs(this.namespace.hashCode()) % ColorTheme.PRESETS.length]; + } + + return new ModOptions(this.namespace, this.name, this.version, this.theme, this.icon, ImmutableList.copyOf(this.pages), ImmutableList.copyOf(this.optionOverrides)); + } + + @Override + public ModOptionsBuilder setName(String name) { + this.name = name; + return this; + } + + @Override + public ModOptionsBuilder setVersion(String version) { + this.version = version; + return this; + } + + @Override + public ModOptionsBuilder formatVersion(Function versionFormatter) { + this.version = versionFormatter.apply(this.version); + return this; + } + + @Override + public ModOptionsBuilder setColorTheme(ColorThemeBuilder theme) { + this.theme = ((ColorThemeBuilderImpl) theme).build(); + return this; + } + + @Override + public ModOptionsBuilder setIcon(ResourceLocation texture) { + this.icon = texture; + return this; + } + + @Override + public ModOptionsBuilder addPage(PageBuilder builder) { + this.pages.add(((PageBuilderImpl) builder).build()); + return this; + } + + @Override + public ModOptionsBuilder registerOptionOverride(OptionOverrideBuilder override) { + this.optionOverrides.add(((OptionOverrideBuilderImpl) override).build(this.namespace)); + return this; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java new file mode 100644 index 0000000000..51a20265c9 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java @@ -0,0 +1,64 @@ +package net.caffeinemc.mods.sodium.client.config.builder; + +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; +import net.caffeinemc.mods.sodium.api.config.ConfigState; +import net.caffeinemc.mods.sodium.api.config.structure.OptionBuilder; +import net.caffeinemc.mods.sodium.client.config.structure.Option; +import net.caffeinemc.mods.sodium.client.config.value.ConstantValue; +import net.caffeinemc.mods.sodium.client.config.value.DependentValue; +import net.caffeinemc.mods.sodium.client.config.value.DynamicValue; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.lang3.Validate; + +import java.util.Collection; +import java.util.function.Function; + +public abstract class OptionBuilderImpl implements OptionBuilder { + final ResourceLocation id; + + Component name; + DependentValue enabled; + + OptionBuilderImpl(ResourceLocation id) { + this.id = id; + } + + abstract Option build(); + + void prepareBuild() { + Validate.notNull(this.name, "Name must be set"); + + if (this.enabled == null) { + this.enabled = new ConstantValue<>(true); + } + } + + Collection getDependencies() { + var dependencies = new ObjectLinkedOpenHashSet(); + dependencies.addAll(this.enabled.getDependencies()); + return dependencies; + } + + @Override + public OptionBuilder setName(Component name) { + Validate.notNull(name, "Argument must not be null"); + + this.name = name; + return this; + } + + @Override + public OptionBuilder setEnabled(boolean available) { + this.enabled = new ConstantValue<>(available); + return this; + } + + @Override + public OptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies) { + Validate.notNull(provider, "Argument must not be null"); + + this.enabled = new DynamicValue<>(provider, dependencies); + return this; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionGroupBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionGroupBuilderImpl.java new file mode 100644 index 0000000000..c07685ee69 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionGroupBuilderImpl.java @@ -0,0 +1,34 @@ +package net.caffeinemc.mods.sodium.client.config.builder; + +import net.caffeinemc.mods.sodium.api.config.structure.OptionBuilder; +import net.caffeinemc.mods.sodium.api.config.structure.OptionGroupBuilder; +import net.caffeinemc.mods.sodium.client.config.structure.Option; +import net.caffeinemc.mods.sodium.client.config.structure.OptionGroup; +import net.minecraft.network.chat.Component; +import org.apache.commons.lang3.Validate; + +import java.util.ArrayList; +import java.util.List; + +class OptionGroupBuilderImpl implements OptionGroupBuilder { + private Component name; + private final List

+ * The {@link ConfigBuilder} instance passed to these methods already knows which mod is registering the configuration, so there is typically no need to specify the mod metadata manually. + */ public interface ConfigEntryPoint { + /** + * Register configuration structure early in the mod loading process. + * This method is called before most mods have had a chance to initialize. + * Use this method only if you need your configuration options to be available very early. + * + * @param builder The configuration builder to register options with. + */ default void registerConfigEarly(ConfigBuilder builder) { - }; + } + /** + * Register configuration structure later in the mod loading process. + * This method is called after most mods have initialized. + * This is the preferred method for registering configuration options. + * + * @param builder The configuration builder to register options with. + */ void registerConfigLate(ConfigBuilder builder); } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigEntryPointForge.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigEntryPointForge.java index 6c3432fc1b..b56265a19a 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigEntryPointForge.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigEntryPointForge.java @@ -5,6 +5,11 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Marks a class as a configuration entry point for Sodium on the NeoForge platform. + * This annotation should be placed on classes that implement the configuration + * entry point interface to associate them with a specific mod id. + */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ConfigEntryPointForge { diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigState.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigState.java index daad859ac5..0347661886 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigState.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/ConfigState.java @@ -2,12 +2,38 @@ import net.minecraft.resources.ResourceLocation; +/** + * Represents the current state of configuration options. This interface is accessed through dynamic value providers throughout the API. Only declared dependencies of a dynamic value provider are allowed to be queried (and doing otherwise will result in a crash). + */ public interface ConfigState { + /** + * Special option ID to be passed as a dependency of a dynamic value provider to indicate that the provider should be re-evaluated when the configuration screen is rebuilt. It is unlikely that you need to use this. + */ ResourceLocation UPDATE_ON_REBUILD = ResourceLocation.parse("__meta__:update_on_rebuild"); - + + /** + * Reads a boolean option from the configuration state. + * + * @param id The ID of the option. + * @return The current value of the boolean option. + */ boolean readBooleanOption(ResourceLocation id); + /** + * Reads an integer option from the configuration state. + * + * @param id The ID of the option. + * @return The current value of the integer option. + */ int readIntOption(ResourceLocation id); + /** + * Reads an enum option from the configuration state. + * + * @param id The ID of the option. + * @param enumClass The class of the enum. + * @param The enum type. + * @return The current value of the enum option. + */ > E readEnumOption(ResourceLocation id, Class enumClass); } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/StorageEventHandler.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/StorageEventHandler.java index 98e0a14cb4..9555ec63e7 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/StorageEventHandler.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/StorageEventHandler.java @@ -1,6 +1,12 @@ package net.caffeinemc.mods.sodium.api.config; +/** + * Functional interface for handling storage events, such as after a configuration has been saved. + */ @FunctionalInterface public interface StorageEventHandler { + /** + * Called after options have been saved to their bindings. This is typically used for flushing changes to disk. + */ void afterSave(); } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md index 9ef2d26e0c..534e1358f6 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md @@ -182,7 +182,7 @@ The API is largely self-explanatory and an example is provided above. Also see S The `ConfigBuilder` instance passed to the registration method allows quick and easy registration of a mod's own options using `ConfigBuilder.registerOwnModOptions`. The mod's id, name, version or a formatter for the existing version, and the color theme can be configured on the returned `ModOptionsBuilder`. It's also possible to register options for additional mods using `ConfigBuilder.registerModOptions`. Which mod is the "own" mod for `registerOwnModOptions` is determined by the mod that owns the metadata-based entrypoint or the mod id passed to the `@ConfigEntryPointForge("examplemod")` annotation. -Each registered mod gets its own header in the page list. The color of the header and the corresponding entries is randomly selected from a predefined list by default, but can be customized using `ModOptionsBuilder.setColorTheme`. A color theme is created either by specifying three RGB colors or a single base color with the lighter and darker colors getting derived automatically. A mod can also specify an icon with `ModOptionsBuilder.setIcon`, which takes a `ResourceLocation` pointing to a texture, which will be tinted in the theme color and rendered in its entirety as a square. +Each registered mod gets its own header in the page list. The color of the header and the corresponding entries is randomly selected from a predefined list by default, but can be customized using `ModOptionsBuilder.setColorTheme`. A color theme is created either by specifying three (A)RGB colors or a single base color with the lighter and darker colors getting derived automatically. A mod can also specify an icon with `ModOptionsBuilder.setIcon`, which takes a `ResourceLocation` pointing to a texture, which will be tinted in the theme color and rendered in its entirety as a square. To simply switch to a new `Screen` when an entry in the video settings screen's page list is clicked, use `ConfigBuilder.createExternalPage` and add the returned page normally after configuring it with a name and a `Consumer` that receives the current screen and switches to your custom screen. @@ -196,7 +196,7 @@ Optionally a performance impact can be specified with `OptionBuilder.setImpact` Flags set with `OptionBuilder.setFlags` control what things are reset when this option is applied. They include reloading chunks or reloading resource packs. See `OptionFlag` for the available values. -The default value set with `OptionBuilder.setDefaultValue`, or dynamically with `OptionBuilder.setDefaultProvider`, is used if the value returned by the binding does not fulfill the option's value constraint (in the case of a integer or enum option). +The default value set with `OptionBuilder.setDefaultValue`, or dynamically with `OptionBuilder.setDefaultProvider`, is used if the value returned by the binding does not fulfill the option's value constraint (in the case of an integer or enum option). Disabling an option with `OptionBuilder.setEnabled(false)` shows the option as strikethrough and makes it non-interactive. Otherwise, especially with regard to value constraints, it will behave as usual. diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/ControlValueFormatter.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/ControlValueFormatter.java index 674e077dcf..e6df0bc8c5 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/ControlValueFormatter.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/ControlValueFormatter.java @@ -2,6 +2,16 @@ import net.minecraft.network.chat.Component; +/** + * A formatter for control values, converting integer values into display components. + */ +@FunctionalInterface public interface ControlValueFormatter { + /** + * Formats the given integer value into a display component. + * + * @param value the integer value to format + * @return the formatted value + */ Component format(int value); } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/NameProvider.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/NameProvider.java index 01186c0384..be5149f1c4 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/NameProvider.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/NameProvider.java @@ -2,6 +2,14 @@ import net.minecraft.network.chat.Component; +/** + * Base interface extended by enums whose members can provide display names. + */ public interface NameProvider { + /** + * Gets the display name of this item. + * + * @return the display name + */ Component getName(); } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionBinding.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionBinding.java index db2072e59b..e1cf4038bc 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionBinding.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionBinding.java @@ -1,7 +1,22 @@ package net.caffeinemc.mods.sodium.api.config.option; +/** + * Interface for binding an option to a storage mechanism. + * + * @param The type of the option value. + */ public interface OptionBinding { + /** + * Saves the given value to the storage mechanism. + * + * @param value The value to save. + */ void save(V value); + /** + * Loads the value from the storage mechanism. + * + * @return The loaded value. + */ V load(); } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionFlag.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionFlag.java index 7f71f462b1..8a86dc16b4 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionFlag.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionFlag.java @@ -1,9 +1,31 @@ package net.caffeinemc.mods.sodium.api.config.option; +/** + * Flags that indicate specific actions required when an option is changed. + */ public enum OptionFlag { + /** + * Indicates that the renderer needs to be reloaded. This means all meshes will be discarded and rebuilt. This flag should only be applied if necessary, since rebuilding all meshes is a disruptive operation. + */ REQUIRES_RENDERER_RELOAD, + + /** + * Indicates that the renderer needs to be updated. This means that the culling state may have changed and occlusion culling will be recalculated. This causes no noticeable disruption to the user. + */ REQUIRES_RENDERER_UPDATE, + + /** + * Indicates that assets need to be reloaded. This reloads resource packs, and then reloads the renderer, causing all meshes to be discarded and rebuilt. + */ REQUIRES_ASSET_RELOAD, + + /** + * Indicates that the video mode needs to be updated. This causes a disruption as the video mode is changed. + */ REQUIRES_VIDEOMODE_RELOAD, + + /** + * Indicates that the game needs to be restarted for the option change to take effect. + */ REQUIRES_GAME_RESTART } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionImpact.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionImpact.java index 3c69beb0bc..0a7e591e5c 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionImpact.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionImpact.java @@ -3,10 +3,28 @@ import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; +/** + * Represents the performance impact level of a configuration option. + */ public enum OptionImpact implements NameProvider { + /** + * Low impact on performance. Changing this option won't affect performance in a measurable or noticeable way. + */ LOW(ChatFormatting.GREEN, "sodium.option_impact.low"), + + /** + * Medium impact on performance. Changing this option may have a noticeable effect on performance in some scenarios and some systems. + */ MEDIUM(ChatFormatting.YELLOW, "sodium.option_impact.medium"), + + /** + * High impact on performance. Changing this option will likely have a significant effect on performance in most scenarios. + */ HIGH(ChatFormatting.GOLD, "sodium.option_impact.high"), + + /** + * Varies in impact on performance. The effect of changing this option on performance is highly dependent on the specific scenario and system. + */ VARIES(ChatFormatting.WHITE, "sodium.option_impact.varies"); private final Component text; diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/Range.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/Range.java index 5bac75b144..147010dfcd 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/Range.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/Range.java @@ -1,5 +1,12 @@ package net.caffeinemc.mods.sodium.api.config.option; +/** + * A record representing a range of integer values with a specified step. + * + * @param min The minimum value of the range (inclusive). + * @param max The maximum value of the range (inclusive). + * @param step The step increment between valid values in the range. + */ public record Range(int min, int max, int step) { public Range { if (min > max) { @@ -10,10 +17,21 @@ public record Range(int min, int max, int step) { } } + /** + * Checks if a given value is valid within this range. + * + * @param value The value to check. + * @return True if the value is valid, false otherwise. + */ public boolean isValueValid(int value) { return value >= this.min && value <= this.max && (value - this.min) % this.step == 0; } + /** + * Gets the spread of the range (max - min). + * + * @return The spread of the range. + */ public int getSpread() { return this.max - this.min; } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/BooleanOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/BooleanOptionBuilder.java index d1b57294a8..44103c5784 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/BooleanOptionBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/BooleanOptionBuilder.java @@ -12,6 +12,9 @@ import java.util.function.Function; import java.util.function.Supplier; +/** + * Builder interface for defining boolean options. Refines builder methods to return this class instead of the base interface and have a {@link Boolean} value type. + */ public interface BooleanOptionBuilder extends StatefulOptionBuilder { @Override BooleanOptionBuilder setName(Component name); diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ColorThemeBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ColorThemeBuilder.java index 20a52a933b..fec48bb1c3 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ColorThemeBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ColorThemeBuilder.java @@ -1,7 +1,26 @@ package net.caffeinemc.mods.sodium.api.config.structure; +/** + * Builder interface for defining color themes. + * + * Colors use RGB integers (bits 0 to 24). This is ARGB format with alpha bits ignored. + */ public interface ColorThemeBuilder { + /** + * Sets the base theme color. + * + * @param theme Theme color as an RGB integer. + * @return The current builder instance. + */ ColorThemeBuilder setBaseThemeRGB(int theme); + /** + * Sets the full theme colors. + * + * @param theme Theme color as an RGB integer. + * @param themeHighlight Theme highlight color as an RGB integer. + * @param themeDisabled Theme disabled color as an RGB integer. + * @return The current builder instance. + */ ColorThemeBuilder setFullThemeRGB(int theme, int themeHighlight, int themeDisabled); } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ConfigBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ConfigBuilder.java index 25c975e8e2..4f804d9ea8 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ConfigBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ConfigBuilder.java @@ -2,28 +2,103 @@ import net.minecraft.resources.ResourceLocation; +/** + * Root builder interface for defining configuration structures. An implementation of this class is passed to your entry point and lets you create builders for everything in the API. Builders do not need to be built manually, as they are automatically closed when the enclosing scope ends. + *

+ * Refer to USAGE.md for usage instructions. + */ public interface ConfigBuilder { - ModOptionsBuilder registerModOptions(String namespace, String name, String version); + /** + * Registers a new mod options structure with the given configId, name, and version. Note that a mod may register multiple mod options structures under different configIds if desired. + * + * @param configId The configId of these mod options. + * @param name The human-readable name of the mod. + * @param version The version of the mod. + * @return A builder for defining the mod's configuration structure. + */ + ModOptionsBuilder registerModOptions(String configId, String name, String version); + /** + * Registers a new mod options structure for the mod with the given configId. The mod's name and version will be looked up automatically. + * + * @param namespace The configId of these mod options. + * @return A builder for defining the mod's configuration structure. + */ ModOptionsBuilder registerModOptions(String namespace); + /** + * Registers a new mod options structure for the mod of the current entrypoint. The mod's name and version will be looked up automatically. + * + * @return A builder for defining the mod's configuration structure. + */ ModOptionsBuilder registerOwnModOptions(); + /** + * Creates a new option override builder. + * + * @return A builder for defining an option override. + */ OptionOverrideBuilder createOptionOverride(); + /** + * Creates a new color theme builder. + * + * @return A builder for defining a color theme. + */ ColorThemeBuilder createColorTheme(); + /** + * Creates a new option page builder. + * + * @return A builder for defining an option page. + */ OptionPageBuilder createOptionPage(); + /** + * Creates a new external page builder. + * + * @return A builder for defining an external page. + */ ExternalPageBuilder createExternalPage(); + /** + * Creates a new option group builder. + * + * @return A builder for defining an option group. + */ OptionGroupBuilder createOptionGroup(); + /** + * Creates a new boolean option builder. + * + * @param id The unique identifier for this option. + * @return A builder for defining a boolean option. + */ BooleanOptionBuilder createBooleanOption(ResourceLocation id); + /** + * Creates a new integer option builder. + * + * @param id The unique identifier for this option. + * @return A builder for defining an integer option. + */ IntegerOptionBuilder createIntegerOption(ResourceLocation id); + /** + * Creates a new enum option builder. + * + * @param id The unique identifier for this option. + * @param enumClass The enum class for this option. + * @param The enum type. + * @return A builder for defining an enum option. + */ > EnumOptionBuilder createEnumOption(ResourceLocation id, Class enumClass); + /** + * Creates a new external button option builder. + * + * @param id The unique identifier for this option. + * @return A builder for defining an external button option. + */ ExternalButtonOptionBuilder createExternalButtonOption(ResourceLocation id); } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/EnumOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/EnumOptionBuilder.java index 164fba4d9c..758ad4a94c 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/EnumOptionBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/EnumOptionBuilder.java @@ -13,15 +13,46 @@ import java.util.function.Function; import java.util.function.Supplier; +/** + * Builder interface for defining enum options. Refines builder methods to return this class instead of the base interface and have an {@link Enum} value type. + * + * @param The enum type for this option. + */ public interface EnumOptionBuilder> extends StatefulOptionBuilder { + /** + * Creates a name provider function that maps enum constants to the provided names based on their ordinal values. + * + * @param names The array of names corresponding to the enum constants. + * @param The enum type. + * @return A function that provides names for enum constants. + */ static > Function nameProviderFrom(Component... names) { return e -> names[e.ordinal()]; } + /** + * Sets the allowed values for this enum option. + * + * @param allowedValues The set of allowed enum values. + * @return This builder instance. + */ EnumOptionBuilder setAllowedValues(Set allowedValues); + /** + * Sets a provider function to determine the allowed values for this enum option based on the current configuration state. + * + * @param provider The function that provides the set of allowed enum values. + * @param dependencies The options that this provider depends on. + * @return This builder instance. + */ EnumOptionBuilder setAllowedValuesProvider(Function> provider, ResourceLocation... dependencies); + /** + * Sets a provider function to determine the display name for each enum constant. + * + * @param provider The function that provides the display name for each enum constant. + * @return This builder instance. + */ EnumOptionBuilder setElementNameProvider(Function provider); @Override diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalButtonOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalButtonOptionBuilder.java index a7b127b346..b168f3a56a 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalButtonOptionBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalButtonOptionBuilder.java @@ -8,7 +8,15 @@ import java.util.function.Consumer; import java.util.function.Function; +/** + * Builder interface for defining external button options. + */ public interface ExternalButtonOptionBuilder extends OptionBuilder { + /** Sets the screen provider for the external button option. + * + * @param currentScreenConsumer A consumer that accepts the current screen and opens the external configuration screen. + * @return The current builder instance. + */ ExternalButtonOptionBuilder setScreenProvider(Consumer currentScreenConsumer); @Override diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalPageBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalPageBuilder.java index e10bc40fee..cd88dc38e0 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalPageBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ExternalPageBuilder.java @@ -5,8 +5,23 @@ import java.util.function.Consumer; +/** + * Builder interface for defining external configuration pages. + */ public interface ExternalPageBuilder extends PageBuilder { + /** + * Sets the name of the external configuration page. + * + * @param name The name component. + * @return The current builder instance. + */ ExternalPageBuilder setName(Component name); + /** + * Sets the screen provider for the external configuration page. + * + * @param currentScreenConsumer A consumer that accepts the current screen and opens the external configuration screen. + * @return The current builder instance. + */ ExternalPageBuilder setScreenProvider(Consumer currentScreenConsumer); } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java index fc161d3dc0..eca493bb4c 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java @@ -10,13 +10,43 @@ import java.util.function.Function; import java.util.function.Supplier; +/** + * Builder interface for defining integer options. Refines builder methods to return this class instead of the base interface and have an {@link Integer} value type. + */ public interface IntegerOptionBuilder extends StatefulOptionBuilder { + /** + * Sets the range for this integer option. + * + * @param min The minimum value (inclusive). + * @param max The maximum value (inclusive). + * @param step The step value for increments. + * @return The current builder instance. + */ IntegerOptionBuilder setRange(int min, int max, int step); + /** + * Sets the range for this integer option. + * + * @param range The range object defining min, max, and step. + * @return The current builder instance. + */ IntegerOptionBuilder setRange(Range range); + /** + * Sets a provider function to determine the range for this integer option based on the current configuration state. + * + * @param provider The function that provides the range. + * @param dependencies The options that this provider depends on. + * @return The current builder instance. + */ IntegerOptionBuilder setRangeProvider(Function provider, ResourceLocation... dependencies); + /** + * Sets the value formatter for this integer option. + * + * @param formatter The formatter to format the integer value of this option. + * @return The current builder instance. + */ IntegerOptionBuilder setValueFormatter(ControlValueFormatter formatter); @Override diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java index 806a6c2274..cdcddb397e 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java @@ -4,18 +4,72 @@ import java.util.function.Function; +/** + * Builder interface for defining options belonging to a mod and its metadata. A set of mod options contains some list of option pages. + */ public interface ModOptionsBuilder { + /** + * Sets the display name of the mod. + * + * @param name The mod name. + * @return The current builder instance. + */ ModOptionsBuilder setName(String name); + /** + * Sets the version string of the mod. This value is typically automatically populated from the mod metadata known to the mod loader. + * + * @param version The version string. + * @return The current builder instance. + */ ModOptionsBuilder setVersion(String version); + /** + * Sets a formatter function for the mod version string. This converts from the raw version string to a display version string. + * + * @param versionFormatter The version formatter function. + * @return The current builder instance. + */ ModOptionsBuilder formatVersion(Function versionFormatter); + /** + * Sets the color theme for the mod options UI. + * + * @param colorTheme The color theme builder. + * @return The current builder instance. + */ ModOptionsBuilder setColorTheme(ColorThemeBuilder colorTheme); - + + /** + * Sets the icon texture for the mod. The icon should be centered within the square texture and the background should be transparent. The icon will be rendered monochrome tinted in the mod's theme color. + * + * @param texture The resource location of the icon texture. + * @return The current builder instance. + */ ModOptionsBuilder setIcon(ResourceLocation texture); + /** + * Sets the icon texture for the mod to be rendered in full color (non-monochrome). + * + * We ask for this to be only in special cases where the icon's design relies on multiple adjacent colors. Typically, the theme color is sufficient for branding. + * + * @return The current builder instance. + */ + ModOptionsBuilder setIconNonMonochrome(); + + /** + * Adds a configuration page to the mod options. + * + * @param page The page builder. + * @return The current builder instance. + */ ModOptionsBuilder addPage(PageBuilder page); + /** + * Registers an option override provided by this mod. Overrides allow modifying the behavior or appearance of options defined by other mods. + * + * @param override The option override builder. + * @return The current builder instance. + */ ModOptionsBuilder registerOptionOverride(OptionOverrideBuilder override); } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionBuilder.java index 59b05ccb5c..e07e734d7b 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionBuilder.java @@ -6,12 +6,40 @@ import java.util.function.Function; +/** + * The base interface for option builders. + */ public interface OptionBuilder { + /** + * Sets the name of the option. + * + * @param name The display name of the option. + * @return The current builder instance. + */ OptionBuilder setName(Component name); + /** + * Sets the tooltip of the option. + * + * @param tooltip The tooltip component. + * @return The current builder instance. + */ OptionBuilder setTooltip(Component tooltip); + /** + * Sets whether the option is enabled. + * + * @param available True if the option is enabled, false otherwise. + * @return The current builder instance. + */ OptionBuilder setEnabled(boolean available); + /** + * Sets a provider function to determine whether the option is enabled based on the current configuration state. + * + * @param provider The function that provides the enabled state. + * @param dependencies The options that this provider depends on. + * @return The current builder instance. + */ OptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies); } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionGroupBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionGroupBuilder.java index 9e136000aa..71feff80ae 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionGroupBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionGroupBuilder.java @@ -2,8 +2,25 @@ import net.minecraft.network.chat.Component; +/** + * Builder interface for defining option groups, which are collections of related options. + *

+ * Option groups only exist to visually group options together in the configuration UI, and a title is not required and won't be displayed unless set. + */ public interface OptionGroupBuilder { + /** + * Sets the name of this option group. This is optional and won't be displayed unless set. We recommend only using this if necessary, as usually option groups without names are enough grouping and option names provide sufficiently detailed labels of the option categories. + * + * @param name This option group's display name. + * @return The current builder instance. + */ OptionGroupBuilder setName(Component name); + /** + * Adds an option to this option group. + * + * @param option The option to add. + * @return The current builder instance. + */ OptionGroupBuilder addOption(OptionBuilder option); } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionOverrideBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionOverrideBuilder.java index 06cab5eb3d..c5c62622cf 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionOverrideBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionOverrideBuilder.java @@ -2,8 +2,23 @@ import net.minecraft.resources.ResourceLocation; +/** + * Builder interface for defining option overrides, which replace an existing option with a new one. + */ public interface OptionOverrideBuilder { + /** + * Sets the target option to be overridden. + * + * @param target The ID of the target option. + * @return The current builder instance. + */ OptionOverrideBuilder setTarget(ResourceLocation target); + /** + * Sets the replacement option. + * + * @param option The option builder for the replacement option. + * @return The current builder instance. + */ OptionOverrideBuilder setReplacement(OptionBuilder option); } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionPageBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionPageBuilder.java index 75e00d1b35..48d27a8fdf 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionPageBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionPageBuilder.java @@ -2,8 +2,31 @@ import net.minecraft.network.chat.Component; +/** + * Builder interface for defining option pages, which are lists of option groups. + */ public interface OptionPageBuilder extends PageBuilder { + /** + * Sets the name of the option page. + * + * @param name The name component. + * @return The current builder instance. + */ OptionPageBuilder setName(Component name); + /** + * Adds an option group to the option page. + * + * @param group The option group builder. + * @return The current builder instance. + */ OptionPageBuilder addOptionGroup(OptionGroupBuilder group); + + /** + * Adds an option directly to the option page. Options added directly to the page are grouped into an implicit unnamed option group at the bottom of the page. + * + * @param option The option to add. + * @return The current builder instance. + */ + OptionPageBuilder addOption(OptionBuilder option); } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/PageBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/PageBuilder.java index 73783d433b..3fa2370c2f 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/PageBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/PageBuilder.java @@ -1,4 +1,7 @@ package net.caffeinemc.mods.sodium.api.config.structure; +/** + * Base interface for all option page builders. + */ public interface PageBuilder { } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/StatefulOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/StatefulOptionBuilder.java index a81afdc03f..5107aebc87 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/StatefulOptionBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/StatefulOptionBuilder.java @@ -12,20 +12,75 @@ import java.util.function.Function; import java.util.function.Supplier; +/** + * Builder interface for defining stateful options. + * + * @param The type of the option's value. + */ public interface StatefulOptionBuilder extends OptionBuilder { + /** + * Sets the storage handler for this option. + * + * @param storage The storage event handler. + * @return The current builder instance. + */ StatefulOptionBuilder setStorageHandler(StorageEventHandler storage); + /** + * Sets a functional tooltip for this option that changes the text based on the selected value. + * + * @param tooltip The function that provides the tooltip based on the option's value. + * @return The current builder instance. + */ StatefulOptionBuilder setTooltip(Function tooltip); + /** + * Sets the performance impact level of this option. + * + * @param impact The option's performance impact. + * @return The current builder instance. + */ StatefulOptionBuilder setImpact(OptionImpact impact); + /** + * Sets flags for this option. + * + * @param flags The option flags. + * @return The current builder instance. + */ StatefulOptionBuilder setFlags(OptionFlag... flags); + /** + * Sets the default value for this option. The default value is used when the binding returns an invalid value, such as during the first load. + * + * @param value The default value. + * @return The current builder instance. + */ StatefulOptionBuilder setDefaultValue(V value); + /** + * Sets a provider function to determine the default value for this option based on the current configuration state. + * + * @param provider The function that provides the default value. + * @param dependencies The options that this provider depends on. + * @return The current builder instance. + */ StatefulOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies); + /** + * Sets a binding for this option using save and load functions. + * + * @param save The function to save the option's value. + * @param load The function to load the option's value. + * @return The current builder instance. + */ StatefulOptionBuilder setBinding(Consumer save, Supplier load); + /** + * Sets a binding for this option using an {@link OptionBinding} instance. + * + * @param binding The option binding. + * @return The current builder instance. + */ StatefulOptionBuilder setBinding(OptionBinding binding); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ColorThemeBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ColorThemeBuilderImpl.java index 0f0f14bcbf..07a610df5f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ColorThemeBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ColorThemeBuilderImpl.java @@ -22,15 +22,15 @@ ColorTheme build() { @Override public ColorThemeBuilder setBaseThemeRGB(int theme) { - this.baseTheme = theme; + this.baseTheme = theme | 0xFF000000; return this; } @Override public ColorThemeBuilder setFullThemeRGB(int theme, int themeHighlight, int themeDisabled) { - this.baseTheme = theme; - this.themeHighlight = themeHighlight; - this.themeDisabled = themeDisabled; + this.baseTheme = theme | 0xFF000000; + this.themeHighlight = themeHighlight | 0xFF000000; + this.themeDisabled = themeDisabled | 0xFF000000; return this; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ConfigBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ConfigBuilderImpl.java index 7687e76e49..4630f8e38e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ConfigBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ConfigBuilderImpl.java @@ -14,11 +14,11 @@ public class ConfigBuilderImpl implements ConfigBuilder { private final List pendingModConfigBuilders = new ArrayList<>(1); private final Function modInfoFunction; - private final String defaultNamespace; + private final String defaultConfigId; - public ConfigBuilderImpl(Function modInfoFunction, String defaultNamespace) { + public ConfigBuilderImpl(Function modInfoFunction, String defaultConfigId) { this.modInfoFunction = modInfoFunction; - this.defaultNamespace = defaultNamespace; + this.defaultConfigId = defaultConfigId; } public Collection build() { @@ -30,21 +30,21 @@ public Collection build() { } @Override - public ModOptionsBuilder registerModOptions(String namespace, String name, String version) { - var builder = new ModOptionsBuilderImpl(namespace, name, version); + public ModOptionsBuilder registerModOptions(String configId, String name, String version) { + var builder = new ModOptionsBuilderImpl(configId, name, version); this.pendingModConfigBuilders.add(builder); return builder; } @Override - public ModOptionsBuilder registerModOptions(String namespace) { - var metadata = this.modInfoFunction.apply(namespace); - return this.registerModOptions(namespace, metadata.modName(), metadata.modVersion()); + public ModOptionsBuilder registerModOptions(String configId) { + var metadata = this.modInfoFunction.apply(configId); + return this.registerModOptions(configId, metadata.modName(), metadata.modVersion()); } @Override public ModOptionsBuilder registerOwnModOptions() { - return this.registerModOptions(this.defaultNamespace); + return this.registerModOptions(this.defaultConfigId); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java index 23beaf9671..20b073f2cf 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java @@ -22,6 +22,7 @@ class ModOptionsBuilderImpl implements ModOptionsBuilder { private String version; private ColorTheme theme; private ResourceLocation icon; + private boolean iconMonochrome = true; private final List pages = new ArrayList<>(); private final List optionOverrides = new ArrayList<>(0); @@ -44,7 +45,7 @@ ModOptions build() { this.theme = ColorTheme.PRESETS[Math.abs(this.namespace.hashCode()) % ColorTheme.PRESETS.length]; } - return new ModOptions(this.namespace, this.name, this.version, this.theme, this.icon, ImmutableList.copyOf(this.pages), ImmutableList.copyOf(this.optionOverrides)); + return new ModOptions(this.namespace, this.name, this.version, this.theme, this.icon, this.iconMonochrome, ImmutableList.copyOf(this.pages), ImmutableList.copyOf(this.optionOverrides)); } @Override @@ -77,6 +78,12 @@ public ModOptionsBuilder setIcon(ResourceLocation texture) { return this; } + @Override + public ModOptionsBuilder setIconNonMonochrome() { + this.iconMonochrome = false; + return this; + } + @Override public ModOptionsBuilder addPage(PageBuilder builder) { this.pages.add(((PageBuilderImpl) builder).build()); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionPageBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionPageBuilderImpl.java index f846e93b0c..be7478c010 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionPageBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionPageBuilderImpl.java @@ -1,6 +1,7 @@ package net.caffeinemc.mods.sodium.client.config.builder; import com.google.common.collect.ImmutableList; +import net.caffeinemc.mods.sodium.api.config.structure.OptionBuilder; import net.caffeinemc.mods.sodium.api.config.structure.OptionGroupBuilder; import net.caffeinemc.mods.sodium.api.config.structure.OptionPageBuilder; import net.caffeinemc.mods.sodium.client.config.structure.OptionGroup; @@ -13,6 +14,7 @@ class OptionPageBuilderImpl extends PageBuilderImpl implements OptionPageBuilder { private final List groups = new ArrayList<>(); + private final List looseOptions = new ArrayList<>(); @Override void prepareBuild() { @@ -24,6 +26,13 @@ void prepareBuild() { @Override OptionPage build() { this.prepareBuild(); + + if (!this.looseOptions.isEmpty()) { + var implicitGroup = new OptionGroupBuilderImpl(); + this.looseOptions.forEach(implicitGroup::addOption); + this.addOptionGroup(implicitGroup); + } + return new OptionPage(this.name, ImmutableList.copyOf(this.groups)); } @@ -33,6 +42,12 @@ public OptionPageBuilder addOptionGroup(OptionGroupBuilder group) { return this; } + @Override + public OptionPageBuilder addOption(OptionBuilder option) { + this.looseOptions.add(option); + return this; + } + @Override public OptionPageBuilderImpl setName(Component name) { super.setName(name); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java index f0a2d46966..01fe7c161f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java @@ -8,7 +8,7 @@ import java.util.List; -public record ModOptions(String namespace, String name, String version, ColorTheme theme, ResourceLocation icon, ImmutableList pages, List overrides) implements Searchable { +public record ModOptions(String namespace, String name, String version, ColorTheme theme, ResourceLocation icon, boolean iconMonochrome, ImmutableList pages, List overrides) implements Searchable { @Override public void registerTextSources(SearchIndex index) { for (Page page : this.pages) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java index 755426b9f0..7f289fb8a3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java @@ -415,16 +415,21 @@ public Dim2i getDimensions() { return new Dim2i(0, 0, this.width, this.height); } - public static void renderIcon(GuiGraphics graphics, ResourceLocation icon, int color, int x, int y, int size) { - // assume 16x16 icon texture - GuiTint.withTint(color, () -> - graphics.blit(icon, x, y, size, size, 0.0f, 0.0f, 16, 16, 16, 16)); + public static int renderIconWithSpacing(GuiGraphics graphics, ResourceLocation icon, int color, int x, int y, int height, int margin) { + return renderIconWithSpacing(graphics, icon, color, true, x, y, height, margin); } - public static int renderIconWithSpacing(GuiGraphics graphics, ResourceLocation icon, int color, int x, int y, int height, int margin) { + public static int renderIconWithSpacing(GuiGraphics graphics, ResourceLocation icon, int color, boolean iconMonochrome, int x, int y, int height, int margin) { int iconSize = height - margin * 2; - renderIcon(graphics, icon, color, x + margin, y + height / 2 - iconSize / 2, iconSize); + // assume 16x16 icon texture + final int blitX = x + margin; + final int blitY = y + height / 2 - iconSize / 2; + if (iconMonochrome) { + GuiTint.withTint(color, () -> graphics.blit(icon, blitX, blitY, iconSize, iconSize, 0.0f, 0.0f, 16, 16, 16, 16)); + } else { + GuiTint.withTint(Colors.FOREGROUND, () -> graphics.blit(icon, blitX, blitY, iconSize, iconSize, 0.0f, 0.0f, 16, 16, 16, 16)); + } return margin * 2 + iconSize; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatterImpls.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatterImpls.java index 1b8ad75957..71242b1541 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatterImpls.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlValueFormatterImpls.java @@ -38,6 +38,8 @@ public static ControlValueFormatter brightness() { return (v) -> { if (v == 0) { return Component.translatable("options.gamma.min"); + } else if (v == 50) { + return Component.translatable("options.gamma.default"); } else if (v == 100) { return Component.translatable("options.gamma.max"); } else { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java index d7476ef2d9..3806498ab1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java @@ -271,9 +271,7 @@ private static class ModHeaderWidget extends HeaderWidget { final ResourceLocation icon; public ModHeaderWidget(AbstractOptionList list, Dim2i dim, String title, ColorTheme theme, ResourceLocation icon) { - // super(list, dim, ChatFormatting.UNDERLINE + title, theme.themeLighter, Colors.BACKGROUND_DEFAULT); - // super(list, dim, ChatFormatting.BOLD + title, theme.themeLighter, ColorARGB.withAlpha(theme.themeDarker, 0x60)); - super(list, dim, ChatFormatting.BOLD + title, theme.themeLighter, Colors.BACKGROUND_DARKER); + super(list, dim, ChatFormatting.BOLD + title, theme.themeLighter, Colors.BACKGROUND_DARKER); this.icon = icon; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java index 3b6a6d0df3..293aa4a127 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java @@ -146,10 +146,12 @@ public int getY() { private class HeaderEntryWidget extends EntryWidget { private final ResourceLocation icon; + private final boolean iconMonochrome; HeaderEntryWidget(Dim2i dim, ModOptions modOptions, ColorTheme theme) { super(dim, Component.literal(modOptions.name()), Component.literal(modOptions.version()), false, theme); this.icon = modOptions.icon(); + this.iconMonochrome = modOptions.iconMonochrome(); } @Override @@ -158,7 +160,7 @@ protected int renderIcon(GuiGraphics graphics, int textColor) { return super.renderIcon(graphics, textColor); } - return VideoSettingsScreen.renderIconWithSpacing(graphics, this.icon, textColor, + return VideoSettingsScreen.renderIconWithSpacing(graphics, this.icon, textColor, this.iconMonochrome, this.getX(), this.getY(), this.getHeight(), Layout.ICON_MARGIN); } } From 211d5770bccd0a556afbd978e2a4e49a314f3a47 Mon Sep 17 00:00:00 2001 From: IMS212 Date: Tue, 25 Nov 2025 19:02:33 +0100 Subject: [PATCH 129/215] Remove support for monochrome icons --- .../api/config/structure/ModOptionsBuilder.java | 9 --------- .../config/builder/ModOptionsBuilderImpl.java | 9 +-------- .../sodium/client/config/structure/ModOptions.java | 2 +- .../mods/sodium/client/gui/SodiumConfigBuilder.java | 2 +- .../mods/sodium/client/gui/VideoSettingsScreen.java | 10 +--------- .../sodium/client/gui/widgets/PageListWidget.java | 4 +--- .../mods/sodium/mixin/core/MinecraftMixin.java | 1 - .../textures/gui/{icon.png => config-icon.png} | Bin 8 files changed, 5 insertions(+), 32 deletions(-) rename common/src/main/resources/assets/sodium/textures/gui/{icon.png => config-icon.png} (100%) diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java index cdcddb397e..ad374093e7 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java @@ -48,15 +48,6 @@ public interface ModOptionsBuilder { */ ModOptionsBuilder setIcon(ResourceLocation texture); - /** - * Sets the icon texture for the mod to be rendered in full color (non-monochrome). - * - * We ask for this to be only in special cases where the icon's design relies on multiple adjacent colors. Typically, the theme color is sufficient for branding. - * - * @return The current builder instance. - */ - ModOptionsBuilder setIconNonMonochrome(); - /** * Adds a configuration page to the mod options. * diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java index 20b073f2cf..23beaf9671 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java @@ -22,7 +22,6 @@ class ModOptionsBuilderImpl implements ModOptionsBuilder { private String version; private ColorTheme theme; private ResourceLocation icon; - private boolean iconMonochrome = true; private final List pages = new ArrayList<>(); private final List optionOverrides = new ArrayList<>(0); @@ -45,7 +44,7 @@ ModOptions build() { this.theme = ColorTheme.PRESETS[Math.abs(this.namespace.hashCode()) % ColorTheme.PRESETS.length]; } - return new ModOptions(this.namespace, this.name, this.version, this.theme, this.icon, this.iconMonochrome, ImmutableList.copyOf(this.pages), ImmutableList.copyOf(this.optionOverrides)); + return new ModOptions(this.namespace, this.name, this.version, this.theme, this.icon, ImmutableList.copyOf(this.pages), ImmutableList.copyOf(this.optionOverrides)); } @Override @@ -78,12 +77,6 @@ public ModOptionsBuilder setIcon(ResourceLocation texture) { return this; } - @Override - public ModOptionsBuilder setIconNonMonochrome() { - this.iconMonochrome = false; - return this; - } - @Override public ModOptionsBuilder addPage(PageBuilder builder) { this.pages.add(((PageBuilderImpl) builder).build()); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java index 01fe7c161f..f0a2d46966 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java @@ -8,7 +8,7 @@ import java.util.List; -public record ModOptions(String namespace, String name, String version, ColorTheme theme, ResourceLocation icon, boolean iconMonochrome, ImmutableList pages, List overrides) implements Searchable { +public record ModOptions(String namespace, String name, String version, ColorTheme theme, ResourceLocation icon, ImmutableList pages, List overrides) implements Searchable { @Override public void registerTextSources(SearchIndex index) { for (Page page : this.pages) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java index 7bbe6007f7..4cde7a0a2f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java @@ -33,7 +33,7 @@ // TODO: get initialValue from the vanilla options (it's private) public class SodiumConfigBuilder implements ConfigEntryPoint { - private static final ResourceLocation SODIUM_ICON = ResourceLocation.fromNamespaceAndPath("sodium", "textures/gui/icon.png"); + private static final ResourceLocation SODIUM_ICON = ResourceLocation.fromNamespaceAndPath("sodium", "textures/gui/config-icon.png"); private static final SodiumOptions DEFAULTS = SodiumOptions.defaults(); private final Options vanillaOpts; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java index 7f289fb8a3..8a362d82da 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java @@ -416,20 +416,12 @@ public Dim2i getDimensions() { } public static int renderIconWithSpacing(GuiGraphics graphics, ResourceLocation icon, int color, int x, int y, int height, int margin) { - return renderIconWithSpacing(graphics, icon, color, true, x, y, height, margin); - } - - public static int renderIconWithSpacing(GuiGraphics graphics, ResourceLocation icon, int color, boolean iconMonochrome, int x, int y, int height, int margin) { int iconSize = height - margin * 2; // assume 16x16 icon texture final int blitX = x + margin; final int blitY = y + height / 2 - iconSize / 2; - if (iconMonochrome) { - GuiTint.withTint(color, () -> graphics.blit(icon, blitX, blitY, iconSize, iconSize, 0.0f, 0.0f, 16, 16, 16, 16)); - } else { - GuiTint.withTint(Colors.FOREGROUND, () -> graphics.blit(icon, blitX, blitY, iconSize, iconSize, 0.0f, 0.0f, 16, 16, 16, 16)); - } + GuiTint.withTint(color, () -> graphics.blit(icon, blitX, blitY, iconSize, iconSize, 0.0f, 0.0f, 16, 16, 16, 16)); return margin * 2 + iconSize; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java index 293aa4a127..3b6a6d0df3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java @@ -146,12 +146,10 @@ public int getY() { private class HeaderEntryWidget extends EntryWidget { private final ResourceLocation icon; - private final boolean iconMonochrome; HeaderEntryWidget(Dim2i dim, ModOptions modOptions, ColorTheme theme) { super(dim, Component.literal(modOptions.name()), Component.literal(modOptions.version()), false, theme); this.icon = modOptions.icon(); - this.iconMonochrome = modOptions.iconMonochrome(); } @Override @@ -160,7 +158,7 @@ protected int renderIcon(GuiGraphics graphics, int textColor) { return super.renderIcon(graphics, textColor); } - return VideoSettingsScreen.renderIconWithSpacing(graphics, this.icon, textColor, this.iconMonochrome, + return VideoSettingsScreen.renderIconWithSpacing(graphics, this.icon, textColor, this.getX(), this.getY(), this.getHeight(), Layout.ICON_MARGIN); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/MinecraftMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/MinecraftMixin.java index 027156c20d..578ace0ae4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/MinecraftMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/MinecraftMixin.java @@ -90,5 +90,4 @@ private void postInit(CallbackInfoReturnable cir) { private void postResourceReload(CallbackInfoReturnable> cir) { ResourcePackScanner.checkIfCoreShaderLoaded(this.resourceManager); } - } diff --git a/common/src/main/resources/assets/sodium/textures/gui/icon.png b/common/src/main/resources/assets/sodium/textures/gui/config-icon.png similarity index 100% rename from common/src/main/resources/assets/sodium/textures/gui/icon.png rename to common/src/main/resources/assets/sodium/textures/gui/config-icon.png From 1850eb40ac155fc2c0267ba9385d1afb2a04ed73 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 30 Nov 2025 18:41:05 +0100 Subject: [PATCH 130/215] Update API usage docs to reflect maven changes (#3357) * Update API usage docs to reflect maven changes * change to implementation dependency declaration in the docs for neoforge * use latest beta 1 snapshot build for now --- .../mods/sodium/api/config/USAGE.md | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md index 534e1358f6..130407203e 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md @@ -50,7 +50,7 @@ Here's a summary of the features this config API provides: ### Dependency on Sodium's API -Sodium publishes its api package on a maven repository that you can depend on in your buildscript. +Sodium publishes its api package on a maven repository that you can depend on in your buildscript. It needs `modImplementation` on Fabric 1.21.11, and `implementation` on Fabric 1.21.12+ and NeoForge 1.21.11+. Fabric: @@ -58,7 +58,10 @@ Fabric: dependencies { // ... other dependencies - modImplementation "net.caffeinemc.mods:sodium-fabric:0.8.0+mc1.21.11" + // using a tagged API build + modImplementation "net.caffeinemc:sodium-fabric-api:0.8.0+mc1.21.11" + // OR using a snapshot build + modImplementation "net.caffeinemc:sodium-fabric-api:0.8.0-SNAPSHOT+mc1.21.11-pre3-build.773" } ``` @@ -68,10 +71,31 @@ NeoForge: dependencies { // ... other dependencies - implementation "net.caffeinemc.mods:sodium-neoforge:0.8.0+mc1.21.11" + // using a tagged API build + implementation "net.caffeinemc:sodium-neoforge-api:0.8.0+mc1.21.11" + // OR using a snapshot build + implementation "net.caffeinemc:sodium-neoforge-api:0.8.0-SNAPSHOT+mc1.21.11-pre3-build.773" } ``` +Make sure you have our Maven repository declared: + +```groovy +maven { + name "CaffeineMC" + url "https://maven.caffeinemc.net/releases" // or /snapshots +} +``` +Kotlin: +```kotlin +maven { + name = "CaffeineMC" + url = uri("https://maven.caffeinemc.net/releases") // or /snapshots +} +``` + +The Maven Repository has a web frontend that you can use to view the list of available versions: https://maven.caffeinemc.net + ### Creating an Entrypoint Entrypoint classes that Sodium calls to run your options registration code can be declared either in your mod's metadata file, or on NeoForge with a special annotation. From d47b8e9a543b54a157bc6b820c9950661ebfb8a3 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 30 Nov 2025 18:42:37 +0100 Subject: [PATCH 131/215] Config API Fine-Grained Option Overlays (#3360) Adds a requested feature that allows mods to not just override entire options but change individual aspects of them while retaining validation. --- .../api/config/structure/ConfigBuilder.java | 11 +-- .../config/structure/ModOptionsBuilder.java | 24 +++-- .../structure/OptionOverrideBuilder.java | 24 ----- .../sodium/client/config/ConfigManager.java | 12 +-- .../builder/BooleanOptionBuilderImpl.java | 10 ++- .../config/builder/ConfigBuilderImpl.java | 5 -- .../config/builder/EnumOptionBuilderImpl.java | 47 +++++++--- .../ExternalButtonOptionBuilderImpl.java | 22 +++-- .../builder/IntegerOptionBuilderImpl.java | 34 ++++++-- .../config/builder/ModOptionsBuilderImpl.java | 49 +++++++---- .../config/builder/OptionBuilderImpl.java | 50 +++++++++-- .../builder/OptionOverrideBuilderImpl.java | 32 ------- .../builder/StatefulOptionBuilderImpl.java | 59 +++++++++---- .../builder/StaticOptionBuilderImpl.java | 13 +-- .../client/config/structure/Config.java | 87 +++++++++++++------ .../client/config/structure/EnumOption.java | 12 +++ .../structure/ExternalButtonOption.java | 4 + .../config/structure/IntegerOption.java | 8 ++ .../client/config/structure/ModOptions.java | 2 +- .../client/config/structure/Option.java | 6 +- .../config/structure/OptionOverlay.java | 7 ++ .../config/structure/OptionOverride.java | 2 +- .../config/structure/StatefulOption.java | 18 +++- 23 files changed, 354 insertions(+), 184 deletions(-) delete mode 100644 common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionOverrideBuilder.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionOverrideBuilderImpl.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/OptionOverlay.java diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ConfigBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ConfigBuilder.java index 4f804d9ea8..da258aba8d 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ConfigBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ConfigBuilder.java @@ -21,10 +21,10 @@ public interface ConfigBuilder { /** * Registers a new mod options structure for the mod with the given configId. The mod's name and version will be looked up automatically. * - * @param namespace The configId of these mod options. + * @param configId The configId of these mod options. * @return A builder for defining the mod's configuration structure. */ - ModOptionsBuilder registerModOptions(String namespace); + ModOptionsBuilder registerModOptions(String configId); /** * Registers a new mod options structure for the mod of the current entrypoint. The mod's name and version will be looked up automatically. @@ -33,13 +33,6 @@ public interface ConfigBuilder { */ ModOptionsBuilder registerOwnModOptions(); - /** - * Creates a new option override builder. - * - * @return A builder for defining an option override. - */ - OptionOverrideBuilder createOptionOverride(); - /** * Creates a new color theme builder. * diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java index ad374093e7..a48154c6f3 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java @@ -5,7 +5,7 @@ import java.util.function.Function; /** - * Builder interface for defining options belonging to a mod and its metadata. A set of mod options contains some list of option pages. + * Builder interface for defining options belonging to a mod and its metadata. A set of mod options contains some list of option pages, option overrides, and option overlays. At least one page, override, or overlay must be defined. */ public interface ModOptionsBuilder { /** @@ -33,7 +33,7 @@ public interface ModOptionsBuilder { ModOptionsBuilder formatVersion(Function versionFormatter); /** - * Sets the color theme for the mod options UI. + * Sets the color theme for the mod options UI. A color theme is optional and a theme will be chosen at random (deterministically) from a predetermined set of reasonable colors if none is provided. * * @param colorTheme The color theme builder. * @return The current builder instance. @@ -41,7 +41,7 @@ public interface ModOptionsBuilder { ModOptionsBuilder setColorTheme(ColorThemeBuilder colorTheme); /** - * Sets the icon texture for the mod. The icon should be centered within the square texture and the background should be transparent. The icon will be rendered monochrome tinted in the mod's theme color. + * Sets the icon texture for the mod. The icon should be centered within the square texture and the background should be transparent. The icon will be rendered monochrome tinted in the mod's theme color. No icon will be shown if none is provided and the layout adjusted accordingly. * * @param texture The resource location of the icon texture. * @return The current builder instance. @@ -58,9 +58,23 @@ public interface ModOptionsBuilder { /** * Registers an option override provided by this mod. Overrides allow modifying the behavior or appearance of options defined by other mods. + *

+ * The ID of the provided replacement option can match the original option to allow other mods to apply overlays targeting the original option ID. If the replacement option has a different ID, overlays must target the new ID. * - * @param override The option override builder. + * @param target The ID of the option to override. + * @param replacement The option builder that defines the replacement option. * @return The current builder instance. */ - ModOptionsBuilder registerOptionOverride(OptionOverrideBuilder override); + ModOptionsBuilder registerOptionReplacement(ResourceLocation target, OptionBuilder replacement); + + /** + * Registers an option overlay provided by this mod. Overlays allow partially changing an option instead of replacing it entirely. + *

+ * The target option ID must match the ID of an existing option, either defined by another mod or by a replacement option defined by this mod. If the target option has been replaced, overlays must target the ID of the replacement option, which may or may not be the same as the original option ID. + * + * @param target The ID of the option to overlay. + * @param overlay The option builder that defines the overlay changes. + * @return The current builder instance. + */ + ModOptionsBuilder registerOptionOverlay(ResourceLocation target, OptionBuilder overlay); } diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionOverrideBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionOverrideBuilder.java deleted file mode 100644 index c5c62622cf..0000000000 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/OptionOverrideBuilder.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.caffeinemc.mods.sodium.api.config.structure; - -import net.minecraft.resources.ResourceLocation; - -/** - * Builder interface for defining option overrides, which replace an existing option with a new one. - */ -public interface OptionOverrideBuilder { - /** - * Sets the target option to be overridden. - * - * @param target The ID of the target option. - * @return The current builder instance. - */ - OptionOverrideBuilder setTarget(ResourceLocation target); - - /** - * Sets the replacement option. - * - * @param option The option builder for the replacement option. - * @return The current builder instance. - */ - OptionOverrideBuilder setReplacement(OptionBuilder option); -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/ConfigManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/ConfigManager.java index 6f904c0944..2902abb471 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/ConfigManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/ConfigManager.java @@ -78,7 +78,7 @@ public static void registerConfigsLate() { } private static void registerConfigs(BiConsumer registerMethod) { - var namespaces = new ObjectOpenHashSet<>(); + var configIds = new ObjectOpenHashSet<>(); ModOptions sodiumModOptions = null; var modConfigs = new ObjectArrayList(); @@ -95,14 +95,14 @@ private static void registerConfigs(BiConsumer builtConfigs = builder.build(); for (var modConfig : builtConfigs) { - var namespace = modConfig.namespace(); - if (namespaces.contains(namespace)) { - throw new IllegalArgumentException("Mod '" + configUser.modId + "' provided a duplicate mod id: " + namespace); + var configId = modConfig.configId(); + if (configIds.contains(configId)) { + throw new IllegalArgumentException("Mod '" + configUser.modId + "' provided a duplicate mod id: " + configId); } - namespaces.add(namespace); + configIds.add(configId); - if (namespace.equals("sodium")) { + if (configId.equals("sodium")) { sodiumModOptions = modConfig; } else { modConfigs.add(modConfig); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/BooleanOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/BooleanOptionBuilderImpl.java index e5e96f2541..20a809c484 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/BooleanOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/BooleanOptionBuilderImpl.java @@ -14,7 +14,7 @@ import java.util.function.Function; import java.util.function.Supplier; -class BooleanOptionBuilderImpl extends StatefulOptionBuilderImpl implements BooleanOptionBuilder { +class BooleanOptionBuilderImpl extends StatefulOptionBuilderImpl implements BooleanOptionBuilder { BooleanOptionBuilderImpl(ResourceLocation id) { super(id); } @@ -22,7 +22,13 @@ class BooleanOptionBuilderImpl extends StatefulOptionBuilderImpl implem @Override BooleanOption build() { this.prepareBuild(); - return new BooleanOption(this.id, this.getDependencies(), this.name, this.enabled, this.storage, this.tooltipProvider, this.impact, this.flags, this.defaultValue, this.binding); + + return new BooleanOption(this.id, this.getDependencies(), this.getName(), this.getEnabled(), this.getStorage(), this.getTooltipProvider(), this.getImpact(), this.getFlags(), this.getDefaultValue(), this.getBinding()); + } + + @Override + Class getOptionClass() { + return BooleanOption.class; } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ConfigBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ConfigBuilderImpl.java index 4630f8e38e..b9999ed75b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ConfigBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ConfigBuilderImpl.java @@ -47,11 +47,6 @@ public ModOptionsBuilder registerOwnModOptions() { return this.registerModOptions(this.defaultConfigId); } - @Override - public OptionOverrideBuilder createOptionOverride() { - return new OptionOverrideBuilderImpl(); - } - @Override public ColorThemeBuilder createColorTheme() { return new ColorThemeBuilderImpl(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java index a71b5f070a..76bed498ba 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java @@ -7,7 +7,6 @@ import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; import net.caffeinemc.mods.sodium.api.config.structure.EnumOptionBuilder; import net.caffeinemc.mods.sodium.client.config.structure.EnumOption; -import net.caffeinemc.mods.sodium.client.config.structure.Option; import net.caffeinemc.mods.sodium.client.config.value.ConstantValue; import net.caffeinemc.mods.sodium.client.config.value.DependentValue; import net.caffeinemc.mods.sodium.client.config.value.DynamicValue; @@ -22,11 +21,11 @@ import java.util.function.Function; import java.util.function.Supplier; -class EnumOptionBuilderImpl> extends StatefulOptionBuilderImpl implements EnumOptionBuilder { - final Class enumClass; +class EnumOptionBuilderImpl> extends StatefulOptionBuilderImpl, E> implements EnumOptionBuilder { + private final Class enumClass; - DependentValue> allowedValues; - Function elementNameProvider; + private DependentValue> allowedValues; + private Function elementNameProvider; EnumOptionBuilderImpl(ResourceLocation id, Class enumClass) { super(id); @@ -34,29 +33,53 @@ class EnumOptionBuilderImpl> extends StatefulOptionBuilderImpl } @Override - Option build() { - this.prepareBuild(); + void validateData() { + super.validateData(); + + Validate.notNull(this.getElementNameProvider(), "Element name provider must be set or enum class must implement TextProvider"); + } - if (this.allowedValues == null) { + @Override + EnumOption build() { + if (this.getAllowedValues() == null) { this.allowedValues = new ConstantValue<>(Set.of(this.enumClass.getEnumConstants())); } - if (this.elementNameProvider == null && TextProvider.class.isAssignableFrom(this.enumClass)) { + if (this.getElementNameProvider() == null && TextProvider.class.isAssignableFrom(this.enumClass)) { this.elementNameProvider = e -> ((TextProvider) e).getLocalizedName(); } - Validate.notNull(this.elementNameProvider, "Element name provider must be set or enum class must implement TextProvider"); + this.prepareBuild(); + + return new EnumOption<>(this.id, this.getDependencies(), this.getName(), this.getEnabled(), this.getStorage(), this.getTooltipProvider(), this.getImpact(), this.getFlags(), this.getDefaultValue(), this.getBinding(), this.getEnumClass(), this.getAllowedValues(), this.getElementNameProvider()); + } - return new EnumOption<>(this.id, this.getDependencies(), this.name, this.enabled, this.storage, this.tooltipProvider, this.impact, this.flags, this.defaultValue, this.binding, this.enumClass, this.allowedValues, this.elementNameProvider); + @Override + Class> getOptionClass() { + @SuppressWarnings("unchecked") + Class> clazz = (Class>) (Class) EnumOption.class; + return clazz; } @Override Collection getDependencies() { var deps = super.getDependencies(); - deps.addAll(this.allowedValues.getDependencies()); + deps.addAll(this.getAllowedValues().getDependencies()); return deps; } + Class getEnumClass() { + return getFirstNotNull(this.enumClass, EnumOption::getEnumClass); + } + + DependentValue> getAllowedValues() { + return getFirstNotNull(this.allowedValues, EnumOption::getAllowedValues); + } + + Function getElementNameProvider() { + return getFirstNotNull(this.elementNameProvider, EnumOption::getElementNameProvider); + } + @Override public EnumOptionBuilder setAllowedValues(Set allowedValues) { this.allowedValues = new ConstantValue<>(allowedValues); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java index 1d27f4446c..71aab0423d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java @@ -3,7 +3,6 @@ import net.caffeinemc.mods.sodium.api.config.ConfigState; import net.caffeinemc.mods.sodium.api.config.structure.ExternalButtonOptionBuilder; import net.caffeinemc.mods.sodium.client.config.structure.ExternalButtonOption; -import net.caffeinemc.mods.sodium.client.config.structure.Option; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; @@ -12,7 +11,7 @@ import java.util.function.Consumer; import java.util.function.Function; -class ExternalButtonOptionBuilderImpl extends StaticOptionBuilderImpl implements ExternalButtonOptionBuilder { +class ExternalButtonOptionBuilderImpl extends StaticOptionBuilderImpl implements ExternalButtonOptionBuilder { private Consumer currentScreenConsumer; ExternalButtonOptionBuilderImpl(ResourceLocation id) { @@ -20,17 +19,26 @@ class ExternalButtonOptionBuilderImpl extends StaticOptionBuilderImpl implements } @Override - void prepareBuild() { - super.prepareBuild(); + void validateData() { + super.validateData(); - Validate.notNull(this.currentScreenConsumer, "Screen provider must be set"); + Validate.notNull(this.getCurrentScreenConsumer(), "Screen provider must be set"); } @Override - Option build() { + ExternalButtonOption build() { this.prepareBuild(); - return new ExternalButtonOption(this.id, this.getDependencies(), this.name, this.enabled, this.tooltip, this.currentScreenConsumer); + return new ExternalButtonOption(this.id, this.getDependencies(), this.getName(), this.getEnabled(), this.getTooltip(), this.getCurrentScreenConsumer()); + } + + @Override + Class getOptionClass() { + return ExternalButtonOption.class; + } + + Consumer getCurrentScreenConsumer() { + return getFirstNotNull(this.currentScreenConsumer, ExternalButtonOption::getCurrentScreenConsumer); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java index 3653e6b8d3..965a1ec43c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java @@ -17,31 +17,49 @@ import java.util.function.Function; import java.util.function.Supplier; -class IntegerOptionBuilderImpl extends StatefulOptionBuilderImpl implements IntegerOptionBuilder { - DependentValue rangeProvider; - ControlValueFormatter valueFormatter; +class IntegerOptionBuilderImpl extends StatefulOptionBuilderImpl implements IntegerOptionBuilder { + private DependentValue rangeProvider; + private ControlValueFormatter valueFormatter; IntegerOptionBuilderImpl(ResourceLocation id) { super(id); } + @Override + void validateData() { + super.validateData(); + + Validate.notNull(this.getRangeProvider(), "Range provider must be set"); + Validate.notNull(this.getValueFormatter(), "Value formatter must be set"); + } + @Override IntegerOption build() { this.prepareBuild(); - Validate.notNull(this.rangeProvider, "Range provider must be set"); - Validate.notNull(this.valueFormatter, "Value formatter must be set"); - - return new IntegerOption(this.id, this.getDependencies(), this.name, this.enabled, this.storage, this.tooltipProvider, this.impact, this.flags, this.defaultValue, this.binding, this.rangeProvider, this.valueFormatter); + return new IntegerOption(this.id, this.getDependencies(), this.getName(), this.getEnabled(), this.getStorage(), this.getTooltipProvider(), this.getImpact(), this.getFlags(), this.getDefaultValue(), this.getBinding(), this.getRangeProvider(), this.getValueFormatter()); } @Override Collection getDependencies() { var deps = super.getDependencies(); - deps.addAll(this.rangeProvider.getDependencies()); + deps.addAll(this.getRangeProvider().getDependencies()); return deps; } + @Override + Class getOptionClass() { + return IntegerOption.class; + } + + DependentValue getRangeProvider() { + return getFirstNotNull(this.rangeProvider, IntegerOption::getRangeProvider); + } + + ControlValueFormatter getValueFormatter() { + return getFirstNotNull(this.valueFormatter, IntegerOption::getValueFormatter); + } + @Override public IntegerOptionBuilder setRange(int min, int max, int step) { return this.setRange(new Range(min, max, step)); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java index 23beaf9671..0d29c30a5f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java @@ -1,13 +1,8 @@ package net.caffeinemc.mods.sodium.client.config.builder; import com.google.common.collect.ImmutableList; -import net.caffeinemc.mods.sodium.api.config.structure.ColorThemeBuilder; -import net.caffeinemc.mods.sodium.api.config.structure.ModOptionsBuilder; -import net.caffeinemc.mods.sodium.api.config.structure.OptionOverrideBuilder; -import net.caffeinemc.mods.sodium.api.config.structure.PageBuilder; -import net.caffeinemc.mods.sodium.client.config.structure.ModOptions; -import net.caffeinemc.mods.sodium.client.config.structure.OptionOverride; -import net.caffeinemc.mods.sodium.client.config.structure.Page; +import net.caffeinemc.mods.sodium.api.config.structure.*; +import net.caffeinemc.mods.sodium.client.config.structure.*; import net.caffeinemc.mods.sodium.client.gui.ColorTheme; import net.minecraft.resources.ResourceLocation; import org.apache.commons.lang3.Validate; @@ -17,16 +12,17 @@ import java.util.function.Function; class ModOptionsBuilderImpl implements ModOptionsBuilder { - private final String namespace; + private final String configId; private String name; private String version; private ColorTheme theme; private ResourceLocation icon; private final List pages = new ArrayList<>(); - private final List optionOverrides = new ArrayList<>(0); + private List optionOverrides; + private List optionOverlays; - ModOptionsBuilderImpl(String namespace, String name, String version) { - this.namespace = namespace; + ModOptionsBuilderImpl(String configId, String name, String version) { + this.configId = configId; this.name = name; this.version = version; } @@ -35,16 +31,19 @@ ModOptions build() { Validate.notEmpty(this.name, "Name must not be empty"); Validate.notEmpty(this.version, "Version must not be empty"); - if (this.optionOverrides.isEmpty() && this.pages.isEmpty()) { - throw new IllegalStateException("At least one page or option override must be added"); + var overrides = this.optionOverrides == null ? List.of() : this.optionOverrides; + var overlays = this.optionOverlays == null ? List.of() : this.optionOverlays; + + if (this.pages.isEmpty() && overrides.isEmpty() && overlays.isEmpty()) { + throw new IllegalStateException("At least one page, option override, or option overlay must be added"); } - // if no theme is specified, pick one pseudo-randomly based on the namespace + // if no theme is specified, pick one pseudo-randomly based on the configId if (this.theme == null) { - this.theme = ColorTheme.PRESETS[Math.abs(this.namespace.hashCode()) % ColorTheme.PRESETS.length]; + this.theme = ColorTheme.PRESETS[Math.abs(this.configId.hashCode()) % ColorTheme.PRESETS.length]; } - return new ModOptions(this.namespace, this.name, this.version, this.theme, this.icon, ImmutableList.copyOf(this.pages), ImmutableList.copyOf(this.optionOverrides)); + return new ModOptions(this.configId, this.name, this.version, this.theme, this.icon, ImmutableList.copyOf(this.pages), overrides, overlays); } @Override @@ -84,8 +83,22 @@ public ModOptionsBuilder addPage(PageBuilder builder) { } @Override - public ModOptionsBuilder registerOptionOverride(OptionOverrideBuilder override) { - this.optionOverrides.add(((OptionOverrideBuilderImpl) override).build(this.namespace)); + public ModOptionsBuilder registerOptionReplacement(ResourceLocation target, OptionBuilder replacement) { + var override = new OptionOverride(target, this.configId, ((OptionBuilderImpl) replacement).build()); + if (this.optionOverrides == null) { + this.optionOverrides = new ArrayList<>(); + } + this.optionOverrides.add(override); + return this; + } + + @Override + public ModOptionsBuilder registerOptionOverlay(ResourceLocation target, OptionBuilder overlay) { + var optionOverlay = new OptionOverlay(target, this.configId, ((OptionBuilderImpl) overlay)); + if (this.optionOverlays == null) { + this.optionOverlays = new ArrayList<>(); + } + this.optionOverlays.add(optionOverlay); return this; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java index 51a20265c9..61007f53dc 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java @@ -14,32 +14,68 @@ import java.util.Collection; import java.util.function.Function; -public abstract class OptionBuilderImpl implements OptionBuilder { +public abstract class OptionBuilderImpl implements OptionBuilder { final ResourceLocation id; - Component name; - DependentValue enabled; + private O baseOption; + + private Component name; + private DependentValue enabled; OptionBuilderImpl(ResourceLocation id) { this.id = id; } - abstract Option build(); + abstract O build(); + + abstract Class getOptionClass(); + + public O buildWithBaseOption(Option baseOption) { + Validate.isTrue(this.getOptionClass().isInstance(baseOption), "Base option must be of type %s", this.getOptionClass().getSimpleName()); + + @SuppressWarnings("unchecked") + O castedBaseOption = (O) baseOption; + this.baseOption = castedBaseOption; + + return this.build(); + } + + void validateData() { + Validate.notNull(this.getName(), "Name must be set"); + } void prepareBuild() { - Validate.notNull(this.name, "Name must be set"); + this.validateData(); - if (this.enabled == null) { + if (this.getEnabled() == null) { this.enabled = new ConstantValue<>(true); } } Collection getDependencies() { var dependencies = new ObjectLinkedOpenHashSet(); - dependencies.addAll(this.enabled.getDependencies()); + dependencies.addAll(this.getEnabled().getDependencies()); return dependencies; } + public V getFirstNotNull(V overlayValue, Function extractor) { + if (overlayValue != null) { + return overlayValue; + } else if (this.baseOption != null) { + return extractor.apply(this.baseOption); + } else { + return null; + } + } + + Component getName() { + return getFirstNotNull(this.name, Option::getName); + } + + DependentValue getEnabled() { + return getFirstNotNull(this.enabled, Option::getEnabled); + } + @Override public OptionBuilder setName(Component name) { Validate.notNull(name, "Argument must not be null"); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionOverrideBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionOverrideBuilderImpl.java deleted file mode 100644 index 01298a1d89..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionOverrideBuilderImpl.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.caffeinemc.mods.sodium.client.config.builder; - -import net.caffeinemc.mods.sodium.api.config.structure.OptionBuilder; -import net.caffeinemc.mods.sodium.api.config.structure.OptionOverrideBuilder; -import net.caffeinemc.mods.sodium.client.config.structure.Option; -import net.caffeinemc.mods.sodium.client.config.structure.OptionOverride; -import net.minecraft.resources.ResourceLocation; -import org.apache.commons.lang3.Validate; - -public class OptionOverrideBuilderImpl implements OptionOverrideBuilder { - private ResourceLocation target; - private Option replacement; - - OptionOverride build(String source) { - Validate.notNull(this.target, "Target must be set"); - Validate.notNull(this.replacement, "Replacement must be set"); - - return new OptionOverride(this.target, source, this.replacement); - } - - @Override - public OptionOverrideBuilder setTarget(ResourceLocation target) { - this.target = target; - return this; - } - - @Override - public OptionOverrideBuilder setReplacement(OptionBuilder option) { - this.replacement = ((OptionBuilderImpl) option).build(); - return this; - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java index 95db23f641..a201ebafed 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java @@ -7,6 +7,7 @@ import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; import net.caffeinemc.mods.sodium.api.config.structure.StatefulOptionBuilder; import net.caffeinemc.mods.sodium.client.config.AnonymousOptionBinding; +import net.caffeinemc.mods.sodium.client.config.structure.StatefulOption; import net.caffeinemc.mods.sodium.client.config.value.ConstantValue; import net.caffeinemc.mods.sodium.client.config.value.DependentValue; import net.caffeinemc.mods.sodium.client.config.value.DynamicValue; @@ -21,34 +22,59 @@ import java.util.function.Function; import java.util.function.Supplier; -abstract class StatefulOptionBuilderImpl extends OptionBuilderImpl implements StatefulOptionBuilder { - StorageEventHandler storage; - Function tooltipProvider; - OptionImpact impact; - EnumSet flags = EnumSet.noneOf(OptionFlag.class); - DependentValue defaultValue; - OptionBinding binding; +abstract class StatefulOptionBuilderImpl, V> extends OptionBuilderImpl implements StatefulOptionBuilder { + private StorageEventHandler storage; + private Function tooltipProvider; + private OptionImpact impact; + private EnumSet flags; + private DependentValue defaultValue; + private OptionBinding binding; StatefulOptionBuilderImpl(ResourceLocation id) { super(id); } - void prepareBuild() { - super.prepareBuild(); + @Override + void validateData() { + super.validateData(); - Validate.notNull(this.storage, "Storage handler must be set"); - Validate.notNull(this.tooltipProvider, "Tooltip provider must be set"); - Validate.notNull(this.defaultValue, "Default value must be set"); + Validate.notNull(this.getStorage(), "Storage handler must be set"); + Validate.notNull(this.getTooltipProvider(), "Tooltip provider must be set"); + Validate.notNull(this.getDefaultValue(), "Default value must be set"); - Validate.notNull(this.binding, "Binding must be set"); + Validate.notNull(this.getBinding(), "Binding must be set"); } Collection getDependencies() { var dependencies = super.getDependencies(); - dependencies.addAll(this.defaultValue.getDependencies()); + dependencies.addAll(this.getDefaultValue().getDependencies()); return dependencies; } + StorageEventHandler getStorage() { + return getFirstNotNull(this.storage, StatefulOption::getStorage); + } + + Function getTooltipProvider() { + return getFirstNotNull(this.tooltipProvider, StatefulOption::getTooltipProvider); + } + + OptionImpact getImpact() { + return getFirstNotNull(this.impact, StatefulOption::getImpact); + } + + EnumSet getFlags() { + return getFirstNotNull(this.flags, StatefulOption::getFlags); + } + + DependentValue getDefaultValue() { + return getFirstNotNull(this.defaultValue, StatefulOption::getDefaultValue); + } + + OptionBinding getBinding() { + return getFirstNotNull(this.binding, StatefulOption::getBinding); + } + @Override public StatefulOptionBuilder setStorageHandler(StorageEventHandler storage) { Validate.notNull(storage, "Argument must not be null"); @@ -83,7 +109,10 @@ public StatefulOptionBuilder setImpact(OptionImpact impact) { @Override public StatefulOptionBuilder setFlags(OptionFlag... flags) { - Collections.addAll(this.flags, flags); + if (this.getFlags() == null) { + this.flags = EnumSet.noneOf(OptionFlag.class); + } + Collections.addAll(this.getFlags(), flags); return this; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StaticOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StaticOptionBuilderImpl.java index ce770efc4f..38b1ca058e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StaticOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StaticOptionBuilderImpl.java @@ -1,22 +1,25 @@ package net.caffeinemc.mods.sodium.client.config.builder; import net.caffeinemc.mods.sodium.api.config.structure.OptionBuilder; +import net.caffeinemc.mods.sodium.client.config.structure.StaticOption; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import org.apache.commons.lang3.Validate; -abstract class StaticOptionBuilderImpl extends OptionBuilderImpl { - Component tooltip; +abstract class StaticOptionBuilderImpl extends OptionBuilderImpl { + private Component tooltip; StaticOptionBuilderImpl(ResourceLocation id) { super(id); } @Override - void prepareBuild() { - super.prepareBuild(); + void validateData() { + Validate.notNull(this.getTooltip(), "Tooltip must be set"); + } - Validate.notNull(this.tooltip, "Tooltip must be set"); + Component getTooltip() { + return getFirstNotNull(this.tooltip, StaticOption::getTooltip); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java index 4bed7d1c67..2601a79881 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.EnumSet; +import java.util.List; import java.util.Map; public class Config implements ConfigState { @@ -32,7 +33,7 @@ public Config(ImmutableList modOptions) { this.modOptions = modOptions; this.collectOptions(); - this.applyOverrides(); + this.applyOptionChanges(); this.validateDependencies(); // load options initially from their bindings @@ -57,8 +58,8 @@ private void collectOptions() { for (var page : modConfig.pages()) { for (var group : page.groups()) { for (var option : group.options()) { - if (!option.id.getNamespace().equals(modConfig.namespace())) { - throw new IllegalArgumentException("Namespace of option id '" + option.id + "' does not match the namespace '" + modConfig.namespace() + "' of the enclosing mod config"); + if (!option.id.getNamespace().equals(modConfig.configId())) { + throw new IllegalArgumentException("Namespace of option id '" + option.id + "' does not match the configId '" + modConfig.configId() + "' of the enclosing mod config"); } this.options.put(option.id, option); @@ -69,8 +70,32 @@ private void collectOptions() { } } - private void applyOverrides() { - var overrides = getOverrides(); + private void applyOptionChanges() { + var overrides = new Object2ReferenceOpenHashMap(); + var overlays = new Object2ReferenceOpenHashMap(); + + // collect overrides and overlays and validate them, also against each other + for (var modConfig : this.modOptions) { + for (var override : modConfig.overrides()) { + if (override.target().getNamespace().equals(modConfig.configId())) { + throw new IllegalArgumentException("Override by mod '" + modConfig.configId() + "' targets its own option '" + override.target() + "'"); + } + + if (overrides.put(override.target(), override) != null) { + throw new IllegalArgumentException("Multiple overrides for option '" + override.target() + "'"); + } + } + + for (var overlay : modConfig.overlays()) { + if (overlay.target().getNamespace().equals(modConfig.configId())) { + throw new IllegalArgumentException("Overlay by mod '" + modConfig.configId() + "' targets its own option '" + overlay.target() + "'"); + } + + if (overlays.put(overlay.target(), overlay) != null) { + throw new IllegalArgumentException("Multiple overlays for option '" + overlay.target() + "'"); + } + } + } // apply overrides for (var modConfig : this.modOptions) { @@ -80,35 +105,44 @@ private void applyOverrides() { for (int i = 0; i < options.size(); i++) { var option = options.get(i); var override = overrides.get(option.id); + + // apply override to option if it exists if (override != null) { - var replacement = override.replacement(); - options.set(i, replacement); - this.options.remove(option.id); - this.options.put(replacement.id, replacement); - replacement.setParentConfig(this); - option.setParentConfig(null); + var replacement = override.change(); + exchangeOption(options, i, replacement, option); } } } } } - } - private Object2ReferenceOpenHashMap getOverrides() { - // collect overrides and validate them - var overrides = new Object2ReferenceOpenHashMap(); + // apply overlays for (var modConfig : this.modOptions) { - for (var override : modConfig.overrides()) { - if (override.target().getNamespace().equals(modConfig.namespace())) { - throw new IllegalArgumentException("Override by mod '" + modConfig.namespace() + "' targets its own option '" + override.target() + "'"); - } + for (var page : modConfig.pages()) { + for (var group : page.groups()) { + var options = group.options(); + for (int i = 0; i < options.size(); i++) { + var option = options.get(i); + var overlay = overlays.get(option.id); - if (overrides.put(override.target(), override) != null) { - throw new IllegalArgumentException("Multiple overrides for option '" + override.target() + "'"); + // apply overlay to option if it exists + if (overlay != null) { + var change = overlay.change(); + var overlaidOption = change.buildWithBaseOption(option); + exchangeOption(options, i, overlaidOption, option); + } + } } } } - return overrides; + } + + private void exchangeOption(List

* | 32 | 24 | 16 | 8 | * | 0110 1100 | 0110 1100 | 0110 1100 | 0110 1100 | * | Alpha | Red | Green | Blue | @@ -22,6 +22,7 @@ public class ColorARGB implements ColorU8 { /** * Packs the specified color components into big-endian format for consumption by OpenGL. + * * @param r The red component of the color * @param g The green component of the color * @param b The blue component of the color @@ -37,6 +38,7 @@ public static int pack(int r, int g, int b, int a) { /** * Packs the specified color components into big-endian format for consumption by OpenGL. The alpha * channel is fully opaque. + * * @param r The red component of the color * @param g The green component of the color * @param b The blue component of the color @@ -114,17 +116,29 @@ public static int fromABGR(int color) { /** * Packs the specified color components into ARGB format. - * @param rgb The red/green/blue component of the color + * + * @param rgb The red/green/blue component of the color * @param alpha The alpha component of the color */ public static int withAlpha(int rgb, int alpha) { return (alpha << ALPHA_COMPONENT_OFFSET) | (rgb & ~(COMPONENT_MASK << ALPHA_COMPONENT_OFFSET)); } + /** + * Replaces the alpha component of the specified color with the alpha component of another color. + * + * @param color The packed 32-bit ARGB color to modify + * @param alphaColor The packed 32-bit ARGB color to take the alpha component from + * @return The modified packed 32-bit ARGB color + */ + public static int transferAlpha(int color, int alphaColor) { + return withAlpha(color, unpackAlpha(alphaColor)); + } + /** * Multiplies the RGB components of the color with the provided factor. The alpha component is not modified. * - * @param color The packed 32-bit ABGR color to be multiplied + * @param color The packed 32-bit ABGR color to be multiplied * @param factor The darkening factor (in the range of 0..255) to multiply with */ public static int mulRGB(int color, int factor) { @@ -135,10 +149,82 @@ public static int mulRGB(int color, int factor) { * See {@link #mulRGB(int, int)}. This function is identical, but it accepts a float in [0.0, 1.0] instead, which * is then mapped to [0, 255]. * - * @param color The packed 32-bit ABGR color to be multiplied + * @param color The packed 32-bit ABGR color to be multiplied * @param factor The darkening factor (in the range of 0.0..1.0) to multiply with */ public static int mulRGB(int color, float factor) { return mulRGB(color, ColorU8.normalizedFloatToByte(factor)); } + + /** + * Converts the specified packed ARGB color into HSV color space. + *

+ * Used with permission from patbox. + * + * @param color The packed 32-bit ARGB color to convert + * @return An array containing the hue, saturation and value components in [0,1] in that order. + */ + public static float[] toHSV(int color) { + float r = (float) unpackRed(color) / 255; + float g = (float) unpackGreen(color) / 255; + float b = (float) unpackBlue(color) / 255; + + float cmax = Math.max(r, Math.max(g, b)); + float cmin = Math.min(r, Math.min(g, b)); + float diff = cmax - cmin; + float h = -1, s = -1; + + if (cmax == cmin) { + h = 0; + } else if (cmax == r) { + h = (0.1666f * ((g - b) / diff) + 1) % 1; + } else if (cmax == g) { + h = (0.1666f * ((b - r) / diff) + 0.333f) % 1; + } else if (cmax == b) { + h = (0.1666f * ((r - g) / diff) + 0.666f) % 1; + } + if (cmax == 0) { + s = 0; + } else { + s = (diff / cmax); + } + + return new float[] { h, s, cmax }; + } + + private static int pack(float r, float g, float b) { + return pack( + ColorU8.normalizedFloatToByte(r), + ColorU8.normalizedFloatToByte(g), + ColorU8.normalizedFloatToByte(b) + ); + } + + /** + * Converts the specified HSV color components into a packed ARGB color. + *

+ * Used with permission from patbox + * + * @param hue The hue component in [0,1] + * @param saturation The saturation component in [0,1] + * @param value The value component in [0,1] + * @return The packed 32-bit ARGB color + */ + public static int fromHSV(float hue, float saturation, float value) { + int h = (int) (hue * 6) % 6; + float f = hue * 6 - h; + float p = value * (1 - saturation); + float q = value * (1 - f * saturation); + float t = value * (1 - (1 - f) * saturation); + + return switch (h) { + case 0 -> pack(value, t, p); + case 1 -> pack(q, value, p); + case 2 -> pack(p, value, t); + case 3 -> pack(p, q, value); + case 4 -> pack(t, p, value); + case 5 -> pack(value, p, q); + default -> 0; + }; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/ConfigManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/ConfigManager.java index 2902abb471..f3ba1e9a0f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/ConfigManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/ConfigManager.java @@ -1,7 +1,6 @@ package net.caffeinemc.mods.sodium.client.config; -import com.google.common.collect.ImmutableList; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.caffeinemc.mods.sodium.api.config.ConfigEntryPoint; @@ -122,7 +121,7 @@ private static void registerConfigs(BiConsumer modConfigs.add(0, sodiumModOptions); try { - CONFIG = new Config(ImmutableList.copyOf(modConfigs)); + CONFIG = new Config(modConfigs); } catch (Exception e) { crashWithMessage("Failed to build config options", e); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/BooleanOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/BooleanOptionBuilderImpl.java index 20a809c484..62f656902a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/BooleanOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/BooleanOptionBuilderImpl.java @@ -23,7 +23,19 @@ class BooleanOptionBuilderImpl extends StatefulOptionBuilderImpl pr return this; } + @Override + public BooleanOptionBuilder setControlHiddenWhenDisabled(boolean hidden) { + super.setControlHiddenWhenDisabled(hidden); + return this; + } + @Override public BooleanOptionBuilder setBinding(Consumer save, Supplier load) { super.setBinding(save, load); @@ -102,4 +126,10 @@ public BooleanOptionBuilder setBinding(OptionBinding binding) { super.setBinding(binding); return this; } + + @Override + public BooleanOptionBuilder setApplyHook(Consumer hook) { + super.setApplyHook(hook); + return this; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java index 76bed498ba..30cddd225f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java @@ -51,7 +51,20 @@ EnumOption build() { this.prepareBuild(); - return new EnumOption<>(this.id, this.getDependencies(), this.getName(), this.getEnabled(), this.getStorage(), this.getTooltipProvider(), this.getImpact(), this.getFlags(), this.getDefaultValue(), this.getBinding(), this.getEnumClass(), this.getAllowedValues(), this.getElementNameProvider()); + return new EnumOption<>(this.id, + this.getDependencies(), + this.getName(), + this.getEnabled(), + this.getStorage(), + this.getTooltipProvider(), this.getImpact(), + this.getFlags(), + this.getDefaultValue(), + this.getControlHiddenWhenDisabled(), + this.getBinding(), + this.getApplyHook(), + this.getEnumClass(), + this.getAllowedValues(), + this.getElementNameProvider()); } @Override @@ -81,26 +94,26 @@ Function getElementNameProvider() { } @Override - public EnumOptionBuilder setAllowedValues(Set allowedValues) { - this.allowedValues = new ConstantValue<>(allowedValues); + public EnumOptionBuilder setName(Component name) { + super.setName(name); return this; } @Override - public EnumOptionBuilder setAllowedValuesProvider(Function> provider, ResourceLocation... dependencies) { - this.allowedValues = new DynamicValue<>(provider, dependencies); + public EnumOptionBuilder setElementNameProvider(Function provider) { + this.elementNameProvider = provider; return this; } @Override - public EnumOptionBuilder setElementNameProvider(Function provider) { - this.elementNameProvider = provider; + public EnumOptionBuilder setEnabled(boolean available) { + super.setEnabled(available); return this; } @Override - public EnumOptionBuilder setName(Component name) { - super.setName(name); + public EnumOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies) { + super.setEnabledProvider(provider, dependencies); return this; } @@ -135,26 +148,26 @@ public EnumOptionBuilder setFlags(OptionFlag... flags) { } @Override - public EnumOptionBuilder setDefaultValue(E value) { - super.setDefaultValue(value); + public EnumOptionBuilder setFlags(ResourceLocation... flags) { + super.setFlags(flags); return this; } @Override - public EnumOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies) { - super.setDefaultProvider(provider, dependencies); + public EnumOptionBuilder setDefaultValue(E value) { + super.setDefaultValue(value); return this; } @Override - public EnumOptionBuilder setEnabled(boolean available) { - super.setEnabled(available); + public EnumOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies) { + super.setDefaultProvider(provider, dependencies); return this; } @Override - public EnumOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies) { - super.setEnabledProvider(provider, dependencies); + public EnumOptionBuilder setControlHiddenWhenDisabled(boolean hidden) { + super.setControlHiddenWhenDisabled(hidden); return this; } @@ -169,4 +182,23 @@ public EnumOptionBuilder setBinding(OptionBinding binding) { super.setBinding(binding); return this; } + + @Override + public EnumOptionBuilder setApplyHook(Consumer hook) { + super.setApplyHook(hook); + return this; + } + + @Override + public EnumOptionBuilder setAllowedValues(Set allowedValues) { + this.allowedValues = new ConstantValue<>(allowedValues); + return this; + } + + @Override + public EnumOptionBuilder setAllowedValuesProvider(Function> provider, ResourceLocation... dependencies) { + this.allowedValues = new DynamicValue<>(provider, dependencies); + return this; + } + } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java index 1c4ea03ae5..f1a80d2b5d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java @@ -41,12 +41,6 @@ Consumer getCurrentScreenConsumer() { return getFirstNotNull(this.currentScreenConsumer, ExternalButtonOption::getCurrentScreenConsumer); } - @Override - public ExternalButtonOptionBuilder setScreenConsumer(Consumer currentScreenConsumer) { - this.currentScreenConsumer = currentScreenConsumer; - return this; - } - @Override public ExternalButtonOptionBuilder setName(Component name) { super.setName(name); @@ -70,4 +64,10 @@ public ExternalButtonOptionBuilder setTooltip(Component tooltip) { super.setTooltip(tooltip); return this; } + + @Override + public ExternalButtonOptionBuilder setScreenConsumer(Consumer currentScreenConsumer) { + this.currentScreenConsumer = currentScreenConsumer; + return this; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java index 965a1ec43c..cb3e66b5a1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java @@ -18,7 +18,7 @@ import java.util.function.Supplier; class IntegerOptionBuilderImpl extends StatefulOptionBuilderImpl implements IntegerOptionBuilder { - private DependentValue rangeProvider; + private DependentValue validatorProvider; private ControlValueFormatter valueFormatter; IntegerOptionBuilderImpl(ResourceLocation id) { @@ -29,7 +29,7 @@ class IntegerOptionBuilderImpl extends StatefulOptionBuilderImpl getDependencies() { var deps = super.getDependencies(); - deps.addAll(this.getRangeProvider().getDependencies()); + deps.addAll(this.getValidatorProvider().getDependencies()); return deps; } @@ -52,8 +62,8 @@ Class getOptionClass() { return IntegerOption.class; } - DependentValue getRangeProvider() { - return getFirstNotNull(this.rangeProvider, IntegerOption::getRangeProvider); + DependentValue getValidatorProvider() { + return getFirstNotNull(this.validatorProvider, IntegerOption::getValidatorProvider); } ControlValueFormatter getValueFormatter() { @@ -61,31 +71,20 @@ ControlValueFormatter getValueFormatter() { } @Override - public IntegerOptionBuilder setRange(int min, int max, int step) { - return this.setRange(new Range(min, max, step)); - } - - @Override - public IntegerOptionBuilder setRange(Range range) { - this.rangeProvider = new ConstantValue<>(range); - return this; - } - - @Override - public IntegerOptionBuilder setRangeProvider(Function provider, ResourceLocation... dependencies) { - this.rangeProvider = new DynamicValue<>(provider, dependencies); + public IntegerOptionBuilder setName(Component name) { + super.setName(name); return this; } @Override - public IntegerOptionBuilder setValueFormatter(ControlValueFormatter formatter) { - this.valueFormatter = formatter; + public IntegerOptionBuilder setEnabled(boolean available) { + super.setEnabled(available); return this; } @Override - public IntegerOptionBuilder setName(Component name) { - super.setName(name); + public IntegerOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies) { + super.setEnabledProvider(provider, dependencies); return this; } @@ -120,26 +119,26 @@ public IntegerOptionBuilder setFlags(OptionFlag... flags) { } @Override - public IntegerOptionBuilder setDefaultValue(Integer value) { - super.setDefaultValue(value); + public IntegerOptionBuilder setFlags(ResourceLocation... flags) { + super.setFlags(flags); return this; } @Override - public IntegerOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies) { - super.setDefaultProvider(provider, dependencies); + public IntegerOptionBuilder setDefaultValue(Integer value) { + super.setDefaultValue(value); return this; } @Override - public IntegerOptionBuilder setEnabled(boolean available) { - super.setEnabled(available); + public IntegerOptionBuilder setDefaultProvider(Function provider, ResourceLocation... dependencies) { + super.setDefaultProvider(provider, dependencies); return this; } @Override - public IntegerOptionBuilder setEnabledProvider(Function provider, ResourceLocation... dependencies) { - super.setEnabledProvider(provider, dependencies); + public IntegerOptionBuilder setControlHiddenWhenDisabled(boolean hidden) { + super.setControlHiddenWhenDisabled(hidden); return this; } @@ -154,4 +153,45 @@ public IntegerOptionBuilder setBinding(OptionBinding binding) { super.setBinding(binding); return this; } + + @Override + public IntegerOptionBuilder setApplyHook(Consumer hook) { + super.setApplyHook(hook); + return this; + } + + @Override + public IntegerOptionBuilder setRange(int min, int max, int step) { + return this.setRange(new Range(min, max, step)); + } + + @Override + public IntegerOptionBuilder setRange(Range range) { + this.validatorProvider = new ConstantValue<>(range); + return this; + } + + @Override + public IntegerOptionBuilder setRangeProvider(Function provider, ResourceLocation... dependencies) { + this.validatorProvider = new DynamicValue<>(provider, dependencies); + return this; + } + + @Override + public IntegerOptionBuilder setValidator(SteppedValidator validator) { + this.validatorProvider = new ConstantValue<>(validator); + return this; + } + + @Override + public IntegerOptionBuilder setValidatorProvider(Function provider, ResourceLocation... dependencies) { + this.validatorProvider = new DynamicValue<>(provider, dependencies); + return this; + } + + @Override + public IntegerOptionBuilder setValueFormatter(ControlValueFormatter formatter) { + this.valueFormatter = formatter; + return this; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java index 0d29c30a5f..1187fccf3a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java @@ -1,14 +1,22 @@ package net.caffeinemc.mods.sodium.client.config.builder; import com.google.common.collect.ImmutableList; -import net.caffeinemc.mods.sodium.api.config.structure.*; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.caffeinemc.mods.sodium.api.config.ConfigState; +import net.caffeinemc.mods.sodium.api.config.option.FlagHook; +import net.caffeinemc.mods.sodium.api.config.structure.ColorThemeBuilder; +import net.caffeinemc.mods.sodium.api.config.structure.ModOptionsBuilder; +import net.caffeinemc.mods.sodium.api.config.structure.OptionBuilder; +import net.caffeinemc.mods.sodium.api.config.structure.PageBuilder; import net.caffeinemc.mods.sodium.client.config.structure.*; import net.caffeinemc.mods.sodium.client.gui.ColorTheme; import net.minecraft.resources.ResourceLocation; import org.apache.commons.lang3.Validate; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.function.BiConsumer; import java.util.function.Function; class ModOptionsBuilderImpl implements ModOptionsBuilder { @@ -20,6 +28,7 @@ class ModOptionsBuilderImpl implements ModOptionsBuilder { private final List pages = new ArrayList<>(); private List optionOverrides; private List optionOverlays; + private Collection flagHooks; ModOptionsBuilderImpl(String configId, String name, String version) { this.configId = configId; @@ -43,7 +52,7 @@ ModOptions build() { this.theme = ColorTheme.PRESETS[Math.abs(this.configId.hashCode()) % ColorTheme.PRESETS.length]; } - return new ModOptions(this.configId, this.name, this.version, this.theme, this.icon, ImmutableList.copyOf(this.pages), overrides, overlays); + return new ModOptions(this.configId, this.name, this.version, this.theme, this.icon, ImmutableList.copyOf(this.pages), overrides, overlays, this.flagHooks); } @Override @@ -101,4 +110,18 @@ public ModOptionsBuilder registerOptionOverlay(ResourceLocation target, OptionBu this.optionOverlays.add(optionOverlay); return this; } + + @Override + public ModOptionsBuilder registerFlagHook(BiConsumer, ConfigState> hook, ResourceLocation... triggers) { + return this.registerFlagHook(new FlagHookImpl(hook, List.of(triggers))); + } + + @Override + public ModOptionsBuilder registerFlagHook(FlagHook hook) { + if (this.flagHooks == null) { + this.flagHooks = new ObjectArrayList<>(); + } + this.flagHooks.add(hook); + return this; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java index a201ebafed..1078ef1047 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java @@ -16,8 +16,7 @@ import org.apache.commons.lang3.Validate; import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -26,9 +25,11 @@ abstract class StatefulOptionBuilderImpl, V> extends private StorageEventHandler storage; private Function tooltipProvider; private OptionImpact impact; - private EnumSet flags; + private Set flags; private DependentValue defaultValue; + private Boolean controlHiddenWhenDisabled; private OptionBinding binding; + private Consumer applyHook; StatefulOptionBuilderImpl(ResourceLocation id) { super(id); @@ -63,7 +64,7 @@ OptionImpact getImpact() { return getFirstNotNull(this.impact, StatefulOption::getImpact); } - EnumSet getFlags() { + Set getFlags() { return getFirstNotNull(this.flags, StatefulOption::getFlags); } @@ -71,10 +72,18 @@ DependentValue getDefaultValue() { return getFirstNotNull(this.defaultValue, StatefulOption::getDefaultValue); } + Boolean getControlHiddenWhenDisabled() { + return getFirstNotNull(this.controlHiddenWhenDisabled, StatefulOption::getControlHiddenWhenDisabled); + } + OptionBinding getBinding() { return getFirstNotNull(this.binding, StatefulOption::getBinding); } + Consumer getApplyHook() { + return getFirstNotNull(this.applyHook, StatefulOption::getApplyHook); + } + @Override public StatefulOptionBuilder setStorageHandler(StorageEventHandler storage) { Validate.notNull(storage, "Argument must not be null"); @@ -109,10 +118,16 @@ public StatefulOptionBuilder setImpact(OptionImpact impact) { @Override public StatefulOptionBuilder setFlags(OptionFlag... flags) { - if (this.getFlags() == null) { - this.flags = EnumSet.noneOf(OptionFlag.class); + var idFlags = new ResourceLocation[flags.length]; + for (int i = 0; i < flags.length; i++) { + idFlags[i] = flags[i].getId(); } - Collections.addAll(this.getFlags(), flags); + return this.setFlags(idFlags); + } + + @Override + public StatefulOptionBuilder setFlags(ResourceLocation... flags) { + this.flags = Set.of(flags); return this; } @@ -166,4 +181,16 @@ public StatefulOptionBuilder setEnabledProvider(Function setControlHiddenWhenDisabled(boolean hidden) { + this.controlHiddenWhenDisabled = hidden; + return this; + } + + @Override + public StatefulOptionBuilder setApplyHook(Consumer hook) { + this.applyHook = hook; + return this; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/BooleanOption.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/BooleanOption.java index 8458045877..15721e4ae9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/BooleanOption.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/BooleanOption.java @@ -1,8 +1,8 @@ package net.caffeinemc.mods.sodium.client.config.structure; +import net.caffeinemc.mods.sodium.api.config.ConfigState; import net.caffeinemc.mods.sodium.api.config.StorageEventHandler; import net.caffeinemc.mods.sodium.api.config.option.OptionBinding; -import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; import net.caffeinemc.mods.sodium.api.config.option.OptionImpact; import net.caffeinemc.mods.sodium.client.config.value.DependentValue; import net.caffeinemc.mods.sodium.client.gui.options.control.Control; @@ -11,16 +11,35 @@ import net.minecraft.resources.ResourceLocation; import java.util.Collection; -import java.util.EnumSet; +import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; public class BooleanOption extends StatefulOption { - public BooleanOption(ResourceLocation id, Collection dependencies, Component name, DependentValue enabled, StorageEventHandler storage, Function tooltipProvider, OptionImpact impact, EnumSet flags, DependentValue defaultValue, OptionBinding binding) { - super(id, dependencies, name, enabled, storage, tooltipProvider, impact, flags, defaultValue, binding); + public BooleanOption( + ResourceLocation id, + Collection dependencies, + Component name, + DependentValue enabled, + StorageEventHandler storage, + Function tooltipProvider, + OptionImpact impact, + Set flags, + DependentValue defaultValue, + Boolean controlHiddenWhenDisabled, + OptionBinding binding, + Consumer applyHook + ) { + super(id, dependencies, name, enabled, storage, tooltipProvider, impact, flags, defaultValue, controlHiddenWhenDisabled, binding, applyHook); } @Override Control createControl() { return new TickBoxControl(this); } + + @Override + Boolean validateValue(Boolean value) { + return value; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java index 2601a79881..223c182894 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java @@ -1,12 +1,12 @@ package net.caffeinemc.mods.sodium.client.config.structure; -import com.google.common.collect.ImmutableList; import it.unimi.dsi.fastutil.objects.Object2ReferenceLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import net.caffeinemc.mods.sodium.api.config.ConfigState; import net.caffeinemc.mods.sodium.api.config.StorageEventHandler; +import net.caffeinemc.mods.sodium.api.config.option.FlagHook; import net.caffeinemc.mods.sodium.api.config.option.OptionFlag; import net.caffeinemc.mods.sodium.client.config.search.BigramSearchIndex; import net.caffeinemc.mods.sodium.client.config.search.SearchIndex; @@ -17,23 +17,24 @@ import net.minecraft.client.Minecraft; import net.minecraft.resources.ResourceLocation; -import java.util.Collection; -import java.util.EnumSet; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.function.Consumer; public class Config implements ConfigState { private final Map options = new Object2ReferenceLinkedOpenHashMap<>(); - private final ObjectOpenHashSet pendingStorageHandlers = new ObjectOpenHashSet<>(); - private final ImmutableList modOptions; + private final Set pendingStorageHandlers = new ObjectOpenHashSet<>(); + private final List modOptions; private final SearchIndex searchIndex = new BigramSearchIndex(this::registerSearchIndex); private final Collection> globalRebuildDependents = new ObjectArrayList<>(); + private final Map> flagHooks = new Object2ReferenceOpenHashMap<>(); + private final Set triggeredHooks = new ObjectOpenHashSet<>(); - public Config(ImmutableList modOptions) { + public Config(List modOptions) { this.modOptions = modOptions; this.collectOptions(); this.applyOptionChanges(); + this.collectApplyHooks(); this.validateDependencies(); // load options initially from their bindings @@ -53,6 +54,12 @@ public SearchQuerySession startSearchQuery() { return this.searchIndex.startQuery(); } + private void registerHook(FlagHook hook) { + for (var trigger : hook.getTriggers()) { + this.flagHooks.computeIfAbsent(trigger, k -> new ObjectArrayList<>()).add(hook); + } + } + private void collectOptions() { for (var modConfig : this.modOptions) { for (var page : modConfig.pages()) { @@ -67,6 +74,12 @@ private void collectOptions() { } } } + + if (modConfig.flagHooks() != null) { + for (var hook : modConfig.flagHooks()) { + this.registerHook(hook); + } + } } } @@ -128,8 +141,12 @@ private void applyOptionChanges() { // apply overlay to option if it exists if (overlay != null) { var change = overlay.change(); - var overlaidOption = change.buildWithBaseOption(option); - exchangeOption(options, i, overlaidOption, option); + try { + var overlaidOption = change.buildWithBaseOption(option); + exchangeOption(options, i, overlaidOption, option); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to apply overlay from '" + overlay.source() + "' to option '" + option.id + "'", e); + } } } } @@ -145,10 +162,40 @@ private void exchangeOption(List

extends EntryWidget { + final P page; + final int scrollTargetStart; - PageEntryWidget(Dim2i dim, OptionPage page, ColorTheme theme, int scrollTargetStart) { + PageEntryWidget(Dim2i dim, P page, ColorTheme theme, int scrollTargetStart) { super(dim, page.name(), true, theme); this.page = page; this.scrollTargetStart = scrollTargetStart; } + } + + private class OptionPageEntryWidget extends PageEntryWidget { + OptionPageEntryWidget(Dim2i dim, Page page, ColorTheme theme, int scrollTargetStart) { + super(dim, page, theme, scrollTargetStart); + } @Override public int getScrollTargetStart() { @@ -185,12 +192,9 @@ void onAction() { } } - private class ExternalPageEntryWidget extends EntryWidget { - private final ExternalPage page; - - ExternalPageEntryWidget(Dim2i dim, ExternalPage page, ColorTheme theme) { - super(dim, page.name(), true, theme); - this.page = page; + private class ExternalPageEntryWidget extends PageEntryWidget { + ExternalPageEntryWidget(Dim2i dim, ExternalPage page, ColorTheme theme, int scrollTargetStart) { + super(dim, page, theme, scrollTargetStart); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ScrollableTooltip.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ScrollableTooltip.java index 4df458856f..b3a9a45f7e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ScrollableTooltip.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ScrollableTooltip.java @@ -4,13 +4,15 @@ import net.caffeinemc.mods.sodium.client.gui.Colors; import net.caffeinemc.mods.sodium.client.gui.GuiTint; import net.caffeinemc.mods.sodium.client.gui.Layout; -import net.caffeinemc.mods.sodium.client.gui.VideoSettingsScreen; import net.caffeinemc.mods.sodium.client.gui.options.control.ControlElement; import net.caffeinemc.mods.sodium.client.util.Dim2i; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Renderable; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.FormattedCharSequence; @@ -27,13 +29,8 @@ public class ScrollableTooltip { private static final int SPRITE_WIDTH = 10; private static final int ARROW_HEIGHT = 9; - private static final int MIN_TOOLTIP_WIDTH = 100; - private static final int MAX_TOOLTIP_WIDTH = 200; private static final int TEXT_HORIZONTAL_PADDING = Layout.INNER_MARGIN - 1; private static final int TEXT_VERTICAL_PADDING = TEXT_HORIZONTAL_PADDING; - private static final int LEFT_BOX_MARGIN = ARROW_WIDTH; // arrow includes one pixel of margin - private static final int OUTER_BOX_MARGIN = 3; - private static final int UPPER_BOX_MARGIN = Layout.BUTTON_SHORT + OUTER_BOX_MARGIN; private final Font font = Minecraft.getInstance().font; private ControlElement hoveredElement; @@ -42,13 +39,23 @@ public class ScrollableTooltip { private Dim2i visibleDim; private boolean overlayMode; private final List content = new ArrayList<>(); - private final VideoSettingsScreen parent; + private final TooltipParent parent; + private Dim2i tooltipArea; // area for the tooltip to be within private final Vector2i reservedArea = new Vector2i(); // area reserved for action buttons - public ScrollableTooltip(VideoSettingsScreen parent) { + public ScrollableTooltip(TooltipParent parent) { this.parent = parent; } + public interface TooltipParent { + T addRenderableWidget(T guiEventListener); + void removeWidget(GuiEventListener guiEventListener); + } + + public void setTooltipArea(Dim2i area) { + this.tooltipArea = area; + } + public void onControlHover(ControlElement hovered, int mouseX, int mouseY) { if (hovered != null) { this.hoveredElement = hovered; @@ -112,17 +119,15 @@ private int generateTooltipContent(int boxWidth, boolean needsScrolling) { } private boolean positionTooltip(boolean needsScrolling) { - int elementLimitX = this.hoveredElement.getLimitX() + LEFT_BOX_MARGIN + Layout.SCROLLBAR_WIDTH; - int defaultBoxWidth = Math.min(this.parent.width - elementLimitX - OUTER_BOX_MARGIN, MAX_TOOLTIP_WIDTH); + int defaultBoxWidth = Math.min(this.tooltipArea.getLimitX() - this.tooltipArea.x(), Layout.MAX_TOOLTIP_WIDTH); int defaultBoxY = this.hoveredElement.getY(); - // noinspection UnnecessaryLocalVariable - int defaultBoxX = elementLimitX; + int defaultBoxX = this.tooltipArea.x(); int boxWidth = 0, boxX = 0, boxY = 0; boolean fixedBoxY = false; - int boxYCutoff = this.parent.height - OUTER_BOX_MARGIN; + int boxYCutoff = this.tooltipArea.getLimitY(); - this.overlayMode = defaultBoxWidth < MIN_TOOLTIP_WIDTH; + this.overlayMode = defaultBoxWidth < Layout.MIN_TOOLTIP_WIDTH; if (!this.overlayMode) { // Hovered element above the area so tooltip has full width, needs to be up high enough to not intersect with the area @@ -136,11 +141,11 @@ private boolean positionTooltip(boolean needsScrolling) { } // Hovered element left of the area but enough space to show tooltip with reduced width - else if (this.hoveredElement.getLimitX() < this.reservedArea.x) { - int availableWidth = this.reservedArea.x - elementLimitX; + else if (this.tooltipArea.x() < this.reservedArea.x) { + int availableWidth = this.reservedArea.x - this.tooltipArea.x(); - if (availableWidth >= MIN_TOOLTIP_WIDTH) { - boxWidth = Math.min(availableWidth, MAX_TOOLTIP_WIDTH); + if (availableWidth >= Layout.MIN_TOOLTIP_WIDTH) { + boxWidth = Math.min(availableWidth, Layout.MAX_TOOLTIP_WIDTH); boxX = defaultBoxX; boxY = defaultBoxY; } else { @@ -158,24 +163,23 @@ else if (this.hoveredElement.getLimitX() < this.reservedArea.x) { if (this.overlayMode) { // in overlay mode the tooltip is shown on top of the options list, either above or below - boxWidth = this.hoveredElement.getWidth() - 2 * OUTER_BOX_MARGIN; - boxX = this.hoveredElement.getX() + OUTER_BOX_MARGIN; + boxWidth = this.hoveredElement.getWidth() - 2 * Layout.TOOLTIP_OUTER_MARGIN; + boxX = this.hoveredElement.getX() + Layout.TOOLTIP_OUTER_MARGIN; // place the content above or below the hovered element depending on which side has more space - int spaceAbove = this.hoveredElement.getY() - UPPER_BOX_MARGIN; - int spaceBelow = this.parent.height - this.hoveredElement.getLimitY() - OUTER_BOX_MARGIN; + int spaceAbove = this.hoveredElement.getY() - this.tooltipArea.y(); + int spaceBelow = this.tooltipArea.getLimitY() - this.hoveredElement.getLimitY() - Layout.TOOLTIP_OUTER_MARGIN; if (spaceBelow >= spaceAbove) { - boxY = this.hoveredElement.getLimitY() + OUTER_BOX_MARGIN; - boxYCutoff = this.parent.height - OUTER_BOX_MARGIN; + boxY = this.hoveredElement.getLimitY() + Layout.TOOLTIP_OUTER_MARGIN; + boxYCutoff = this.tooltipArea.getLimitY() - Layout.TOOLTIP_OUTER_MARGIN; // fix the box's upper y position since moving it up would cause it to overlap the hovered element fixedBoxY = true; } else { - boxY = this.hoveredElement.getY() - OUTER_BOX_MARGIN; - boxYCutoff = this.hoveredElement.getY() - OUTER_BOX_MARGIN; + boxY = this.hoveredElement.getY() - Layout.TOOLTIP_OUTER_MARGIN; + boxYCutoff = this.hoveredElement.getY() - Layout.TOOLTIP_OUTER_MARGIN; // it automatically gets moved up as far as necessary later } - } int contentHeight = this.generateTooltipContent(boxWidth, needsScrolling); @@ -188,8 +192,8 @@ else if (this.hoveredElement.getLimitX() < this.reservedArea.x) { } // prevent it from moving up further than the tooltip safe area - if (boxY < UPPER_BOX_MARGIN) { - boxY = UPPER_BOX_MARGIN; + if (boxY < this.tooltipArea.y()) { + boxY = this.tooltipArea.y(); } } @@ -223,11 +227,14 @@ private void renderInternal(@NotNull GuiGraphics graphics) { int arrowX = this.visibleDim.x() - ARROW_WIDTH; int arrowY = this.hoveredElement.getCenterY() - (ARROW_HEIGHT / 2); - // blit(ResourceLocation, x, y, uOffset, vOffset, width, height, textureWidth, textureHeight) + // constrain the arrow to be within the tooltip area + arrowY = Math.max(arrowY, this.tooltipArea.y()); + int arrowYConstrained = Math.min(arrowY + ARROW_HEIGHT, this.tooltipArea.getLimitY()) - ARROW_HEIGHT; + GuiTint.withTint(Colors.BACKGROUND_LIGHT, () -> - graphics.blit(ARROW_TEXTURE, arrowX, arrowY, ARROW_WIDTH, 0, ARROW_WIDTH, ARROW_HEIGHT, SPRITE_WIDTH, ARROW_HEIGHT)); + graphics.blit(ARROW_TEXTURE, arrowX, arrowYConstrained, ARROW_WIDTH, 0, ARROW_WIDTH, ARROW_HEIGHT, SPRITE_WIDTH, ARROW_HEIGHT)); GuiTint.withTint(Colors.BACKGROUND_DEFAULT, () -> - graphics.blit(ARROW_TEXTURE, arrowX, arrowY, 0, 0, ARROW_WIDTH, ARROW_HEIGHT, SPRITE_WIDTH, ARROW_HEIGHT)); + graphics.blit(ARROW_TEXTURE, arrowX, arrowYConstrained, 0, 0, ARROW_WIDTH, ARROW_HEIGHT, SPRITE_WIDTH, ARROW_HEIGHT)); } int lineHeight = this.getLineHeight(); @@ -264,6 +271,6 @@ public boolean mouseScrolled(double d, double e, double amount) { * @param y The y coordinate of the top left corner of the reserved area */ public void setReservedAreaTopLeftCorner(int x, int y) { - this.reservedArea.set(x - OUTER_BOX_MARGIN, y - OUTER_BOX_MARGIN); + this.reservedArea.set(x - Layout.TOOLTIP_OUTER_MARGIN, y - Layout.TOOLTIP_OUTER_MARGIN); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/SearchWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/SearchWidget.java index 083dff07eb..bbf81c3f77 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/SearchWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/SearchWidget.java @@ -6,9 +6,11 @@ import net.caffeinemc.mods.sodium.client.gui.Colors; import net.caffeinemc.mods.sodium.client.gui.Layout; import net.caffeinemc.mods.sodium.client.util.Dim2i; +import net.minecraft.ChatFormatting; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.EditBox; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.Style; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -66,7 +68,9 @@ private void rebuildForWidth(int width) { this.searchBox.setMaxLength(200); this.searchBox.setBordered(false); this.searchBox.setResponder(this::triggerSearch); - this.searchBox.setHint(Component.translatable("sodium.options.search.hint")); + this.searchBox.setHint( + Component.translatable("sodium.options.search.hint") + .withStyle(Style.EMPTY.withColor(ChatFormatting.GRAY))); this.addChild(this.searchBox); this.addChild(this.clearButton); From 982400b09581534b4a0c73012ca0713a3c9bb7dd Mon Sep 17 00:00:00 2001 From: mjwells2002 <36360408+mjwells2002@users.noreply.github.com> Date: Wed, 17 Dec 2025 17:46:40 +0100 Subject: [PATCH 136/215] Add AMD Workarounds (#3391) --- .../mixin/workarounds/context_creation/WindowMixin.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java index f15795c221..4fd95b2f7b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/workarounds/context_creation/WindowMixin.java @@ -8,8 +8,9 @@ import com.mojang.blaze3d.platform.WindowEventHandler; import net.caffeinemc.mods.sodium.client.compatibility.checks.ModuleScanner; import net.caffeinemc.mods.sodium.client.compatibility.checks.PostLaunchChecks; -import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; import net.caffeinemc.mods.sodium.client.compatibility.environment.GlContextInfo; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.amd.AmdWorkarounds; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; import net.caffeinemc.mods.sodium.client.platform.NativeWindowHandle; import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; import net.minecraft.Util; @@ -43,11 +44,13 @@ public class WindowMixin { @Redirect(method = "", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwCreateWindow(IILjava/lang/CharSequence;JJ)J"), expect = 0, require = 0) private long wrapGlfwCreateWindow(int width, int height, CharSequence title, long monitor, long share) { NvidiaWorkarounds.applyEnvironmentChanges(); + AmdWorkarounds.applyEnvironmentChanges(); try { return GLFW.glfwCreateWindow(width, height, title, monitor, share); } finally { NvidiaWorkarounds.undoEnvironmentChanges(); + AmdWorkarounds.undoEnvironmentChanges(); } } @@ -59,6 +62,7 @@ private long wrapGlfwCreateWindowForge(final IntSupplier width, final IntSupplie if (applyWorkaroundsLate) { NvidiaWorkarounds.applyEnvironmentChanges(); + AmdWorkarounds.applyEnvironmentChanges(); } try { @@ -66,6 +70,7 @@ private long wrapGlfwCreateWindowForge(final IntSupplier width, final IntSupplie } finally { if (applyWorkaroundsLate) { NvidiaWorkarounds.undoEnvironmentChanges(); + AmdWorkarounds.undoEnvironmentChanges(); } } } From bce2de6a08642bf3260f7b71a793fbd17acd4328 Mon Sep 17 00:00:00 2001 From: IMS212 Date: Wed, 17 Dec 2025 17:49:25 -0800 Subject: [PATCH 137/215] Remove check on option namespace matching mod namespace --- .../mods/sodium/client/config/structure/Config.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java index 223c182894..c64babae1d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java @@ -65,10 +65,6 @@ private void collectOptions() { for (var page : modConfig.pages()) { for (var group : page.groups()) { for (var option : group.options()) { - if (!option.id.getNamespace().equals(modConfig.configId())) { - throw new IllegalArgumentException("Namespace of option id '" + option.id + "' does not match the configId '" + modConfig.configId() + "' of the enclosing mod config"); - } - this.options.put(option.id, option); option.setParentConfig(this); } From 03dea8747a8b868abe3c3ad056132cc64ef3e102 Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 18 Dec 2025 03:56:18 +0100 Subject: [PATCH 138/215] Fix setRangeProvider Type Breakage (#3409) * Fix breakage unintentionally caused by changing the return type of the function passed into setRangeProvider * make list of mod options in Config unmodifiable --- .../api/config/structure/IntegerOptionBuilder.java | 4 ++-- .../config/builder/IntegerOptionBuilderImpl.java | 14 +++++++++----- .../sodium/client/config/structure/Config.java | 2 +- .../client/config/structure/IntegerOption.java | 6 +++--- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java index 51d900524a..85965a8508 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/IntegerOptionBuilder.java @@ -84,7 +84,7 @@ public interface IntegerOptionBuilder extends StatefulOptionBuilder { * @param dependencies The options that this provider depends on. * @return The current builder instance. */ - IntegerOptionBuilder setRangeProvider(Function provider, ResourceLocation... dependencies); + IntegerOptionBuilder setRangeProvider(Function provider, ResourceLocation... dependencies); /** * Sets a validator for this integer option. A {@link Range} is a type of stepped validator. @@ -101,7 +101,7 @@ public interface IntegerOptionBuilder extends StatefulOptionBuilder { * @param dependencies The options that this provider depends on. * @return The current builder instance. */ - IntegerOptionBuilder setValidatorProvider(Function provider, ResourceLocation... dependencies); + IntegerOptionBuilder setValidatorProvider(Function provider, ResourceLocation... dependencies); /** * Sets the value formatter for this integer option. diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java index cb3e66b5a1..bad96cbf60 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java @@ -18,7 +18,7 @@ import java.util.function.Supplier; class IntegerOptionBuilderImpl extends StatefulOptionBuilderImpl implements IntegerOptionBuilder { - private DependentValue validatorProvider; + private DependentValue validatorProvider; private ControlValueFormatter valueFormatter; IntegerOptionBuilderImpl(ResourceLocation id) { @@ -46,7 +46,11 @@ IntegerOption build() { this.getTooltipProvider(), this.getImpact(), this.getFlags(), - this.getDefaultValue(), this.getControlHiddenWhenDisabled(), this.getBinding(), this.getApplyHook(), this.getValidatorProvider(), + this.getDefaultValue(), + this.getControlHiddenWhenDisabled(), + this.getBinding(), + this.getApplyHook(), + this.getValidatorProvider(), this.getValueFormatter()); } @@ -62,7 +66,7 @@ Class getOptionClass() { return IntegerOption.class; } - DependentValue getValidatorProvider() { + DependentValue getValidatorProvider() { return getFirstNotNull(this.validatorProvider, IntegerOption::getValidatorProvider); } @@ -172,7 +176,7 @@ public IntegerOptionBuilder setRange(Range range) { } @Override - public IntegerOptionBuilder setRangeProvider(Function provider, ResourceLocation... dependencies) { + public IntegerOptionBuilder setRangeProvider(Function provider, ResourceLocation... dependencies) { this.validatorProvider = new DynamicValue<>(provider, dependencies); return this; } @@ -184,7 +188,7 @@ public IntegerOptionBuilder setValidator(SteppedValidator validator) { } @Override - public IntegerOptionBuilder setValidatorProvider(Function provider, ResourceLocation... dependencies) { + public IntegerOptionBuilder setValidatorProvider(Function provider, ResourceLocation... dependencies) { this.validatorProvider = new DynamicValue<>(provider, dependencies); return this; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java index c64babae1d..601ea8c814 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java @@ -30,7 +30,7 @@ public class Config implements ConfigState { private final Set triggeredHooks = new ObjectOpenHashSet<>(); public Config(List modOptions) { - this.modOptions = modOptions; + this.modOptions = Collections.unmodifiableList(modOptions); this.collectOptions(); this.applyOptionChanges(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/IntegerOption.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/IntegerOption.java index b3f40fd550..782a586da8 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/IntegerOption.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/IntegerOption.java @@ -18,7 +18,7 @@ import java.util.function.Function; public class IntegerOption extends StatefulOption { - private final DependentValue validator; + private final DependentValue validator; private final ControlValueFormatter valueFormatter; public IntegerOption( @@ -34,7 +34,7 @@ public IntegerOption( Boolean controlHiddenWhenDisabled, OptionBinding binding, Consumer applyHook, - DependentValue validator, + DependentValue validator, ControlValueFormatter valueFormatter ) { super(id, dependencies, name, enabled, storage, tooltipProvider, impact, flags, defaultValue, controlHiddenWhenDisabled, binding, applyHook); @@ -70,7 +70,7 @@ public Component formatValue(int value) { return this.valueFormatter.format(value); } - public DependentValue getValidatorProvider() { + public DependentValue getValidatorProvider() { return this.validator; } From f92d9f4f7f9b7ad615ef2e5a432540cd4b85a594 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 21 Dec 2025 02:40:33 +0100 Subject: [PATCH 139/215] Inset config ui later based on user feedback (#3421) --- .../java/net/caffeinemc/mods/sodium/client/gui/Layout.java | 2 +- .../mods/sodium/client/gui/VideoSettingsScreen.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/Layout.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/Layout.java index 2892d52847..4a56ac7f6d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/Layout.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/Layout.java @@ -30,7 +30,7 @@ public class Layout { public static final int MAX_TOOLTIP_WIDTH = 200; public static final int TOOLTIP_OUTER_MARGIN = 3; - public static final int CONTENT_BORDER_WIDTH = 25; + public static final int CONTENT_BORDER_MIN_WIDTH = 100; public static final int CONTENT_BORDER_HEIGHT = OPTION_MOD_MARGIN; public static final int CONTENT_MIN_HEIGHT = 300; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java index 5b4bea5c7a..aba2a79b84 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java @@ -221,12 +221,12 @@ private void updateScreenDimensions() { var minContentWidth = baseContentWidth + (Layout.MAX_TOOLTIP_WIDTH - Layout.MIN_TOOLTIP_WIDTH) / 2 + Layout.MIN_TOOLTIP_WIDTH; var maxContentWidth = baseContentWidth + Layout.MAX_TOOLTIP_WIDTH; var maxInterpolatingBorderWidth = 100; - var widthInterpolationStart = minContentWidth + Layout.CONTENT_BORDER_WIDTH; + var widthInterpolationStart = minContentWidth + Layout.CONTENT_BORDER_MIN_WIDTH; var widthInterpolationEnd = maxContentWidth + maxInterpolatingBorderWidth; int contentWidth = this.width; this.insetX = false; - if (this.width > minContentWidth + Layout.CONTENT_BORDER_WIDTH) { + if (this.width > minContentWidth + Layout.CONTENT_BORDER_MIN_WIDTH) { // interpolate between min and max content width based on current width if (this.width < widthInterpolationEnd) { float t = (float) (this.width - widthInterpolationStart) / (widthInterpolationEnd - widthInterpolationStart); From 51847fc16c30e802f8ecb26d95ad4a87eeb6aabb Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 21 Dec 2025 02:40:44 +0100 Subject: [PATCH 140/215] Fix duplicate click sounds (#3414) --- .../mods/sodium/client/gui/options/control/CyclingControl.java | 2 -- .../mods/sodium/client/gui/options/control/TickBoxControl.java | 1 - 2 files changed, 3 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java index cdf29eecdc..13dd00e227 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java @@ -94,8 +94,6 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { private void cycleControl(boolean reverse) { this.playClickSound(); - this.playClickSound(); - var currentValue = this.option.getValidatedValue(); int startIndex = 0; for (; startIndex < this.baseValues.length; startIndex++) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java index 51aa4fc1a0..c8113d18d1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java @@ -116,7 +116,6 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { private void toggleControl() { this.playClickSound(); - this.playClickSound(); this.option.modifyValue(!this.option.getValidatedValue()); } From bc97b628240733d751753bf74a81dac96867e604 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 21 Dec 2025 02:40:56 +0100 Subject: [PATCH 141/215] Fix crashes on non-latin locales caused by missing use of Locale.ROOT in toLowerCase() (#3412) --- .../mods/sodium/api/config/option/OptionFlag.java | 4 +++- .../sodium/client/config/search/BigramSearchIndex.java | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionFlag.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionFlag.java index d7558cc545..f999c28a7f 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionFlag.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/OptionFlag.java @@ -2,6 +2,8 @@ import net.minecraft.resources.ResourceLocation; +import java.util.Locale; + /** * Flags that indicate specific actions required when an option is changed. */ @@ -31,7 +33,7 @@ public enum OptionFlag { */ REQUIRES_GAME_RESTART; - private final ResourceLocation id = ResourceLocation.fromNamespaceAndPath("sodium", "builtin_option_flag." + this.name().toLowerCase()); + private final ResourceLocation id = ResourceLocation.fromNamespaceAndPath("sodium", "builtin_option_flag." + this.name().toLowerCase(Locale.ROOT)); /** * Gets the {@link ResourceLocation} for this option flag. diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/BigramSearchIndex.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/BigramSearchIndex.java index df2f36142c..9c3633025a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/BigramSearchIndex.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/BigramSearchIndex.java @@ -5,6 +5,7 @@ import java.util.Comparator; import java.util.List; +import java.util.Locale; import java.util.regex.Pattern; public class BigramSearchIndex extends SourceStoringIndex { @@ -54,7 +55,7 @@ public void rebuildIndex() { } private static String conditionText(String text) { - text = text.toLowerCase(); + text = text.toLowerCase(Locale.ROOT); text = NON_WORD.matcher(text).replaceAll(" "); return text; } @@ -134,9 +135,9 @@ public List getSearchResults(String query) { var score = queryBigramDensity * ((float) Math.log(sourceCount) + 1) * prevalenceInv; // if the query matches exactly the start or part of the source, it is probably significantly more important. - if (source.getText().toLowerCase().startsWith(query.trim())) { + if (source.getText().toLowerCase(Locale.ROOT).startsWith(query.trim())) { score *= 3.0f; - } else if (source.getText().toLowerCase().contains(query.trim())) { + } else if (source.getText().toLowerCase(Locale.ROOT).contains(query.trim())) { score *= 2.0f; } From fa0f899261a2c4c63ea1bdcc9fbede5df00fd435 Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 22 Dec 2025 21:15:47 +0100 Subject: [PATCH 142/215] Re-add support for non-tinted icons and add basic theme validation (#3422) * Re-add support for monochrome icons and add basic theme validation * improve wording --- .../mods/sodium/api/config/USAGE.md | 10 +++++-- .../config/structure/ModOptionsBuilder.java | 27 ++++++++++++++++--- .../mods/sodium/api/util/ColorARGB.java | 4 +++ .../config/builder/ColorThemeBuilderImpl.java | 12 +++++++-- .../config/builder/ModOptionsBuilderImpl.java | 10 ++++++- .../client/config/structure/ModOptions.java | 2 +- .../mods/sodium/client/gui/Colors.java | 8 +++++- .../mods/sodium/client/gui/GuiTint.java | 11 +++++--- .../client/gui/VideoSettingsScreen.java | 8 ++++-- .../client/gui/widgets/OptionListWidget.java | 10 ++++--- .../client/gui/widgets/PageListWidget.java | 4 ++- 11 files changed, 86 insertions(+), 20 deletions(-) diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md index e8585a6e68..42e71fc7b1 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/USAGE.md @@ -199,7 +199,13 @@ Each mod adds a page for its options, within each page there are groups of optio Some attributes of an option can be provided dynamically, meaning the returned value can depend on the state of another option. The default value, option enablement, and the allowed values can be computed dynamically. The methods for setting a dynamic value provider also require you to specify a list of dependencies, which are the resource locations of the options that the dynamic value provider reads from. Since dynamically evaluated attributes may change the state of an option, cyclic dependencies will lead to option registration failing and the game crashing. -Sodium constructs one instance of the entrypoint class, and then calls the early and late registration methods at the right time. +Sodium constructs one instance of the entrypoint class, and then calls the early and late registration methods at the right time. + +### Icon Design + +The UI is designed with monochrome, binary-alpha icons in mind. These icons are tinted the color of each mod's theme. We recommend that API users avoid displaying an icon that isn't appropriately styled. However, users can choose to deviate from this convention by presenting a regular, full-color icon using the means provided in the API. + +It is not necessary to explicitly set a theme color, as the system will automatically select one from a predefined set. However, if you set a custom theme color, it should have a minimum level of saturation to remain true to its purpose as an accent color. For aesthetic and accessibility reasons, it is also not reasonable to set the brightness/value of the theme color to be too dark. The theme base color should also not be too bright, otherwise the highlight color, which is brighter, will have no contrast against it. ## API Notes @@ -209,7 +215,7 @@ The API is largely self-explanatory and an example is provided above. Also see S The `ConfigBuilder` instance passed to the registration method allows quick and easy registration of a mod's own options using `ConfigBuilder.registerOwnModOptions`. The mod's id, name, version or a formatter for the existing version, and the color theme can be configured on the returned `ModOptionsBuilder`. It's also possible to register options for additional mods using `ConfigBuilder.registerModOptions`. Which mod is the "own" mod for `registerOwnModOptions` is determined by the mod that owns the metadata-based entrypoint or the mod id passed to the `@ConfigEntryPointForge("examplemod")` annotation. -Each registered mod gets its own header in the page list. The color of the header and the corresponding entries is randomly selected from a predefined list by default, but can be customized using `ModOptionsBuilder.setColorTheme`. A color theme is created either by specifying three (A)RGB colors or a single base color with the lighter and darker colors getting derived automatically. A mod can also specify an icon with `ModOptionsBuilder.setIcon`, which takes a `ResourceLocation` pointing to a texture, which will be tinted in the theme color and rendered in its entirety as a square. +Each registered mod gets its own header in the page list. The color of the header and the corresponding entries is randomly selected from a predefined list by default, but can be customized using `ModOptionsBuilder.setColorTheme`. A color theme is created either by specifying three (A)RGB colors or a single base color with the lighter and darker colors getting derived automatically. A mod can also specify an icon with `ModOptionsBuilder.setIcon`, which takes a `ResourceLocation` pointing to a texture, which will be tinted in the theme color and rendered in its entirety as a square. Icons set with `ModOptionsBuilder.setNonTintedIcon` are not tinted. To simply switch to a new `Screen` when an entry in the video settings screen's page list is clicked, use `ConfigBuilder.createExternalPage` and add the returned page normally after configuring it with a name and a `Consumer` that receives the current screen and switches to your custom screen. diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java index ca35819576..d4e8d6e76c 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ModOptionsBuilder.java @@ -39,6 +39,8 @@ public interface ModOptionsBuilder { /** * Sets the color theme for the mod options UI. A color theme is optional and a theme will be chosen at random (deterministically) from a predetermined set of reasonable colors if none is provided. + *

+ * Basic validation is applied to the color theme to ensure it's not grayscale and not too dark. * * @param colorTheme The color theme builder. * @return The current builder instance. @@ -46,13 +48,32 @@ public interface ModOptionsBuilder { ModOptionsBuilder setColorTheme(ColorThemeBuilder colorTheme); /** - * Sets the icon texture for the mod. The icon should be centered within the square texture and the background should be transparent. The icon will be rendered monochrome tinted in the mod's theme color. No icon will be shown if none is provided and the layout adjusted accordingly. + * Sets the icon texture for the mod. See the documentation for more information about appropriate icon design. The summary is as follows. An icon should: * - * @param texture The resource location of the icon texture. + *

    + *
  • Be a square texture (e.g. 64x64, 128x128, etc).
  • + *
  • Have a transparent background unless the main content of the icon fills the entire square.
  • + *
  • Have binary alpha (fully opaque or fully transparent).
  • + *
  • Have limited detail since it will be rendered at a small size.
  • + *
+ *

+ * Icons set with this method are tinted monochrome in the mod's theme color. This means the texture itself should be fully white to result in the theme when tinted. + *

+ * No icon will be shown if none is provided and the layout adjusted accordingly. + * + * @param texture The ID of the icon texture. * @return The current builder instance. */ ModOptionsBuilder setIcon(ResourceLocation texture); + /** + * Sets the icon texture for the mod. Same as {@link #setIcon(ResourceLocation)}, but the texture will be rendered in its original color instead of being tinted. + * + * @param texture The ID of the icon texture. + * @return The current builder instance. + */ + ModOptionsBuilder setNonTintedIcon(ResourceLocation texture); + /** * Adds a configuration page to the mod options. * @@ -86,7 +107,7 @@ public interface ModOptionsBuilder { /** * Registers a hook that will be called after an option which has any of the specified flags changed. This can be used to implement custom behavior in response to option changes. To hook on built-in flags, use the identifiers given by {@link OptionFlag#getId()}. The hook is given an array of all flags that triggered the hook. Note that the hook may be called with a set of flags larger than the set of flags it is interested in for performance reasons, since this lets us avoid generating a different flag set for every hook. * - * @param hook The hook to run, with the set of all triggered flags. + * @param hook The hook to run, with the set of all triggered flags. * @param triggers The flags to listen for. * @return The current builder instance. */ diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorARGB.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorARGB.java index 3a98d1793a..8786262a31 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorARGB.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorARGB.java @@ -227,4 +227,8 @@ public static int fromHSV(float hue, float saturation, float value) { default -> 0; }; } + + public static int fromHSV(float[] hsv) { + return fromHSV(hsv[0], hsv[1], hsv[2]); + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ColorThemeBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ColorThemeBuilderImpl.java index 07a610df5f..e0bedd91fe 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ColorThemeBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ColorThemeBuilderImpl.java @@ -2,8 +2,12 @@ import net.caffeinemc.mods.sodium.api.config.structure.ColorThemeBuilder; import net.caffeinemc.mods.sodium.client.gui.ColorTheme; +import net.caffeinemc.mods.sodium.client.gui.Colors; public class ColorThemeBuilderImpl implements ColorThemeBuilder { + private static final float MIN_THEME_SATURATION = 0.2f; + private static final float MIN_THEME_BRIGHTNESS = 0.55f; + private int baseTheme; private int themeHighlight; private int themeDisabled; @@ -13,9 +17,13 @@ ColorTheme build() { throw new IllegalStateException("Base theme must be set"); } + this.baseTheme = Colors.constrainColorHSV(this.baseTheme, MIN_THEME_SATURATION, MIN_THEME_BRIGHTNESS); + if (this.themeHighlight == 0 || this.themeDisabled == 0) { return new ColorTheme(this.baseTheme); } else { + this.themeHighlight = Colors.constrainColorHSV(this.themeHighlight, MIN_THEME_SATURATION, MIN_THEME_BRIGHTNESS); + this.themeDisabled = Colors.constrainColorHSV(this.themeDisabled, MIN_THEME_SATURATION, 0); return new ColorTheme(this.baseTheme, this.themeHighlight, this.themeDisabled); } } @@ -28,8 +36,8 @@ public ColorThemeBuilder setBaseThemeRGB(int theme) { @Override public ColorThemeBuilder setFullThemeRGB(int theme, int themeHighlight, int themeDisabled) { - this.baseTheme = theme | 0xFF000000; - this.themeHighlight = themeHighlight | 0xFF000000; + this.baseTheme = theme | 0xFF000000; + this.themeHighlight = themeHighlight | 0xFF000000; this.themeDisabled = themeDisabled | 0xFF000000; return this; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java index 1187fccf3a..c5a35cff2c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ModOptionsBuilderImpl.java @@ -25,6 +25,7 @@ class ModOptionsBuilderImpl implements ModOptionsBuilder { private String version; private ColorTheme theme; private ResourceLocation icon; + private boolean iconMonochrome = true; private final List pages = new ArrayList<>(); private List optionOverrides; private List optionOverlays; @@ -52,7 +53,7 @@ ModOptions build() { this.theme = ColorTheme.PRESETS[Math.abs(this.configId.hashCode()) % ColorTheme.PRESETS.length]; } - return new ModOptions(this.configId, this.name, this.version, this.theme, this.icon, ImmutableList.copyOf(this.pages), overrides, overlays, this.flagHooks); + return new ModOptions(this.configId, this.name, this.version, this.theme, this.icon, this.iconMonochrome, ImmutableList.copyOf(this.pages), overrides, overlays, this.flagHooks); } @Override @@ -85,6 +86,13 @@ public ModOptionsBuilder setIcon(ResourceLocation texture) { return this; } + @Override + public ModOptionsBuilder setNonTintedIcon(ResourceLocation texture) { + this.icon = texture; + this.iconMonochrome = false; + return this; + } + @Override public ModOptionsBuilder addPage(PageBuilder builder) { this.pages.add(((PageBuilderImpl) builder).build()); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java index 9781f45a23..9632400447 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/ModOptions.java @@ -10,7 +10,7 @@ import java.util.Collection; import java.util.List; -public record ModOptions(String configId, String name, String version, ColorTheme theme, ResourceLocation icon, ImmutableList pages, List overrides, List overlays, Collection flagHooks) implements Searchable { +public record ModOptions(String configId, String name, String version, ColorTheme theme, ResourceLocation icon, boolean iconMonochrome, ImmutableList pages, List overrides, List overlays, Collection flagHooks) implements Searchable { @Override public void registerTextSources(SearchIndex index) { for (Page page : this.pages) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/Colors.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/Colors.java index ec97f62a05..4485409b71 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/Colors.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/Colors.java @@ -10,7 +10,6 @@ public class Colors { public static final int THEME_DARKER = 0xFF7A9E9E; public static final int FOREGROUND = 0xFFFFFFFF; public static final int FOREGROUND_DISABLED = 0xFFAAAAAA; - public static final int FOREGROUND_INVERTED = 0xFF000000; public static final int BACKGROUND_LIGHT = 0x40000000; public static final int BACKGROUND_MEDIUM = 0x60000000; @@ -39,4 +38,11 @@ public static int adjust(int color, float factor) { var b = Mth.clamp(hsv[2] * (1 + factor), 0, 1); return ColorARGB.transferAlpha(ColorARGB.fromHSV(hsv[0], s, b), color); } + + public static int constrainColorHSV(int color, float minSaturation, float minBrightness) { + float[] hsv = ColorARGB.toHSV(color); + hsv[1] = Math.max(hsv[1], minSaturation); + hsv[2] = Math.max(hsv[2], minBrightness); + return ColorARGB.fromHSV(hsv); + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/GuiTint.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/GuiTint.java index b45956b930..604d10c238 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/GuiTint.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/GuiTint.java @@ -4,13 +4,14 @@ // On 1.21.1 tinting works by setting the shader color and blend mode, drawing, and then resetting. public final class GuiTint { - private GuiTint() {} + private GuiTint() { + } public static void withTint(int argb, Runnable draw) { float a = ((argb >>> 24) & 0xFF) / 255.0f; float r = ((argb >>> 16) & 0xFF) / 255.0f; - float g = ((argb >>> 8) & 0xFF) / 255.0f; - float b = ( argb & 0xFF) / 255.0f; + float g = ((argb >>> 8) & 0xFF) / 255.0f; + float b = (argb & 0xFF) / 255.0f; RenderSystem.enableBlend(); RenderSystem.defaultBlendFunc(); RenderSystem.setShaderColor(r, g, b, a); @@ -21,4 +22,8 @@ public static void withTint(int argb, Runnable draw) { RenderSystem.disableBlend(); } } + + public static void noTint(Runnable draw) { + withTint(0xFFFFFFFF, draw); + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java index aba2a79b84..6e338931c5 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java @@ -488,13 +488,17 @@ public Dim2i getDimensions() { return this.dim; } - public static int renderIconWithSpacing(GuiGraphics graphics, ResourceLocation icon, int color, int x, int y, int height, int margin) { + public static int renderIconWithSpacing(GuiGraphics graphics, ResourceLocation icon, int color, boolean iconMonochrome, int x, int y, int height, int margin) { int iconSize = height - margin * 2; // assume 16x16 icon texture final int blitX = x + margin; final int blitY = y + height / 2 - iconSize / 2; - GuiTint.withTint(color, () -> graphics.blit(icon, blitX, blitY, iconSize, iconSize, 0.0f, 0.0f, 16, 16, 16, 16)); + if (iconMonochrome) { + GuiTint.withTint(color, () -> graphics.blit(icon, blitX, blitY, iconSize, iconSize, 0.0f, 0.0f, 16, 16, 16, 16)); + } else { + GuiTint.noTint(() -> graphics.blit(icon, blitX, blitY, iconSize, iconSize, 0.0f, 0.0f, 16, 16, 16, 16)); + } return margin * 2 + iconSize; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java index b8695437fa..33c4f95297 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java @@ -87,7 +87,7 @@ private int renderFilteredOptions(Screen screen, int x, int y, int width) { // Add mod header if mod has changed if (lastSource == null || lastSource.getModOptions() != modOptions) { listHeight += Layout.OPTION_MOD_MARGIN; - var modHeader = new ModHeaderWidget(this, new Dim2i(x, y + listHeight, width, this.entryHeight), modOptions.name(), theme, modOptions.icon()); + var modHeader = new ModHeaderWidget(this, new Dim2i(x, y + listHeight, width, this.entryHeight), modOptions.name(), theme, modOptions.icon(), modOptions.iconMonochrome()); this.addRenderableChild(modHeader); listHeight += this.entryHeight; } @@ -130,7 +130,7 @@ private int renderAllPages(Screen screen, int x, int y, int width) { // Add mod header listHeight += Layout.OPTION_MOD_MARGIN; var modHeaderStart = listHeight; - var modHeader = new ModHeaderWidget(this, new Dim2i(x, y + listHeight, width, this.entryHeight), modOptions.name(), theme, modOptions.icon()); + var modHeader = new ModHeaderWidget(this, new Dim2i(x, y + listHeight, width, this.entryHeight), modOptions.name(), theme, modOptions.icon(), modOptions.iconMonochrome()); this.addRenderableChild(modHeader); listHeight += this.entryHeight; @@ -271,10 +271,12 @@ public int getY() { private static class ModHeaderWidget extends HeaderWidget { final ResourceLocation icon; + final boolean iconMonochrome; - public ModHeaderWidget(AbstractOptionList list, Dim2i dim, String title, ColorTheme theme, ResourceLocation icon) { + public ModHeaderWidget(AbstractOptionList list, Dim2i dim, String title, ColorTheme theme, ResourceLocation icon, boolean iconMonochrome) { super(list, dim, ChatFormatting.BOLD + title, theme.themeLighter, Colors.BACKGROUND_DARKER); this.icon = icon; + this.iconMonochrome = iconMonochrome; } @Override @@ -286,7 +288,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { int textOffset = Layout.OPTION_TEXT_SIDE_PADDING; int textY = this.getCenterY() + Layout.REGULAR_TEXT_BASELINE_OFFSET; if (this.icon != null) { - textOffset = VideoSettingsScreen.renderIconWithSpacing(graphics, this.icon, this.textColor, this.getX(), this.getY(), this.getHeight(), Layout.ICON_MARGIN); + textOffset = VideoSettingsScreen.renderIconWithSpacing(graphics, this.icon, this.textColor, this.iconMonochrome, this.getX(), this.getY(), this.getHeight(), Layout.ICON_MARGIN); textY = this.getCenterY() + Layout.ICON_TEXT_BASELINE_OFFSET; } this.drawString(graphics, truncateTextToFit(this.title, this.getWidth() - textOffset), this.getX() + textOffset, textY, this.textColor); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java index cb3e59a7bb..2d21ff10b4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java @@ -147,10 +147,12 @@ public int getY() { private class HeaderEntryWidget extends EntryWidget { private final ResourceLocation icon; + private final boolean iconMonochrome; HeaderEntryWidget(Dim2i dim, ModOptions modOptions, ColorTheme theme) { super(dim, Component.literal(modOptions.name()), Component.literal(modOptions.version()), false, theme); this.icon = modOptions.icon(); + this.iconMonochrome = modOptions.iconMonochrome(); } @Override @@ -159,7 +161,7 @@ protected int renderIcon(GuiGraphics graphics, int textColor) { return super.renderIcon(graphics, textColor); } - return VideoSettingsScreen.renderIconWithSpacing(graphics, this.icon, textColor, + return VideoSettingsScreen.renderIconWithSpacing(graphics, this.icon, textColor, this.iconMonochrome, this.getX(), this.getY(), this.getHeight(), Layout.ICON_MARGIN); } } From 526b5749e125ebb62dd3b6493bc7d50e59be9afc Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 11 Jan 2026 22:00:54 +0100 Subject: [PATCH 143/215] Config UI strings improvement (#3456) * Validate search index contents and option names to not be blank * make general page header and chunk fade time translatable * remove duplicated view bobbing option, vanilla doesn't duplicate it on the video settings either anymore --- .../mods/sodium/client/config/builder/OptionBuilderImpl.java | 1 + .../client/config/builder/StatefulOptionBuilderImpl.java | 1 + .../client/config/builder/StaticOptionBuilderImpl.java | 1 + .../mods/sodium/client/config/search/BigramSearchIndex.java | 3 +++ .../mods/sodium/client/config/structure/Option.java | 5 +++++ .../mods/sodium/client/gui/SodiumConfigBuilder.java | 2 +- common/src/main/resources/assets/sodium/lang/en_us.json | 2 ++ 7 files changed, 14 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java index 61007f53dc..72d281f8a3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java @@ -42,6 +42,7 @@ public O buildWithBaseOption(Option baseOption) { void validateData() { Validate.notNull(this.getName(), "Name must be set"); + Validate.notBlank(this.getName().getString(), "Name must not be blank"); } void prepareBuild() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java index 1078ef1047..17c5a3ecd6 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java @@ -95,6 +95,7 @@ public StatefulOptionBuilder setStorageHandler(StorageEventHandler storage) { @Override public StatefulOptionBuilder setTooltip(Component tooltip) { Validate.notNull(tooltip, "Argument must not be null"); + Validate.notBlank(tooltip.getString(), "Tooltip must not be blank"); this.tooltipProvider = v -> tooltip; return this; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StaticOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StaticOptionBuilderImpl.java index 38b1ca058e..4faa28a809 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StaticOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StaticOptionBuilderImpl.java @@ -16,6 +16,7 @@ abstract class StaticOptionBuilderImpl extends OptionBui @Override void validateData() { Validate.notNull(this.getTooltip(), "Tooltip must be set"); + Validate.notBlank(this.getTooltip().getString(), "Tooltip must not be blank"); } Component getTooltip() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/BigramSearchIndex.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/BigramSearchIndex.java index 9c3633025a..50ed0cc6d6 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/BigramSearchIndex.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/BigramSearchIndex.java @@ -34,6 +34,9 @@ public void rebuildIndex() { if (text == null) { continue; } + if (text.isBlank()) { + throw new IllegalStateException("Text source " + source + " returned blank text"); + } text = conditionText(text).trim(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Option.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Option.java index a76f4e459b..99ef4103a7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Option.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Option.java @@ -128,6 +128,11 @@ public Option getOption() { protected String getTextFromSource() { return Option.this.getName().getString(); } + + @Override + public String toString() { + return "OptionNameSource{option id=" + Option.this.id + "}"; + } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java index a986374550..c297720a91 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java @@ -112,7 +112,7 @@ private void buildFullConfig(ConfigBuilder builder) { } private OptionPageBuilder buildGeneralPage(ConfigBuilder builder) { - var generalPage = builder.createOptionPage().setName(Component.literal("General")); + var generalPage = builder.createOptionPage().setName(Component.translatable("sodium.options.pages.general")); generalPage.addOptionGroup(builder.createOptionGroup() .addOption( // TODO: make RD option respect Vanilla's >16 RD only allowed if memory >1GB constraint diff --git a/common/src/main/resources/assets/sodium/lang/en_us.json b/common/src/main/resources/assets/sodium/lang/en_us.json index 9533c6be7b..5544489865 100644 --- a/common/src/main/resources/assets/sodium/lang/en_us.json +++ b/common/src/main/resources/assets/sodium/lang/en_us.json @@ -4,6 +4,7 @@ "sodium.option_impact.high": "High", "sodium.option_impact.extreme": "Extreme", "sodium.option_impact.varies": "Varies", + "sodium.options.pages.general" : "General", "sodium.options.pages.quality": "Quality", "sodium.options.pages.performance": "Performance", "sodium.options.pages.advanced": "Advanced", @@ -28,6 +29,7 @@ "sodium.options.leaves_quality.fancy": "Fancy", "sodium.options.leaves_quality.tooltip": "Controls whether leaves will be rendered as transparent (fancy) or opaque (fast).", "sodium.options.particle_quality.tooltip": "Controls the maximum number of particles which can be present on screen at any one time.", + "sodium.options.chunk_fade_time.value" : "%s Seconds", "sodium.options.smooth_lighting.tooltip": "Enables the smooth lighting and shading of blocks in the world. This can very slightly increase the amount of time it takes to load or update a chunk, but it doesn't affect frame rates.", "sodium.options.biome_blend.value": "%sx%s Blocks", "sodium.options.biome_blend.tooltip": "The area (in blocks) where biome colors are smoothly blended across. Default is 5x5. Higher values will greatly increase the amount of time it takes to load or update chunks, for diminishing improvements in quality.", From 88fec51f9df85e0d5c95e9b51130331b441578d8 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 11 Jan 2026 22:01:12 +0100 Subject: [PATCH 144/215] fix presentation of the donation prompt (#3459) --- .../mods/sodium/client/gui/VideoSettingsScreen.java | 4 ++-- .../mods/sodium/client/gui/prompt/ScreenPrompt.java | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java index 6e338931c5..9be78fa769 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java @@ -297,10 +297,10 @@ public void render(@NotNull GuiGraphics graphics, int mouseX, int mouseY, float super.render(graphics, this.prompt != null ? -1 : mouseX, this.prompt != null ? -1 : mouseY, delta); - this.tooltip.render(graphics); - if (this.prompt != null) { this.prompt.render(graphics, mouseX, mouseY, delta); + } else { + this.tooltip.render(graphics); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/prompt/ScreenPrompt.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/prompt/ScreenPrompt.java index 43cd1a70f5..e8eded94c4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/prompt/ScreenPrompt.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/prompt/ScreenPrompt.java @@ -42,8 +42,8 @@ public ScreenPrompt(ScreenPromptable parent, List text, int width public void init() { var parentDimensions = this.parent.getDimensions(); - int boxX = (parentDimensions.width() / 2) - (this.width / 2); - int boxY = (parentDimensions.height() / 2) - (this.height / 2); + int boxX = parentDimensions.getCenterX() - (this.width / 2); + int boxY = parentDimensions.getCenterY() - (this.height / 2); this.closeButton = new FlatButtonWidget(new Dim2i((boxX + this.width) - 84, (boxY + this.height) - 24, 80, Layout.BUTTON_SHORT), Component.literal("Close"), this::close, true, false, PROMPT_THEME); @@ -57,12 +57,12 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { var parentDimensions = this.parent.getDimensions(); - graphics.fill(0, 0, parentDimensions.width(), parentDimensions.height(), 0x70090909); + graphics.fill(0, 0, graphics.guiWidth(), graphics.guiHeight(), 0x70090909); matrices.translate(0.0f, 0.0f, 50.0f); - int boxX = (parentDimensions.width() / 2) - (this.width / 2); - int boxY = (parentDimensions.height() / 2) - (this.height / 2); + int boxX = parentDimensions.getCenterX() - (this.width / 2); + int boxY = parentDimensions.getCenterY() - (this.height / 2); graphics.fill(boxX, boxY, boxX + this.width, boxY + this.height, 0xFF171717); graphics.renderOutline(boxX, boxY, this.width, this.height, 0xFF121212); From 55c6c245ec1e6247573f56231b8a0283a51d8d7e Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 11 Jan 2026 22:01:36 +0100 Subject: [PATCH 145/215] Reload video mode when preferred video mode changes (#3467) --- .../caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java | 1 + 1 file changed, 1 insertion(+) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java index c297720a91..7026cc7d02 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java @@ -217,6 +217,7 @@ private OptionPageBuilder buildGeneralPage(ConfigBuilder builder) { state.readBooleanOption(ResourceLocation.parse("sodium:general.fullscreen")); }, ResourceLocation.parse("sodium:general.fullscreen")) + .setFlags(OptionFlag.REQUIRES_VIDEOMODE_RELOAD) ) .addOption( builder.createBooleanOption(ResourceLocation.parse("sodium:general.vsync")) From 4341e90094a9802e9ccb01592a792624ca058b37 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 11 Jan 2026 22:02:04 +0100 Subject: [PATCH 146/215] Fix rare crash resulting from splitting of bent quads and fix corruption from too strict coplanarity check (#3439) * Fix rare issue where bent quads are split on an edge, violating overly broad assumptions I made about the possible configurations of the quads. * Fix issue where sometimes quads were splitting their own coplanar water surface "siblings" which were actually also in the split plane, leading to corruption. --- .../bsp_tree/InnerPartitionBSPNode.java | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java index 413ac28b59..b3c3671637 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java @@ -16,6 +16,7 @@ import net.caffeinemc.mods.sodium.client.util.MathUtil; import net.caffeinemc.mods.sodium.client.util.sorting.RadixSort; import net.minecraft.util.Mth; +import org.joml.Vector3f; import org.joml.Vector3fc; import java.util.Arrays; @@ -471,6 +472,10 @@ static void flushBestSplittingGroup(IntArrayList splittingGroup, IntArrayList be splittingGroup.clear(); } + private static boolean floatEquals(float a, float b) { + return Float.floatToIntBits(a) == Float.floatToIntBits(b) || Math.abs(a - b) <= TQuad.VERTEX_EPSILON; + } + static private BSPNode handleUnsortableBySplitting(BSPWorkspace workspace, IntArrayList indexes, int depth, BSPNode oldNode, IntArrayList splittingGroup) { // pick the first quad if there's no prepared splitting group int representativeIndex; @@ -487,6 +492,9 @@ static private BSPNode handleUnsortableBySplitting(BSPWorkspace workspace, IntAr // split all quads by the splitting group's plane var splitPlane = representative.getVeryAccurateNormal(); var splitDistance = representative.getAccurateDotProduct(); + var splitPlaneNeg = splitPlane.negate(new Vector3f()); + var splitDistanceNeg = -splitDistance; + var splitPlaneIsAligned = representativeFacing.isAligned(); IntArrayList inside = new IntArrayList(); IntArrayList outside = new IntArrayList(); @@ -507,11 +515,17 @@ static private BSPNode handleUnsortableBySplitting(BSPWorkspace workspace, IntAr var quadFacing = insideQuad.getFacing(); // eliminate quads that lie in the split plane - if (quadFacing == representativeFacing && insideQuad.getAccurateDotProduct() == splitDistance && - (representativeFacing != ModelQuadFacing.UNASSIGNED || - insideQuad.getVeryAccurateNormal().equals(splitPlane))) { - splittingGroup.add(candidateIndex); - continue; + if (quadFacing == representativeFacing) { + var accurateNormal = insideQuad.getVeryAccurateNormal(); + var accurateDotProduct = insideQuad.getAccurateDotProduct(); + var coplanar = floatEquals(accurateDotProduct, splitDistance) && (splitPlaneIsAligned || + accurateNormal.equals(splitPlane, TQuad.VERTEX_EPSILON)); + var antiCoplanar = coplanar || floatEquals(accurateDotProduct, splitDistanceNeg) && (splitPlaneIsAligned || + accurateNormal.equals(splitPlaneNeg, TQuad.VERTEX_EPSILON)); + if (coplanar || antiCoplanar) { + splittingGroup.add(candidateIndex); + continue; + } } // split the geometry with the plane @@ -646,18 +660,19 @@ else if (insideCount == 1) { splitTriangleCorner(cornerIndex, outsideQuad, insideQuad, splitPlane, splitDistance); } } else { // uniqueVertices == 4, masked == unmasked - // after dealing with the other cases, now two vertices being on the plane implies the quad is split exactly along its diagonal - // insideCount is 2, onPlaneMap is 0b0101 or 0b1010 - if (onPlaneCount == 2) { - // this case can be treated like even splitting if the two on-plane vertices are declared as each part of one of the sides - if (onPlaneMap == 0b0101) { - insideMap |= 0b0001; - } else { - insideMap |= 0b0010; - } + // it's split along the diagonal. + // this case can be treated like even splitting if the two on-plane vertices are declared as each part of one of the sides + if (onPlaneCount == 2 && onPlaneMap == 0b0101) { + insideMap |= 0b0001; + insideCount = 2; + } else if (onPlaneCount == 2 && onPlaneMap == 0b1010) { + insideMap |= 0b0010; insideCount = 2; } + // or it's a bent quad where one edge lies on the split and the opposite edge crosses the split. + // in this case it simply falls through to one of the odd splitting modes + // one vertex being on the plane now implies the quad is split on a vertex and through an edge. // if there is one vertex inside (and two outside), move the on-plane vertex inside to produce an even split case. // in the other case nothing needs to be done since for splitting the 0-bits in the insideMap are treated as outside. From 3b416e263b4c40fc8f3b257345da2741e6c7e3e0 Mon Sep 17 00:00:00 2001 From: IMS Date: Sun, 11 Jan 2026 13:05:14 -0800 Subject: [PATCH 147/215] Apply AMD workarounds to early loading screen --- .../net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java b/neoforge/src/main/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java index eb106c0ad0..577ae48187 100644 --- a/neoforge/src/main/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java +++ b/neoforge/src/main/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java @@ -4,6 +4,7 @@ import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe; import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds; import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.amd.AmdWorkarounds; import net.neoforged.neoforgespi.earlywindow.GraphicsBootstrapper; public class SodiumWorkarounds implements GraphicsBootstrapper { @@ -20,5 +21,6 @@ public void bootstrap(String[] arguments) { // Context creation happens earlier on NeoForge, so we need to apply this now NvidiaWorkarounds.applyEnvironmentChanges(); + AmdWorkarounds.applyEnvironmentChanges(); } } From 250fa58614c0d113cddc9a068c7530ff03d22fd7 Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 15 Jan 2026 20:26:10 +0100 Subject: [PATCH 148/215] Add method option to initially jump to a certain page when opening the sodium config screen (#3474) Thanks to @cortex for writing the initial patch for this. --- .../client/gui/VideoSettingsScreen.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java index 9be78fa769..846c9af3da 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java @@ -4,6 +4,7 @@ import net.caffeinemc.mods.sodium.client.config.ConfigManager; import net.caffeinemc.mods.sodium.client.config.structure.IntegerOption; import net.caffeinemc.mods.sodium.client.config.structure.Option; +import net.caffeinemc.mods.sodium.client.config.structure.OptionPage; import net.caffeinemc.mods.sodium.client.config.structure.Page; import net.caffeinemc.mods.sodium.client.data.fingerprint.HashedFingerprint; import net.caffeinemc.mods.sodium.client.gui.options.control.ControlElement; @@ -34,6 +35,8 @@ public class VideoSettingsScreen extends Screen implements ScreenPromptable, ScrollableTooltip.TooltipParent { private final Screen prevScreen; + private final @Nullable OptionPage initiallyFocusedPage; + private Dim2i dim; private boolean insetX, insetY; @@ -51,9 +54,14 @@ public class VideoSettingsScreen extends Screen implements ScreenPromptable, Scr private @Nullable ScreenPrompt prompt; private VideoSettingsScreen(Screen prevScreen) { + this(prevScreen, null); + } + + private VideoSettingsScreen(Screen prevScreen, @Nullable OptionPage initiallyFocusedPage) { super(Component.literal("Sodium Renderer Settings")); this.prevScreen = prevScreen; + this.initiallyFocusedPage = initiallyFocusedPage; this.checkPromptTimers(); @@ -115,10 +123,14 @@ private void openDonationPrompt(SodiumOptions options) { } public static Screen createScreen(Screen currentScreen) { + return createScreen(currentScreen, null); + } + + public static Screen createScreen(Screen currentScreen, @Nullable OptionPage initiallyFocusedPage) { if (SodiumClientMod.options().isReadOnly()) { return new ConfigCorruptedScreen(currentScreen, VideoSettingsScreen::new); } else { - return new VideoSettingsScreen(currentScreen); + return new VideoSettingsScreen(currentScreen, initiallyFocusedPage); } } @@ -132,6 +144,11 @@ protected void init() { if (this.prompt != null) { this.prompt.init(); } + + if (this.initiallyFocusedPage != null) { + this.jumpToPage(this.initiallyFocusedPage); + this.onSectionFocused(this.initiallyFocusedPage); + } } private int ifInsetX(int value) { From d3274ef6a5b971bd3cea5cc2f6ff9d7333fb5b5f Mon Sep 17 00:00:00 2001 From: lumiscosity Date: Thu, 15 Jan 2026 20:35:15 +0100 Subject: [PATCH 149/215] Optimize PNG assets (#3476) --- .../assets/sodium/textures/gui/coffee_cup.png | Bin 2740 -> 95 bytes .../sodium/textures/gui/config-icon.png | Bin 6331 -> 1832 bytes .../sodium/textures/gui/tooltip_arrows.png | Bin 3343 -> 87 bytes common/src/main/resources/sodium-icon.png | Bin 5372 -> 5371 bytes 4 files changed, 0 insertions(+), 0 deletions(-) diff --git a/common/src/main/resources/assets/sodium/textures/gui/coffee_cup.png b/common/src/main/resources/assets/sodium/textures/gui/coffee_cup.png index 2ed28323981f37fc0793dc2ca10e8d10293c471a..872b41ff20be377414504d81c2e76bbe519b4575 100644 GIT binary patch delta 76 zcmdlY8b3kOjfH`MLEW0&3`nVYx;TbZ+)8F>{QrNUibMj3pqN{DE8Bvm84TTC8x$Cs e1KzlcGcj;2OP^`i+Bg@egTd3)&t;ucLK6ThN)}B3 literal 2740 zcmV;l3QP5gP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+QnIIa^pG<{LfSD5eO0>$m8&{YHzT|e+^1<>`anz zoV;4AGV;hW2sVI5Lw56j|6T4scrp3pSTv`SlE;e+mr(I0`?uFT&&B&??-_q|UZdMx z6gEM<+s1J zMqb#nv%9%+0zT>7)U}oU4mUOc6Zh8=4#l6~z3!vn$iDjSi2d;0HyoV`1&V$V)1XR= z=011oaFN3digO4(#C+P}C90iku}k!*mkRHol{@?+p@;%P1iPLSl-F(3DsaD`sKTnsP=CRf)nPl0+p-k(y%SBq=3L zR*O}mvWg~E&04hPm^n*MS#!=MS64!bs8uPcXvw9NTCr$_<%)0DTydd=78^HdscFlt zwAztAoxAkZwdY=Xbt8;8c*saYM;>L=NfW6wdCE*vXP#x&g;ZN{@sgF6uDr^sx2Wx? zx`&wk8M&{cMrYIzFt=mIxao=-E~{Q9$mm2YW{6l|Ld4Y~fP@YfGoOh$Smd0=%$Ih6wVcAd7CXdyCv3adW8qSK}7$Bj+r1zY#fSq5B|mf5z>Ds14)yZguEE zY*vN_s*fl?+%R(u8Fz#>5uOZ;a6NEV4)(4o2yuj5@HzWx_NFho;8CD~h|V#i-N-dC z%q-j#%%p$v?4Gz0*@#_4EXHM~V#}>W0?%w=0dTXE6LX}o>gpx-vAvodmAq_|C1oFH zdSWqm?eW7cu5v;J<~Oh|s5VgEZ>X@+@YvdkZ2sx_GSs@-8Y$1UO3+f(8kVH(-5{`) zHgEsU03ZM{&8;yqqetm2uI6Oaj=V2LfDk-JuSY6ObgqbAv8J*Wco&kj6Jd3f$UROc)pae|^Rv9)uGMAt>z@fN8+$N=kuGVA0!co$Y=C}-M zvoG+pmN?<(O0|@g<}8I6&P*nRgL($`{o|0;?a>E8dkonZ*?or067p9<_K6`>=gOm9 zUdTAS(CtN@Z#5Q^eX=*B4!Do8&J>J$KcB(yW#YPLGxynqd=`$Dk&`VV=7U>@1S4rO z1hS^(FhVL*8a~k^4WcWFuR^1Rm|7?unFN5O&UF;PPJgJ=vjE^ZO2A>Gy8@7PCNNMA z*u1xot4}lU8+C>a>jc>Xr3kLb9Z8(2RihZxO>;-~T5$}BH<|_Qu^?#pGA}Zo4|6fd zsKIlP5?mgojp=h`SWd0B+|ivJ(Gb>DSJcm7d5Ur!{-1#WRk#f3OpzqC2pVgk1+@Sq z5#4t9fS#zzbz<|}Gx#{ss78HR5w#8AB)HvoQG%|=P(Xmj3doFu-no1Ce&U_Gckd_OxzE7+ zu6OP;@V@JvdnUBcc;}u8?K9rF?(Z^wcQ_&EGp_dGqKP8p3uBl)Ih79XIFP%{$!1 zhkJ)}hif~p!jV^(bBAlEW7b>DHFxvv-nADHbB~a{j+lFd>~+N4BV?~5<{lw?9WnP+ zl6?m;_f?X82Ql|ml6?m;_f?WTi5SR|fKdm*k!Z$fJ551J9}pP5Kx084;uFkHEFnqH zfxSJ1!bret3WmvMatLw~lEXnauE={!&DVp8G0}>VX~~~SK+x`dyySjIWj`4DTpwQY z&<3PK@9y1f;kCE3MZ~e6S9bGocKMfPi2uK#)&~kfR<0_T+Oop{m-+@68n`c0pIjLx!&2sXcYs}fCU(r(wUWh~O~EZ=cVioP{H%L3{gldha) z0UZw^+Yq2O6%{4OXmpXDWCjcvc~zbz)|N2qrrfA3cp5{1mne|dQV$q~i?C-6XtsXX zY|7qSgClpn`RVyfL!TwzeePYN#5CmnV-K7Cw=6LMWwHd-A1~7cS+YFa-7t{%P?5|^ z#e&eMGOV>-UQaA*mWzHc9Q#~c2(kThK9g@(h z|5Sx_!@q6Dg$&CInF)gBLC}2EQznf-GuARoY1#`C8<}elH{chu8yIRsZL~__6e3nf z-q(epjN>VS6MQtqdNdxC!`K=aJbR4(dxCd*4*nMIx4}P-{1-Eql(zCR*?S6hR7=FJ zp?i@Qdxz5!?i+KG`<+Ro4QIb!3{2Fh)MKU|_J|4{Tp@+i1Oap|gS}FdQ9K6Wz$y}l zQ9yeANyMM$2`P};a?tN=54UHF-jw@(J}aosFfifw(4->vy%Ky5)6b~_gz<+s5oVhr z)FYelk5^gx?h7M^57t7hY~m$I`wAZ`TP2ty5Eac_mhd}UJ<&F)au%L z6%7@=8x$8Oh|PWgnRV<1lQxX9NBbPJuBOVa@9r7wc`QDoI{Rxy=RT8F@EiSL8I=io zI&!W^fdI)Eq!k18NO3?)pj5Y5B?q?|R0)!ueLw43Whi>Umx369n|rxKj+kh7pHGIz zasL44FOMIdj~dhf000JJOGiWi z000000Qp0^e*gdg32;bRa{vGf6951U69E94oEQKA00(qQO+^Rj3ljnhDAsykZ~y=R zN=ZaPR2b8hkUX?%_x u?h`PTS1{79D}Rg%`UGn^SqoF)?ZZ2dVIU1vi|Aee0000sXzb#2JcBkK~#9!?b{DzSNk8w@prPM$t06Z#&yL^ z%t$0I6N$JaA`%fJ5-}ovTq7bJACl}(| z<5mCeT6|l);6Hs!h2W^G;Cg%;7RppexqF4=(s8Zqe2J#wRCk(iv;u1Pgxd9$TV$_|ZiZr8h<#ZBl5nG8#zHeFQ?~E;n+>6%WiVc9+lAeH5ylqSXbL@|L zv4a^K0a2~H47g~+)Y=#F121iwNqVCm*=8oNfo|0H=8xDw%M3*I=;g6FA=cs+TcT== zVq*hX@qcvn_kR)dLgt)w+3HQREvn>Y#Jsq}A@(|A9gatJJmgFBoBp&w(cx{YH{tH6 zig(QKxkvsgAjhrVrKkq|`Ky@spLb~N^QBdrb9+>Q|CtkVE&jnXzO`yqw8b1?-ki9_ zD))j5SS{|;6=mbPc|GI1SVgvl`&g-WoQ`tQ?SCutS@2k_A^VOAt2NR3z5ffb4i36h z@iWjLc}|9+bUaXE=GAc>}zvijsq2~DyHHQJ*_+YI~fVG?fja4;fT3> z;B1tGPM?{_12#tz26>tsz%(~~Y90@w91fbtEQ67ZA>SkgFv|cP?03R+i|mRrIBXun zk$;N`-vqqi=vS>qhs!zsykoj&cIRGUt|6}D4S#kNStn0TcO{B*z-Okr9J$fM6I{~= zM?T+>$7JJlM^TU?dU%AGWRb(QE`bckSK_1}T+J9;S9_Vl6v*W$ z%EzW!p|8B@5=3s%z3SqbQP;^aojy0!G=Ck14%=`0H^>uu*Jz;TF%7?E9Z>|k{M}U7 zq7c4wMiCL2Vj$MwJyX4AXVE|EgK6G*Dhl#A3y8>ThGI>Qm}-SRQ3U%;fXF<9Q5dIL zMrK(zOpc{Lm+Xxq7)TD_F+JrRymOr&zIa>aOx0if2}r^Nx}qlT_EB8{^qcAq+kc`a zZuh>4=!+uQ>&MgptStbxdDk=_i+%_6x@?*U)>Z;^a?do&^hObMvqaif)uelBoK1RY z0%R%cE}HdxS^zv_Fls_CQzk&>v(6~SDU*;7Kcoi06Myw?;KM$_vDNkOi(YL|2Z%ygr6`jQJp+<`b&JCBNPb zKXa6xSO;6^^Q`w-Li{QhtKN)wY7ws;_bO)?VjsQiraSA-cG1K440_aIe{tG(1t!g= z-5u59gbS8{kGyfwSG?qvOD>l(V)u$4qiSqs#1bH@UT~2BxWuNYUVqljeMD>uMC5+f zUEjgPWa9#O%>JlPc8KSV3Sd4v81>>buWSewI34xlgiAKT5+|ZHJny2dw&?k29gq9M zR(j#_Xf01VYpcw9GFp!VKC~4cav++4es5W~TlPnDu$?pfhIK$@IY(DC4Tty@tA>m- z6wSmAPC03nCOO59Xn!&Xyyk*cSoE3$(R^&8-%CzgB4)V4Ae-aA!~KprYZ+#_%E4?? z{P)nqQAeG0`Ktz`SDbRxFYF>V9-HXnEVrHUZE^r_onee~^tm}U1~#$Nevf$0QO1~X z+8Jm6_L*^-3C6g}Fh}WUXKY+-XAk`jxc{#Y1N76Ib=6ntZ5?fGZEbCBZT|EQ3;2mt*bd-wc3uDP)XD{bP*X3M%-Cn?F*x zK3KLn+1|&PP$|l@R$Ghz>k}XBV zIxHfJK2XMPd7GS$PIAwzxksjG!O2A1&Fg&z=a#z0w-}9Vp>kS>9^tPO=e0ZwA@hqK zQT;uFVvh+K$OfA|&pIz`mX$<&h8w5}+QWKD+w3KnA~Xzll!^cdMmlkv;sAGzBeOME zXLhjrDD&&uKb`4eu$RsPfdx}$^Okr^)eQJ;bO&7zJ_wZe2AlLo^ieu>^b3N#gmptq z4+x*DxcSrL3Me6l#ohByDx$aNUS2PjS{c%3gerj%2~J^WUB*b?1){MJi!s_2<;5R( zT=ir((87#C$%Q;=R@&voY0rya=}OKRxw<#DG2|pwmaoGt+^bK9OLEiX>w|WmFnRqP zP1a<0m>kp08+F=xDn9af+pWHhK10abW!(gB?S67!%6P7IVz_Omb$*Yi;=XL)cwZ?d zaBh=hQlw&!aqvccuf~u}bP^b}9!yp7ob zdFk)i?dl~O@&jd~@bxB5z6vxZ`oISq(!@tZ9PfY5EtLIStl02n2KjHwuEbI%^zlu7 zj6!!+dPKaSA9Fwr9nQnanLcM0E~mgvsY0QyKA^kM9i5eQ&fT)Bo?0S2JG>C`w-3pc zV@H=)D0nLNX%IJ@pg0M)-xDM#1!ZLobut&Pd3o<5X@^s_mgwPCnS?#SG;w1bW|}QX zLUnAe&sxg{-86t4NN&#X$H`&VM?T$e`kbIthb*(+o^DPbsaiVw@X)g%)cIBArQz2n z&QhmCl7l(*M6~+Pb!Br`;&bio8o|=9U#vW#F)bO{t{Qx-p^qtAv?WVs5~vqk`F(2s zo%(d2P@zX2Ud^y;Zhq8baalp_9su#!@5C8rtYax-R#@Lsk-OfHi8Wt)B*Y>=V!;VR zA~GAgnZ70R_vEbWcnvvLeX&P!d z;=V1k62Y_J!0^pHx*-nX@~}X5MoIR8UQt5`3+!4we~je5+T!Lz;qS~x)t_wTDwIRg zY|Z*fG|B7CO&#E|G~W5bnq}|#u4&(u$$0y!Y!Chl7ct{+!fLiKi5Fcc+^eVW53+9* zCbMP6l$TB@;NR>9n3b{IxTKgnRHhpyk)2PPn|jN>cxtJ>`Ms5i@n;v}o&axV8Bb}8 zMW*HAG`BdLT&y5tieCN+Ug;{v)|f;ZFR^smowZK--t#eKUQYTDFzKIWvz{o?6&1th zUTEzaE_558R^f-o$&~q=k!Ty-sVFjuhi^>by|6(UH}$u=5XMb$I=0Dblr$)&-Z30$ z)FfJDJ#@I)@czBj?=g?pQH>=w$6C3K6Ur&`LIj^9jS1I%3aGqxC1OJF$4X#$FcSK8 zWuH+Z$M-7Sor{0)gj?t;6to|!Ow z?i$#li_}&y(&Z$l^LJ{P_;ounKj|vmv5T z_aOoL@#n3C5JUs>WQ2QKEG_$#l>=9W80I9K*W$H@KR4T9MD37Q+)s8mK(Nlm8Quxi z#KIaBS1u~0qU*yd{{vtUd{i-lG^1Yry{)I8Q;32W9c=%9LzDFzD-S_aP zR+VYCLYCk`N{k>+zO_lBWv;KQa@Opy<$za~wvB{~-p{bhUAXTn0@#~9v&7+PDx zxkO2^1P_St%ekukNxc5ht!mPjMIyY7N@i4`>*lKe9xf7NdI9Y{qwUoL(wQgY+*pGV zFP6f0R#D^WpIseaP;`Wd@>t1lo;)x97JcOFn@80503od;RxbUB$%S&0R(IqZWHNGB zyX05n^UiAea$u}j?&a5w1ewqBS?|3&xK|*v@HGf}gw_1nj_B!qYfxNskE=PaUP3kZ zPrp2rijFXG!bq`$f;f6Rg zLwH)0Kc!wIbTtUSL`%dSA@2g6`4kRn{hx{DilX&=|1c zWXp0qe*H&3>emJ{d@{+@)wW#JCT&P9F)Sm6+Z*&2Dpc3>oY702(YdvUoSv6AF*jx2 zz+HH(gFVy&{~K|pK1fRcl3rv+EfkKzug=ULon;N=Eitja3>@_gi#i#hRUK;&HHzNE zH+-CU6ug9~C}8aS5q-_wiTeP;#1iqx>%LJImGaa%w~yb&y{)l0UM+qY5%Z==2VqYY z-VwQ9^(jy6`B)K~&4K_(Gy_$V!|LN^ek?v8&?=vz=sr?0mi}n}zognVuJT5bnFt#r zVP)7dCuxG2a64>XcX3HNwCK0_ztR(iElJwV5LnlW(O@y3JmR_ouNM;6}ywA35aP@D!(|l;Yo#Hh#KY zx|Zp#0MifSSxYk6xxoIds*mvZU;lZCeNR2{{4po0SNn&if&1)tsBN26xwzI)xz>P( zLjaqDM`&1ZVZ45L(&kZhKw4axCavV8TPc^4#>RdPBD&DF%d|CwzQTI?k`-3B>Hfu! zTWq6ebb_DxovOaREeDV9#oXn#rr*ktHk}Hs(zLVHia)hzVom5E)z2sDiC?X^wtk37 z8rP5PO|YtnAHuV#2#!p1+arRI6hV<}(|qkSm$-Pzt#zJkeYuMu9PdD>e^b=*D<$X9 zkif7{f7@RdE)x8`>f?C1dlRhwtU+mY{#%i!TWvCGcmbi6wAq>+{?Xtqx%?9Rb=QqjF5YZJ+?>uE=z1#oa0>I@Rh58Es zzG48dWeWgunE=3uz_c1ZxeE~5>u9Op**jdQwf!#}2?A=4yt@|Y{wE+H8^d%*68XS% zHHbDzKos&4*1PVU06;DQQ&%zepWC%|iA$Se4qZatmnWc4=4W|TtrM?mkjgC*om}R; zXrax?!!l8#UQw>2VmUv@psFJQcX6Z8EHrVYqqxT?@WaS$sLZb1fo8#w=3eYHfn!AX z*^_Ip5s4YHZ{)}>WY>Nh^26GVZKmuQ<}75mEtewt{{ue@R4Uo+J^F7}e^>I_W7q&> zk%?!#J~d3TPd%?!((thYfOaf`k)TtG z)>zaoq<48&wgpH^v`82%FJ3@HHH{Vt`eF>xKaMXlQm#Z{Q>p-Lo=3ya;8T{8)p%Pd z;fyu_o6|lV=byFbzx6@8rqT)!A9KJ~@s9m~zSW{X<1~(8=#hsS#^y;)P)b;Bml{Mk z={SN4j?)l+ZIgKoy@7?Rc*9sANfPjJOOqkdXvkkh(4KLwvNua$B}jo3K`<=Xk>jb8Sry*@4JPy$U)L-}=bL-(_v+ ziiXIxNi&WwQr5;s;F^z!qS8B7YHhfLrVqJ0FCp9RsCPv1sQKrj#9kO05oyVj4=);>kO20?IO|GB~6JGbp*&QuW`kLv%$yMaDN z_t#{HJEk#@HV2xLSl@TLH3vS%3hbv+D?tL()o3_jXj)VrAj4hJ{47qQ{vicDo`3zR zb7W2IFL4W=c*Jlue>=g!pPe}Y8%9XxE2=UUpkrufD{zOq;vHEg@yd=&`56n{Rx*v* zeaBl`;94ywR0%heJxc^cf&VT75Ho>Iw+$;xE@4!bE->1q_cf5XI}ObXc22$aE6tp} z&=5w%{TfodFfcR9l1p|&`_!=Dk@4JztCNY9>$xnm0-<}DAl&U6Z;yA_a^PkTfUi6O z=u{=yzduT0d+Dg7inD{wgW{0G3eze|ggLWx6b4~tWGlwv^G5f`#6DB@6QP!UvB91s zaJ+K@lrRBbw=z&f`X3ldN##-}AjaA)+D09{B)aLOQA2Z%qq)5I1sps-m_Q+vm;o9@ z47+a@9yo$BEnm`R&PDY~<3`C9+ozW({`~Zxh;Q zSa}K*oV6{#ay&XaP1G(Pmjj-G+(g7kG|_CSI^c7)dnz+^JPIN%ubYUr7m(;sfrtJE zOwR6w%-t;kjlhG!odp!wnrvmJF>V5@Fi0IE?ulJA zJJqArd8Gol(vTYjHP*t7>N4Y6d%s+S{)3$|D>t44+g4!LL6@!fx)*_TBD6+ImNlk{ z?PLwrcwhq2ryTP{7ePWWzmscBAn(rK2}WJWg%Z1Q&nihX0NoEk7rq(Dy+~?`yZ*#$ zeo*{p3IE{1#scZa0_DcSW0)Ti8WtwbiXj6-UlEFN+kSEKu-*2JO(c00Gp7VX#3Maa zOyk~gD>;YQ#* zEpN>FvjIAE8P>)Mdcv-(cnu;Szyl?a@VbuFm{+}io+?Yw1r#sPHH@hS`GH^K+&PSDSX<=P(AE8#VhC|BN&y#k&E z|0|PUwIo|9N^P4Z>t=LyvCU(VHz4K2#RE%Plq;B^QJb?Hdf*6yY{W0|3?dtsb|{o` zfT1Ak`Gjai&?kD?aHNN$_A0KM&X&{rt%bGnR(X6@QamyY%7zMm8f1QIH1}vis^-lg@hES(aQu&i_fK$~0FeYI zC_DML9kXbS6wEz~mUDMfbDRrJ>7fkXAzJB9oxl3I&B+Ru*{~RLL$WBg*;6grDek_K zU;}eX+6M)=pXv-}nFD}XUS0m>ny`sBiB{GuDs{ff{M2QK*n&~nOpP@Oc$0h>J}dDu zFN(m-(v`C==iY&OSf%qS7I0L11`lQJ@9h!i>mC4>w9U=5z%ri=Q}?O7u_0<(t^r)# ztOq)!4_B)Q9|L?`*J|RTfwV7m?n=f-{Atp~F(XN+o&JDTN|PvQ#CyZam0bt`o1?^m z;7U;NZ5ALxK6+ds!Q%Ch;*bX$yfbG?AgZ&C|1_%kYp;aM?+~D7EeZgZG%lW6%`rtE zhNx*!+(hNfO5;g%EDTEh=>rpYgMp{pR>46Iqk|taeDpwR%QJ%?@ss-JQS{!r?N!Nq zWB@FdN0@|jpa`^Jplm8eqr7F>3bhb$@!BK4t1C%&O8D@PJfamjbO-5UoEF}BEp{@! zQE=@No8j9@yz);P+qGF>4a|U=(_cOtPE%s*9)bq!=k>l8U0wLo-T@UM^*8O-LrcSz zwlI9_Iu=lcgIrDc`G*g z0C&0N|N6f`@f>qwFu7R07ARQwf!Z;9zhIi+RrrLT!Sja8$&E?*kbtSISw()1h9}*F zmqk}UzmxEL>dS-H-4~}kucLa5fMv1zGg26!A5r_^3tkf5Ih~y4kQ*y%_r^I*yxuWY zfBy92WyoYePt)psn9{F_H%xc$PmcWM2W-*cPCDi-A5W?u`-WUG`q&b`*^=Mqq5q61 zLL}KI58GlQ?fx=itMUEtw1{e#zlDp7fFhDdVFmN-KN0}A8x`UxOW?_y=-~qilx6$i zSN-U~1R)rj#_^Ph!(7W`pT)IZU{k)>XtO0Sq^5F%Q>WO7tiR@UJDo`UNO5d|&wsex|%$Lx^~m-akTdrI&rNC7Ze5l;A))Ktn=!5fZlSis&36 z6q^{FRtN8BM_hyAKnP(}3QIA31$i|E?BO3oE9o=W5^=|IUYPxrJPXXX|JGmjq<_x) zX+$R=a!~53$&flQa{jJ}L1shoJ^tSh7i{1aZ(ub!g=byl&nXqie{>MO)>0ISsDn^+ zD~ro>bj!RADf!4y_Yq%=s5=FK%Wa_B2RThQ-f&kftmt_bp>Y1$MtHUV3KvB>Vza88I;=4jxu1Q z+{=RipFbV%1*8gs|NWT1sb}xzy&C}Wkb3r7!EcP2lEaktaVz-%_rL$gwOSIAxfmJ66z^ymwoaV+gOMTg Xo&3Y6Yqxy{s$}qV^>bP0l+XkKBwrZh literal 3343 zcmV+q4e;`bP) zaB^>EX>4U6ba`-PAZ2)IW&i+q+Qpe^avV9bg#Y6da|Emb;5b;#<_7Qh^94{^ z?lyZYNmwVxOR;Z1>v62sZv7tl>mKLa z>n>+LTzGzSeBPa3^0?|b6F7$Z>ubKC1BG`3$?NF8I`7h_^WinU|L{iTZD#-ee2sS7 z_dn(B@7}X))@=0d*rE_0c`WL=#Pf|CJAn!3G2xT>oA{p3r}8Pb*6q+hx2?4K!}(-@iH!efE5>#6KAvc**W4^ zz^C!%KA7k`5V(~5(+CottN}JNNqJ?`cv9AX1Q!fu@EARxze1Cgxb+P)i}j zl#-;JN~)09CFMlOU}9Enp+7amQ{R}hCG-c*lW}R)h1(jA@Y01i~th(BU zYCG(>)0Ulg*>$%UY6n&G0kdC``x!O&Kn=jm!7Osq6E#lf{2aomoFv5z#C&uho)rNI z?G-ayT#R0kGm4q5o~%fow@eDobcz@V#^rq4?uFeOa=+ka$os#Gn}3g-QRw~+BkMLG+(&G|Jl*}=>z}jKx>5MWR=>8d)do}RShv+ zZOYzKx~qGv4MpY}wRb@*N2tl$vS8bAZMU!qz;dx>BCX$>%UzGgXIWe=D_KkM9R+jd zt(Rp!76EOvqzo%Tv$+m)bZ#~W17X};pe0Z zEC@^r<>_;*Gqt(bbP~d@b4g5n){tcu9?8BTcY}gdLfSE(xAe%!hNt>V^e_bS%)vD` znvy|7tBldQ{|d|v%OfT%aV3^)evXBOmbM+h?9WH@b|Oq3vo#a7v>24N>6EdqsBg78 zW9~wLnZ&2uSpnaACcwmlrS;YKJD%+F$JE-)V9pfJ3C{4=GQnefqV(fizOBb_-(Lp- zmM#}^@jXr)K`yN=q+lkWd&k3+HcVa10d|@xm2PuP!_Cn~a3zP?r|%`SEq2d36w~Hb zJ5Y#iRo_iUf_)Q0Dn22UT{yj1XjA+StcM|Grt_d32#Y;LXtqj+F$~P56;fKm;UP(l zl2^|gZbL8BvcZ-W%CvsTX5fTT2?8-(+MSE!q6AxN&lXLUu)PXf6jtGFPOGNDTMS0a zWoQbyewQ&f4{1Kme8@RO`G%$wJiO0M+EpV;tQp3e5u~37(qE?oxwFV-(UKE@yo{Hd zuswmRuJdH#PsgO;wi=)zumyeHU9#Do@Dd5PJGtdn0v`-t4tdQ7-fsNrr zLE&Gxca3#*x#8e7r z6Fe>&Yp1NybMm2e2X!0a!YUL<0~(t*iuFZK1y0wfi)V#K4mXJtpy?`i4<~U)jTB8t zFdP{L7iMW(D1ul)Jj5&Zcp?fhLMBBV6PK-A5zS53+=3@9!$|5sa>XrRV5r3&f;(Il zw$S0y)aOQ9f-LW48Jzm4Ze(Ny54yGM+Bg4{mnZ+=<$$xBN-J zeyQ+P3cO{2hIqsXEAn!EJLbde8b9sAS9DZlEmUUcLUmasx-Kd5#;m~S=IfeisTzJ+ zm(1q^wN$=PG3jK~pr?_ULhQZIKz4id311_rH~6m#M)r>~8`-ZChA>U(E&hOtf*0~A2u~iQ>Q+2= z`i|K!ZzIGb3CF5<Q5*zKJ z*>rA^sX|kO^(*sX{6H{!TyJv`2oq~ut`0RrK2(WNE5u-HEK@V#dzv$9?-R$UTT~&( zb=veM)UJh9**FxNKfr6g_e9I9!6$5MtBYup$;SqQ;3*Q#V>X;-EnQ8fT}OrDviL*6 z^P(k%dP;2__S6d>5xJlc+t!bJoGz-4?(hj?^JgG`!kC&$b={UG4(o?ew=yZ8lwlM` zBI*|sch#hsQ0ZMI6c$jUN466lKIQh=T9|J4R1b`hT#Y2aCqgY4N$m)+`pqjp_{|e&4e+gTIL>RIerRmW z=M$NzQ*Lz;nG88rPisZfQc1TAN>mw*yU$08Vb>X}SX*UCT*I^pMxzNs*=r>x$ky7H5 zk`GIfO{uYqjzmYw1tzLmi?XIJ{JegG)&Z-vtQL6_b!wCL^P|naXS2-iGNFqtX#g@9D->z2{6)}{QPCQw~Jr*nC zDusc^Ri2hN>KY$fWQ%{e#uVrE_%_XJVVrhgNN#<)Mu+|4-r@4qE!wO@nt1jy4wz1# zPQjYY$_*2UuB$edjfFzpey6E!UvSF&k{&b% zwFw;>^#%($F?5?gSau>m3KhJQIHGpX`6|$>dKW2>by&+1hF5@DQhqjdC`cMt8OuO6 zMmpr{8dM^2BKQtcb1}V$YOW^L8VsQH-uUNQ1{Sad2)oMuiq!5tx>mpuctPGnoj;Yj z4#}2E-K<6+|CYD;d^FE5f6?2g4?WKp9gk_(ustUo*CNO<43183uX*cPY;<3`5u|$^ z^bfb%#gfyxuJ%}WTS_^*F@LgY*Y5-(=?a_58ndzl>9j`Lv|KeD=NN*vzWv-JheTfw zZGLm3NBaxrpd&ha!_YlQt1S&Dm8twR9X08G($!I#lOMPJtZ`*uZu=Q>{Ef{8^V9Ca zFPjUQbT3u6_0XGwyur)`ODVwy*3BM~N42!mql%%$LDn@)M3$NfCgmClu`98>h`WS| z`SUK1`C^+VSLDE*9W;F2eCHX$sOl(GFP;tSb13uocZ@#P1oOjY$}DuNS+{g=i>6sP zYmlj#NvjJ#NV7?WoMoEY5r^40Id=e}*)XklH?e2|K-b6hv(ZkK%eB$MyH6>1&sJ-# zJ0%-8sd`Dxcxb>pR_Ej?*9+uFRCHIw^T=Ek=xJW7)WofGVgZqnWebIf^crzNfjEl# zJ8qym_CIf8zTd+%|6~*M{T`mBc8M@ry>j1PS-WpZeY?+my72(@8slEL&;S1J@$c^U}2*lGx*-dM9V&ZesVxo&z*jT4MP4Bwot0(C9A{grBGlU7_-}x^V^Uwc}=Kpb^t;uix2MHFQZ8{zd5&!@I24YJ` zL;wH)0002_L%V+f000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2j~kE0XGr&$`IZF z001*dL_t&-)1A+;2>>t*13~@%Lw~xE!USQJ;xJaSjq1>KNR<}>43|t28gyZcs~>l( Ze>p7#JJddeh+hBz002ovPDHLkV1nV3X7K<3 diff --git a/common/src/main/resources/sodium-icon.png b/common/src/main/resources/sodium-icon.png index b47ebbd93040b728a080347f45fd79ca3b7c6f5e..2a9874a9e83f72911b3e1faea14628b96f85bd6a 100644 GIT binary patch delta 4457 zcmV-v5ti=!Df=mqBmu&)C7c6)=EjvJ3hRenSZ3xhEHlGpW@aW>vjfY_%uIoPI;zxm z?I@?lYTL2%>UPz)Ni@)?kt|EvL^hK)6Y9SQX7g-?_YDc`iMAJpxi8tqSYK?F4aGLz z=RbG>jCMA6Et5NsZpq!pw`E)RO5XRs0(d^Q5!=8#h3}EO0o^loJZ;y1@k)74?dVw} z_a5IB2;}~gd$Ox{oy>E5Q(d~9%t?G==)Cn#Fl4mXxw(XSy8#KrZU-JT9{hy%Z0lYX zCES$das|6CPbeckavWJGLI7{-|o$TmRwdY$zK3k);YvfKi>{@|*VDt8{; zM!^uBYBNm=h;NViKi|Qzoj}@&E}-%8@64`kfllP`uL4N2D+!r@pW6glGc)AE-E)#u zZalaq%X5q3g@t)_8>JyVnYY_w1)UB~4uC-i-yyXCMkR|<A`@3Ade_2-LmsGCsNrAJzmD(W#fAzjt(nJd)UJOZ6k@h_Zx(XLkQm&wYP)Pnm z8FY@3?D>CWPzD8may?u{Ru>u)Bw6wI0g2`?#PdZ75J(3!IEg3iUi^-hrXz0nkH^1^ z5>W~1x|ofU8|fm;<@}-Ki=*>OxDs^c<`G|CR1ze`WdVoAndx%p4qPS43?K$2n8(uh z^}SoAZ{UekN>E5Yo%ovjl^P?v&;CIR?%bU-(wv#*_sQ3PUIT*R8^+*gU9vL2SnfQ& zEtQtOfj)VA|E^(m{(kBQ?`m`K+CEv78fW*d+QpC}1+XwRhBn%bALA(^+uVy&6XeY8 zlTt0g<>NJlre}12ee`1)7#xri1Sf~3Cp5*AVq*-A8|x3E0#L9qAt?ZK+d{$uBMZUM zGF)vrA1>#A4e3dd-goxD6s-)U3JT@JgKxMyg9Rt1{^VN;cw$&lSc*l*SPSm0{X3<9 zuwO<(au;^XmhR=zrQ@V>KOD2J*qSU6#<)g(fkj6|Q~+aTAxRWTAaCF8kh0;<)sdSc zw;tV;krM35_)ftpXYZVrCFzN>!1V{Q2A-WGzWP;vi?I^mAG-V~=uFVb^!Io>h;Q?= zDD1!YO1F$Q`TNYTk*|wfXdKC*lC7KsEjWjG^0ffU$LFAZu8$ABDT9N9GTHyP%_Y`KkZ_(rGTI|`go`U{|ZOghjOEKZH%l?bZ80#KcWiy0a1 z^4qC@?{R&=fycPoxV8kt8#S)-FoTHRm-lSYv;I*m5Bna-nnELd^82rlgei-acsuX! z=s^LbwSyg^*f__MG%om@lK&Ukccmv$lDx2S2BHE8(aF+Exkgxx{QU5{75SnEALVcy zuC)vd_J=FldQU9;abIM?lfPhbYJ7Z9y7Xm#2IGMmUtXm*Wv0lDhu5TBc{1=s-q^QI zRu>xeRq+T(rQ?XL3pI5_sh@h_xY z!Na&vKxVh1S;jvVBd$1(dfe!-cAHftjeQ1Aw z;Iy}9rpv8|H>6z2Ur5Q4)P!ejLSZ2ZwUpDhPT>6J8J~HwT}V9r^PqJa& z?~i{jGYc*0saXgiAI|pf5E7Z z#`Gk)a{rQ)Be-aUTeOC=eQ`)jc6xY!Qg~KsDMzj!v?tm?ILC}z%C=E3>0{V}Jdqx?8dj(3?_wIq0qeRq7 zGUxxUuL3Y6tYSq2{c8#hWu2v;yK@@H{rIvEh~7U=|0q(%iu0)Np;!eUOJQ$+k{AKn zu8?*l_W4r|33LbiLdvkNY3Xww@JyeqwK9W|ADbwofdAhf20|FD>id z#P<)r60@yQ@%!Qw2!Z5Qa~2FGN{wJ-05{SK<~eJ>?2G&G%(|E6=0*>AFd@$tqTpb+ z@l%Ul9_Fz$lX%VmNo`=H72Q^US(fJ)`tQ3Va_i9zY0u6w>xMtudspHMo03fR3qkEC|Csp{L`3@qd`K)sI!Y-5p_!W^zb`kl^;%g=@hN% zi2LHAK7D@VUA^;{f655^y%(F~n`57tuL-=mcT+q*u*p-j5tSr+;F0k$4l5 z^l=ENG7dGeAq|qvh0CMX%e{$g_O!QVr{N15rYjsjnU3wfToj8OzJ4H4@|p;~;wCzD z1*jFx#&74{Pg-l2lTkMJD_m*IRuvnefOm$JIxRN#AC zc4h(_6?1MJ0{eu_b!@l9mh$eJ?`ZfPSBhU ze*u}12hLj#0cUTYe%2}V!Ets9sIc8SFMu9!{?1v;4Wl>=`V-}8_uEO^Q25AYEO;15b>INZq;zeCE=8wX7< zb6A?6mq>Ti=0f`thz=~{hKoV)y;R!)vC&?%$a~-h#&x!T1;qg}3N0G6C9K%&`G0LU zSRU9J*``D`kszVX!JBj*7uWywU9B=`_!tP^HLg<>W;3(Ri{{SFVSs;X$yY}|6pQRW z_b0{!ErN{VI$KS?KK8L#~1$*M8WYB_xNFsT7#>FNd-Q` zcXtIFX#$*oOjUGJ;t<)6>nQTqpu z5l%45r_~Q`kHS7}T1MYH_;MmU{DrFc;pKGh)_35)C@8V*V^{M61*ACIxDlEo|=7i*nL&gPe zhc?AOfuBU<_Fe6fa%BJ2URhR{=W*KZ6AJA66Q66L8t(@7>-V&=zBHGdfF_%x0Rg;d z=qkdD-rl>a?8mq--8(NY?_EE-Uf~fGn=&gR`rT#-#+TX`cmIs4B7m*$9N6{L&*5l~!BM7in(?yuZQ6rcVtj=qPhYjts^|K-v59or)+l~@)NH~#)S z0cTqf54;lJ&fEP7<#ZZZ)72owi|OcT-jNpQ#I^l(T}@{DdAvskEdYB0X*-{z@`Zro zF0LHn9X=zGc>E_;+xM)5?s~4)e{fa) zJ@q>kvT{pqVcjUKR3Dv zlREbYUO|U%1(C!=3CEbTcTP$A{9p3M{w=uCL~W~-PaJ~|0@ID(Q2^*rJ|^RoLiO}v z?v=%b^76j*^3Cy&!@c-_Hypc%$cmCdTiE9ld2QdO=#2hl0$0bnreTnrE;&$&jV&$;q3;EAa5ygnw#(AleP4hV+0vGtsLc z{R_HB(Ycv(a2HN_ST7%%>~S!4Mgm2oM~W%3E@%yX{)xDJnoFTLWkG)>ekq{Y)Iubl zYP#GKqcbTQ)SlFV`o@0%&@-^YIKpIr_l*qD9@PEf^mV>;*)Z zXNArtGvs@nu4?BJC(b&O@BrvZs1pb*CWHHAeREISj zYxHpMfsIpqtk1vGH$X!Mhh4{p(zP)kXq-3>Gd7>Et#O)v7x9M(F*XOF`$~x8WUY+> z!%`rZhl~LXjli7w!_DY1^9}>-*NGdH{mfD5>z$>zqf++{AKL)0Q`Q|4moh;6KGX~{ zGuHR0rQDkmsxVk+7_s*4^g?pKypr z;S~IbrZ@?I*;y>-J={wC5?GM#6?Bi!2RS{fVpNA}Q%)YP7i9VYSli;dH3rr2ZvYaC z;)HY_Pd*3d*}mtkVeREKcDK_gn|=;2M*SBcoE1*wCuCwcp7NY$ z*nBJUK9Ny7KyR-j@dh}Lof)Dj;vF=zPz2T!jJc10t32Jd-!lfDaez9lfeHkI!?K8R zW>(&iaf+W{O*$kVruM8g=XinR6O2mQAm+{K_mru~H7Pc6`}FVv6|^N?ex2b%2+X`- zo`N??!c5mNz~wwG&ZtlYJ{C^^$6+JJ$p+Dz0DFE)MqoMa68{3;re?p)U?Evns5eCZ z+kv!PrS*Hm>1`f^LqPdKb7yA3M15iHT)rmWO8mqh6PJmrAA%Bv4JssW82++n{Ht{Y vWB3EflQ*gYIXljwXLH|wow&rskF);^?QMcdY9gIT00000NkvXXu0mjfR@Szo delta 4438 zcmV-c5vlI`Df}srBmu**C7c6)=eD&ajO!1*P-f;Zl$qf&Gc)t8*?}@MGgDxnK24@O zJ<2!7THCSn=I+d|NpxUQN3txbiEJiqCe(iq%;wn&?;8@>6KyXHb6>KJvA)^IEDt&tHF&<_Ek2+A&E3nw zCI5~vK@sfiSx5EoSR`qG=dmaUQ=U{ewJpFul}&K6zW(v0?WyaFZP78`C-k4R}wORKeq|AW@gBRyXPdS z+<0(Jmgg453k&n=HcCT!GH=rAhs7!v@1B>I%#4J^ z`J()L_IJ5@|FW#gFR5JNlLBXbE44!g{_1_Pq=^%_F|Ps3b^=%K{FKGt=eH9k@!889)q5Fps70 z>wC9K-@p^8l%SA)I`K94D>X)TpZ$Xt+_^hvq&YLq?~|{8y#@rsH;lp0x@2X3vD|rl zTPiJm1AX%L{$0cB{QcAq-qq&dwSBTEHO}r^wTmG|3SePs3~jUB0`*%wJ zV84unF@D&5Z~r! zQP_X)m2Mer^7olvBVQM}&^VGqC0jWOT5t~WW4-@QqHvcN93G^cO(+m~@~mSezQiD-l$G1)w?$7c(;2 z<+oFR-{bm#1CMdFacv2PH)>qvVFnSsFYno)XZ@pC9`-$uHHAj__-v2l(iX)cE+Ibm_}~48{XBzPw6r%1n_P53fnN@?_wNys>Ya ztS&U_tztElQ@4%_jdcWz5n>xh1uNRy`*&&*)Mq8dadV>u!(*xN_Fze@10Mv?KY}T6`q2J= zz-e#IOqW{^Z%Db4zmSq8sR_^6gu+4+YAL60oxu6cGd}ZVyO4PL$8mjH034#+pJc6$`yQD*qoUfvk8Iky@lz5A?~#?he>e7X%IIiPBJGBcT6ixM!3hxL2TLeeWK4IZ8yW zBy;}n`YHfJ!YWoY(7&e8P}W)cxjUzE+>bB&fav|>^p7HCtT>PQ9*R})u@v@yCW#TC z?Fwn2&|6~8Y|fe=V;HD|$4qSOdR25=*-V4kz~%f7e|&#ZfCZf^8|2NUvaAqoz5 z8$Y$^vu^may>}(Puqk=9d2`?P zr{l4Vqkt@e7gw}ortw5(Ks92ng@RQuz(0-YI2t5$j5@pc6;Y>@PY=H%R{7z?mrl{D zj<_!_>eJ^(-qkyQ`KOGq-+Qq+zB%@Z`I^A1dpE`71DiZW8=*N-p9yS#=hXrrMS$%b zw{L=RY80%-b{f90VY?Y{1>ePLlRZb9O5FP^^cstM@NyVbz4m62Jftp1{$5 zq_5JNosJVQ!$-eI(w>{;&YK|FyzlG#w#bu#$4{>>bU=@5y8{Y`pF?nY(05;2veW#r zbld%u90y(ic1kw7EjLs82l@?r&Sk6~9vf^nxRGNd>;Q zWoIU^Q8DMnA+S$q47dX4ar?CC@hy|%CH6&ettc!=F#i;}eD9*^)^VIf+T&h+;RMau z@E4F7dEmU|5ODVP>1Ul%9~@_=fC}5K^8)Ar=kJ`g+%Ss6pg&QbcE6pp9R)B&A~FD- zD@+y)+lHmn^vKtLrF$2Ko0B*XoC4u^{Gs}=Dp@XMAqLJ4SduZa= zGKZ!4d5LsKZ7#Ggf#|?8Znzi(-%GU}5F719i@XPJU|eT^TTmPzqtK#3Tf&OXp8wZ& zgXMvpk!?z36A2R99K1>AadG`$-_ti2_MZP@x0ZFzOQji25i`o~!#_o30MHC!AagQJ7s5Q7+m{j01 ze0Nu{ktV=@$y7xrB@U78IDVQh2fwj@TiJFh)5H>sRmBE*)SvwBpzEQ}@ZDfD!!%dW zl7r(4o6w_3#}k+%pyzVI_>#v%JZY+1AxUplh<`kQxFN+Lq0f14AJ^%Pz4`%I9<_hq z7~uq?d|LhB_9*Prre*ZKgD)qt!(XV1FD~o4nxpG~`N?GvK685vv|4hPLjhP5Y3NJ9 z7cS-(LEv?*D+&wY%#iNsz*rRA4|jW8sN>+ZJ;vu3ZsN-2+z%3RY zv_!^#Z0hbT>-Dc)m$G3I%;5I8{<*d)UY|ZY@-8k$S)W=CeeDp9t-@lgXiiAJIAmPl zc4$-l6ZlCqZr{}&DM$8S?UiMPc^;?jKB2(AKk>O1s_|}MzkW{}>q~RV323r88W6yX zhOQ#a=GrRGEH0Kkmy2b+u?)W9E@Vv(OPFJ4b_Phi z*O%g7fT~thNpfW~{QUqi2o7S7qVr-XYoH4kQ?jR~MI5>}}wF+vLwPKSdX8Dj~Rk zV`x?A8|JPfbOmS!bz;=#N8d9pwfw3f_Fy~;hNOOa`0eO0v8ks$OmwSk?Oi5s9oQk? z9{(gWefqfGJ{sQ;3ft>8uAn0y ztpKy!4rquIwkfkBqTg+XV0@{4are)tDgxO0&VgM|{Tz<=7<@(5F|l^KO}9ILIdcd) zPyisu0q7TzBTz>e=*t~(=YC`V)@VtscflbGwni~?$yXE20gJ7IoMX&T0Vqx}TBLGkJT;^=$0x>grw`d=P>-?2TSQi)|TapUjL z6L7W#@xUwb?Y!NeP)?_jHC+u-yqJ!j<{fE)PF&kx*VSaUpT~P-&;qb0khb$VDqjdV z?&8WJ-r+M6iN}9pwSCV@=&o1%dC~%yqYb_ZN2IFlb8PDAkblnphDW=9EB;-=^#@nw z-*dl5Iywsp0&n!K!%x{ova~QKEP(JIX4KNKI6x;MY$JV`(m5P57ny?uA{sBt@^hn$ zFsXBY;1zWERuD-{lyHnWd*_s-&;KQF?B9YLP1LqZ`NT2kATZtd9R+|6k=OQZiq7c&*KQf*whaS8ln2V;+Bnex zn#l5bd=5$%$*=!iEer?)V}u878No$d;1z+$-JM+yWl`;qtyJ!{`FX&MOWc;`0;+E| zYBj4@0g7*3d;g2m#3bdu0V|>^elHK>_rhmg&^UP}dj%dooA6J64Me-a)Q}#qXC`_T zq<=y8C^|QD4(`G!ckAUtlid%dE=Zt=^hhy9)&;Gh&p#2DPje|0r>y9iOjn_7s( zQ%#p!Vss`&gW8iiP~Z3u0D1;C7)O{5@Vb)$+JoAk0E+>b-tfSqIgNN~~=%pUn#Bwd4!uVR29PqV)A}4a{L% zdS?Gd#v8q-p-~TOG`Y!5b>aCPf_MWQ{DzH1`thL8JsLZDkp$*d#=t*QgRJHlh3c?| zV~rl}J+N_#kM;Qv`UYso=&4Z!S$5Wou0-J9`-Ul*je+TI8btK*Zr>QeT zG)26F7FLSDdV(qUah0dr_j|^`GY(LvHBf;-a99>G&cenUGR^TbtVxH&!_=O&<`OS( ze1b_y8^pXh{hl%xxg^CVZl4}rpn~?K%dazh2!WXw%v10tNto#p1~{MQ#RV0rz{lbM za2z&boNN%i39#pfbYue7|1Wd;k$vO>Kf^4|`mEv?@hPH*!Rya$vYG)&gE<7rNqDZW9B?_@k3CeutA074Z~mdjDNLGU<`jCdGbb8AZN!p^lWbXFEi)3 c_;L1s0qt#qNkK!|)Bpeg07*qoM6N<$g7 Date: Sat, 24 Jan 2026 02:58:10 +0800 Subject: [PATCH 150/215] Add a text highlight on page which is hovered. (#3482) --- .../mods/sodium/client/gui/widgets/CenteredFlatWidget.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/CenteredFlatWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/CenteredFlatWidget.java index 6885f52b10..440b505784 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/CenteredFlatWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/CenteredFlatWidget.java @@ -44,7 +44,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { this.hovered = this.isMouseOver(mouseX, mouseY); int backgroundColor = this.hovered ? this.theme.bgHighlight : (this.selected ? this.theme.bgDefault : this.theme.bgInactive); - int textColor = this.selected || !this.isSelectable ? this.theme.themeLighter : this.theme.themeDarker; + int textColor = this.selected || !this.isSelectable ? this.theme.themeLighter : this.hovered ? this.theme.theme : theme.themeDarker; int x1 = this.getX(); int y1 = this.getY(); From 217de9504d39e89d44c190a82b0c466710f2097f Mon Sep 17 00:00:00 2001 From: douira Date: Fri, 23 Jan 2026 19:58:44 +0100 Subject: [PATCH 151/215] Fix a rare bug where sections would be missed during occlusion culling when above the world. (#3484) This was caused by the above-world init adding every section of the top of the world to the queue at once, resulting in some sections being visited too early such that they are not traversed through with all potentially incoming directions but marked as finished. --- .../chunk/occlusion/OcclusionCuller.java | 55 ++++++++++++++----- .../util/collections/DoubleBufferedQueue.java | 5 ++ .../client/util/collections/WriteQueue.java | 2 + 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java index 87593ccd4a..d61969525c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java @@ -19,6 +19,10 @@ public class OcclusionCuller { private final DoubleBufferedQueue queue = new DoubleBufferedQueue<>(); + private int outOfWorldRadius; + private int outOfWorldHeight; + private int outOfWorldDirection; + public OcclusionCuller(Long2ReferenceMap sections, Level level) { this.sections = sections; this.level = level; @@ -33,9 +37,22 @@ public void findVisible(RenderSectionVisitor visitor, final var queues = this.queue; queues.reset(); - this.init(visitor, queues.write(), viewport, searchDistance, useOcclusionCulling, frame); + var initWriteQueue = this.queue.write(); + this.init(visitor, initWriteQueue, viewport, useOcclusionCulling, frame); + + // initial write so that the first flip doesn't stop the loop + if (this.outOfWorldRadius == 0) { + while (initWriteQueue.isEmpty() && this.initOutsideWorldHeight(initWriteQueue, viewport, searchDistance, frame)) { + this.outOfWorldRadius++; + } + } while (queues.flip()) { + if (this.outOfWorldRadius > 0) { + this.initOutsideWorldHeight(queues.write(), viewport, searchDistance, frame); + this.outOfWorldRadius++; + } + processQueue(visitor, viewport, searchDistance, useOcclusionCulling, frame, queues.read(), queues.write()); } @@ -264,7 +281,6 @@ private void addNearbySections(RenderSectionVisitor visitor, Viewport viewport, private void init(RenderSectionVisitor visitor, WriteQueue queue, Viewport viewport, - float searchDistance, boolean useOcclusionCulling, int frame) { @@ -272,13 +288,16 @@ private void init(RenderSectionVisitor visitor, if (origin.getY() < this.level.getMinSection()) { // below the level - this.initOutsideWorldHeight(queue, viewport, searchDistance, frame, - this.level.getMinSection(), GraphDirection.DOWN); + this.outOfWorldRadius = 0; + this.outOfWorldHeight = this.level.getMinSection(); + this.outOfWorldDirection = GraphDirection.DOWN; } else if (origin.getY() >= this.level.getMaxSection()) { // above the level - this.initOutsideWorldHeight(queue, viewport, searchDistance, frame, - this.level.getMaxSection() - 1, GraphDirection.UP); + this.outOfWorldRadius = 0; + this.outOfWorldHeight = this.level.getMaxSection() - 1; + this.outOfWorldDirection = GraphDirection.UP; } else { + this.outOfWorldRadius = -1; this.initWithinWorld(visitor, queue, viewport, useOcclusionCulling, frame); } } @@ -313,21 +332,23 @@ private void initWithinWorld(RenderSectionVisitor visitor, WriteQueueW->S->E). - private void initOutsideWorldHeight(WriteQueue queue, + private boolean initOutsideWorldHeight(WriteQueue queue, Viewport viewport, float searchDistance, - int frame, - int height, - int direction) - { + int frame) { var origin = viewport.getChunkCoord(); var radius = Mth.floor(searchDistance / 16.0f); + var height = this.outOfWorldHeight; + var direction = this.outOfWorldDirection; + var layer = this.outOfWorldRadius; // Layer 0 - this.tryVisitNode(queue, origin.getX(), height, origin.getZ(), direction, frame, viewport); + if (layer == 0) { + this.tryVisitNode(queue, origin.getX(), height, origin.getZ(), direction, frame, viewport); + } // Complete layers, excluding layer 0 - for (int layer = 1; layer <= radius; layer++) { + else if (layer <= radius) { for (int z = -layer; z < layer; z++) { int x = Math.abs(z) - layer; this.tryVisitNode(queue, origin.getX() + x, height, origin.getZ() + z, direction, frame, viewport); @@ -340,7 +361,7 @@ private void initOutsideWorldHeight(WriteQueue queue, } // Incomplete layers - for (int layer = radius + 1; layer <= 2 * radius; layer++) { + else if (layer <= 2 * radius) { int l = layer - radius; for (int z = -radius; z <= -l; z++) { @@ -363,6 +384,12 @@ private void initOutsideWorldHeight(WriteQueue queue, this.tryVisitNode(queue, origin.getX() + x, height, origin.getZ() + z, direction, frame, viewport); } } + + // nothing more to init + else { + return false; + } + return true; } private void tryVisitNode(WriteQueue queue, int x, int y, int z, int direction, int frame, Viewport viewport) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/DoubleBufferedQueue.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/DoubleBufferedQueue.java index 605623eb66..25393b70eb 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/DoubleBufferedQueue.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/DoubleBufferedQueue.java @@ -109,5 +109,10 @@ private void resize(int length) { private static int getNextSize(int minimumSize, int currentSize) { return Math.max(minimumSize, currentSize << 1); } + + @Override + public boolean isEmpty() { + return this.readIndex == this.writeIndex; + } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/WriteQueue.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/WriteQueue.java index 061ebd16cc..7f71903faa 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/WriteQueue.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/WriteQueue.java @@ -6,4 +6,6 @@ public interface WriteQueue { void ensureCapacity(int numElements); void enqueue(@NotNull E e); + + boolean isEmpty(); } From fa9670bf2a3824540c6c17136e6c5e2d584992a2 Mon Sep 17 00:00:00 2001 From: douira Date: Fri, 23 Jan 2026 20:00:35 +0100 Subject: [PATCH 152/215] Ensure quads are sorted by quad index when their dot products are same in the SNR fast path, which directly transforms the points array, for generating multi-leaves in the BSP tree. this fixes a bug with mesh-baked banners in BBE as reported by @ceeden (#3480) --- .../bsp_tree/InnerPartitionBSPNode.java | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java index b3c3671637..79b7aa6c08 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java @@ -265,6 +265,7 @@ static BSPNode build(BSPWorkspace workspace, IntArrayList indexes, int depth, BS var oppositeDirection = axis + 3; int alignedFacingBitmap = 0; boolean onlyIntervalSide = true; + int positiveSignCount = 0; // collect all the geometry's start and end points in this direction points.clear(); @@ -282,7 +283,11 @@ static BSPNode build(BSPWorkspace workspace, IntArrayList indexes, int depth, BS onlyIntervalSide = false; } - alignedFacingBitmap |= 1 << quad.getFacing().ordinal(); + var facing = quad.getFacing(); + if (facing.getSign() > 0) { + positiveSignCount++; + } + alignedFacingBitmap |= 1 << facing.ordinal(); } // simplified SNR heuristic as seen in TranslucentGeometryCollector#sortTypeHeuristic (case D) @@ -295,7 +300,7 @@ static BSPNode build(BSPWorkspace workspace, IntArrayList indexes, int depth, BS // check if the geometry is aligned to the axis if (onlyIntervalSide) { // this means the already generated points array can be used - return buildSNRLeafNodeFromPoints(workspace, points); + return buildSNRLeafNodeFromPoints(workspace, points, positiveSignCount); } else { return buildSNRLeafNodeFromQuads(workspace, indexes); } @@ -1028,7 +1033,7 @@ static private BSPNode buildSNRLeafNodeFromQuads(BSPWorkspace workspace, IntArra perm[i] = i; } - RadixSort.sortIndirect(perm, keys, false); + RadixSort.sortIndirect(perm, keys, true); for (int i = 0; i < indexCount; i++) { perm[i] = indexBuffer[perm[i]]; @@ -1037,23 +1042,38 @@ static private BSPNode buildSNRLeafNodeFromQuads(BSPWorkspace workspace, IntArra return new LeafMultiBSPNode(BSPSortState.compressIndexes(IntArrayList.wrap(perm), false)); } - static private BSPNode buildSNRLeafNodeFromPoints(BSPWorkspace workspace, LongArrayList points) { + static private BSPNode buildSNRLeafNodeFromPoints(BSPWorkspace workspace, LongArrayList points, int positiveSignCount) { + int pointCount = points.size(); + if (positiveSignCount < pointCount) { + // invert the distance for all points where the quad is facing backwards, + // this is necessary to make the quad order stable relative to the quad index + for (int i = 0; i < pointCount; i++) { + // based one each quad's facing, order them forwards or backwards, + // this means forwards is written from the start and backwards is written from the end + var point = points.getLong(i); + var quadIndex = decodeQuadIndex(point); + if (workspace.get(quadIndex).getFacing().getSign() == -1) { + points.set(i, point ^ 0xFFFFFFFF00000000L); // invert distance bits + } + } + } + // also sort by ascending encoded point but then process as an SNR result - Arrays.sort(points.elements(), 0, points.size()); + Arrays.sort(points.elements(), 0, pointCount); // since the quads are aligned and are all INTERVAL_SIDE, there's no issues with duplicates. // the length of the array is exactly how many quads there are. - int[] quadIndexes = new int[points.size()]; - int forwards = 0; - int backwards = quadIndexes.length - 1; - for (int i = 0; i < points.size(); i++) { + int[] quadIndexes = new int[pointCount]; + int positive = 0; + int negative = positiveSignCount; + for (int i = 0; i < pointCount; i++) { // based one each quad's facing, order them forwards or backwards, // this means forwards is written from the start and backwards is written from the end var quadIndex = decodeQuadIndex(points.getLong(i)); if (workspace.get(quadIndex).getFacing().getSign() == 1) { - quadIndexes[forwards++] = quadIndex; + quadIndexes[positive++] = quadIndex; } else { - quadIndexes[backwards--] = quadIndex; + quadIndexes[negative++] = quadIndex; } } From 0d67681debdf5aab25de37de4af282867356db88 Mon Sep 17 00:00:00 2001 From: IMS212 Date: Wed, 11 Feb 2026 03:26:18 +0100 Subject: [PATCH 153/215] Avoid loading configs if mod list is in a bad state --- .../mods/sodium/client/SodiumClientMod.java | 3 --- .../mods/sodium/fabric/SodiumFabricMod.java | 5 ++++- .../mixin/platform/neoforge/EntrypointMixin.java | 12 +++++++++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java index 9882546b14..efe539a697 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/SodiumClientMod.java @@ -1,6 +1,5 @@ package net.caffeinemc.mods.sodium.client; -import net.caffeinemc.mods.sodium.client.config.ConfigManager; import net.caffeinemc.mods.sodium.client.console.Console; import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; import net.caffeinemc.mods.sodium.client.data.fingerprint.FingerprintMeasure; @@ -23,8 +22,6 @@ public static void onInitialization(String version) { OPTIONS = loadConfig(); - ConfigManager.registerConfigsEarly(); - try { updateFingerprint(); } catch (Throwable t) { diff --git a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/SodiumFabricMod.java b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/SodiumFabricMod.java index efe5bec7f2..aefaba9638 100644 --- a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/SodiumFabricMod.java +++ b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/SodiumFabricMod.java @@ -1,6 +1,7 @@ package net.caffeinemc.mods.sodium.fabric; import net.caffeinemc.mods.sodium.client.SodiumClientMod; +import net.caffeinemc.mods.sodium.client.config.ConfigManager; import net.caffeinemc.mods.sodium.client.render.frapi.SodiumRenderer; import net.caffeinemc.mods.sodium.client.util.FlawlessFrames; import net.caffeinemc.mods.sodium.fabric.config.ConfigLoaderFabric; @@ -19,9 +20,11 @@ public void onInitializeClient() { .getModContainer("sodium") .orElseThrow(NullPointerException::new); - ConfigLoaderFabric.collectConfigEntryPoints(); SodiumClientMod.onInitialization(mod.getMetadata().getVersion().getFriendlyString()); + ConfigLoaderFabric.collectConfigEntryPoints(); + ConfigManager.registerConfigsEarly(); + FabricLoader.getInstance() .getEntrypoints("frex_flawless_frames", Consumer.class) .forEach(api -> api.accept(FlawlessFrames.getProvider())); diff --git a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/EntrypointMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/EntrypointMixin.java index 69ec4fb5f0..c32298fe7d 100644 --- a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/EntrypointMixin.java +++ b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/EntrypointMixin.java @@ -1,10 +1,12 @@ package net.caffeinemc.mods.sodium.mixin.platform.neoforge; import net.caffeinemc.mods.sodium.client.SodiumClientMod; +import net.caffeinemc.mods.sodium.client.config.ConfigManager; import net.caffeinemc.mods.sodium.neoforge.config.ConfigLoaderForge; import net.minecraft.client.Minecraft; import net.minecraft.client.main.GameConfig; import net.neoforged.fml.ModList; +import net.neoforged.fml.loading.FMLLoader; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -17,7 +19,15 @@ public class EntrypointMixin { @Inject(method = "", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Options;loadSelectedResourcePacks(Lnet/minecraft/server/packs/repository/PackRepository;)V")) private void sodium$loadConfig(GameConfig gameConfig, CallbackInfo ci) { - ConfigLoaderForge.collectConfigEntryPoints(); SodiumClientMod.onInitialization(ModList.get().getModContainerById("sodium").map(t -> t.getModInfo().getVersion().toString()).orElse("UNKNOWN")); + + // If any of these return false, FML is in a bad state. We can't continue like this. + if (FMLLoader.getLoadingModList().hasErrors() || !ModList.get().isLoaded("sodium")) { + SodiumClientMod.logger().error("Something has gone horribly wrong in mod loading; Sodium cannot continue."); + return; + } + + ConfigLoaderForge.collectConfigEntryPoints(); + ConfigManager.registerConfigsEarly(); } } From ba6227149a51f2e125aa54278ca10ca3b78e9d4f Mon Sep 17 00:00:00 2001 From: douira Date: Wed, 18 Feb 2026 16:10:04 +0100 Subject: [PATCH 154/215] fix crash when sections are immediate-presented but are already in the render list by only adding the new render flags to the render list (#3494) --- .../client/render/chunk/RenderSectionFlags.java | 7 +++---- .../render/chunk/RenderSectionManager.java | 17 ++++++++++------- .../render/chunk/lists/ChunkRenderList.java | 13 ++++--------- .../render/chunk/lists/SectionCollector.java | 16 ++++++++++++---- 4 files changed, 29 insertions(+), 24 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionFlags.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionFlags.java index c544947de5..904cae0364 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionFlags.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionFlags.java @@ -15,9 +15,8 @@ public class RenderSectionFlags { public static boolean needsRender(int flags) { return (flags & MASK_NEEDS_RENDER) != 0; } - - public static boolean renderingMoreTypesNow(int prevFlags, int newFlags) { - // true if there is some bit that is set now and was not set previously - return ((newFlags & MASK_NEEDS_RENDER) & ~(prevFlags & MASK_NEEDS_RENDER)) != 0; + + public static int getNewRenderFlags(int prevFlags, int newFlags) { + return ((newFlags & MASK_NEEDS_RENDER) & ~(prevFlags & MASK_NEEDS_RENDER)); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index 4eb95fdfee..17c7c6d387 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -436,14 +436,17 @@ private boolean processChunkBuildResults(ArrayList results) { // if result was blocking (or is approximately visible) and section is now newly renderable, force render it since it's probably a newly uncovered chunk. // This also fixes flickering issues with pistons moving blocks and switching between being a mesh and a BE. if (job != null - && (job.isBlocking() || this.isSectionImmediatePresentationCandidate(result.render)) - && RenderSectionFlags.renderingMoreTypesNow(prevFlags, chunkBuildOutput.info.flags)) { - // if there is currently no section collector since there was no graph traversal, - // reuse the previous section collector and use it to generate new extended render lists - if (this.sectionCollector == null) { - this.sectionCollector = this.lastSectionCollector; + && (job.isBlocking() || this.isSectionImmediatePresentationCandidate(result.render))) { + // make sure only to add the section's new render flags to avoid duplicate entries in the render list + var newFlags = RenderSectionFlags.getNewRenderFlags(prevFlags, chunkBuildOutput.info.flags); + if (newFlags != 0) { + // if there is currently no section collector since there was no graph traversal, + // reuse the previous section collector and use it to generate new extended render lists + if (this.sectionCollector == null) { + this.sectionCollector = this.lastSectionCollector; + } + this.sectionCollector.visitWithFlags(result.render, newFlags); } - this.sectionCollector.visit(result.render); } result.render.setLastMeshResultSize(resultSize); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java index eb65bae46c..426f76b9ce 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/ChunkRenderList.java @@ -1,7 +1,6 @@ package net.caffeinemc.mods.sodium.client.render.chunk.lists; import net.caffeinemc.mods.sodium.client.render.chunk.LocalSectionIndex; -import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.RenderSectionFlags; import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; import net.caffeinemc.mods.sodium.client.util.iterator.ByteArrayIterator; @@ -126,16 +125,12 @@ private void sortSections(int relativeCameraSectionX, int relativeCameraSectionY } } - public void add(RenderSection render) { - if (this.size >= RenderRegion.REGION_SIZE) { - throw new ArrayIndexOutOfBoundsException("Render list is full"); - } - + /* + In immediate presentation mode this method is called during the processing of results for immediate-mode built sections. Sometimes this means that their render flags change as a result of the build. However, when the section was already entered into the render list and the render list was full, this resulted in an exception since previously this method threw when the render list was full. It's also not good to have the section be in the render list twice. The method now accepts a flags parameter, which is set to only the new flags when updating the render lists with immediate built results, to prevent this issue. + */ + public void add(int index, int flags) { this.size++; - int index = render.getSectionIndex(); - int flags = render.getFlags(); - if (((flags >>> RenderSectionFlags.HAS_BLOCK_GEOMETRY) & 1) == 1) { this.sectionsWithGeometryMap[index >> 6] |= 1L << (index & 0b111111); if (this.addedSectionsAreSorted) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java index 891590d18e..5e540babbf 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/lists/SectionCollector.java @@ -34,10 +34,9 @@ public SectionCollector(int frame, TaskQueueType importantRebuildQueueType, Task } } - @Override - public void visit(RenderSection section) { + private void visit(RenderSection section, int flags) { // only process section (and associated render list) if it has content that needs rendering - if (section.getFlags() != 0) { + if (flags != 0) { RenderRegion region = section.getRegion(); ChunkRenderList renderList = region.getRenderList(); @@ -47,7 +46,7 @@ public void visit(RenderSection section) { this.renderLists.add(renderList); } - renderList.add(section); + renderList.add(section.getSectionIndex(), flags); } // always add to rebuild lists though, because it might just not be built yet @@ -71,6 +70,15 @@ public void visit(RenderSection section) { } } + @Override + public void visit(RenderSection section) { + this.visit(section, section.getFlags()); + } + + public void visitWithFlags(RenderSection section, int flags) { + this.visit(section, flags); + } + @Override public ObjectArrayList getUnsortedRenderLists() { return this.renderLists; From fdd4361a22734a5daee4ceef3d7be488c6c8df38 Mon Sep 17 00:00:00 2001 From: MoePus <547007249@qq.com> Date: Thu, 19 Feb 2026 08:26:19 +0800 Subject: [PATCH 155/215] faster section abb test (#3099) --- .../chunk/occlusion/OcclusionCuller.java | 10 +- .../client/render/viewport/Viewport.java | 24 ++--- .../viewport/frustum/SimpleFrustum.java | 94 +++++++++++++++++++ 3 files changed, 109 insertions(+), 19 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java index d61969525c..2cb5ae616b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java @@ -234,16 +234,12 @@ private static int nearestToZero(int min, int max) { public static final float CHUNK_SECTION_SIZE = CHUNK_SECTION_RADIUS + CHUNK_SECTION_MARGIN; public static boolean isWithinFrustum(Viewport viewport, RenderSection section) { - return viewport.isBoxVisible(section.getCenterX(), section.getCenterY(), section.getCenterZ(), - CHUNK_SECTION_SIZE, CHUNK_SECTION_SIZE, CHUNK_SECTION_SIZE); + return viewport.isBoxVisible(section.getCenterX(), section.getCenterY(), section.getCenterZ()); } - // this bigger chunk section size is only used for frustum-testing nearby sections with large models - private static final float CHUNK_SECTION_SIZE_NEARBY = CHUNK_SECTION_RADIUS + 2.0f /* bigger model extent */ + 0.125f /* epsilon */; - + // Only used for nearby sections with large models public static boolean isWithinNearbySectionFrustum(Viewport viewport, RenderSection section) { - return viewport.isBoxVisible(section.getCenterX(), section.getCenterY(), section.getCenterZ(), - CHUNK_SECTION_SIZE_NEARBY, CHUNK_SECTION_SIZE_NEARBY, CHUNK_SECTION_SIZE_NEARBY); + return viewport.isBoxVisibleLooser(section.getCenterX(), section.getCenterY(), section.getCenterZ()); } // This method visits sections near the origin that are not in the path of the graph traversal diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java index 999310be3c..9d515c4c8c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java @@ -1,18 +1,18 @@ package net.caffeinemc.mods.sodium.client.render.viewport; -import net.caffeinemc.mods.sodium.client.render.viewport.frustum.Frustum; +import net.caffeinemc.mods.sodium.client.render.viewport.frustum.SimpleFrustum; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import org.joml.Vector3d; public final class Viewport { - private final Frustum frustum; + private final SimpleFrustum frustum; private final CameraTransform transform; private final SectionPos sectionCoords; private final BlockPos blockCoords; - public Viewport(Frustum frustum, Vector3d position) { + public Viewport(SimpleFrustum frustum, Vector3d position) { this.frustum = frustum; this.transform = new CameraTransform(position.x, position.y, position.z); @@ -25,20 +25,20 @@ public Viewport(Frustum frustum, Vector3d position) { this.blockCoords = BlockPos.containing(position.x, position.y, position.z); } - public boolean isBoxVisible(int intOriginX, int intOriginY, int intOriginZ, float floatSizeX, float floatSizeY, float floatSizeZ) { + public boolean isBoxVisible(int intOriginX, int intOriginY, int intOriginZ) { float floatOriginX = (intOriginX - this.transform.intX) - this.transform.fracX; float floatOriginY = (intOriginY - this.transform.intY) - this.transform.fracY; float floatOriginZ = (intOriginZ - this.transform.intZ) - this.transform.fracZ; - return this.frustum.testAab( - floatOriginX - floatSizeX, - floatOriginY - floatSizeY, - floatOriginZ - floatSizeZ, + return this.frustum.testCubeQuick(floatOriginX, floatOriginY, floatOriginZ); + } - floatOriginX + floatSizeX, - floatOriginY + floatSizeY, - floatOriginZ + floatSizeZ - ); + public boolean isBoxVisibleLooser(int intOriginX, int intOriginY, int intOriginZ) { + float floatOriginX = (intOriginX - this.transform.intX) - this.transform.fracX; + float floatOriginY = (intOriginY - this.transform.intY) - this.transform.fracY; + float floatOriginZ = (intOriginZ - this.transform.intZ) - this.transform.fracZ; + + return this.frustum.testCubeWithExtend(floatOriginX, floatOriginY, floatOriginZ, 1.0625f); } public boolean isBoxVisibleDirect(float floatOriginX, float floatOriginY, float floatOriginZ, float floatSize) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java index 2ada093971..031b4100ac 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java @@ -1,12 +1,106 @@ package net.caffeinemc.mods.sodium.client.render.viewport.frustum; +import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.OcclusionCuller; import org.joml.FrustumIntersection; +import org.joml.Vector4f; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; public final class SimpleFrustum implements Frustum { + private float nxX, nxY, nxZ, negNxW; + private float pxX, pxY, pxZ, negPxW; + private float nyX, nyY, nyZ, negNyW; + private float pyX, pyY, pyZ, negPyW; + private float nzX, nzY, nzZ, negNzW; + private float pzX, pzY, pzZ, negPzW; + private final FrustumIntersection frustum; + private static final MethodHandle PLANES_GETTER; + static { + try { + Field field = FrustumIntersection.class.getDeclaredField("planes"); + field.setAccessible(true); + PLANES_GETTER = MethodHandles.lookup().unreflectGetter(field); + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new RuntimeException("Failed to find planes field in JOML", e); + } + } + public SimpleFrustum(FrustumIntersection frustumIntersection) { this.frustum = frustumIntersection; + Vector4f[] planes; + try { + planes = (Vector4f[]) PLANES_GETTER.invokeExact(frustumIntersection); + } catch (Throwable e) { + throw new RuntimeException("Failed to access planes field in FrustumIntersection", e); + } + + nxX = planes[0].x; + nxY = planes[0].y; + nxZ = planes[0].z; + pxX = planes[1].x; + pxY = planes[1].y; + pxZ = planes[1].z; + nyX = planes[2].x; + nyY = planes[2].y; + nyZ = planes[2].z; + pyX = planes[3].x; + pyY = planes[3].y; + pyZ = planes[3].z; + nzX = planes[4].x; + nzY = planes[4].y; + nzZ = planes[4].z; + pzX = planes[5].x; + pzY = planes[5].y; + pzZ = planes[5].z; + + final float size = OcclusionCuller.CHUNK_SECTION_SIZE; + negNxW = -(planes[0].w + nxX * (nxX < 0 ? -size : size) + + nxY * (nxY < 0 ? -size : size) + + nxZ * (nxZ < 0 ? -size : size)); + negPxW = -(planes[1].w + pxX * (pxX < 0 ? -size : size) + + pxY * (pxY < 0 ? -size : size) + + pxZ * (pxZ < 0 ? -size : size)); + negNyW = -(planes[2].w + nyX * (nyX < 0 ? -size : size) + + nyY * (nyY < 0 ? -size : size) + + nyZ * (nyZ < 0 ? -size : size)); + negPyW = -(planes[3].w + pyX * (pyX < 0 ? -size : size) + + pyY * (pyY < 0 ? -size : size) + + pyZ * (pyZ < 0 ? -size : size)); + negNzW = -(planes[4].w + nzX * (nzX < 0 ? -size : size) + + nzY * (nzY < 0 ? -size : size) + + nzZ * (nzZ < 0 ? -size : size)); + negPzW = -(planes[5].w + pzX * (pzX < 0 ? -size : size) + + pzY * (pzY < 0 ? -size : size) + + pzZ * (pzZ < 0 ? -size : size)); + } + + public boolean testCubeQuick(float x, float y, float z) { + // Skip far plane checks because it has been ensured by searchDistance and isWithinRenderDistance check in OcclusionCuller + return nxX * x + nxY * y + nxZ * z >= negNxW && + pxX * x + pxY * y + pxZ * z >= negPxW && + nyX * x + nyY * y + nyZ * z >= negNyW && + pyX * x + pyY * y + pyZ * z >= negPyW && + nzX * x + nzY * y + nzZ * z >= negNzW; + } + + public boolean testCubeWithExtend(float floatOriginX, float floatOriginY, float floatOriginZ, float extend) { + float minX = floatOriginX - extend; + float maxX = floatOriginX + extend; + float minY = floatOriginY - extend; + float maxY = floatOriginY + extend; + float minZ = floatOriginZ - extend; + float maxZ = floatOriginZ + extend; + + return nxX * (nxX < 0 ? minX : maxX) + nxY * (nxY < 0 ? minY : maxY) + nxZ * (nxZ < 0 ? minZ : maxZ) >= negNxW && + pxX * (pxX < 0 ? minX : maxX) + pxY * (pxY < 0 ? minY : maxY) + pxZ * (pxZ < 0 ? minZ : maxZ) >= negPxW && + nyX * (nyX < 0 ? minX : maxX) + nyY * (nyY < 0 ? minY : maxY) + nyZ * (nyZ < 0 ? minZ : maxZ) >= negNyW && + pyX * (pyX < 0 ? minX : maxX) + pyY * (pyY < 0 ? minY : maxY) + pyZ * (pyZ < 0 ? minZ : maxZ) >= negPyW && + nzX * (nzX < 0 ? minX : maxX) + nzY * (nzY < 0 ? minY : maxY) + nzZ * (nzZ < 0 ? minZ : maxZ) >= negNzW && + pzX * (pzX < 0 ? minX : maxX) + pzY * (pzY < 0 ? minY : maxY) + pzZ * (pzZ < 0 ? minZ : maxZ) >= negPzW; } @Override From caff1b721a57e144a7b25f4386127e2f73a64c6d Mon Sep 17 00:00:00 2001 From: IMS212 Date: Mon, 23 Feb 2026 23:32:41 +0100 Subject: [PATCH 156/215] Avoid requiring SimpleFrustum in Viewport --- .../mods/sodium/client/render/viewport/Viewport.java | 10 +++++----- .../sodium/client/render/viewport/frustum/Frustum.java | 4 ++++ .../client/render/viewport/frustum/SimpleFrustum.java | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java index 9d515c4c8c..44ac07e23e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java @@ -1,18 +1,18 @@ package net.caffeinemc.mods.sodium.client.render.viewport; -import net.caffeinemc.mods.sodium.client.render.viewport.frustum.SimpleFrustum; +import net.caffeinemc.mods.sodium.client.render.viewport.frustum.Frustum; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; import org.joml.Vector3d; public final class Viewport { - private final SimpleFrustum frustum; + private final Frustum frustum; private final CameraTransform transform; private final SectionPos sectionCoords; private final BlockPos blockCoords; - public Viewport(SimpleFrustum frustum, Vector3d position) { + public Viewport(Frustum frustum, Vector3d position) { this.frustum = frustum; this.transform = new CameraTransform(position.x, position.y, position.z); @@ -30,7 +30,7 @@ public boolean isBoxVisible(int intOriginX, int intOriginY, int intOriginZ) { float floatOriginY = (intOriginY - this.transform.intY) - this.transform.fracY; float floatOriginZ = (intOriginZ - this.transform.intZ) - this.transform.fracZ; - return this.frustum.testCubeQuick(floatOriginX, floatOriginY, floatOriginZ); + return this.frustum.testSection(floatOriginX, floatOriginY, floatOriginZ); } public boolean isBoxVisibleLooser(int intOriginX, int intOriginY, int intOriginZ) { @@ -38,7 +38,7 @@ public boolean isBoxVisibleLooser(int intOriginX, int intOriginY, int intOriginZ float floatOriginY = (intOriginY - this.transform.intY) - this.transform.fracY; float floatOriginZ = (intOriginZ - this.transform.intZ) - this.transform.fracZ; - return this.frustum.testCubeWithExtend(floatOriginX, floatOriginY, floatOriginZ, 1.0625f); + return this.frustum.testSectionExpanded(floatOriginX, floatOriginY, floatOriginZ, 1.0625f); } public boolean isBoxVisibleDirect(float floatOriginX, float floatOriginY, float floatOriginZ, float floatSize) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/Frustum.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/Frustum.java index c7495555dd..91e06ebfe4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/Frustum.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/Frustum.java @@ -4,4 +4,8 @@ public interface Frustum { boolean testAab(float minX, float minY, float minZ, float maxX, float maxY, float maxZ); int intersectAab(float minX, float minY, float minZ, float maxX, float maxY, float maxZ); + + boolean testSection(float floatOriginX, float floatOriginY, float floatOriginZ); + + boolean testSectionExpanded(float floatOriginX, float floatOriginY, float floatOriginZ, float v); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java index 031b4100ac..66c86e1158 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java @@ -78,7 +78,7 @@ public SimpleFrustum(FrustumIntersection frustumIntersection) { pzZ * (pzZ < 0 ? -size : size)); } - public boolean testCubeQuick(float x, float y, float z) { + public boolean testSection(float x, float y, float z) { // Skip far plane checks because it has been ensured by searchDistance and isWithinRenderDistance check in OcclusionCuller return nxX * x + nxY * y + nxZ * z >= negNxW && pxX * x + pxY * y + pxZ * z >= negPxW && @@ -87,7 +87,7 @@ public boolean testCubeQuick(float x, float y, float z) { nzX * x + nzY * y + nzZ * z >= negNzW; } - public boolean testCubeWithExtend(float floatOriginX, float floatOriginY, float floatOriginZ, float extend) { + public boolean testSectionExpanded(float floatOriginX, float floatOriginY, float floatOriginZ, float extend) { float minX = floatOriginX - extend; float maxX = floatOriginX + extend; float minY = floatOriginY - extend; From 6d2b762b60ba016a38f339a0c8c6b664894c3b68 Mon Sep 17 00:00:00 2001 From: douira Date: Sat, 28 Feb 2026 21:42:44 +0100 Subject: [PATCH 157/215] Refactor and move chunk section size constants. TraversableTree was using the wrong radius without padding, which is now fixed. --- .../render/chunk/occlusion/OcclusionCuller.java | 11 ++--------- .../client/render/chunk/tree/TraversableTree.java | 5 ++--- .../mods/sodium/client/render/viewport/Viewport.java | 11 ++++++++++- .../client/render/viewport/frustum/SimpleFrustum.java | 4 ++-- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java index 2cb5ae616b..5f0d9b3074 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java @@ -56,7 +56,7 @@ public void findVisible(RenderSectionVisitor visitor, processQueue(visitor, viewport, searchDistance, useOcclusionCulling, frame, queues.read(), queues.write()); } - this.addNearbySections(visitor, viewport, searchDistance, frame); + this.addNearbySections(visitor, viewport, frame); } private static void processQueue(RenderSectionVisitor visitor, @@ -226,13 +226,6 @@ private static int nearestToZero(int min, int max) { return clamped; } - // The bounding box of a chunk section must be large enough to contain all possible geometry within it. Block models - // can extend outside a block volume by +/- 1.0 blocks on all axis. Additionally, we make use of a small epsilon - // to deal with floating point imprecision during a frustum check (see GH#2132). - public static final float CHUNK_SECTION_RADIUS = 8.0f /* chunk bounds */; - public static final float CHUNK_SECTION_MARGIN = 1.0f /* maximum model extent */ + 0.125f /* epsilon */; - public static final float CHUNK_SECTION_SIZE = CHUNK_SECTION_RADIUS + CHUNK_SECTION_MARGIN; - public static boolean isWithinFrustum(Viewport viewport, RenderSection section) { return viewport.isBoxVisible(section.getCenterX(), section.getCenterY(), section.getCenterZ()); } @@ -247,7 +240,7 @@ public static boolean isWithinNearbySectionFrustum(Viewport viewport, RenderSect // for all neighboring, even diagonally neighboring, sections around the origin to render them // if their extended bounding box is visible, and they may render large models that extend // outside the 16x16x16 base volume of the section. - private void addNearbySections(RenderSectionVisitor visitor, Viewport viewport, float searchDistance, int frame) { + private void addNearbySections(RenderSectionVisitor visitor, Viewport viewport, int frame) { var origin = viewport.getChunkCoord(); var originX = origin.getX(); var originY = origin.getY(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableTree.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableTree.java index 841e206376..65a7777307 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableTree.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableTree.java @@ -1,7 +1,6 @@ package net.caffeinemc.mods.sodium.client.render.chunk.tree; import net.caffeinemc.mods.sodium.client.render.chunk.lists.CoordinateSectionVisitor; -import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.OcclusionCuller; import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; import org.joml.FrustumIntersection; @@ -188,7 +187,7 @@ void testChild(int childOrigin, int childHalfDim, int level, int inside) { (worldX + childHalfDim) - transform.fracX, (worldY + childHalfDim) - transform.fracY, (worldZ + childHalfDim) - transform.fracZ, - childHalfDim + OcclusionCuller.CHUNK_SECTION_MARGIN); + childHalfDim + Viewport.CHUNK_SECTION_MARGIN); if (intersectionResult == FrustumIntersection.INSIDE) { inside |= INSIDE_FRUSTUM; } else { @@ -238,7 +237,7 @@ boolean testLeafNode(int x, int y, int z, int inside) { (x + 8) - transform.fracX, (y + 8) - transform.fracY, (z + 8) - transform.fracZ, - OcclusionCuller.CHUNK_SECTION_RADIUS)) { + Viewport.CHUNK_SECTION_PADDED_RADIUS)) { return false; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java index 44ac07e23e..b6ea6eb10b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/Viewport.java @@ -6,6 +6,15 @@ import org.joml.Vector3d; public final class Viewport { + // The bounding box of a chunk section must be large enough to contain all possible geometry within it. Block models + // can extend outside a block volume by +/- 1.0 blocks on all axis. Additionally, we make use of a small epsilon + // to deal with floating point imprecision during a frustum check (see GH#2132). + public static final float CHUNK_SECTION_RADIUS = 8.0f /* chunk bounds */; + public static final float CHUNK_SECTION_MARGIN = 1.0f /* maximum model extent */ + 0.125f /* epsilon */; + public static final float CHUNK_SECTION_NEARBY_MARGIN = 2.0f /* larger model extent */ + 0.125f /* epsilon */; + public static final float CHUNK_SECTION_PADDED_RADIUS = CHUNK_SECTION_RADIUS + CHUNK_SECTION_MARGIN; + private static final float LOOSER_MARGIN_EXTRA = CHUNK_SECTION_NEARBY_MARGIN - CHUNK_SECTION_MARGIN; + private final Frustum frustum; private final CameraTransform transform; @@ -38,7 +47,7 @@ public boolean isBoxVisibleLooser(int intOriginX, int intOriginY, int intOriginZ float floatOriginY = (intOriginY - this.transform.intY) - this.transform.fracY; float floatOriginZ = (intOriginZ - this.transform.intZ) - this.transform.fracZ; - return this.frustum.testSectionExpanded(floatOriginX, floatOriginY, floatOriginZ, 1.0625f); + return this.frustum.testSectionExpanded(floatOriginX, floatOriginY, floatOriginZ, LOOSER_MARGIN_EXTRA); } public boolean isBoxVisibleDirect(float floatOriginX, float floatOriginY, float floatOriginZ, float floatSize) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java index 66c86e1158..21b886322b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.render.viewport.frustum; -import net.caffeinemc.mods.sodium.client.render.chunk.occlusion.OcclusionCuller; +import net.caffeinemc.mods.sodium.client.render.viewport.Viewport; import org.joml.FrustumIntersection; import org.joml.Vector4f; @@ -57,7 +57,7 @@ public SimpleFrustum(FrustumIntersection frustumIntersection) { pzY = planes[5].y; pzZ = planes[5].z; - final float size = OcclusionCuller.CHUNK_SECTION_SIZE; + final float size = Viewport.CHUNK_SECTION_PADDED_RADIUS; negNxW = -(planes[0].w + nxX * (nxX < 0 ? -size : size) + nxY * (nxY < 0 ? -size : size) + nxZ * (nxZ < 0 ? -size : size)); From 9085b7ea0da9a909b917c2a22e9455d7f56f63e2 Mon Sep 17 00:00:00 2001 From: douira Date: Sat, 7 Mar 2026 04:39:06 +0100 Subject: [PATCH 158/215] Show quad splitting setting on the options screen unconditionally --- .../client/gui/SodiumConfigBuilder.java | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java index 7026cc7d02..4723bfdb96 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java @@ -18,7 +18,6 @@ import net.caffeinemc.mods.sodium.client.gui.options.control.ControlValueFormatterImpls; import net.caffeinemc.mods.sodium.client.render.chunk.DeferMode; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.QuadSplittingMode; -import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; import net.minecraft.client.*; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; @@ -495,21 +494,19 @@ private OptionPageBuilder buildPerformancePage(ConfigBuilder builder) { ) ); - if (PlatformRuntimeInformation.getInstance().isDevelopmentEnvironment()) { - performancePage.addOptionGroup(builder.createOptionGroup() - .addOption( - builder.createEnumOption(ResourceLocation.parse("sodium:performance.quad_splitting"), QuadSplittingMode.class) - .setStorageHandler(this.sodiumStorage) - .setName(Component.translatable("sodium.options.quad_splitting.name")) - .setTooltip(Component.translatable("sodium.options.quad_splitting.tooltip")) - .setImpact(OptionImpact.MEDIUM) - .setDefaultValue(DEFAULTS.performance.quadSplittingMode) - .setBinding(value -> this.sodiumOpts.performance.quadSplittingMode = value, () -> this.sodiumOpts.performance.quadSplittingMode) - .setEnabled(SodiumClientMod.options().debug.terrainSortingEnabled) - .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) - ) - ); - } + performancePage.addOptionGroup(builder.createOptionGroup() + .addOption( + builder.createEnumOption(ResourceLocation.parse("sodium:performance.quad_splitting"), QuadSplittingMode.class) + .setStorageHandler(this.sodiumStorage) + .setName(Component.translatable("sodium.options.quad_splitting.name")) + .setTooltip(Component.translatable("sodium.options.quad_splitting.tooltip")) + .setImpact(OptionImpact.MEDIUM) + .setDefaultValue(DEFAULTS.performance.quadSplittingMode) + .setBinding(value -> this.sodiumOpts.performance.quadSplittingMode = value, () -> this.sodiumOpts.performance.quadSplittingMode) + .setEnabled(SodiumClientMod.options().debug.terrainSortingEnabled) + .setFlags(OptionFlag.REQUIRES_RENDERER_RELOAD) + ) + ); return performancePage; } From 1dc17a0fa2d868fa52ac875c4a5db7d13711ce5c Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 10 Mar 2026 23:43:21 +0100 Subject: [PATCH 159/215] Properly contextualize exceptions thrown during translucency sorting in crash reports --- .../tasks/ChunkBuilderMeshingTask.java | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java index 253fd5ae73..97a359fe54 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java @@ -169,15 +169,20 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke TranslucentData translucentData = null; if (sortEnabled) { TranslucentData oldData = this.render.getTranslucentData(); - + // Reusing non-dynamic data leads to attempting to sort with it again, // which throws an exception since it can only generate a sorter once. // To prevent this, reusing data is prevented when forceSort is enabled and the data is not dynamic. if (this.forceSort && !(oldData instanceof DynamicData)) { oldData = null; } - - translucentData = collector.getTranslucentData(oldData, this); + + try { + translucentData = collector.getTranslucentData(oldData, this); + } catch (Exception ex) { + // Create a new crash report for exceptions thrown during sort preparation + throw fillCrashInfo(CrashReport.forThrowable(ex, "Encountered exception while preparing for translucency sorting"), slice, null); + } reuseUploadedData = !this.forceSort && translucentData == oldData; } @@ -215,9 +220,14 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke if (reuseUploadedData) { output.markAsReusingUploadedData(); } else if (translucentData instanceof PresentTranslucentData present) { - var sorter = present.getSorter(); - sorter.writeIndexBuffer(this, true); - output.setSorter(sorter); + try { + var sorter = present.getSorter(); + sorter.writeIndexBuffer(this, true); + output.setSorter(sorter); + } catch (Exception ex) { + // Create a new crash report for exceptions thrown during sorting + throw fillCrashInfo(CrashReport.forThrowable(ex, "Encountered exception while writing index buffer for translucent geometry"), slice, null); + } } } @@ -227,11 +237,14 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke private ReportedException fillCrashInfo(CrashReport report, LevelSlice slice, BlockPos pos) { CrashReportCategory crashReportSection = report.addCategory("Block being rendered", 1); - BlockState state = null; - try { - state = slice.getBlockState(pos); - } catch (Exception ignored) {} - CrashReportCategory.populateBlockDetails(crashReportSection, slice, pos, state); + if (pos != null) { + BlockState state = null; + try { + state = slice.getBlockState(pos); + } catch (Exception ignored) { + } + CrashReportCategory.populateBlockDetails(crashReportSection, slice, pos, state); + } crashReportSection.setDetail("Chunk section", this.render); if (this.renderContext != null) { From 62df2a9f7ace05a378ed405716b057104d4f843c Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 4 May 2026 15:32:16 +0200 Subject: [PATCH 160/215] Add `"requireAnnotations":true` to mixins and fix TextureAtlasAccessor accessor (#3539) --- .../chunk/shader/DefaultShaderInterface.java | 4 +-- .../render/texture/TextureAtlasAccessor.java | 10 +++--- .../main/resources/sodium-common.mixins.json | 33 ++++++++++--------- .../main/resources/sodium-fabric.mixins.json | 3 +- .../mod/resources/sodium-neoforge.mixins.json | 3 +- 5 files changed, 28 insertions(+), 25 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/DefaultShaderInterface.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/DefaultShaderInterface.java index 58ac975356..4417b93232 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/DefaultShaderInterface.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/DefaultShaderInterface.java @@ -61,8 +61,8 @@ public void setupState() { double subTexelOffset = 1.0f / CompactChunkVertex.TEXTURE_MAX_VALUE; this.uniformTexCoordShrink.set( - (float) (subTexelOffset - (((1.0D / textureAtlas.getWidth()) / subTexelPrecision))), - (float) (subTexelOffset - (((1.0D / textureAtlas.getHeight()) / subTexelPrecision))) + (float) (subTexelOffset - (((1.0D / textureAtlas.sodium$getWidth()) / subTexelPrecision))), + (float) (subTexelOffset - (((1.0D / textureAtlas.sodium$getHeight()) / subTexelPrecision))) ); this.fogShader.setup(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/texture/TextureAtlasAccessor.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/texture/TextureAtlasAccessor.java index 7f9a6b060b..8d12e742bf 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/texture/TextureAtlasAccessor.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/texture/TextureAtlasAccessor.java @@ -2,13 +2,13 @@ import net.minecraft.client.renderer.texture.TextureAtlas; import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; @Mixin(TextureAtlas.class) public interface TextureAtlasAccessor { - @Accessor - int getWidth(); + @Invoker("getWidth") + int sodium$getWidth(); - @Accessor - int getHeight(); + @Invoker("getHeight") + int sodium$getHeight(); } diff --git a/common/src/main/resources/sodium-common.mixins.json b/common/src/main/resources/sodium-common.mixins.json index abd6c3e28c..62c78ddbe7 100644 --- a/common/src/main/resources/sodium-common.mixins.json +++ b/common/src/main/resources/sodium-common.mixins.json @@ -7,15 +7,16 @@ "defaultRequire": 1 }, "overwrites": { - "conformVisibility": true + "conformVisibility": true, + "requireAnnotations": true }, "client": [ "core.MinecraftMixin", "core.WindowMixin", "core.gui.LevelLoadStatusManagerMixin", + "core.model.TextureAtlasSpriteMixin", "core.model.colors.BlockColorsMixin", "core.model.colors.ItemColorsMixin", - "core.model.TextureAtlasSpriteMixin", "core.render.BlockEntityTypeMixin", "core.render.TextureAtlasMixin", "core.render.VertexFormatMixin", @@ -27,23 +28,23 @@ "core.render.immediate.consumer.VertexMultiConsumerMixin$DoubleMixin", "core.render.immediate.consumer.VertexMultiConsumerMixin$MultipleMixin", "core.render.texture.TextureAtlasAccessor", - "core.render.world.RenderBuffersMixin", "core.render.world.LevelRendererMixin", + "core.render.world.RenderBuffersMixin", "core.world.biome.ClientLevelMixin", - "core.world.chunk.ZeroBitStorageMixin", - "core.world.chunk.SimpleBitStorageMixin", "core.world.chunk.PalettedContainerMixin", + "core.world.chunk.SimpleBitStorageMixin", + "core.world.chunk.ZeroBitStorageMixin", "core.world.map.ClientChunkCacheMixin", - "core.world.map.ClientPacketListenerMixin", "core.world.map.ClientLevelMixin", + "core.world.map.ClientPacketListenerMixin", "debug.checks.threading.MixinRenderSystem", "features.gui.hooks.console.GameRendererMixin", "features.gui.hooks.debug.DebugScreenOverlayMixin", "features.gui.hooks.settings.OptionsScreenMixin", "features.gui.screen.LevelLoadingScreenMixin", "features.options.overlays.GuiMixin", - "features.options.render_layers.LeavesBlockMixin", "features.options.render_layers.ItemBlockRenderTypesMixin", + "features.options.render_layers.LeavesBlockMixin", "features.options.weather.LevelRendererMixin", "features.render.compositing.RenderTargetMixin", "features.render.entity.CubeMixin", @@ -51,9 +52,9 @@ "features.render.entity.cull.EntityRendererMixin", "features.render.entity.shadows.EntityRenderDispatcherMixin", "features.render.frapi.BakedModelMixin", - "features.render.frapi.ModelBlockRendererMixin", - "features.render.frapi.ItemRendererMixin", "features.render.frapi.ItemRendererAccessor", + "features.render.frapi.ItemRendererMixin", + "features.render.frapi.ModelBlockRendererMixin", "features.render.gui.font.BakedGlyphMixin", "features.render.gui.outlines.LevelRendererMixin", "features.render.immediate.DirectionMixin", @@ -67,26 +68,26 @@ "features.render.model.item.ItemRendererMixin", "features.render.particle.SingleQuadParticleMixin", "features.render.world.clouds.LevelRendererMixin", - "features.render.world.sky.FogRendererMixin", "features.render.world.sky.ClientLevelMixin", + "features.render.world.sky.FogRendererMixin", "features.render.world.sky.LevelRendererMixin", "features.shader.uniform.ShaderInstanceMixin", "features.textures.NativeImageAccessor", "features.textures.SpriteContentsInvoker", - "features.textures.animations.tracking.ModelBlockRendererMixin", + "features.textures.animations.tracking.AnimatedTextureAccessor", "features.textures.animations.tracking.GuiGraphicsMixin", + "features.textures.animations.tracking.ModelBlockRendererMixin", + "features.textures.animations.tracking.SpriteContentsFrameInfoAccessor", + "features.textures.animations.tracking.SpriteContentsMixin", + "features.textures.animations.tracking.SpriteContentsTickerMixin", "features.textures.animations.tracking.TextureAtlasMixin", "features.textures.animations.tracking.TextureAtlasSpriteMixin", "features.textures.animations.tracking.TextureSheetParticleMixin", - "features.textures.animations.tracking.AnimatedTextureAccessor", - "features.textures.animations.tracking.SpriteContentsFrameInfoAccessor", - "features.textures.animations.tracking.SpriteContentsTickerMixin", - "features.textures.animations.tracking.SpriteContentsMixin", "features.textures.animations.upload.SpriteContentsAccessor", "features.textures.animations.upload.SpriteContentsAnimatedTextureAccessor", "features.textures.animations.upload.SpriteContentsFrameInfoAccessor", - "features.textures.animations.upload.SpriteContentsTickerAccessor", "features.textures.animations.upload.SpriteContentsInterpolationMixin", + "features.textures.animations.upload.SpriteContentsTickerAccessor", "features.textures.mipmaps.MipmapGeneratorMixin", "features.textures.mipmaps.SpriteContentsMixin", "features.textures.scan.SpriteContentsMixin", diff --git a/fabric/src/main/resources/sodium-fabric.mixins.json b/fabric/src/main/resources/sodium-fabric.mixins.json index 3ac3bf28ce..5722fa5735 100644 --- a/fabric/src/main/resources/sodium-fabric.mixins.json +++ b/fabric/src/main/resources/sodium-fabric.mixins.json @@ -6,7 +6,8 @@ "defaultRequire": 1 }, "overwrites": { - "conformVisibility": true + "conformVisibility": true, + "requireAnnotations": true }, "client": [ "core.model.quad.BakedQuadMixin", diff --git a/neoforge/src/mod/resources/sodium-neoforge.mixins.json b/neoforge/src/mod/resources/sodium-neoforge.mixins.json index 3da278d67d..196ea1a733 100644 --- a/neoforge/src/mod/resources/sodium-neoforge.mixins.json +++ b/neoforge/src/mod/resources/sodium-neoforge.mixins.json @@ -6,7 +6,8 @@ "defaultRequire" : 1 }, "overwrites" : { - "conformVisibility" : true + "conformVisibility" : true, + "requireAnnotations": true }, "client" : [ "core.model.quad.BakedQuadMixin", From f40784d1dc36b23d906afa043a30d34a3637297e Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 3 May 2026 18:20:34 +0200 Subject: [PATCH 161/215] make getSprite a @Shadow mixin to prevent name clashes --- .../sodium/mixin/core/model/quad/BakedQuadMixin.java | 12 +++--------- .../sodium/mixin/core/model/quad/BakedQuadMixin.java | 10 ++-------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/core/model/quad/BakedQuadMixin.java b/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/core/model/quad/BakedQuadMixin.java index 22a9cbd4a0..98cf29d089 100644 --- a/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/core/model/quad/BakedQuadMixin.java +++ b/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/core/model/quad/BakedQuadMixin.java @@ -16,15 +16,11 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(BakedQuad.class) -public class BakedQuadMixin implements BakedQuadView { +public abstract class BakedQuadMixin implements BakedQuadView { @Shadow @Final protected int[] vertices; - @Shadow - @Final - protected TextureAtlasSprite sprite; - @Shadow @Final protected int tintIndex; @@ -84,10 +80,8 @@ public int getLight(int idx) { return this.vertices[ModelQuadUtil.vertexOffset(idx) + ModelQuadUtil.LIGHT_INDEX]; } - @Override - public TextureAtlasSprite getSprite() { - return this.sprite; - } + @Shadow + public abstract TextureAtlasSprite getSprite(); @Override public float getTexU(int idx) { diff --git a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/core/model/quad/BakedQuadMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/core/model/quad/BakedQuadMixin.java index 63099265e6..8ddaa100a6 100644 --- a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/core/model/quad/BakedQuadMixin.java +++ b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/core/model/quad/BakedQuadMixin.java @@ -21,10 +21,6 @@ public abstract class BakedQuadMixin implements BakedQuadView { @Final protected int[] vertices; - @Shadow - @Final - protected TextureAtlasSprite sprite; - @Shadow @Final protected int tintIndex; @@ -86,10 +82,8 @@ public int getLight(int idx) { return this.vertices[ModelQuadUtil.vertexOffset(idx) + ModelQuadUtil.LIGHT_INDEX]; } - @Override - public TextureAtlasSprite getSprite() { - return this.sprite; - } + @Shadow + public abstract TextureAtlasSprite getSprite(); @Override public float getTexU(int idx) { From f45b4f4a8579bf017e5ba8414eee868cdd90eb3d Mon Sep 17 00:00:00 2001 From: xirreal <34745171+xirreal@users.noreply.github.com> Date: Thu, 19 Mar 2026 18:23:09 +0100 Subject: [PATCH 162/215] Round capacity estimates to multiples of 4 to make sure allocations are always aligned (#3522) --- .../mods/sodium/client/gl/arena/GlBufferArena.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java index 9e38fd2f60..2b5df04128 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java @@ -379,7 +379,16 @@ private long estimateNewCapacity(float regionFillFractionInv, List queue) { From 41135048dcdfe8c8c8d69357c90c32b0698cd763 Mon Sep 17 00:00:00 2001 From: IThundxr Date: Thu, 19 Mar 2026 21:19:46 +0100 Subject: [PATCH 163/215] Prefix accessors and invokers and remove unused accessors (#3540) --- .../caffeinemc/mods/sodium/client/util/NativeImageHelper.java | 4 ++-- .../immediate/buffer_builder/sorting/MeshDataAccessor.java | 4 ++-- .../buffer_builder/sorting/MultiBufferSourceMixin.java | 2 +- .../sodium/mixin/features/textures/NativeImageAccessor.java | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/NativeImageHelper.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/NativeImageHelper.java index 2c029e8e32..405279370d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/NativeImageHelper.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/NativeImageHelper.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.util; -import net.caffeinemc.mods.sodium.mixin.features.textures.NativeImageAccessor; import com.mojang.blaze3d.platform.NativeImage; +import net.caffeinemc.mods.sodium.mixin.features.textures.NativeImageAccessor; import java.util.Locale; @@ -13,6 +13,6 @@ public static long getPointerRGBA(NativeImage nativeImage) { } return ((NativeImageAccessor) (Object) nativeImage) // duck type since NativeImage is final - .getPixels(); + .sodium$getPixels(); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MeshDataAccessor.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MeshDataAccessor.java index 096dfd9437..dbb4d47cc8 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MeshDataAccessor.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MeshDataAccessor.java @@ -7,6 +7,6 @@ @Mixin(MeshData.class) public interface MeshDataAccessor { - @Accessor - void setIndexBuffer(ByteBufferBuilder.Result buffer); + @Accessor("indexBuffer") + void sodium$setIndexBuffer(ByteBufferBuilder.Result buffer); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MultiBufferSourceMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MultiBufferSourceMixin.java index 4a6e9d0ee3..a65aa9cf74 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MultiBufferSourceMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/sorting/MultiBufferSourceMixin.java @@ -49,7 +49,7 @@ private static void acceleratedSort(MeshData meshData, ByteBufferBuilder bufferB var sortedPrimitiveIds = VertexSorters.sort(meshData.vertexBuffer(), drawState.vertexCount(), drawState.format().getVertexSize(), sorting); var sortedIndexBuffer = buildSortedIndexBuffer(meshData, bufferBuilder, sortedPrimitiveIds); - ((MeshDataAccessor) meshData).setIndexBuffer(sortedIndexBuffer); + ((MeshDataAccessor) meshData).sodium$setIndexBuffer(sortedIndexBuffer); } @Unique diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/NativeImageAccessor.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/NativeImageAccessor.java index 325176fded..08e3342c7d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/NativeImageAccessor.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/textures/NativeImageAccessor.java @@ -6,6 +6,6 @@ @Mixin(NativeImage.class) public interface NativeImageAccessor { - @Accessor - long getPixels(); + @Accessor("pixels") + long sodium$getPixels(); } From c5b8ce31ef007582907bdfbfa1e21ff57e283fc8 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 29 Mar 2026 16:19:49 +0200 Subject: [PATCH 164/215] Mention which option overrides or overlays are in conflict in the exception Closes #3560 --- .../mods/sodium/client/config/structure/Config.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java index 601ea8c814..72288cf19e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java @@ -90,8 +90,9 @@ private void applyOptionChanges() { throw new IllegalArgumentException("Override by mod '" + modConfig.configId() + "' targets its own option '" + override.target() + "'"); } - if (overrides.put(override.target(), override) != null) { - throw new IllegalArgumentException("Multiple overrides for option '" + override.target() + "'"); + var oldOverride = overrides.put(override.target(), override); + if (oldOverride != null) { + throw new IllegalArgumentException("Multiple overrides for option '" + override.target() + "'! Sources: " + oldOverride.source() + " and " + override.source()); } } @@ -100,8 +101,9 @@ private void applyOptionChanges() { throw new IllegalArgumentException("Overlay by mod '" + modConfig.configId() + "' targets its own option '" + overlay.target() + "'"); } - if (overlays.put(overlay.target(), overlay) != null) { - throw new IllegalArgumentException("Multiple overlays for option '" + overlay.target() + "'"); + var oldOverlay = overlays.put(overlay.target(), overlay); + if (oldOverlay != null) { + throw new IllegalArgumentException("Multiple overlays for option '" + overlay.target() + "'! Sources: " + oldOverlay.source() + " and " + overlay.source()); } } } From b77eb831185eef8050bcea3d6995c56d44801482 Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 27 Apr 2026 16:03:49 +0200 Subject: [PATCH 165/215] Invalidate cached search index sources to be properly indexed after language switch --- .../mods/sodium/client/config/search/SearchIndex.java | 3 +++ .../sodium/client/config/search/SourceStoringIndex.java | 7 +++++++ .../mods/sodium/client/config/search/TextSource.java | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/SearchIndex.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/SearchIndex.java index e3c4a8da3a..7bf6a130af 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/SearchIndex.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/SearchIndex.java @@ -16,6 +16,8 @@ public abstract class SearchIndex { abstract void rebuildIndex(); + abstract void invalidateSourcesForRebuild(); + protected abstract SearchQuerySession createQuery(); public SearchQuerySession startQuery() { @@ -26,6 +28,7 @@ public SearchQuerySession startQuery() { this.buildIndexInitial(); } else if (this.builtLanguage != currentLanguage) { this.builtLanguage = currentLanguage; + this.invalidateSourcesForRebuild(); this.rebuildIndex(); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/SourceStoringIndex.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/SourceStoringIndex.java index 0c935bd54f..a74b33fc9c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/SourceStoringIndex.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/SourceStoringIndex.java @@ -20,4 +20,11 @@ public void register(TextSource source) { public void buildIndexInitial() { this.rebuildIndex(); } + + @Override + protected void invalidateSourcesForRebuild() { + for (var source : this.sources) { + source.invalidateText(); + } + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/TextSource.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/TextSource.java index 41dccd0f5b..0a03277cc2 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/TextSource.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/search/TextSource.java @@ -15,6 +15,10 @@ public String getText() { return this.text; } + void invalidateText() { + this.text = null; + } + public int getLength() { return this.getText().length(); } From 7bd10cf8fd5d6d49ee2458f91596dbce28acd368 Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 27 Apr 2026 16:49:50 +0200 Subject: [PATCH 166/215] Jump to the mod's options when its header in the page list is clicked --- .../client/gui/widgets/PageListWidget.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java index 2d21ff10b4..5b00bb6598 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java @@ -122,7 +122,7 @@ public void switchSelected(Page page) { this.switchSelectedWidget(this.pageToWidget.get(page)); } - private class EntryWidget extends CenteredFlatWidget { + private abstract class EntryWidget extends CenteredFlatWidget { EntryWidget(Dim2i dim, Component label, boolean isSelectable, ColorTheme theme) { super(dim, label, isSelectable, theme); } @@ -131,10 +131,6 @@ private class EntryWidget extends CenteredFlatWidget { super(dim, label, subtitle, isSelectable, theme); } - @Override - void onAction() { - } - public int getScrollTargetStart() { return super.getY(); } @@ -146,11 +142,13 @@ public int getY() { } private class HeaderEntryWidget extends EntryWidget { + private final ModOptions modOptions; private final ResourceLocation icon; private final boolean iconMonochrome; HeaderEntryWidget(Dim2i dim, ModOptions modOptions, ColorTheme theme) { super(dim, Component.literal(modOptions.name()), Component.literal(modOptions.version()), false, theme); + this.modOptions = modOptions; this.icon = modOptions.icon(); this.iconMonochrome = modOptions.iconMonochrome(); } @@ -164,6 +162,21 @@ protected int renderIcon(GuiGraphics graphics, int textColor) { return VideoSettingsScreen.renderIconWithSpacing(graphics, this.icon, textColor, this.iconMonochrome, this.getX(), this.getY(), this.getHeight(), Layout.ICON_MARGIN); } + + @Override + void onAction() { + var pages = this.modOptions.pages(); + if (pages.isEmpty()) { + return; + } + + var firstPage = pages.getFirst(); + var firstPageWidget = PageListWidget.this.pageToWidget.get(firstPage); + if (firstPageWidget != null) { + PageListWidget.this.switchSelectedWidget(firstPageWidget); + } + PageListWidget.this.parent.jumpToPage(firstPage); + } } private abstract class PageEntryWidget

extends EntryWidget { From cac63e00ee2d96a0a5c32a4a62caf7f6ead4adc1 Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 27 Apr 2026 16:58:06 +0200 Subject: [PATCH 167/215] Show that an entry in the options page list leads to an external page more clearly --- .../gui/options/control/ExternalButtonControl.java | 1 + .../mods/sodium/client/gui/widgets/OptionListWidget.java | 2 +- .../mods/sodium/client/gui/widgets/PageListWidget.java | 9 +++++---- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ExternalButtonControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ExternalButtonControl.java index f8ca723a81..e481b393dd 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ExternalButtonControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ExternalButtonControl.java @@ -17,6 +17,7 @@ public class ExternalButtonControl implements Control { public static final Component BASE_BUTTON_TEXT = Component.translatable("sodium.options.open_external_page_button"); + public static final String EXTERNAL_PAGE_PREFIX = "▶ "; private final ExternalButtonOption option; private final Consumer currentScreenConsumer; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java index 33c4f95297..55abb48d98 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java @@ -318,7 +318,7 @@ private static class ExternalPageWidget extends PageHeaderWidget { private final ColorTheme theme; public ExternalPageWidget(Screen screen, AbstractOptionList list, Dim2i dim, ExternalPage page, ColorTheme theme) { - super(list, dim, "▶ ", page.name().getString(), theme); + super(list, dim, ExternalButtonControl.EXTERNAL_PAGE_PREFIX, page.name().getString(), theme); this.screen = screen; this.theme = theme; this.page = page; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java index 5b00bb6598..5aa411c631 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java @@ -12,6 +12,7 @@ import net.caffeinemc.mods.sodium.client.gui.Layout; import net.caffeinemc.mods.sodium.client.gui.VideoSettingsScreen; import net.caffeinemc.mods.sodium.client.gui.options.control.AbstractScrollable; +import net.caffeinemc.mods.sodium.client.gui.options.control.ExternalButtonControl; import net.caffeinemc.mods.sodium.client.util.Dim2i; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.network.chat.Component; @@ -183,8 +184,8 @@ private abstract class PageEntryWidget

extends EntryWidget { final P page; final int scrollTargetStart; - PageEntryWidget(Dim2i dim, P page, ColorTheme theme, int scrollTargetStart) { - super(dim, page.name(), true, theme); + PageEntryWidget(Dim2i dim, P page, Component label, ColorTheme theme, int scrollTargetStart) { + super(dim, label, true, theme); this.page = page; this.scrollTargetStart = scrollTargetStart; } @@ -192,7 +193,7 @@ private abstract class PageEntryWidget

extends EntryWidget { private class OptionPageEntryWidget extends PageEntryWidget { OptionPageEntryWidget(Dim2i dim, Page page, ColorTheme theme, int scrollTargetStart) { - super(dim, page, theme, scrollTargetStart); + super(dim, page, page.name(), theme, scrollTargetStart); } @Override @@ -209,7 +210,7 @@ void onAction() { private class ExternalPageEntryWidget extends PageEntryWidget { ExternalPageEntryWidget(Dim2i dim, ExternalPage page, ColorTheme theme, int scrollTargetStart) { - super(dim, page, theme, scrollTargetStart); + super(dim, page, Component.literal(ExternalButtonControl.EXTERNAL_PAGE_PREFIX).append(page.name()), theme, scrollTargetStart); } @Override From 7d74b8d62020c023ca65efaa5e4c7b418cf203b1 Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 27 Apr 2026 17:22:04 +0200 Subject: [PATCH 168/215] Hide options search bar clear button when search bar is empty --- .../client/gui/widgets/SearchWidget.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/SearchWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/SearchWidget.java index bbf81c3f77..2ef5ffc118 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/SearchWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/SearchWidget.java @@ -3,6 +3,7 @@ import net.caffeinemc.mods.sodium.client.config.ConfigManager; import net.caffeinemc.mods.sodium.client.config.search.SearchQuerySession; import net.caffeinemc.mods.sodium.client.config.structure.Option; +import net.caffeinemc.mods.sodium.client.gui.ButtonTheme; import net.caffeinemc.mods.sodium.client.gui.Colors; import net.caffeinemc.mods.sodium.client.gui.Layout; import net.caffeinemc.mods.sodium.client.util.Dim2i; @@ -20,6 +21,10 @@ public class SearchWidget extends AbstractParentWidget { // maximum distance from its original position that a search result can be moved to improve grouping private static final int MAX_ORDER_DIST_ERROR = 2; + private static final ButtonTheme CLEAR_BUTTON_THEME = new ButtonTheme( + Colors.FOREGROUND, Colors.FOREGROUND, Colors.FOREGROUND_DISABLED, + Colors.BACKGROUND_MEDIUM, Colors.BACKGROUND_LIGHT, Colors.BACKGROUND_LIGHT); + private final Consumer> onSearchResults; private final SearchQuerySession searchQuerySession; private String query = ""; @@ -53,7 +58,8 @@ private void rebuildForWidth(int width) { Component.literal("×"), this::clearSearch, true, - false + false, + CLEAR_BUTTON_THEME ); this.searchBox = new EditBox( @@ -74,11 +80,18 @@ private void rebuildForWidth(int width) { this.addChild(this.searchBox); this.addChild(this.clearButton); + + this.updateClearButtonVisibility(); + } + + private void updateClearButtonVisibility() { + this.clearButton.setVisible(!this.query.isEmpty()); } private void clearSearch() { this.searchBox.setValue(""); this.query = ""; + this.updateClearButtonVisibility(); this.search(); this.setFocused(null); } @@ -89,6 +102,7 @@ private void triggerSearch(String text) { } this.query = text.stripLeading(); + this.updateClearButtonVisibility(); this.search(); } @@ -152,7 +166,7 @@ private int getGroupScore(Option.OptionNameSource a, Option.OptionNameSource b) @Override public void render(@NotNull GuiGraphics graphics, int mouseX, int mouseY, float delta) { - graphics.fill(this.getX(), this.getY(), this.getX() + this.lastRebuildWidth - Layout.BUTTON_SHORT, this.getLimitY(), Colors.BACKGROUND_DEFAULT); + graphics.fill(this.getX(), this.getY(), this.getX() + this.lastRebuildWidth, this.getLimitY(), Colors.BACKGROUND_DEFAULT); this.searchBox.render(graphics, mouseX, mouseY, delta); this.clearButton.render(graphics, mouseX, mouseY, delta); From 92213441e5d021742b468e9ebd92701c0716a67d Mon Sep 17 00:00:00 2001 From: Marco Fernandes Date: Mon, 27 Apr 2026 18:13:38 +0200 Subject: [PATCH 169/215] Implement Alt shortcuts and ESC discard behavior (#2769) (#3604) refactor(gui): introduce KeyBoundButtonWidget to encapsulate ALT keybinding behavior and dynamic label rendering --------- Co-authored-by: douira --- .../client/gui/VideoSettingsScreen.java | 53 ++++++++++++---- .../client/gui/widgets/FlatButtonWidget.java | 15 ++++- .../gui/widgets/KeyBoundButtonWidget.java | 63 +++++++++++++++++++ 3 files changed, 115 insertions(+), 16 deletions(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/KeyBoundButtonWidget.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java index 846c9af3da..0974921299 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java @@ -44,7 +44,8 @@ public class VideoSettingsScreen extends Screen implements ScreenPromptable, Scr private SearchWidget searchWidget; private OptionListWidget optionList; - private FlatButtonWidget applyButton, closeButton, undoButton; + private KeyBoundButtonWidget applyButton, closeButton, undoButton; + private List shortcutButtons = List.of(); private DonationButtonWidget donateButton; private boolean hasPendingChanges; @@ -195,18 +196,7 @@ private void rebuild() { reserveBottomSpace = true; } - this.closeButton = new FlatButtonWidget(new Dim2i(this.getLimitX() - Layout.BUTTON_LONG - ifNotInsetX(Layout.INNER_MARGIN), this.getLimitY() - (ifNotInsetY(Layout.INNER_MARGIN) + Layout.BUTTON_SHORT), Layout.BUTTON_LONG, Layout.BUTTON_SHORT), Component.translatable("gui.done"), this::onClose, true, false); - this.addRenderableWidget(this.closeButton); - - if (stackVertically) { - this.applyButton = new FlatButtonWidget(new Dim2i(this.closeButton.getX(), this.closeButton.getY() - (Layout.INNER_MARGIN + Layout.BUTTON_SHORT), Layout.BUTTON_LONG, Layout.BUTTON_SHORT), Component.translatable("sodium.options.buttons.apply"), ConfigManager.CONFIG::applyAllOptions, true, false); - this.undoButton = new FlatButtonWidget(new Dim2i(this.applyButton.getX(), this.applyButton.getY() - (Layout.INNER_MARGIN + Layout.BUTTON_SHORT), Layout.BUTTON_LONG, Layout.BUTTON_SHORT), Component.translatable("sodium.options.buttons.undo"), this::undoChanges, true, false); - } else { - this.applyButton = new FlatButtonWidget(new Dim2i(this.closeButton.getX() - Layout.INNER_MARGIN - Layout.BUTTON_LONG, this.getLimitY() - (Layout.INNER_MARGIN + Layout.BUTTON_SHORT), Layout.BUTTON_LONG, Layout.BUTTON_SHORT), Component.translatable("sodium.options.buttons.apply"), ConfigManager.CONFIG::applyAllOptions, true, false); - this.undoButton = new FlatButtonWidget(new Dim2i(this.applyButton.getX() - Layout.INNER_MARGIN - Layout.BUTTON_LONG, this.getLimitY() - (Layout.INNER_MARGIN + Layout.BUTTON_SHORT), Layout.BUTTON_LONG, Layout.BUTTON_SHORT), Component.translatable("sodium.options.buttons.undo"), this::undoChanges, true, false); - } - this.addRenderableWidget(this.undoButton); - this.addRenderableWidget(this.applyButton); + this.rebuildActionButtons(stackVertically); this.donateButton = new DonationButtonWidget(this, this::openDonationPage, this::hideDonationButton); this.addRenderableWidget(this.searchWidget); @@ -232,6 +222,27 @@ private void rebuild() { ); } + private void rebuildActionButtons(boolean stackVertically) { + int buttonW = Layout.BUTTON_LONG; + int buttonH = Layout.BUTTON_SHORT; + int closeX = this.getLimitX() - buttonW - ifNotInsetX(Layout.INNER_MARGIN); + int closeY = this.getLimitY() - (ifNotInsetY(Layout.INNER_MARGIN) + buttonH); + + int dx = stackVertically ? 0 : -(Layout.INNER_MARGIN + buttonW); + int dy = stackVertically ? -(Layout.INNER_MARGIN + buttonH) : 0; + int actionRowX = closeX + dx; + int actionRowY = stackVertically ? closeY + dy : this.getLimitY() - (Layout.INNER_MARGIN + buttonH); + + this.closeButton = new KeyBoundButtonWidget(new Dim2i(closeX, closeY, buttonW, buttonH), Component.translatable("gui.done"), this::onClose, true, false, GLFW.GLFW_KEY_D); + this.applyButton = new KeyBoundButtonWidget(new Dim2i(actionRowX, actionRowY, buttonW, buttonH), Component.translatable("sodium.options.buttons.apply"), ConfigManager.CONFIG::applyAllOptions, true, false, GLFW.GLFW_KEY_A); + this.undoButton = new KeyBoundButtonWidget(new Dim2i(actionRowX + dx, actionRowY + dy, buttonW, buttonH), Component.translatable("sodium.options.buttons.undo"), this::undoChanges, true, false, GLFW.GLFW_KEY_U); + + this.addRenderableWidget(this.closeButton); + this.addRenderableWidget(this.undoButton); + this.addRenderableWidget(this.applyButton); + this.shortcutButtons = List.of(this.closeButton, this.applyButton, this.undoButton); + } + private void updateScreenDimensions() { // size screen to not be too wide var baseContentWidth = Layout.PAGE_LIST_WIDTH + Layout.INNER_MARGIN + Layout.OPTION_WIDTH + Layout.OPTION_LIST_SCROLLBAR_OFFSET + Layout.SCROLLBAR_WIDTH + Layout.TOOLTIP_OUTER_MARGIN; @@ -396,6 +407,22 @@ public boolean keyReleased(int keyCode, int scanCode, int modifiers) { } } + // dispatch ALT+letter shortcuts to any keybound buttons on this screen + for (var button : this.shortcutButtons) { + if (button.tryActivateShortcut(keyCode, modifiers)) { + return true; + } + } + + // ESC closes this screen without saving any pending changes + if (keyCode == GLFW.GLFW_KEY_ESCAPE) { + if (this.hasPendingChanges) { + this.undoChanges(); + } + + this.onClose(); + } + return super.keyReleased(keyCode, scanCode, modifiers); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/FlatButtonWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/FlatButtonWidget.java index 86e6a9bb2c..02343160ea 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/FlatButtonWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/FlatButtonWidget.java @@ -67,8 +67,9 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { } if (this.label != null) { - int strWidth = this.font.width(this.label); - this.drawString(graphics, this.label, this.leftAlign ? this.getX() + Layout.TEXT_LEFT_PADDING : (this.getCenterX() - (strWidth / 2)), this.getCenterY() - this.font.lineHeight / 2, textColor); + Component rendered = this.getRenderedLabel(); + int strWidth = this.font.width(rendered); + this.drawString(graphics, rendered, this.leftAlign ? this.getX() + Layout.TEXT_LEFT_PADDING : (this.getCenterX() - (strWidth / 2)), this.getCenterY() - this.font.lineHeight / 2, textColor); } if (this.enabled && this.selected) { @@ -84,6 +85,10 @@ protected int getTextColor() { return this.enabled ? this.theme.themeLighter : this.theme.themeDarker; } + protected Component getRenderedLabel() { + return this.label; + } + public void setSelected(boolean selected) { this.selected = selected; } @@ -116,7 +121,7 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { return false; } - private void doAction() { + protected void doAction() { this.action.run(); this.playClickSound(); } @@ -160,4 +165,8 @@ public static Style defaults() { public boolean isVisible() { return this.visible; } + + public boolean isEnabled() { + return this.enabled; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/KeyBoundButtonWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/KeyBoundButtonWidget.java new file mode 100644 index 0000000000..af60ec726a --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/KeyBoundButtonWidget.java @@ -0,0 +1,63 @@ +package net.caffeinemc.mods.sodium.client.gui.widgets; + +import net.caffeinemc.mods.sodium.client.util.Dim2i; +import net.minecraft.ChatFormatting; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import org.lwjgl.glfw.GLFW; + +/** + * Activates when its bound key is pressed with ALT held. While ALT is held, underlines the first occurrence of the shortcut key in the label; if the label doesn't contain the key (e.g. due to translation), appends the key in square brackets so the binding stays discoverable. + */ +public class KeyBoundButtonWidget extends FlatButtonWidget { + private final int shortcutKey; + private final Component underlinedLabel; + + public KeyBoundButtonWidget(Dim2i dim, Component label, Runnable action, boolean drawBackground, boolean leftAlign, int shortcutKey) { + super(dim, label, action, drawBackground, leftAlign); + this.shortcutKey = shortcutKey; + this.underlinedLabel = buildUnderlinedLabel(label, shortcutKey); + } + + private static Component buildUnderlinedLabel(Component label, int shortcutKey) { + var text = label.getString(); + var keyChar = (char) shortcutKey; // GLFW letter key codes equal their uppercase ASCII char + var index = indexOfIgnoreCase(text, keyChar); + + if (index >= 0) { + return Component.empty() + .append(Component.literal(text.substring(0, index))) + .append(Component.literal(text.substring(index, index + 1)).withStyle(ChatFormatting.UNDERLINE)) + .append(Component.literal(text.substring(index + 1))); + } + + return Component.empty() + .append(label) + .append(Component.literal(" [")) + .append(Component.literal(String.valueOf(keyChar)).withStyle(ChatFormatting.UNDERLINE)) + .append(Component.literal("]")); + } + + private static int indexOfIgnoreCase(String text, char target) { + var lowerTarget = Character.toLowerCase(target); + for (int i = 0; i < text.length(); i++) { + if (Character.toLowerCase(text.charAt(i)) == lowerTarget) { + return i; + } + } + return -1; + } + + @Override + protected Component getRenderedLabel() { + return this.isEnabled() && Screen.hasAltDown() ? this.underlinedLabel : super.getRenderedLabel(); + } + + public boolean tryActivateShortcut(int keyCode, int modifiers) { + if (this.isEnabled() && this.isVisible() && (modifiers & GLFW.GLFW_MOD_ALT) != 0 && keyCode == this.shortcutKey) { + this.doAction(); + return true; + } + return false; + } +} From 490dc7d97194916710b792aa2683b86a505df7af Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 27 Apr 2026 21:33:37 +0200 Subject: [PATCH 170/215] Reset button for options with hold-shift to reset functionality --- .../client/config/structure/Option.java | 4 + .../config/structure/StatefulOption.java | 5 + .../gui/options/control/ControlElement.java | 2 +- .../gui/options/control/CyclingControl.java | 9 +- .../gui/options/control/SliderControl.java | 22 +++-- .../control/StatefulControlElement.java | 43 +++++++++ .../gui/options/control/TickBoxControl.java | 10 +- .../client/gui/widgets/OptionListWidget.java | 72 +++++++++++---- .../client/gui/widgets/ResetButton.java | 87 ++++++++++++++++++ .../sodium/textures/gui/reset_button.png | Bin 0 -> 140 bytes 10 files changed, 217 insertions(+), 37 deletions(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/StatefulControlElement.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ResetButton.java create mode 100644 common/src/main/resources/assets/sodium/textures/gui/reset_button.png diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Option.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Option.java index 99ef4103a7..4503c1557f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Option.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Option.java @@ -63,6 +63,10 @@ void resetFromBinding() { // no-op } + public void resetToDefault() { + // no-op for non-stateful options + } + public boolean isEnabled() { return this.enabled.get(this.state); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/StatefulOption.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/StatefulOption.java index 331373254f..eeab839e11 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/StatefulOption.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/StatefulOption.java @@ -84,6 +84,11 @@ public void modifyValue(V value) { } } + @Override + public void resetToDefault() { + this.modifyValue(this.defaultValue.get(this.state)); + } + @Override void loadValueInitial() { this.value = this.binding.load(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java index 2780a2e2d5..7e20ec8160 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java @@ -70,7 +70,7 @@ protected MutableComponent formatDisabledControlValue(Component value) { .withItalic(true)); } - private String truncateLabelToFit(String name) { + protected String truncateLabelToFit(String name) { return truncateTextToFit(name, this.getWidth() - this.getContentWidth() - 20); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java index 13dd00e227..63b3b53601 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java @@ -38,7 +38,7 @@ public int getMaxWidth() { return 70; } - private static class CyclingControlElement> extends ControlElement { + private static class CyclingControlElement> extends StatefulControlElement { private final EnumOption option; private final T[] baseValues; @@ -50,7 +50,7 @@ public CyclingControlElement(AbstractOptionList list, EnumOption option, Dim2 } @Override - public Option getOption() { + public EnumOption getOption() { return this.option; } @@ -58,7 +58,7 @@ public Option getOption() { public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { super.render(graphics, mouseX, mouseY, delta); - if (!this.option.showControl()) { + if (!this.option.showControl() || this.isResetOverlayActive()) { return; } @@ -71,6 +71,9 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (super.mouseClicked(mouseX, mouseY, button)) return true; + if (this.isResetOverlayActive()) return false; + if (this.option.isEnabled() && button == 0 && this.isMouseOver(mouseX, mouseY)) { cycleControl(Screen.hasShiftDown()); return true; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java index f324ccc633..58778b2e64 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java @@ -2,7 +2,6 @@ import com.mojang.blaze3d.platform.InputConstants; import net.caffeinemc.mods.sodium.client.config.structure.IntegerOption; -import net.caffeinemc.mods.sodium.client.config.structure.Option; import net.caffeinemc.mods.sodium.client.config.structure.StatefulOption; import net.caffeinemc.mods.sodium.client.gui.ColorTheme; import net.caffeinemc.mods.sodium.client.gui.Colors; @@ -34,7 +33,7 @@ public int getMaxWidth() { throw new UnsupportedOperationException("Not implemented"); } - static class SliderControlElement extends ControlElement { + static class SliderControlElement extends StatefulControlElement { private static final int THUMB_WIDTH = 2, TRACK_HEIGHT = 1; private final IntegerOption option; @@ -53,7 +52,7 @@ public SliderControlElement(AbstractOptionList list, IntegerOption option, Dim2i } @Override - public Option getOption() { + public IntegerOption getOption() { return this.option; } @@ -75,6 +74,13 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { int labelWidth = this.font.width(label); + // render the label first and then the slider to prevent the highlight rect from darkening the slider + super.render(graphics, mouseX, mouseY, delta); + + if (!this.option.showControl() || this.isResetOverlayActive()) { + return; + } + boolean drawSlider = isEnabled && (this.hovered || this.isFocused()); if (drawSlider) { this.contentWidth = sliderWidth + labelWidth; @@ -82,13 +88,6 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { this.contentWidth = labelWidth; } - // render the label first and then the slider to prevent the highlight rect from darkening the slider - super.render(graphics, mouseX, mouseY, delta); - - if (!this.option.showControl()) { - return; - } - if (drawSlider) { this.thumbPosition = this.getThumbPositionForValue(value); @@ -148,6 +147,9 @@ private int getValueForThumbPosition() { public boolean mouseClicked(double mouseX, double mouseY, int button) { this.sliderHeld = false; + if (super.mouseClicked(mouseX, mouseY, button)) return true; + if (this.isResetOverlayActive()) return false; + if (this.option.isEnabled() && button == 0 && this.isMouseOver(mouseX, mouseY)) { if (this.isMouseOverSlider((int) mouseX, (int) mouseY)) { this.setValueFromMouse(mouseX); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/StatefulControlElement.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/StatefulControlElement.java new file mode 100644 index 0000000000..9f13665a88 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/StatefulControlElement.java @@ -0,0 +1,43 @@ +package net.caffeinemc.mods.sodium.client.gui.options.control; + +import net.caffeinemc.mods.sodium.client.config.structure.StatefulOption; +import net.caffeinemc.mods.sodium.client.gui.ColorTheme; +import net.caffeinemc.mods.sodium.client.gui.widgets.ResetButton; +import net.caffeinemc.mods.sodium.client.util.Dim2i; +import net.minecraft.client.gui.GuiGraphics; + +public abstract class StatefulControlElement extends ControlElement { + protected final ResetButton resetButton; + + public StatefulControlElement(AbstractOptionList list, Dim2i dim, ColorTheme theme) { + super(list, dim, theme); + + this.resetButton = new ResetButton(this, () -> this.getOption().resetToDefault()); + } + + public boolean isResetOverlayActive() { + return this.resetButton.isActive(); + } + + public abstract StatefulOption getOption(); + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + this.hovered = this.isMouseOver(mouseX, mouseY); + + super.render(graphics, mouseX, mouseY, delta); + + this.resetButton.render(graphics, mouseX, mouseY, delta); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + return this.resetButton.mouseClicked(mouseX, mouseY, button); + } + + @Override + protected String truncateLabelToFit(String name) { + int rightReserve = this.isResetOverlayActive() ? this.resetButton.getWidth() : this.getContentWidth() + 20; + return truncateTextToFit(name, this.getWidth() - rightReserve); + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java index c8113d18d1..f204f564d0 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java @@ -1,7 +1,6 @@ package net.caffeinemc.mods.sodium.client.gui.options.control; import net.caffeinemc.mods.sodium.client.config.structure.BooleanOption; -import net.caffeinemc.mods.sodium.client.config.structure.Option; import net.caffeinemc.mods.sodium.client.config.structure.StatefulOption; import net.caffeinemc.mods.sodium.client.gui.ColorTheme; import net.caffeinemc.mods.sodium.client.gui.Colors; @@ -32,7 +31,7 @@ public StatefulOption getOption() { return this.option; } - private static class TickBoxControlElement extends ControlElement { + private static class TickBoxControlElement extends StatefulControlElement { private final BooleanOption option; public TickBoxControlElement(AbstractOptionList list, BooleanOption option, Dim2i dim, ColorTheme theme) { @@ -42,7 +41,7 @@ public TickBoxControlElement(AbstractOptionList list, BooleanOption option, Dim2 } @Override - public Option getOption() { + public BooleanOption getOption() { return this.option; } @@ -50,7 +49,7 @@ public Option getOption() { public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { super.render(graphics, mouseX, mouseY, delta); - if (!this.option.showControl()) { + if (!this.option.showControl() || this.isResetOverlayActive()) { return; } @@ -94,6 +93,9 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (super.mouseClicked(mouseX, mouseY, button)) return true; + if (this.isResetOverlayActive()) return false; + if (this.option.isEnabled() && button == 0 && this.isMouseOver(mouseX, mouseY)) { toggleControl(); return true; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java index 55abb48d98..1051b2187e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java @@ -87,7 +87,7 @@ private int renderFilteredOptions(Screen screen, int x, int y, int width) { // Add mod header if mod has changed if (lastSource == null || lastSource.getModOptions() != modOptions) { listHeight += Layout.OPTION_MOD_MARGIN; - var modHeader = new ModHeaderWidget(this, new Dim2i(x, y + listHeight, width, this.entryHeight), modOptions.name(), theme, modOptions.icon(), modOptions.iconMonochrome()); + var modHeader = new ModHeaderWidget(this, new Dim2i(x, y + listHeight, width, this.entryHeight), modOptions, theme); this.addRenderableChild(modHeader); listHeight += this.entryHeight; } @@ -95,7 +95,7 @@ private int renderFilteredOptions(Screen screen, int x, int y, int width) { // Add page header if page has changed if (lastSource == null || lastSource.getPage() != page) { listHeight += Layout.OPTION_PAGE_MARGIN; - var pageHeader = new PageHeaderWidget(this, new Dim2i(x, y + listHeight, width, this.entryHeight), page.name().getString(), theme); + var pageHeader = new PageHeaderWidget(this, new Dim2i(x, y + listHeight, width, this.entryHeight), page, theme); this.addRenderableChild(pageHeader); listHeight += this.entryHeight; } @@ -130,7 +130,7 @@ private int renderAllPages(Screen screen, int x, int y, int width) { // Add mod header listHeight += Layout.OPTION_MOD_MARGIN; var modHeaderStart = listHeight; - var modHeader = new ModHeaderWidget(this, new Dim2i(x, y + listHeight, width, this.entryHeight), modOptions.name(), theme, modOptions.icon(), modOptions.iconMonochrome()); + var modHeader = new ModHeaderWidget(this, new Dim2i(x, y + listHeight, width, this.entryHeight), modOptions, theme); this.addRenderableChild(modHeader); listHeight += this.entryHeight; @@ -141,7 +141,7 @@ private int renderAllPages(Screen screen, int x, int y, int width) { if (page instanceof OptionPage) { // Add page header listHeight += Layout.OPTION_PAGE_MARGIN; - var pageHeader = new PageHeaderWidget(this, new Dim2i(x, y + listHeight, width, this.entryHeight), page.name().getString(), theme); + var pageHeader = new PageHeaderWidget(this, new Dim2i(x, y + listHeight, width, this.entryHeight), page, theme); this.addRenderableChild(pageHeader); listHeight += this.entryHeight; @@ -237,13 +237,16 @@ private abstract static class HeaderWidget extends AbstractWidget { final String title; final int textColor; final int backgroundColor; + @org.jspecify.annotations.Nullable + final ResetButton resetButton; - public HeaderWidget(AbstractOptionList list, Dim2i dim, String title, int textColor, int backgroundColor) { + public HeaderWidget(AbstractOptionList list, Dim2i dim, String title, int textColor, int backgroundColor, @org.jspecify.annotations.Nullable Runnable resetAction) { super(dim); this.list = list; this.title = title; this.textColor = textColor; this.backgroundColor = backgroundColor; + this.resetButton = resetAction == null ? null : new ResetButton(this, resetAction); } @Override @@ -251,11 +254,24 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { this.hovered = this.isMouseOver(mouseX, mouseY); this.drawRect(graphics, this.getX(), this.getY(), this.getLimitX(), this.getLimitY(), this.backgroundColor); - this.drawString(graphics, this.truncateLabelToFit(this.title), this.getX() + Layout.OPTION_PAGE_MARGIN, this.getCenterY() + Layout.REGULAR_TEXT_BASELINE_OFFSET, this.textColor); + this.drawString(graphics, this.truncateLabelToFit(this.title, 12), this.getX() + Layout.OPTION_PAGE_MARGIN, this.getCenterY() + Layout.REGULAR_TEXT_BASELINE_OFFSET, this.textColor); + + if (this.resetButton != null) { + this.resetButton.render(graphics, mouseX, mouseY, delta); + } } - protected String truncateLabelToFit(String name) { - return truncateTextToFit(name, this.getWidth() - 12); + protected int rightReservedWidth() { + return this.resetButton != null ? this.resetButton.getWidth() : 0; + } + + protected String truncateLabelToFit(String name, int padding) { + return truncateTextToFit(name, this.getWidth() - padding - this.rightReservedWidth()); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + return this.resetButton != null && this.resetButton.mouseClicked(mouseX, mouseY, button); } @Override @@ -273,10 +289,10 @@ private static class ModHeaderWidget extends HeaderWidget { final ResourceLocation icon; final boolean iconMonochrome; - public ModHeaderWidget(AbstractOptionList list, Dim2i dim, String title, ColorTheme theme, ResourceLocation icon, boolean iconMonochrome) { - super(list, dim, ChatFormatting.BOLD + title, theme.themeLighter, Colors.BACKGROUND_DARKER); - this.icon = icon; - this.iconMonochrome = iconMonochrome; + public ModHeaderWidget(AbstractOptionList list, Dim2i dim, ModOptions modOptions, ColorTheme theme) { + super(list, dim, ChatFormatting.BOLD + modOptions.name(), theme.themeLighter, Colors.BACKGROUND_DARKER, () -> resetAllOptions(modOptions)); + this.icon = modOptions.icon(); + this.iconMonochrome = modOptions.iconMonochrome(); } @Override @@ -291,23 +307,41 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { textOffset = VideoSettingsScreen.renderIconWithSpacing(graphics, this.icon, this.textColor, this.iconMonochrome, this.getX(), this.getY(), this.getHeight(), Layout.ICON_MARGIN); textY = this.getCenterY() + Layout.ICON_TEXT_BASELINE_OFFSET; } - this.drawString(graphics, truncateTextToFit(this.title, this.getWidth() - textOffset), this.getX() + textOffset, textY, this.textColor); + this.drawString(graphics, this.truncateLabelToFit(this.title, textOffset), this.getX() + textOffset, textY, this.textColor); + + if (this.resetButton != null) { + this.resetButton.render(graphics, mouseX, mouseY, delta); + } } } private static class PageHeaderWidget extends HeaderWidget { - public PageHeaderWidget(AbstractOptionList list, Dim2i dim, String title, ColorTheme theme) { - this(list, dim, "◆ ", title, theme); + public PageHeaderWidget(AbstractOptionList list, Dim2i dim, Page page, ColorTheme theme) { + this(list, dim, "◆ ", page.name().getString(), theme, () -> resetAllOptions(page)); } - PageHeaderWidget(AbstractOptionList list, Dim2i dim, String prefix, String title, ColorTheme theme) { - super(list, dim, prefix + title, theme.theme, Colors.BACKGROUND_DEFAULT); + PageHeaderWidget(AbstractOptionList list, Dim2i dim, String prefix, String title, ColorTheme theme, @org.jspecify.annotations.Nullable Runnable resetAction) { + super(list, dim, prefix + title, theme.theme, Colors.BACKGROUND_DEFAULT, resetAction); } } private static class GroupHeaderWidget extends HeaderWidget { public GroupHeaderWidget(AbstractOptionList list, Dim2i dim, String title) { - super(list, dim, ChatFormatting.BOLD + title, Colors.FOREGROUND, Colors.BACKGROUND_MEDIUM); + super(list, dim, ChatFormatting.BOLD + title, Colors.FOREGROUND, Colors.BACKGROUND_MEDIUM, null); + } + } + + private static void resetAllOptions(ModOptions modOptions) { + for (Page page : modOptions.pages()) { + resetAllOptions(page); + } + } + + private static void resetAllOptions(Page page) { + for (OptionGroup group : page.groups()) { + for (Option option : group.options()) { + option.resetToDefault(); + } } } @@ -318,7 +352,7 @@ private static class ExternalPageWidget extends PageHeaderWidget { private final ColorTheme theme; public ExternalPageWidget(Screen screen, AbstractOptionList list, Dim2i dim, ExternalPage page, ColorTheme theme) { - super(list, dim, ExternalButtonControl.EXTERNAL_PAGE_PREFIX, page.name().getString(), theme); + super(list, dim, ExternalButtonControl.EXTERNAL_PAGE_PREFIX, page.name().getString(), theme, null); this.screen = screen; this.theme = theme; this.page = page; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ResetButton.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ResetButton.java new file mode 100644 index 0000000000..d234e921e6 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ResetButton.java @@ -0,0 +1,87 @@ +package net.caffeinemc.mods.sodium.client.gui.widgets; + +import net.caffeinemc.mods.sodium.client.gui.GuiTint; +import net.caffeinemc.mods.sodium.client.gui.Layout; +import net.caffeinemc.mods.sodium.client.util.Dim2i; +import net.minecraft.client.gui.ComponentPath; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.navigation.FocusNavigationEvent; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.resources.ResourceLocation; +import org.jspecify.annotations.Nullable; + +/** + * Right-aligned reset overlay shown when the parent row is hovered while SHIFT is held. + * It derives is positioning from the parent so it tracks the parent's scroll position automatically. + */ +public class ResetButton extends AbstractWidget { + private static final ResourceLocation ICON = ResourceLocation.fromNamespaceAndPath("sodium", "textures/gui/reset_button.png"); + private static final int ICON_SIZE = 10; + private static final int COLOR = 0xFFFF8C30; + + private final AbstractWidget parent; + private final Runnable action; + + public ResetButton(AbstractWidget parent, Runnable action) { + super(new Dim2i(0, 0, Layout.BUTTON_SHORT, 0)); + this.parent = parent; + this.action = action; + } + + public static boolean isShiftHeld() { + return Screen.hasShiftDown(); + } + + public boolean isActive() { + return this.parent.isHovered() && isShiftHeld(); + } + + @Override + public int getX() { + return this.parent.getLimitX() - this.getWidth(); + } + + @Override + public int getY() { + return this.parent.getY(); + } + + @Override + public int getHeight() { + return this.parent.getHeight(); + } + + @Override + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + if (!this.isActive()) { + return; + } + + final int x = this.getCenterX() - ICON_SIZE / 2; + final int y = this.getCenterY() - ICON_SIZE / 2; + + GuiTint.withTint(COLOR, () -> + graphics.blit(ICON, x, y, ICON_SIZE, ICON_SIZE, 0.0f, 0.0f, ICON_SIZE, ICON_SIZE, ICON_SIZE, ICON_SIZE)); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!isShiftHeld() || button != 0) { + return false; + } + + if (!this.parent.isMouseOver(mouseX, mouseY) || !this.isMouseOver(mouseX, mouseY)) { + return false; + } + + this.action.run(); + this.playClickSound(); + return true; + } + + @Override + public @Nullable ComponentPath nextFocusPath(FocusNavigationEvent event) { + // Only reachable via SHIFT-hover + click. + return null; + } +} diff --git a/common/src/main/resources/assets/sodium/textures/gui/reset_button.png b/common/src/main/resources/assets/sodium/textures/gui/reset_button.png new file mode 100644 index 0000000000000000000000000000000000000000..88e548a6304f14265b5fcb7156dc714e72ab09ba GIT binary patch literal 140 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4F%}28J29*~C-V}>ar1O>43U`H zI>D2RL4k+a_|gCUGWuLXtDnu-D#Ds$y;ErFMjrLb*d3;3{(qQ!Coz;A+&203O_qSU nSIV8w$u7M9e%i6~d!pDc**HH|%RCzjG?Bs6)z4*}Q$iB}VhJuh literal 0 HcmV?d00001 From cb187bd672bb91126aeb831199292603fb94c22a Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 27 Apr 2026 21:57:21 +0200 Subject: [PATCH 171/215] Refactor layout constants into the Layout class --- .../mods/sodium/client/gui/Layout.java | 25 +++++++++++++++++++ .../client/gui/VideoSettingsScreen.java | 2 +- .../client/gui/console/ConsoleRenderer.java | 23 +++++++++-------- .../gui/options/control/ControlElement.java | 2 +- .../gui/options/control/CyclingControl.java | 4 +-- .../gui/options/control/SliderControl.java | 22 ++++++---------- .../control/StatefulControlElement.java | 3 ++- .../gui/options/control/TickBoxControl.java | 11 ++++---- .../client/gui/prompt/ScreenPrompt.java | 24 ++++++++++++------ .../gui/widgets/CenteredFlatWidget.java | 4 +-- .../client/gui/widgets/OptionListWidget.java | 6 ++--- .../client/gui/widgets/PageListWidget.java | 4 +-- .../client/gui/widgets/ResetButton.java | 2 +- .../client/gui/widgets/SearchWidget.java | 2 +- 14 files changed, 81 insertions(+), 53 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/Layout.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/Layout.java index 4a56ac7f6d..251d3532ae 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/Layout.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/Layout.java @@ -1,5 +1,7 @@ package net.caffeinemc.mods.sodium.client.gui; +import net.minecraft.client.gui.Font; + public class Layout { public static final int BUTTON_SHORT = 20; public static final int BUTTON_LONG = 65; @@ -22,9 +24,16 @@ public class Layout { public static final int OPTION_WIDTH = 210; public static final int OPTION_LIST_SCROLLBAR_OFFSET = 5; public static final int OPTION_TEXT_SIDE_PADDING = 6; + public static final int OPTION_LABEL_END_PADDING = 20; + + public static final int TICKBOX_CONTROL_WIDTH = 30; + public static final int CYCLING_CONTROL_WIDTH = 70; + public static final int SLIDER_WIDTH = 90; + public static final int SLIDER_HEIGHT = 10; public static final int ICON_MARGIN = 4; public static final int ICON_TEXT_BASELINE_OFFSET = -3; + public static final int CONTROL_ICON_SIZE = 10; public static final int MIN_TOOLTIP_WIDTH = 100; public static final int MAX_TOOLTIP_WIDTH = 200; @@ -33,4 +42,20 @@ public class Layout { public static final int CONTENT_BORDER_MIN_WIDTH = 100; public static final int CONTENT_BORDER_HEIGHT = OPTION_MOD_MARGIN; public static final int CONTENT_MIN_HEIGHT = 300; + + public static final int PAGE_ENTRY_SELECTION_BAR_WIDTH = 3; + public static final int PAGE_ENTRY_LABEL_END_PADDING = 14; + + // number of rows below the viewport top used as the highlight target when scrolling + public static final int SECTION_FOCUS_LEAD_ROWS = 3; + + // height of an option row or page-list entry: two lines of text tall + public static int entryHeight(Font font) { + return font.lineHeight * 2; + } + + // height of a mod header in the page list: three lines of text tall + public static int pageHeaderHeight(Font font) { + return font.lineHeight * 3; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java index 0974921299..5fd8691b37 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java @@ -109,7 +109,7 @@ private void checkPromptTimers() { } private void openDonationPrompt(SodiumOptions options) { - var prompt = new ScreenPrompt(this, DONATION_PROMPT_MESSAGE, 320, 190, + var prompt = new ScreenPrompt(this, DONATION_PROMPT_MESSAGE, ScreenPrompt.PROMPT_WIDTH, ScreenPrompt.PROMPT_HEIGHT, new ScreenPrompt.Action(Component.literal("Buy us a coffee"), this::openDonationPage)); prompt.setFocused(true); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/console/ConsoleRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/console/ConsoleRenderer.java index 87c554e977..085591c421 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/console/ConsoleRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/console/ConsoleRenderer.java @@ -1,10 +1,10 @@ package net.caffeinemc.mods.sodium.client.gui.console; +import net.caffeinemc.mods.sodium.api.util.ColorARGB; +import net.caffeinemc.mods.sodium.api.util.ColorU8; import net.caffeinemc.mods.sodium.client.console.Console; import net.caffeinemc.mods.sodium.client.console.message.Message; import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; -import net.caffeinemc.mods.sodium.api.util.ColorARGB; -import net.caffeinemc.mods.sodium.api.util.ColorU8; import net.minecraft.client.Minecraft; import net.minecraft.client.StringSplitter; import net.minecraft.client.gui.GuiGraphics; @@ -21,6 +21,11 @@ import java.util.List; public class ConsoleRenderer { + private static final int BOX_PADDING_X = 3; + private static final int BOX_PADDING_Y = 1; + private static final int BOX_MARGIN = 4; + private static final int CONSOLE_MESSAGE_WIDTH = 270; + static final ConsoleRenderer INSTANCE = new ConsoleRenderer(); private final LinkedList activeMessages = new LinkedList<>(); @@ -52,15 +57,11 @@ public void draw(GuiGraphics context) { matrices.pushPose(); matrices.translate(0.0f, 0.0f, 1000.0f); - - var paddingWidth = 3; - var paddingHeight = 1; - var renders = new ArrayList(); { - int x = 4; - int y = 4; + int x = BOX_MARGIN; + int y = BOX_MARGIN; for (ActiveMessage message : this.activeMessages) { double opacity = getMessageOpacity(message, currentTime); @@ -71,14 +72,14 @@ public void draw(GuiGraphics context) { List lines = new ArrayList<>(); - var messageWidth = 270; + var messageWidth = CONSOLE_MESSAGE_WIDTH; StringSplitter splitter = minecraft.font.getSplitter(); splitter.splitLines(message.text(), messageWidth - 20, Style.EMPTY, (text, lastLineWrapped) -> { lines.add(Language.getInstance().getVisualOrder(text)); }); - var messageHeight = (minecraft.font.lineHeight * lines.size()) + (paddingHeight * 2); + var messageHeight = (minecraft.font.lineHeight * lines.size()) + (BOX_PADDING_Y * 2); renders.add(new MessageRender(x, y, messageWidth, messageHeight, message.level(), lines, opacity)); @@ -122,7 +123,7 @@ public void draw(GuiGraphics context) { for (var line : render.lines()) { // message text - context.drawString(minecraft.font, line, x + paddingWidth + 3, y + paddingHeight, + context.drawString(minecraft.font, line, x + BOX_PADDING_X + 3, y + BOX_PADDING_Y, ColorARGB.withAlpha(colors.text(), weightAlpha(opacity)), false); y += minecraft.font.lineHeight; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java index 7e20ec8160..2d6102718c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java @@ -71,7 +71,7 @@ protected MutableComponent formatDisabledControlValue(Component value) { } protected String truncateLabelToFit(String name) { - return truncateTextToFit(name, this.getWidth() - this.getContentWidth() - 20); + return truncateTextToFit(name, this.getWidth() - this.getContentWidth() - Layout.OPTION_LABEL_END_PADDING); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java index 63b3b53601..eb25e37530 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java @@ -35,7 +35,7 @@ public ControlElement createElement(Screen screen, AbstractOptionList list, Dim2 @Override public int getMaxWidth() { - return 70; + return Layout.CYCLING_CONTROL_WIDTH; } private static class CyclingControlElement> extends StatefulControlElement { @@ -66,7 +66,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { Component name = this.option.getElementName(value); int strWidth = this.getStringWidth(name); - this.drawString(graphics, name, this.getLimitX() - strWidth - 6, this.getCenterY() + Layout.REGULAR_TEXT_BASELINE_OFFSET, Colors.FOREGROUND); + this.drawString(graphics, name, this.getLimitX() - strWidth - Layout.OPTION_TEXT_SIDE_PADDING, this.getCenterY() + Layout.REGULAR_TEXT_BASELINE_OFFSET, Colors.FOREGROUND); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java index 58778b2e64..c022fd6eb1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java @@ -60,8 +60,8 @@ public IntegerOption getOption() { public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { int sliderX = this.getSliderX(); int sliderY = this.getSliderY(); - int sliderWidth = this.getSliderWidth(); - int sliderHeight = this.getSliderHeight(); + int sliderWidth = Layout.SLIDER_WIDTH; + int sliderHeight = Layout.SLIDER_HEIGHT; var value = this.option.getValidatedValue(); var isEnabled = this.option.isEnabled(); @@ -97,30 +97,22 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { this.drawRect(graphics, sliderX, trackY, sliderX + sliderWidth, trackY + TRACK_HEIGHT, this.theme.themeLighter); this.drawRect(graphics, thumbX, sliderY, thumbX + (THUMB_WIDTH * 2), sliderY + sliderHeight, Colors.FOREGROUND); - this.drawString(graphics, label, sliderX - labelWidth - 6, sliderY + (sliderHeight / 2) + Layout.REGULAR_TEXT_BASELINE_OFFSET, Colors.FOREGROUND); + this.drawString(graphics, label, sliderX - labelWidth - Layout.OPTION_TEXT_SIDE_PADDING, sliderY + (sliderHeight / 2) + Layout.REGULAR_TEXT_BASELINE_OFFSET, Colors.FOREGROUND); } else { this.drawString(graphics, label, sliderX + sliderWidth - labelWidth, sliderY + (sliderHeight / 2) + Layout.REGULAR_TEXT_BASELINE_OFFSET, Colors.FOREGROUND); } } public int getSliderX() { - return this.getLimitX() - 96; + return this.getLimitX() - Layout.SLIDER_WIDTH - Layout.OPTION_TEXT_SIDE_PADDING; } public int getSliderY() { - return this.getCenterY() - 5; - } - - public int getSliderWidth() { - return 90; - } - - public int getSliderHeight() { - return 10; + return this.getCenterY() - Layout.SLIDER_HEIGHT / 2; } public boolean isMouseOverSlider(double mouseX, double mouseY) { - return mouseX >= this.getSliderX() && mouseX < this.getSliderX() + this.getSliderWidth() && mouseY >= this.getSliderY() && mouseY < this.getSliderY() + this.getSliderHeight(); + return mouseX >= this.getSliderX() && mouseX < this.getSliderX() + Layout.SLIDER_WIDTH && mouseY >= this.getSliderY() && mouseY < this.getSliderY() + Layout.SLIDER_HEIGHT; } @Override @@ -187,7 +179,7 @@ public boolean mouseDragged(double mouseX, double mouseY, int button, double del } private void setValueFromMouse(double d) { - this.setValue(Mth.clamp((d - (double) this.getSliderX()) / (double) this.getSliderWidth(), 0.0D, 1.0D)); + this.setValue(Mth.clamp((d - (double) this.getSliderX()) / (double) Layout.SLIDER_WIDTH, 0.0D, 1.0D)); } public void setValue(double newThumbPosition) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/StatefulControlElement.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/StatefulControlElement.java index 9f13665a88..679ef33f90 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/StatefulControlElement.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/StatefulControlElement.java @@ -2,6 +2,7 @@ import net.caffeinemc.mods.sodium.client.config.structure.StatefulOption; import net.caffeinemc.mods.sodium.client.gui.ColorTheme; +import net.caffeinemc.mods.sodium.client.gui.Layout; import net.caffeinemc.mods.sodium.client.gui.widgets.ResetButton; import net.caffeinemc.mods.sodium.client.util.Dim2i; import net.minecraft.client.gui.GuiGraphics; @@ -37,7 +38,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { @Override protected String truncateLabelToFit(String name) { - int rightReserve = this.isResetOverlayActive() ? this.resetButton.getWidth() : this.getContentWidth() + 20; + int rightReserve = this.isResetOverlayActive() ? this.resetButton.getWidth() : this.getContentWidth() + Layout.OPTION_LABEL_END_PADDING; return truncateTextToFit(name, this.getWidth() - rightReserve); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java index f204f564d0..e64e4148fc 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java @@ -4,6 +4,7 @@ import net.caffeinemc.mods.sodium.client.config.structure.StatefulOption; import net.caffeinemc.mods.sodium.client.gui.ColorTheme; import net.caffeinemc.mods.sodium.client.gui.Colors; +import net.caffeinemc.mods.sodium.client.gui.Layout; import net.caffeinemc.mods.sodium.client.util.Dim2i; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.navigation.CommonInputs; @@ -23,7 +24,7 @@ public ControlElement createElement(Screen screen, AbstractOptionList list, Dim2 @Override public int getMaxWidth() { - return 30; + return Layout.TICKBOX_CONTROL_WIDTH; } @Override @@ -53,10 +54,10 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { return; } - final int x = this.getLimitX() - 16; - final int y = this.getCenterY() - 5; - final int xEnd = x + 10; - final int yEnd = y + 10; + final int x = this.getLimitX() - Layout.OPTION_TEXT_SIDE_PADDING - Layout.CONTROL_ICON_SIZE; + final int y = this.getCenterY() - Layout.CONTROL_ICON_SIZE / 2; + final int xEnd = x + Layout.CONTROL_ICON_SIZE; + final int yEnd = y + Layout.CONTROL_ICON_SIZE; final boolean enabled = this.option.isEnabled(); final boolean ticked = this.option.getValidatedValue(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/prompt/ScreenPrompt.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/prompt/ScreenPrompt.java index e8eded94c4..9c10fcb663 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/prompt/ScreenPrompt.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/prompt/ScreenPrompt.java @@ -18,6 +18,12 @@ import java.util.List; public class ScreenPrompt implements GuiEventListener, Renderable { + public static final int PROMPT_WIDTH = 320; + public static final int PROMPT_HEIGHT = 190; + private static final int BUTTON_MARGIN = 4; + private static final int CLOSE_BUTTON_WIDTH = 80; + private static final int ACTION_BUTTON_WIDTH = 110; + private static final ButtonTheme PROMPT_THEME = new ButtonTheme(Colors.FOREGROUND, Colors.FOREGROUND, Colors.FOREGROUND, 0xff393939, 0xff2b2b2b, 0xff2b2b2b); private final ScreenPromptable parent; @@ -45,9 +51,13 @@ public void init() { int boxX = parentDimensions.getCenterX() - (this.width / 2); int boxY = parentDimensions.getCenterY() - (this.height / 2); - this.closeButton = new FlatButtonWidget(new Dim2i((boxX + this.width) - 84, (boxY + this.height) - 24, 80, Layout.BUTTON_SHORT), Component.literal("Close"), this::close, true, false, PROMPT_THEME); + int buttonY = (boxY + this.height) - Layout.BUTTON_SHORT - BUTTON_MARGIN; + int closeX = (boxX + this.width) - CLOSE_BUTTON_WIDTH - BUTTON_MARGIN; + int actionX = closeX - ACTION_BUTTON_WIDTH - BUTTON_MARGIN; + + this.closeButton = new FlatButtonWidget(new Dim2i(closeX, buttonY, CLOSE_BUTTON_WIDTH, Layout.BUTTON_SHORT), Component.literal("Close"), this::close, true, false, PROMPT_THEME); - this.actionButton = new FlatButtonWidget(new Dim2i((boxX + this.width) - 198, (boxY + this.height) - 24, 110, Layout.BUTTON_SHORT), this.action.label, this::runAction, true, false, PROMPT_THEME); + this.actionButton = new FlatButtonWidget(new Dim2i(actionX, buttonY, ACTION_BUTTON_WIDTH, Layout.BUTTON_SHORT), this.action.label, this::runAction, true, false, PROMPT_THEME); } public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { @@ -69,12 +79,10 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { matrices.translate(0.0f, 0.0f, 50.0f); - int padding = 5; - - int textX = boxX + padding; - int textY = boxY + padding; + int textX = boxX + Layout.INNER_MARGIN; + int textY = boxY + Layout.INNER_MARGIN; - int textMaxWidth = this.width - (padding * 2); + int textMaxWidth = this.width - (Layout.INNER_MARGIN * 2); var font = Minecraft.getInstance().font; @@ -83,7 +91,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { for (var line : formatted) { graphics.drawString(font, line, textX, textY, Colors.FOREGROUND, true); - textY += font.lineHeight + 2; + textY += font.lineHeight + Layout.TEXT_LINE_SPACING; } textY += Layout.TEXT_PARAGRAPH_SPACING; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/CenteredFlatWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/CenteredFlatWidget.java index 440b505784..885c6f5840 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/CenteredFlatWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/CenteredFlatWidget.java @@ -56,7 +56,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { } if (this.selected) { - this.drawRect(graphics, x2 - 3, y1, x2, y2, this.theme.themeLighter); + this.drawRect(graphics, x2 - Layout.PAGE_ENTRY_SELECTION_BAR_WIDTH, y1, x2, y2, this.theme.themeLighter); } // render icon and get offset for text @@ -84,7 +84,7 @@ protected int renderIcon(GuiGraphics graphics, int textColor) { } private String truncateToFitWidth(Component text, int iconOffset) { - return this.truncateTextToFit(text.getString(), this.getWidth() - 14 - iconOffset); + return this.truncateTextToFit(text.getString(), this.getWidth() - Layout.PAGE_ENTRY_LABEL_END_PADDING - iconOffset); } public void setSelected(boolean selected) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java index 1051b2187e..72aa52cf9e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java @@ -60,7 +60,7 @@ public void rebuild(Screen screen) { this.pageToSectionInfo.clear(); this.scrollbar = this.addRenderableChild(new ScrollbarWidget(new Dim2i(x + width + Layout.OPTION_LIST_SCROLLBAR_OFFSET, y, Layout.SCROLLBAR_WIDTH, height), this::updateSectionFocus)); - this.entryHeight = this.font.lineHeight * 2; + this.entryHeight = Layout.entryHeight(this.font); int listHeight; if (this.filteredOptions != null) { @@ -214,7 +214,7 @@ private void updateSectionFocus(int scrollAmount) { // calculate which y position is considered the "viewed" option, // + y is needed to compensate for the initial offset that the .startY values have - int highlightTarget = scrollAmount + this.getY() + Math.min(this.entryHeight * 3, this.getHeight() / 2); + int highlightTarget = scrollAmount + this.getY() + Math.min(this.entryHeight * Layout.SECTION_FOCUS_LEAD_ROWS, this.getHeight() / 2); // Find which section is currently in the middle of the viewport SectionInfo currentSection = null; @@ -254,7 +254,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { this.hovered = this.isMouseOver(mouseX, mouseY); this.drawRect(graphics, this.getX(), this.getY(), this.getLimitX(), this.getLimitY(), this.backgroundColor); - this.drawString(graphics, this.truncateLabelToFit(this.title, 12), this.getX() + Layout.OPTION_PAGE_MARGIN, this.getCenterY() + Layout.REGULAR_TEXT_BASELINE_OFFSET, this.textColor); + this.drawString(graphics, this.truncateLabelToFit(this.title, Layout.OPTION_TEXT_SIDE_PADDING * 2), this.getX() + Layout.OPTION_PAGE_MARGIN, this.getCenterY() + Layout.REGULAR_TEXT_BASELINE_OFFSET, this.textColor); if (this.resetButton != null) { this.resetButton.render(graphics, mouseX, mouseY, delta); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java index 5aa411c631..f4a88e3f29 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/PageListWidget.java @@ -39,8 +39,8 @@ private void rebuild() { this.clearChildren(); this.scrollbar = this.addRenderableChild(new ScrollbarWidget(new Dim2i(this.getLimitX() - Layout.SCROLLBAR_WIDTH, y, Layout.SCROLLBAR_WIDTH, height), false, false)); - int entryHeight = this.font.lineHeight * 2; - var headerHeight = this.font.lineHeight * 3; + int entryHeight = Layout.entryHeight(this.font); + var headerHeight = Layout.pageHeaderHeight(this.font); int listHeight = 0; for (var modOptions : ConfigManager.CONFIG.getModOptions()) { if (modOptions.pages().isEmpty()) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ResetButton.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ResetButton.java index d234e921e6..ee9dc2c5d1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ResetButton.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ResetButton.java @@ -16,7 +16,7 @@ */ public class ResetButton extends AbstractWidget { private static final ResourceLocation ICON = ResourceLocation.fromNamespaceAndPath("sodium", "textures/gui/reset_button.png"); - private static final int ICON_SIZE = 10; + private static final int ICON_SIZE = Layout.CONTROL_ICON_SIZE; private static final int COLOR = 0xFFFF8C30; private final AbstractWidget parent; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/SearchWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/SearchWidget.java index 2ef5ffc118..58335b1b0e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/SearchWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/SearchWidget.java @@ -66,7 +66,7 @@ private void rebuildForWidth(int width) { this.font, x + Layout.INNER_MARGIN, y + Layout.BUTTON_SHORT / 2 - this.font.lineHeight / 2, - searchBoxWidth - 20, + searchBoxWidth - Layout.BUTTON_SHORT, Layout.BUTTON_SHORT, Component.translatable("sodium.options.search") ); From aeeff531e4cf23bdf692ae0462331ee9ad4cafbf Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 28 Apr 2026 01:24:30 +0200 Subject: [PATCH 172/215] Fix javadoc newlines to use paragraphs --- .../api/config/structure/ColorThemeBuilder.java | 2 +- .../api/config/structure/StatefulOptionBuilder.java | 2 +- .../caffeinemc/mods/sodium/api/math/MatrixHelper.java | 2 +- .../caffeinemc/mods/sodium/api/util/ColorABGR.java | 2 +- .../net/caffeinemc/mods/sodium/api/util/NormI8.java | 4 ++-- .../client/model/light/data/LightDataAccess.java | 10 +++++----- .../model/light/smooth/SmoothLightPipeline.java | 6 +++--- .../render/chunk/compile/tasks/ChunkBuilderTask.java | 4 ++-- .../render/chunk/data/SectionRenderDataUnsafe.java | 2 +- .../render/chunk/shader/ChunkShaderFogComponent.java | 2 +- .../render/chunk/translucent_sorting/SortType.java | 2 +- .../chunk/translucent_sorting/bsp_tree/BSPNode.java | 2 +- .../translucent_sorting/bsp_tree/BSPSortState.java | 9 ++++----- .../translucent_sorting/bsp_tree/BSPWorkspace.java | 2 +- .../bsp_tree/InnerMultiPartitionBSPNode.java | 5 ++--- .../chunk/translucent_sorting/data/AnyOrderData.java | 2 +- .../render/chunk/translucent_sorting/data/NoData.java | 2 +- .../data/StaticNormalRelativeData.java | 2 +- .../translucent_sorting/trigger/DirectTriggers.java | 11 +++++------ .../chunk/translucent_sorting/trigger/Group.java | 4 ++-- .../translucent_sorting/trigger/SortTriggering.java | 2 +- .../mods/sodium/client/util/ModelQuadUtil.java | 4 ++-- 22 files changed, 40 insertions(+), 43 deletions(-) diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ColorThemeBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ColorThemeBuilder.java index fec48bb1c3..546421110b 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ColorThemeBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/ColorThemeBuilder.java @@ -2,7 +2,7 @@ /** * Builder interface for defining color themes. - * + *

* Colors use RGB integers (bits 0 to 24). This is ARGB format with alpha bits ignored. */ public interface ColorThemeBuilder { diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/StatefulOptionBuilder.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/StatefulOptionBuilder.java index 8e29469ff8..60d5ef010d 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/StatefulOptionBuilder.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/structure/StatefulOptionBuilder.java @@ -89,7 +89,7 @@ public interface StatefulOptionBuilder extends OptionBuilder { /** * Sets whether the control for this option should be hidden when the option is disabled. This should only be set to false when the user should know what the state of the option is even when it is disabled, and they cannot interact with it. - * + *

* By default, controls are hidden when disabled. * * @param hidden True to hide the control when disabled, false to show it. diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/math/MatrixHelper.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/math/MatrixHelper.java index df8806b7f2..9d2bb6920d 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/math/MatrixHelper.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/math/MatrixHelper.java @@ -10,7 +10,7 @@ /** * Implements optimized utilities for transforming vectors with a given matrix. - * + *

* Note: Brackets must be used carefully in the transform functions to ensure that floating-point errors are * the same as those produced by JOML, otherwise Z-fighting will occur. */ diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java index 6869755ae0..b9e4437cd4 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/ColorABGR.java @@ -5,7 +5,7 @@ /** * Provides some utilities for packing and unpacking color components from packed integer colors in ABGR format, which * is used by OpenGL for color vectors. - * + *

* | 32 | 24 | 16 | 8 | * | 0110 1100 | 0110 1100 | 0110 1100 | 0110 1100 | * | Alpha | Blue | Green | Red | diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/NormI8.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/NormI8.java index 142e560113..5ab9042091 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/util/NormI8.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/util/NormI8.java @@ -7,7 +7,7 @@ /** * Provides some utilities for working with packed normal vectors. Each normal component provides 8 bits of * precision in the range of [-1.0,1.0]. - * + *

* | 32 | 24 | 16 | 8 | * | 0000 0000 | 0110 1100 | 0110 1100 | 0110 1100 | * | Padding | X | Y | Z | @@ -94,7 +94,7 @@ public static int flipPacked(int norm) { /** * Returns true if the two packed normals are opposite directions. - * + *

* TODO: this could possibly be faster by using normA == (~normB + 0x010101) but * that has to special case when a component is zero since that wouldn't * overflow correctly back to zero. (~0+1 == 0 but not if it's somewhere inside diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/data/LightDataAccess.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/data/LightDataAccess.java index 0ded5bc8c9..02c13470ae 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/data/LightDataAccess.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/data/LightDataAccess.java @@ -12,10 +12,10 @@ /** * The light data cache is used to make accessing the light data and occlusion properties of blocks cheaper. The data * for each block is stored as an integer with packed fields in order to work around the lack of value types in Java. - * + *

* This code is not very pretty, but it does perform significantly faster than the vanilla implementation and has * good cache locality. - * + *

* Each integer contains the following fields: * - BL: World block light, encoded as a 4-bit unsigned integer * - SL: World sky light, encoded as a 4-bit unsigned integer @@ -25,7 +25,7 @@ * - OP: Block opacity test, true if opaque * - FO: Full cube opacity test, true if opaque full cube * - FC: Full cube test, true if full cube - * + *

* You can use the various static pack/unpack methods to extract these values in a usable format. */ public abstract class LightDataAccess { @@ -169,7 +169,7 @@ public static boolean unpackFC(int word) { * Computes the combined lightmap using block light, sky light, and luminance values. * *

This method's logic is equivalent to - * {@link LevelRenderer#getLightColor(BlockAndTintGetter, BlockPos)}, but without the + * {@link LevelRenderer#getLightColor(net.minecraft.world.level.BlockAndTintGetter, BlockPos)}, but without the * emissive check. */ public static int getLightmap(int word) { @@ -181,7 +181,7 @@ public static int getLightmap(int word) { * the {@link LightTexture#FULL_BRIGHT fullbright lightmap} if emissive. * *

This method's logic is equivalent to - * {@link LevelRenderer#getLightColor(BlockAndTintGetter, BlockPos)}. + * {@link LevelRenderer#getLightColor(net.minecraft.world.level.BlockAndTintGetter, BlockPos)}. */ public static int getEmissiveLightmap(int word) { if (unpackEM(word)) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java index 09f888520a..e3e98689f2 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java @@ -14,7 +14,7 @@ /** * A light pipeline which produces smooth interpolated lighting and ambient occlusion for model quads. This * implementation makes a number of improvements over vanilla's own "smooth lighting" option. In no particular order: - * + *

* - Corner blocks are now selected from the correct set of neighbors above block faces (fixes MC-148689 and MC-12558) * - Shading issues caused by anisotropy are fixed by re-orientating quads to a consistent ordering (fixes MC-138211) * - Inset block faces are correctly shaded by their neighbors, fixing a number of problems with non-full blocks such as @@ -22,9 +22,9 @@ * - Blocks next to emissive blocks are too bright (MC-260989) * - Synchronization issues between the main render thread's light engine and chunk build worker threads are corrected * by copying light data alongside block states, fixing a number of inconsistencies in baked chunks (no open issue) - * + *

* This implementation also includes a significant number of optimizations: - * + *

* - Computed light data for a given block face is cached and re-used again when multiple quads exist for a given * facing, making complex block models less expensive to render * - The light data cache encodes as much information as possible into integer words to improve cache locality and diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java index 975ce4c818..fee3395b5a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderTask.java @@ -15,10 +15,10 @@ /** * Build tasks are immutable jobs (with optional prioritization) which contain all the necessary state to perform * chunk mesh updates or quad sorting off the main thread. - * + *

* When a task is constructed on the main thread, it should copy all the state it requires in order to complete the task * without further synchronization. The task will then be scheduled for async execution on a thread pool. - * + *

* After the task completes, it returns a "build result" which contains any computed data that needs to be handled * on the main thread. */ diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java index e3b38045a4..3e79a43584 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/data/SectionRenderDataUnsafe.java @@ -28,7 +28,7 @@ public class SectionRenderDataUnsafe { /** * When the "base element" field is not specified (indicated by setting the MSB to 0), the indices for the geometry set * should be sourced from a monotonic sequence (see {@link net.caffeinemc.mods.sodium.client.render.chunk.SharedQuadIndexBuffer}). - * + *

* Otherwise, indices should be sourced from the index buffer for the render region using the specified offset. */ private static final long OFFSET_BASE_ELEMENT = 0; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/ChunkShaderFogComponent.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/ChunkShaderFogComponent.java index 6855970186..5068200bb0 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/ChunkShaderFogComponent.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/shader/ChunkShaderFogComponent.java @@ -10,7 +10,7 @@ * copying the state into each shader's uniforms. The shader code itself is a straight-forward implementation of the * fog functions themselves from the fixed-function pipeline, except that they use the distance from the camera * rather than the z-buffer to produce better looking fog that doesn't move with the player's view angle. - * + *

* Minecraft itself will actually try to enable distance-based fog by using the proprietary NV_fog_distance extension, * but as the name implies, this only works on graphics cards produced by NVIDIA. The shader implementation however does * not depend on any vendor-specific extensions and is written using very simple GLSL code. diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java index e2eee4b915..92db802af9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/SortType.java @@ -3,7 +3,7 @@ /** * What type of sorting to use for a section. Calculated by a heuristic after * building a section. - * + *

* Invariant: !(needsDirectionMixing && allowSliceReordering) */ public enum SortType { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java index 89aeb8b11c..e0664e8634 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java @@ -13,7 +13,7 @@ * A node in the BSP tree. The BSP tree is made up of nodes that split quads * into groups on either side of a plane and those that lie on the plane. * There's also leaf nodes that contain one or more quads. - * + *

* Implementation note: * - Doing a convex box test doesn't seem to bring a performance boost, even if * it does trigger sometimes with man-made structures. The multi partition node diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPSortState.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPSortState.java index 99b7ad87b4..fc4bb80620 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPSortState.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPSortState.java @@ -1,13 +1,12 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.bsp_tree; -import java.nio.IntBuffer; -import java.lang.Math; - import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntConsumer; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.TranslucentData; import net.caffeinemc.mods.sodium.client.util.NativeBuffer; +import java.nio.IntBuffer; + /** * The sort state is passed around the tree (similar to visitor pattern) and * contains the index buffer being written to alongside additional state for @@ -103,12 +102,12 @@ static int[] compressIndexes(IntArrayList indexes) { /** * Compress a list of quad indexes by applying run length encoding or bit * packing to their deltas. - * + *

* Format: 32 bits, elements described as [length in bits: description] * header at position 0: 0b1[4: width index][10: delta count][17: first index] * header at position 1: 0b[32: base delta] * deltas at position 2..n: 0b[width: delta]... - * + *

* delta bit widths: * 1x32b, 2x16b, 3x10b, 4x8b, 5x6b, * 6x5b, 8x4b, 10x3b, 16x2b, 32x1b diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPWorkspace.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPWorkspace.java index 8fdc24f36b..f7c9affd8e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPWorkspace.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPWorkspace.java @@ -12,7 +12,7 @@ * The BSP workspace holds the state during the BSP building process. (see also * BSPSortState) It brings a number of fixed parameters and receives partition * planes to return as part of the final result. - * + *

* Implementation note: Storing the multi partition node's interval points in a * global array instead of making a new one at each tree level doesn't appear to * have any performance benefit. diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerMultiPartitionBSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerMultiPartitionBSPNode.java index cabcc659bc..38a02885b7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerMultiPartitionBSPNode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerMultiPartitionBSPNode.java @@ -1,15 +1,14 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.bsp_tree; -import org.joml.Vector3fc; - import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.objects.ReferenceArrayList; +import org.joml.Vector3fc; /** * Partitions quads into multiple child BSP nodes with multiple parallel * partition planes. This is uses less memory and time than constructing a * binary BSP tree through more partitioning passes. - * + *

* Implementation note: Detecting and avoiding the double array when possible * brings no performance benefit in sorting speed, only a building speed * detriment. diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java index 3e25926d17..0d3b1109c5 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/AnyOrderData.java @@ -10,7 +10,7 @@ * order. However, they do need to be rendered with some index buffer, so that * vertices are assembled into quads. Since the sort order doesn't matter, all * sections with this sort type can share the same data in the index buffer. - * + *

* NOTE: A possible optimization would be to share the buffer for unordered * translucent sections on the CPU and on the GPU. It would essentially be the * same as SharedQuadIndexBuffer, but it has to be compatible with sections in diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/NoData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/NoData.java index 850e3fe9be..c99c9e9ab9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/NoData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/NoData.java @@ -8,7 +8,7 @@ /** * This class means there is no translucent data and is used to signal that the * section should be removed from triggering data structures. - * + *

* If translucent sorting is disabled, not even this class is used, but null is * passed instead. */ diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java index 95eb399de2..a77e8f033f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/StaticNormalRelativeData.java @@ -9,7 +9,7 @@ /** * Static normal relative sorting orders quads by the dot product of their * normal and position. (referred to as "distance" throughout the code) - * + *

* Unlike sorting by distance, which is descending for translucent rendering to * be correct, sorting by dot product is ascending instead. */ diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/DirectTriggers.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/DirectTriggers.java index 4c1943cb14..de8d651c48 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/DirectTriggers.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/DirectTriggers.java @@ -1,20 +1,19 @@ package net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger; -import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.DynamicTopoData; -import org.joml.Vector3d; -import org.joml.Vector3dc; - import it.unimi.dsi.fastutil.doubles.Double2ObjectRBTreeMap; +import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.DynamicTopoData; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.data.TranslucentData; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.trigger.SortTriggering.SectionTriggers; import net.minecraft.core.SectionPos; +import org.joml.Vector3d; +import org.joml.Vector3dc; /** * Performs direct triggering for sections that are sorted by distance. Direct * triggering means the section is not triggered based on its geometry but * rather the movement of the camera relative to the last position the camera * was in when the section was sorted last. - * + *

* There are two types of direct triggering: Distance triggering is used when * the camera is close to or inside the section. Angle triggering is used * otherwise. Distance triggering sorts the section when the camera has moved at @@ -27,7 +26,7 @@ class DirectTriggers implements SectionTriggers { * A tree map of the directly triggered sections, indexed by their * minimum required camera movement. When the given camera movement is exceeded, * they are tested for triggering the angle or distance condition. - * + *

* The accumulated distance is monotonically increasing and is never reset. This * only becomes a problem when the camera moves more than 10^15 blocks in total. * There will be precision issues at around 10^10 maybe, but it's still not a diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/Group.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/Group.java index d5bcc8b1f4..46db7e91e7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/Group.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/Group.java @@ -64,11 +64,11 @@ void triggerRange(SortTriggering ts, double start, double end) { /** * A pretty good heuristic for equality of captured translucent geometry data. - * + *

* It assumes that if the size, bounds, and hash are equal, they are most likely * the same. We also know that the existing and new data is for the same section * position since the group was retrieved from the map for the right position. - * + *

* TODO: how common are collisions and are they bad? * If they are common, use second or different hash */ diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java index b9c8d54f0a..cf37e6e2e7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/SortTriggering.java @@ -18,7 +18,7 @@ * This class is a central point in translucency sorting. It counts the number * of translucent data objects for each sort type and delegates triggering of * sections for dynamic sorting to the trigger components. - * + *

* TODO: * - investigate why there's a similar number of STA and DYN sections. This might be normal, the counters might be broken or the heuristic is actually wrong. * diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/ModelQuadUtil.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/ModelQuadUtil.java index a2cd31a62d..400a5856cb 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/ModelQuadUtil.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/ModelQuadUtil.java @@ -3,10 +3,10 @@ /** * Provides some utilities and constants for interacting with vanilla's model quad vertex format. - * + *

* This is the current vertex format used by Minecraft for chunk meshes and model quads. Internally, it uses integer * arrays for store baked quad data, and as such the following table provides both the byte and int indices. - * + *

* Byte Index Integer Index Name Format Fields * 0 ..11 0..2 Position 3 floats x, y, z * 12..15 3 Color 4 unsigned bytes a, r, g, b From 4467dede2147a17d90fdacbec3fff217e0b0aa96 Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 28 Apr 2026 01:24:51 +0200 Subject: [PATCH 173/215] Replace verbatim links with html links --- .../mods/sodium/client/gl/shader/ShaderWorkarounds.java | 7 +++---- .../caffeinemc/mods/sodium/client/util/FlawlessFrames.java | 2 +- .../mods/sodium/client/util/collections/BitArray.java | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderWorkarounds.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderWorkarounds.java index b5a5dbb039..ad5c226f73 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderWorkarounds.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderWorkarounds.java @@ -14,16 +14,15 @@ package net.caffeinemc.mods.sodium.client.gl.shader; -import java.nio.ByteBuffer; - import org.lwjgl.PointerBuffer; import org.lwjgl.opengl.GL20C; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; +import java.nio.ByteBuffer; + /** - * Contains a workaround for a crash in nglShaderSource on some AMD drivers. Copied from the following Canvas commit: - * https://github.com/grondag/canvas/commit/820bf754092ccaf8d0c169620c2ff575722d7d96 + * Contains a workaround for a crash in nglShaderSource on some AMD drivers. Copied from this Canvas commit. */ class ShaderWorkarounds { /** diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/FlawlessFrames.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/FlawlessFrames.java index 9202878d83..44ecde8e24 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/FlawlessFrames.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/FlawlessFrames.java @@ -13,7 +13,7 @@ *

* In Sodium's case, this means waiting for all chunks to be fully updated and ready for rendering before each frame. *

- * See https://github.com/grondag/frex/pull/9 + * See this PR */ public class FlawlessFrames { private static final Set ACTIVE = Collections.newSetFromMap(new ConcurrentHashMap<>()); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/BitArray.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/BitArray.java index f018b18e6c..ed7b08edd4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/BitArray.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/collections/BitArray.java @@ -5,7 +5,7 @@ import java.util.Arrays; /** - * Originally authored here: https://github.com/CaffeineMC/sodium/blob/ddfb9f21a54bfb30aa876678204371e94d8001db/src/main/java/net/caffeinemc/sodium/util/collections/BitArray.java + * Originally authored here * @author burgerindividual */ public class BitArray { From 8636c9a589df8057f779544fab2cdd38719ca7cb Mon Sep 17 00:00:00 2001 From: douira Date: Wed, 29 Apr 2026 15:23:54 +0200 Subject: [PATCH 174/215] Fix rare translucency sorting crash when repeatedly interacting with specific chunks with some resource packs (#3609) In rare cases, specific combinations of resource packs and world geometry can cause quad splitting to enter the sorting system into a state where it attempts to reuse a BSP node even though the involved quads have changed and there's many more quads. In particular through Safe-mode quad splitting this collision can occur when a quad was previously not split and then when a block is placed the quad splitting budget is higher, which results in more quads being split. The axis-aligned extents of the quads in some BSP node don't necessarily change when one of its candidate quads becomes split, especially in the case of "bushy leaves" packs where there are diagonal quads. This is a recent problem because they can cause translucent geometry through stray translucent pixels, which as of 26.1 cause the material to be translucent. When BSP nodes are wrongly reused they try to remap indices they aren't equipped to handle and run into an out of bounds exception. In this patch the extent comparison is replaced by a better quad hashing strategy, namely the same one we also use for translucent data reuse in TranslucentGeometryCollector. --- .../bsp_tree/BSPSortState.java | 9 ++++---- .../bsp_tree/InnerPartitionBSPNode.java | 22 ++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPSortState.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPSortState.java index fc4bb80620..e5d54689dc 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPSortState.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPSortState.java @@ -209,14 +209,13 @@ static int[] compressIndexes(IntArrayList indexes, boolean doSort) { return compressed; } - static int decompressOrRead(int[] indexes, IntConsumer consumer) { + static void decompressOrRead(int[] indexes, IntConsumer consumer) { if (isCompressed(indexes)) { - return decompress(indexes, consumer); + decompress(indexes, consumer); } else { for (int i = 0; i < indexes.length; i++) { consumer.accept(indexes[i]); } - return indexes.length; } } @@ -277,10 +276,10 @@ static boolean isCompressed(int[] indexes) { return indexes[0] < 0; } - private IntConsumer indexConsumer = (int index) -> TranslucentData.writeQuadVertexIndexes( + private final IntConsumer indexConsumer = (int index) -> TranslucentData.writeQuadVertexIndexes( this.indexBuffer, index); - private IntConsumer indexMapConsumer = (int index) -> TranslucentData.writeQuadVertexIndexes( + private final IntConsumer indexMapConsumer = (int index) -> TranslucentData.writeQuadVertexIndexes( this.indexBuffer, this.indexMap[index]); void writeIndexes(int[] indexes) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java index 79b7aa6c08..7462eb0e54 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java @@ -79,7 +79,7 @@ abstract class InnerPartitionBSPNode extends BSPNode { * Since the indexes might be compressed, the count needs to be stored * separately from before compression. */ - record NodeReuseData(float[][] quadExtents, int[] indexes, int indexCount, int maxIndex) { + record NodeReuseData(int quadHash, int[] indexes, int indexCount, int maxIndex) { } InnerPartitionBSPNode(NodeReuseData reuseData, int axis) { @@ -101,20 +101,19 @@ static NodeReuseData prepareNodeReuse(BSPWorkspace workspace, IntArrayList index // root node and not anything deeper than its children) if (workspace.prepareNodeReuse && depth == 1 && indexes.size() > NODE_REUSE_THRESHOLD) { // collect the extents of the indexed quads and hash them - var quadExtents = new float[indexes.size()][]; + var quadHash = 1; int maxIndex = -1; for (int i = 0; i < indexes.size(); i++) { var index = indexes.getInt(i); var quad = workspace.get(index); - var extents = quad.getExtents(); - quadExtents[i] = extents; + quadHash = quadHash * 31 + quad.getQuadHash(); maxIndex = Math.max(maxIndex, index); } // compress indexes but without sorting them, as the order needs to be the same // for the extents comparison loop to work return new NodeReuseData( - quadExtents, + quadHash, BSPSortState.compressIndexes(indexes, false), indexes.size(), maxIndex); @@ -166,15 +165,18 @@ static InnerPartitionBSPNode attemptNodeReuse(BSPWorkspace workspace, IntArrayLi return null; } - var oldExtents = reuseData.quadExtents; - if (oldExtents.length != newIndexes.size()) { + if (reuseData.indexCount != newIndexes.size()) { return null; } + var newQuadHash = 1; for (int i = 0; i < newIndexes.size(); i++) { - if (!workspace.get(newIndexes.getInt(i)).extentsEqual(oldExtents[i])) { - return null; - } + var index = newIndexes.getInt(i); + var quad = workspace.get(index); + newQuadHash = newQuadHash * 31 + quad.getQuadHash(); + } + if (newQuadHash != reuseData.quadHash) { + return null; } // reuse old node and either apply a fixed offset or calculate an index map to From 3ee4afd4e330bc968662ec498771d656be98b132 Mon Sep 17 00:00:00 2001 From: Crosby <32882447+crosby-moe@users.noreply.github.com> Date: Thu, 30 Apr 2026 17:32:42 +0200 Subject: [PATCH 175/215] Add caching to `GlStateManager#glViewport` (#3309) --------- Co-authored-by: RacoonDog <32882447+racoondog@users.noreply.github.com> Co-authored-by: douira --- .../client/data/config/MixinConfig.java | 2 ++ .../render/viewport/GlStateManagerMixin.java | 34 +++++++++++++++++++ .../main/resources/sodium-common.mixins.json | 1 + 3 files changed, 37 insertions(+) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/viewport/GlStateManagerMixin.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java index ca32f5ed5c..6ab6189e5a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/data/config/MixinConfig.java @@ -71,6 +71,8 @@ protected MixinConfig() { this.addMixinRule("features.render.particle", true); + this.addMixinRule("features.render.viewport", true); + this.addMixinRule("features.render.world", true); this.addMixinRule("features.render.world.clouds", true); this.addMixinRule("features.render.world.sky", true); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/viewport/GlStateManagerMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/viewport/GlStateManagerMixin.java new file mode 100644 index 0000000000..b76ea06498 --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/viewport/GlStateManagerMixin.java @@ -0,0 +1,34 @@ +package net.caffeinemc.mods.sodium.mixin.features.render.viewport; + +import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.mojang.blaze3d.platform.GlStateManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(GlStateManager.class) +public class GlStateManagerMixin { + @Unique + private static int lastViewportX; + @Unique + private static int lastViewportY; + @Unique + private static int lastViewportWidth; + @Unique + private static int lastViewportHeight; + + @WrapWithCondition( + method = "_viewport", + at = @At(value = "INVOKE", target = "Lorg/lwjgl/opengl/GL11;glViewport(IIII)V") + ) + private static boolean skipRedundantViewport(int x, int y, int w, int h) { + if (x == lastViewportX && y == lastViewportY && w == lastViewportWidth && h == lastViewportHeight) { + return false; + } + lastViewportX = x; + lastViewportY = y; + lastViewportWidth = w; + lastViewportHeight = h; + return true; + } +} diff --git a/common/src/main/resources/sodium-common.mixins.json b/common/src/main/resources/sodium-common.mixins.json index 62c78ddbe7..f48ddca06f 100644 --- a/common/src/main/resources/sodium-common.mixins.json +++ b/common/src/main/resources/sodium-common.mixins.json @@ -67,6 +67,7 @@ "features.render.model.ItemBlockRenderTypesMixin", "features.render.model.item.ItemRendererMixin", "features.render.particle.SingleQuadParticleMixin", + "features.render.viewport.GlStateManagerMixin", "features.render.world.clouds.LevelRendererMixin", "features.render.world.sky.ClientLevelMixin", "features.render.world.sky.FogRendererMixin", From 8b70e94c48e43ccb81604989336f5e3436505ae2 Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 3 May 2026 22:45:54 +0200 Subject: [PATCH 176/215] switch to canonical jetbrains nullable annotations --- common/build.gradle.kts | 5 +---- .../client/compatibility/checks/ModuleScanner.java | 2 +- .../environment/probe/GraphicsAdapterInfo.java | 10 +++++----- .../environment/probe/GraphicsAdapterProbe.java | 2 +- .../environment/probe/GraphicsAdapterVendor.java | 8 ++++---- .../workarounds/intel/IntelWorkarounds.java | 2 +- .../workarounds/nvidia/NvidiaWorkarounds.java | 2 +- .../mods/sodium/client/console/Console.java | 4 ++-- .../mods/sodium/client/console/ConsoleSink.java | 4 ++-- .../mods/sodium/client/platform/MessageBox.java | 2 +- .../sodium/client/platform/PlatformHelper.java | 10 +++++----- .../mods/sodium/client/platform/unix/Libc.java | 2 +- .../platform/windows/WindowsFileVersion.java | 4 ++-- .../client/platform/windows/api/Kernel32.java | 2 +- .../client/platform/windows/api/Shell32.java | 2 +- .../client/platform/windows/api/d3dkmt/D3DKMT.java | 14 +++++++------- .../api/d3dkmt/D3DKMTAdapterInfoStruct.java | 6 +++--- .../d3dkmt/D3DKMTAdapterRegistryInfoStruct.java | 2 +- .../api/d3dkmt/D3DKMTEnumAdaptersStruct.java | 6 +++--- .../windows/api/d3dkmt/D3DKMTOpenGLInfoStruct.java | 6 +++--- .../api/d3dkmt/D3DKMTQueryAdapterInfoStruct.java | 6 +++--- .../windows/api/msgbox/MsgBoxCallbackI.java | 4 ++-- .../platform/windows/api/msgbox/MsgBoxParamSw.java | 6 +++--- .../platform/windows/api/version/Version.java | 2 +- .../platform/windows/api/version/VersionInfo.java | 2 +- .../client/gui/widgets/OptionListWidget.java | 6 +++--- .../sodium/client/gui/widgets/ResetButton.java | 2 +- 27 files changed, 60 insertions(+), 63 deletions(-) diff --git a/common/build.gradle.kts b/common/build.gradle.kts index b4eeeaeb17..d9532ec6e6 100644 --- a/common/build.gradle.kts +++ b/common/build.gradle.kts @@ -60,9 +60,6 @@ dependencies { compileOnly("net.fabricmc:sponge-mixin:0.13.2+mixin.0.8.5") compileOnly("net.fabricmc:fabric-loader:${BuildConfig.FABRIC_LOADER_VERSION}") - // Not shipped by Minecraft 1.21.1 - compileOnly("org.jspecify:jspecify:1.0.0") - fun addDependentFabricModule(name: String) { modCompileOnly(fabricApi.module(name, BuildConfig.FABRIC_API_VERSION)) } @@ -81,7 +78,7 @@ dependencies { configurationPreLaunch("net.java.dev.jna:jna:5.14.0") configurationPreLaunch("net.java.dev.jna:jna-platform:5.14.0") configurationPreLaunch("org.slf4j:slf4j-api:2.0.9") - configurationPreLaunch("org.jspecify:jspecify:1.0.0") + configurationPreLaunch("org.jetbrains:annotations:24.1.0") } loom { diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/ModuleScanner.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/ModuleScanner.java index ba03bcd3f9..8af1a8d70f 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/ModuleScanner.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/checks/ModuleScanner.java @@ -7,7 +7,7 @@ import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; import net.caffeinemc.mods.sodium.client.platform.windows.api.Kernel32; import net.caffeinemc.mods.sodium.client.platform.windows.api.version.Version; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterInfo.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterInfo.java index f2db7f2b37..665671c4e2 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterInfo.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterInfo.java @@ -1,15 +1,15 @@ package net.caffeinemc.mods.sodium.client.compatibility.environment.probe; -import org.jspecify.annotations.NonNull; +import org.jetbrains.annotations.NotNull; public interface GraphicsAdapterInfo { - @NonNull GraphicsAdapterVendor vendor(); + @NotNull GraphicsAdapterVendor vendor(); - @NonNull String name(); + @NotNull String name(); record LinuxPciAdapterInfo( - @NonNull GraphicsAdapterVendor vendor, - @NonNull String name, + @NotNull GraphicsAdapterVendor vendor, + @NotNull String name, String pciVendorId, String pciDeviceId diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterProbe.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterProbe.java index 4b767e5934..64a251bb54 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterProbe.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterProbe.java @@ -2,7 +2,7 @@ import net.caffeinemc.mods.sodium.client.compatibility.environment.OsUtils; import net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt.D3DKMT; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java index 9fa33dcb2b..f18f4c4cb9 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/environment/probe/GraphicsAdapterVendor.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.compatibility.environment.probe; import net.caffeinemc.mods.sodium.client.compatibility.environment.GlContextInfo; -import org.jspecify.annotations.NonNull; +import org.jetbrains.annotations.NotNull; import java.util.regex.Pattern; @@ -31,7 +31,7 @@ public enum GraphicsAdapterVendor { private static final Pattern AMD_ICD_PATTERN = Pattern.compile("(atiglpxx|atig6pxx)\\.dll", Pattern.CASE_INSENSITIVE); - @NonNull + @NotNull static GraphicsAdapterVendor fromPciVendorId(String vendor) { if (vendor.contains("0x1002")) { return AMD; @@ -44,7 +44,7 @@ static GraphicsAdapterVendor fromPciVendorId(String vendor) { return UNKNOWN; } - @NonNull + @NotNull public static GraphicsAdapterVendor fromIcdName(String name) { if (matchesPattern(INTEL_ICD_PATTERN, name)) { return INTEL; @@ -57,7 +57,7 @@ public static GraphicsAdapterVendor fromIcdName(String name) { } } - @NonNull + @NotNull public static GraphicsAdapterVendor fromContext(GlContextInfo context) { var vendor = context.vendor(); diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java index 2dbec228fa..94058d1ded 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/intel/IntelWorkarounds.java @@ -4,7 +4,7 @@ import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe; import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; import net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt.D3DKMT; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.Nullable; public class IntelWorkarounds { // https://github.com/CaffeineMC/sodium/issues/899 diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java index 19ede53790..572dc8b088 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/compatibility/workarounds/nvidia/NvidiaWorkarounds.java @@ -10,7 +10,7 @@ import net.caffeinemc.mods.sodium.client.platform.windows.WindowsCommandLine; import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; import net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt.D3DKMT; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.Nullable; import org.lwjgl.opengl.GL; import org.lwjgl.opengl.GL32C; import org.lwjgl.opengl.KHRDebug; diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/console/Console.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/console/Console.java index af97420ad8..7e4fa1a760 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/console/Console.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/console/Console.java @@ -2,7 +2,7 @@ import net.caffeinemc.mods.sodium.client.console.message.Message; import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; -import org.jspecify.annotations.NonNull; +import org.jetbrains.annotations.NotNull; import java.util.ArrayDeque; import java.util.Deque; @@ -14,7 +14,7 @@ public class Console implements ConsoleSink { private final ArrayDeque messages = new ArrayDeque<>(); @Override - public void logMessage(@NonNull MessageLevel level, @NonNull String text, boolean translatable, double duration) { + public void logMessage(@NotNull MessageLevel level, @NotNull String text, boolean translatable, double duration) { Objects.requireNonNull(level); Objects.requireNonNull(text); diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/console/ConsoleSink.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/console/ConsoleSink.java index 289c2af8c0..fb02fa03aa 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/console/ConsoleSink.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/console/ConsoleSink.java @@ -1,8 +1,8 @@ package net.caffeinemc.mods.sodium.client.console; import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; -import org.jspecify.annotations.NonNull; +import org.jetbrains.annotations.NotNull; public interface ConsoleSink { - void logMessage(@NonNull MessageLevel level, @NonNull String text, boolean translatable, double duration); + void logMessage(@NotNull MessageLevel level, @NotNull String text, boolean translatable, double duration); } diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/MessageBox.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/MessageBox.java index e5afb4f6b0..7aa49791f3 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/MessageBox.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/MessageBox.java @@ -5,7 +5,7 @@ import net.caffeinemc.mods.sodium.client.platform.windows.api.User32; import net.caffeinemc.mods.sodium.client.platform.windows.api.msgbox.MsgBoxCallback; import net.caffeinemc.mods.sodium.client.platform.windows.api.msgbox.MsgBoxParamSw; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.Nullable; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java index a1279bde7c..dfc1f2b77f 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/PlatformHelper.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.platform; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -10,9 +10,9 @@ public class PlatformHelper { public static void showCriticalErrorAndClose( @Nullable NativeWindowHandle window, - @NonNull String messageTitle, - @NonNull String messageBody, - @NonNull String helpUrl) + @NotNull String messageTitle, + @NotNull String messageBody, + @NotNull String helpUrl) { // Always print the information to the log file first, just in case we can't show the message box. LOGGER.error(""" diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/unix/Libc.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/unix/Libc.java index 66a7fad344..45c522e3e5 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/unix/Libc.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/unix/Libc.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.platform.unix; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.Nullable; import org.lwjgl.system.*; public class Libc { diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsFileVersion.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsFileVersion.java index ae8dca8dcd..991a1171ea 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsFileVersion.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/WindowsFileVersion.java @@ -1,10 +1,10 @@ package net.caffeinemc.mods.sodium.client.platform.windows; import net.caffeinemc.mods.sodium.client.platform.windows.api.version.VersionFixedFileInfoStruct; -import org.jspecify.annotations.NonNull; +import org.jetbrains.annotations.NotNull; public record WindowsFileVersion(int x, int y, int z, int w) { - public static @NonNull WindowsFileVersion fromFileVersion(VersionFixedFileInfoStruct fileVersion) { + public static @NotNull WindowsFileVersion fromFileVersion(VersionFixedFileInfoStruct fileVersion) { int x = (fileVersion.getFileVersionMostSignificantBits() >>> 16) & 0xffff; int y = (fileVersion.getFileVersionMostSignificantBits() >>> 0) & 0xffff; int z = (fileVersion.getFileVersionLeastSignificantBits() >>> 16) & 0xffff; diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Kernel32.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Kernel32.java index b9e4537676..f8b6fef62a 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Kernel32.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Kernel32.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.Nullable; import org.lwjgl.PointerBuffer; import org.lwjgl.system.*; diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Shell32.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Shell32.java index 38441da31e..5409b7fcea 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Shell32.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/Shell32.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api; import net.caffeinemc.mods.sodium.client.platform.NativeWindowHandle; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.Nullable; import org.lwjgl.system.JNI; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.SharedLibrary; diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java index dc9560809c..28cb79b0ac 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMT.java @@ -5,8 +5,8 @@ import net.caffeinemc.mods.sodium.client.platform.windows.WindowsFileVersion; import net.caffeinemc.mods.sodium.client.platform.windows.api.Gdi32; import net.caffeinemc.mods.sodium.client.platform.windows.api.version.Version; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.lwjgl.system.MemoryStack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,7 +46,7 @@ public static List findGraphicsAdapters() { } } - private static @NonNull ArrayList queryAdapters(D3DKMTAdapterInfoStruct.@NonNull Buffer adapterInfoBuffer) { + private static @NotNull ArrayList queryAdapters(D3DKMTAdapterInfoStruct.@NotNull Buffer adapterInfoBuffer) { var results = new ArrayList(); for (int adapterIndex = adapterInfoBuffer.position(); adapterIndex < adapterInfoBuffer.limit(); adapterIndex++) { @@ -63,7 +63,7 @@ public static List findGraphicsAdapters() { return results; } - private static void freeAdapters(D3DKMTAdapterInfoStruct.@NonNull Buffer adapterInfoBuffer) { + private static void freeAdapters(D3DKMTAdapterInfoStruct.@NotNull Buffer adapterInfoBuffer) { for (int adapterIndex = adapterInfoBuffer.position(); adapterIndex < adapterInfoBuffer.limit(); adapterIndex++) { var adapterInfo = adapterInfoBuffer.get(adapterIndex); apiCheckError("D3DKMTCloseAdapter", @@ -133,7 +133,7 @@ private static boolean isSupportedAdapterType(int adapterType) { return WindowsFileVersion.fromFileVersion(fileVersion); } - private static @NonNull String queryFriendlyName(int adapter) { + private static @NotNull String queryFriendlyName(int adapter) { try (MemoryStack stack = MemoryStack.stackPush()) { D3DKMTAdapterRegistryInfoStruct registryInfo = D3DKMTAdapterRegistryInfoStruct.calloc(stack); d3dkmtQueryAdapterInfo(adapter, KMTQAITYPE_ADAPTERREGISTRYINFO, memByteBuffer(registryInfo)); @@ -183,8 +183,8 @@ private static void apiCheckError(String name, int error) { } public record WDDMAdapterInfo( - @NonNull GraphicsAdapterVendor vendor, - @NonNull String name, + @NotNull GraphicsAdapterVendor vendor, + @NotNull String name, int adapterType, @Nullable String openglIcdFilePath, @Nullable WindowsFileVersion openglIcdVersion diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterInfoStruct.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterInfoStruct.java index c041ce7cf6..d57327a80f 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterInfoStruct.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterInfoStruct.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt; -import org.jspecify.annotations.NonNull; +import org.jetbrains.annotations.NotNull; import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.Struct; import org.lwjgl.system.StructBuffer; @@ -75,12 +75,12 @@ protected Buffer(long address, int capacity) { } @Override - protected @NonNull D3DKMTAdapterInfoStruct getElementFactory() { + protected @NotNull D3DKMTAdapterInfoStruct getElementFactory() { return ELEMENT_FACTORY; } @Override - protected @NonNull Buffer self() { + protected @NotNull Buffer self() { return this; } } diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterRegistryInfoStruct.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterRegistryInfoStruct.java index 0ab0e12d1f..1dba5ac999 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterRegistryInfoStruct.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTAdapterRegistryInfoStruct.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.Nullable; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.Struct; diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTEnumAdaptersStruct.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTEnumAdaptersStruct.java index 8fdc427a3d..ad07d37ea8 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTEnumAdaptersStruct.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTEnumAdaptersStruct.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.Struct; @@ -38,7 +38,7 @@ private D3DKMTEnumAdaptersStruct(long address, @Nullable ByteBuffer container) { } @Override - protected @NonNull D3DKMTEnumAdaptersStruct create(long address, ByteBuffer container) { + protected @NotNull D3DKMTEnumAdaptersStruct create(long address, ByteBuffer container) { return new D3DKMTEnumAdaptersStruct(address, container); } diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTOpenGLInfoStruct.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTOpenGLInfoStruct.java index 7f56aba924..b7008787d9 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTOpenGLInfoStruct.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTOpenGLInfoStruct.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.Struct; @@ -44,7 +44,7 @@ private D3DKMTOpenGLInfoStruct(long address, @Nullable ByteBuffer container) { } @Override - protected @NonNull D3DKMTOpenGLInfoStruct create(long address, ByteBuffer container) { + protected @NotNull D3DKMTOpenGLInfoStruct create(long address, ByteBuffer container) { return new D3DKMTOpenGLInfoStruct(address, container); } diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoStruct.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoStruct.java index 08dfae60d4..f211b8a241 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoStruct.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/d3dkmt/D3DKMTQueryAdapterInfoStruct.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.d3dkmt; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.Pointer; import org.lwjgl.system.Struct; @@ -48,7 +48,7 @@ private D3DKMTQueryAdapterInfoStruct(long address, @Nullable ByteBuffer containe } @Override - protected @NonNull D3DKMTAdapterInfoStruct create(long address, ByteBuffer container) { + protected @NotNull D3DKMTAdapterInfoStruct create(long address, ByteBuffer container) { return new D3DKMTAdapterInfoStruct(address, container); } diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallbackI.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallbackI.java index 6b4c1310b7..0994026e18 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallbackI.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxCallbackI.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.msgbox; -import org.jspecify.annotations.NonNull; +import org.jetbrains.annotations.NotNull; import org.lwjgl.system.CallbackI; import org.lwjgl.system.NativeType; import org.lwjgl.system.libffi.FFICIF; @@ -19,7 +19,7 @@ public interface MsgBoxCallbackI extends CallbackI { ); @Override - default @NonNull FFICIF getCallInterface() { + default @NotNull FFICIF getCallInterface() { return CIF; } diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxParamSw.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxParamSw.java index 10b4cbebe7..5108bb138a 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxParamSw.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/msgbox/MsgBoxParamSw.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.msgbox; -import org.jspecify.annotations.NonNull; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.Pointer; @@ -62,7 +62,7 @@ private MsgBoxParamSw(long address, @Nullable ByteBuffer container) { } @Override - protected @NonNull MsgBoxParamSw create(long address, ByteBuffer container) { + protected @NotNull MsgBoxParamSw create(long address, ByteBuffer container) { return new MsgBoxParamSw(address, container); } diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/Version.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/Version.java index aaff46c0bd..ea7c10b339 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/Version.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/Version.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.version; import net.caffeinemc.mods.sodium.client.platform.windows.api.Kernel32; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.Nullable; import org.lwjgl.PointerBuffer; import org.lwjgl.system.*; diff --git a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionInfo.java b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionInfo.java index d9e5d5c55f..cdb95f2bda 100644 --- a/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionInfo.java +++ b/common/src/boot/java/net/caffeinemc/mods/sodium/client/platform/windows/api/version/VersionInfo.java @@ -1,6 +1,6 @@ package net.caffeinemc.mods.sodium.client.platform.windows.api.version; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.Nullable; import org.lwjgl.system.MemoryUtil; import java.io.Closeable; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java index 72aa52cf9e..c5347917f9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java @@ -237,10 +237,10 @@ private abstract static class HeaderWidget extends AbstractWidget { final String title; final int textColor; final int backgroundColor; - @org.jspecify.annotations.Nullable + @Nullable final ResetButton resetButton; - public HeaderWidget(AbstractOptionList list, Dim2i dim, String title, int textColor, int backgroundColor, @org.jspecify.annotations.Nullable Runnable resetAction) { + public HeaderWidget(AbstractOptionList list, Dim2i dim, String title, int textColor, int backgroundColor, @Nullable Runnable resetAction) { super(dim); this.list = list; this.title = title; @@ -320,7 +320,7 @@ public PageHeaderWidget(AbstractOptionList list, Dim2i dim, Page page, ColorThem this(list, dim, "◆ ", page.name().getString(), theme, () -> resetAllOptions(page)); } - PageHeaderWidget(AbstractOptionList list, Dim2i dim, String prefix, String title, ColorTheme theme, @org.jspecify.annotations.Nullable Runnable resetAction) { + PageHeaderWidget(AbstractOptionList list, Dim2i dim, String prefix, String title, ColorTheme theme, @Nullable Runnable resetAction) { super(list, dim, prefix + title, theme.theme, Colors.BACKGROUND_DEFAULT, resetAction); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ResetButton.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ResetButton.java index ee9dc2c5d1..e0ea057b96 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ResetButton.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/ResetButton.java @@ -8,7 +8,7 @@ import net.minecraft.client.gui.navigation.FocusNavigationEvent; import net.minecraft.client.gui.screens.Screen; import net.minecraft.resources.ResourceLocation; -import org.jspecify.annotations.Nullable; +import org.jetbrains.annotations.Nullable; /** * Right-aligned reset overlay shown when the parent row is hovered while SHIFT is held. From 0659231efb694ffb1da6d2fff7585dc3f15dabbd Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 3 May 2026 22:55:24 +0200 Subject: [PATCH 177/215] Optimize imports --- .../mods/sodium/client/services/PlatformBlockAccess.java | 2 -- .../mods/sodium/client/services/PlatformModelAccess.java | 1 - 2 files changed, 3 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformBlockAccess.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformBlockAccess.java index a124dcabb7..69a58a3304 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformBlockAccess.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformBlockAccess.java @@ -2,7 +2,6 @@ import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadView; import net.caffeinemc.mods.sodium.client.render.frapi.render.AmbientOcclusionMode; -import net.fabricmc.fabric.api.util.TriState; import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.resources.model.BakedModel; @@ -13,7 +12,6 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.FluidState; -import org.jetbrains.annotations.Nullable; public interface PlatformBlockAccess { PlatformBlockAccess INSTANCE = Services.load(PlatformBlockAccess.class); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformModelAccess.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformModelAccess.java index efca9b4d44..af68c4f700 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformModelAccess.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/services/PlatformModelAccess.java @@ -9,7 +9,6 @@ import net.minecraft.core.SectionPos; import net.minecraft.util.RandomSource; import net.minecraft.world.level.BlockAndTintGetter; -import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.state.BlockState; import org.jetbrains.annotations.ApiStatus; From a6ba00758d2566558752dbf2885c277ebd386eb8 Mon Sep 17 00:00:00 2001 From: JellySquid Date: Sun, 13 Apr 2025 00:30:07 +0200 Subject: [PATCH 178/215] Use `GL_MAX_TEXTURE_LOD_BIAS` as the lod-bias in texture sampling The -4.0 bias is not enough on some hardware as OpenGL applies the bias *before* clamping the computed lod. Fixes #2631 --- .../client/gl/device/GLRenderDevice.java | 5 +++++ .../sodium/client/gl/device/RenderDevice.java | 1 + .../render/chunk/ShaderChunkRenderer.java | 19 +++++++++++++++++-- .../shaders/blocks/block_layer_opaque.fsh | 18 +++++++++++------- .../shaders/blocks/block_layer_opaque.vsh | 11 ++--------- .../shaders/include/chunk_material.glsl | 4 ++-- 6 files changed, 38 insertions(+), 20 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java index 0870f59114..958b504c86 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java @@ -75,6 +75,11 @@ public int getSubTexelPrecisionBits() { return 8; } + @Override + public int getMaxTextureLodBias() { + return GL30C.glGetInteger(GL30C.GL_MAX_TEXTURE_LOD_BIAS); + } + private void checkDeviceActive() { if (!this.isActive) { throw new IllegalStateException("Tried to access device from unmanaged context"); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/RenderDevice.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/RenderDevice.java index 9787b829b6..ed2f53261c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/RenderDevice.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/RenderDevice.java @@ -24,4 +24,5 @@ static void exitManagedCode() { DeviceFunctions getDeviceFunctions(); int getSubTexelPrecisionBits(); + int getMaxTextureLodBias(); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ShaderChunkRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ShaderChunkRenderer.java index 675e8ae632..708ca6987b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ShaderChunkRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ShaderChunkRenderer.java @@ -4,11 +4,12 @@ import net.caffeinemc.mods.sodium.client.gl.attribute.GlVertexFormat; import net.caffeinemc.mods.sodium.client.gl.device.CommandList; import net.caffeinemc.mods.sodium.client.gl.device.RenderDevice; +import net.caffeinemc.mods.sodium.client.gl.shader.*; import net.caffeinemc.mods.sodium.client.render.chunk.shader.*; import net.caffeinemc.mods.sodium.client.render.chunk.terrain.TerrainRenderPass; import net.caffeinemc.mods.sodium.client.render.chunk.vertex.format.ChunkVertexType; -import net.caffeinemc.mods.sodium.client.gl.shader.*; import net.minecraft.resources.ResourceLocation; + import java.util.Map; public abstract class ShaderChunkRenderer implements ChunkRenderer { @@ -38,7 +39,7 @@ protected GlProgram compileProgram(ChunkShaderOptions opti } private GlProgram createShader(String path, ChunkShaderOptions options) { - ShaderConstants constants = options.constants(); + ShaderConstants constants = createShaderConstants(options); GlShader vertShader = ShaderLoader.loadShader(ShaderType.VERTEX, ResourceLocation.fromNamespaceAndPath("sodium", path + ".vsh"), constants); @@ -62,6 +63,20 @@ private GlProgram createShader(String path, ChunkShaderOpt } } + private static ShaderConstants createShaderConstants(ChunkShaderOptions options) { + ShaderConstants.Builder builder = ShaderConstants.builder(); + builder.addAll(options.fog().getDefines()); + + if (options.pass().supportsFragmentDiscard()) { + builder.add("USE_FRAGMENT_DISCARD"); + } + + builder.add("USE_VERTEX_COMPRESSION"); // TODO: allow compact vertex format to be disabled + builder.add("MAX_TEXTURE_LOD_BIAS", String.valueOf(RenderDevice.INSTANCE.getMaxTextureLodBias())); + + return builder.build(); + } + protected void begin(TerrainRenderPass pass) { pass.startDrawing(); diff --git a/common/src/main/resources/assets/sodium/shaders/blocks/block_layer_opaque.fsh b/common/src/main/resources/assets/sodium/shaders/blocks/block_layer_opaque.fsh index 314e696962..49de52fc33 100644 --- a/common/src/main/resources/assets/sodium/shaders/blocks/block_layer_opaque.fsh +++ b/common/src/main/resources/assets/sodium/shaders/blocks/block_layer_opaque.fsh @@ -1,13 +1,17 @@ #version 330 core +#ifndef MAX_TEXTURE_LOD_BIAS +#error "MAX_TEXTURE_LOD_BIAS constant not specified" +#endif + #import +#import in vec4 v_Color; // The interpolated vertex color in vec2 v_TexCoord; // The interpolated block texture coordinates in float v_FragDistance; // The fragment's distance from the camera -in float v_MaterialMipBias; -in float v_MaterialAlphaCutoff; +flat in uint v_Material; uniform sampler2D u_BlockTex; // The block texture @@ -18,16 +22,16 @@ uniform float u_FogEnd; // The ending position of the shader fog out vec4 fragColor; // The output fragment for the color framebuffer void main() { - vec4 diffuseColor = texture(u_BlockTex, v_TexCoord, v_MaterialMipBias); + float lodBias = _material_use_mips(v_Material) ? 0.0 : float(-MAX_TEXTURE_LOD_BIAS); - // Apply per-vertex color - diffuseColor *= v_Color; + vec4 color = texture(u_BlockTex, v_TexCoord, lodBias); + color *= v_Color; // Apply per-vertex color modulator #ifdef USE_FRAGMENT_DISCARD - if (diffuseColor.a < v_MaterialAlphaCutoff) { + if (color.a < _material_alpha_cutoff(v_Material)) { discard; } #endif - fragColor = _linearFog(diffuseColor, v_FragDistance, u_FogColor, u_FogStart, u_FogEnd); + fragColor = _linearFog(color, v_FragDistance, u_FogColor, u_FogStart, u_FogEnd); } \ No newline at end of file diff --git a/common/src/main/resources/assets/sodium/shaders/blocks/block_layer_opaque.vsh b/common/src/main/resources/assets/sodium/shaders/blocks/block_layer_opaque.vsh index fcd0ddfc50..4ba93a38e5 100644 --- a/common/src/main/resources/assets/sodium/shaders/blocks/block_layer_opaque.vsh +++ b/common/src/main/resources/assets/sodium/shaders/blocks/block_layer_opaque.vsh @@ -3,15 +3,11 @@ #import #import #import -#import out vec4 v_Color; out vec2 v_TexCoord; -out float v_MaterialMipBias; -#ifdef USE_FRAGMENT_DISCARD -out float v_MaterialAlphaCutoff; -#endif +flat out uint v_Material; #ifdef USE_FOG out float v_FragDistance; @@ -50,8 +46,5 @@ void main() { v_Color = _vert_color * texture(u_LightTex, _vert_tex_light_coord); v_TexCoord = (_vert_tex_diffuse_coord_bias * u_TexCoordShrink) + _vert_tex_diffuse_coord; // FMA for precision - v_MaterialMipBias = _material_mip_bias(_material_params); -#ifdef USE_FRAGMENT_DISCARD - v_MaterialAlphaCutoff = _material_alpha_cutoff(_material_params); -#endif + v_Material = _material_params; } diff --git a/common/src/main/resources/assets/sodium/shaders/include/chunk_material.glsl b/common/src/main/resources/assets/sodium/shaders/include/chunk_material.glsl index bd73ad3976..11e8e0112b 100644 --- a/common/src/main/resources/assets/sodium/shaders/include/chunk_material.glsl +++ b/common/src/main/resources/assets/sodium/shaders/include/chunk_material.glsl @@ -3,8 +3,8 @@ const uint MATERIAL_ALPHA_CUTOFF_OFFSET = 1u; const float[4] ALPHA_CUTOFF = float[4](0.0, 0.1, 0.1, 1.0); -float _material_mip_bias(uint material) { - return ((material >> MATERIAL_USE_MIP_OFFSET) & 1u) != 0u ? 0.0 : -4.0; +bool _material_use_mips(uint material) { + return ((material >> MATERIAL_USE_MIP_OFFSET) & 1u) != 0u; } float _material_alpha_cutoff(uint material) { From 333c366e414b7024da530e31436160d0bd98ed2c Mon Sep 17 00:00:00 2001 From: douira Date: Wed, 6 May 2026 16:00:02 +0200 Subject: [PATCH 179/215] Fix crash with WindowMixin when running with mixin debug enabled --- .../net/caffeinemc/mods/sodium/mixin/core/WindowMixin.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/WindowMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/WindowMixin.java index 78792f3971..a87357da30 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/WindowMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/WindowMixin.java @@ -3,9 +3,9 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.mojang.blaze3d.platform.Window; -import net.caffeinemc.mods.sodium.client.platform.NativeWindowHandle; import net.caffeinemc.mods.sodium.client.SodiumClientMod; import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds; +import net.caffeinemc.mods.sodium.client.platform.NativeWindowHandle; import net.caffeinemc.mods.sodium.client.services.PlatformRuntimeInformation; import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFWNativeWin32; @@ -20,7 +20,7 @@ public class WindowMixin implements NativeWindowHandle { @Final private long window; - @WrapOperation(method = "", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwCreateWindow(IILjava/lang/CharSequence;JJ)J"), require = 0) + @WrapOperation(method = "", at = @At(value = "INVOKE", target = "Lorg/lwjgl/glfw/GLFW;glfwCreateWindow(IILjava/lang/CharSequence;JJ)J"), require = 0, expect = 0) public long setAdditionalWindowHints(int titleEncoded, int width, CharSequence height, long title, long monitor, Operation original) { if (!PlatformRuntimeInformation.getInstance().platformHasEarlyLoadingScreen()) { if (SodiumClientMod.options().performance.useNoErrorGLContext) { From 9703ff560bb41387aced1e68ed442d2687a4f178 Mon Sep 17 00:00:00 2001 From: douira Date: Wed, 6 May 2026 16:33:33 +0200 Subject: [PATCH 180/215] Modernize java compatibility level --- common/src/main/resources/sodium-common.mixins.json | 2 +- fabric/src/main/resources/sodium-fabric.mixins.json | 2 +- neoforge/src/mod/resources/sodium-neoforge.mixins.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/main/resources/sodium-common.mixins.json b/common/src/main/resources/sodium-common.mixins.json index f48ddca06f..061c044ab8 100644 --- a/common/src/main/resources/sodium-common.mixins.json +++ b/common/src/main/resources/sodium-common.mixins.json @@ -1,7 +1,7 @@ { "package": "net.caffeinemc.mods.sodium.mixin", "required": true, - "compatibilityLevel": "JAVA_17", + "compatibilityLevel": "JAVA_21", "plugin": "net.caffeinemc.mods.sodium.mixin.SodiumMixinPlugin", "injectors": { "defaultRequire": 1 diff --git a/fabric/src/main/resources/sodium-fabric.mixins.json b/fabric/src/main/resources/sodium-fabric.mixins.json index 5722fa5735..2bf1ee4ff3 100644 --- a/fabric/src/main/resources/sodium-fabric.mixins.json +++ b/fabric/src/main/resources/sodium-fabric.mixins.json @@ -1,7 +1,7 @@ { "package": "net.caffeinemc.mods.sodium.mixin", "required": true, - "compatibilityLevel": "JAVA_17", + "compatibilityLevel": "JAVA_21", "injectors": { "defaultRequire": 1 }, diff --git a/neoforge/src/mod/resources/sodium-neoforge.mixins.json b/neoforge/src/mod/resources/sodium-neoforge.mixins.json index 196ea1a733..7657fd0a88 100644 --- a/neoforge/src/mod/resources/sodium-neoforge.mixins.json +++ b/neoforge/src/mod/resources/sodium-neoforge.mixins.json @@ -1,7 +1,7 @@ { "package" : "net.caffeinemc.mods.sodium.mixin", "required" : true, - "compatibilityLevel" : "JAVA_17", + "compatibilityLevel" : "JAVA_21", "injectors" : { "defaultRequire" : 1 }, From 3bac67d1b297eac8b53bfdbc90a7ddcd13c6e4d9 Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 4 May 2026 02:27:31 +0200 Subject: [PATCH 181/215] Update forgified fabric renderer api and implement indigo changes to fix multiple issues --- .../render/AbstractBlockRenderContext.java | 39 ++++++++++++++++-- neoforge/build.gradle.kts | 10 +---- .../AbstractBlockRenderContextMixin.java | 41 ++++++++++++++++++- 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractBlockRenderContext.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractBlockRenderContext.java index 828e2663ae..f736dc8435 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractBlockRenderContext.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractBlockRenderContext.java @@ -32,6 +32,8 @@ import net.minecraft.world.level.block.state.BlockState; import org.jetbrains.annotations.Nullable; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.List; import java.util.function.Supplier; @@ -58,6 +60,7 @@ public abstract class AbstractBlockRenderContext extends AbstractRenderContext { STANDARD_MATERIALS[i] = SodiumRenderer.INSTANCE.materialFinder().ambientOcclusion(state).find(); } } + private final MutableQuadViewImpl editorQuad = new MutableQuadViewImpl() { { data = new int[EncodingFormat.TOTAL_STRIDE]; @@ -103,6 +106,18 @@ public void emitDirectly() { */ protected SodiumModelData modelData; + /** + * Submodel ModelData stack pushed by FFAPI's MultipartBakedModelMixin during multipart traversal. + * See this commit. + */ + protected final Deque modelDataStack = new ArrayDeque<>(); + + /** + * Submodel AO override pushed by FFAPI's MultipartBakedModelMixin; non-DEFAULT forces the parent multipart's choice. + * See this commit. + */ + protected TriState useAO = TriState.DEFAULT; + private final BlockOcclusionCache occlusionCache = new BlockOcclusionCache(); private boolean enableCulling = true; // Cull cache (as it's checked per-quad instead of once per side like in vanilla) @@ -158,6 +173,19 @@ public ItemDisplayContext itemTransformationMode() { throw new UnsupportedOperationException("itemTransformationMode can only be called on an item render context."); } + protected SodiumModelData currentModelData() { + SodiumModelData top = this.modelDataStack.peek(); + return top != null ? top : this.modelData; + } + + private static AmbientOcclusionMode applyAOOverride(AmbientOcclusionMode mode, TriState override) { + return switch (override) { + case TRUE -> AmbientOcclusionMode.ENABLED; + case FALSE -> AmbientOcclusionMode.DISABLED; + default -> mode; + }; + } + @SuppressWarnings("removal") @Deprecated @Override @@ -220,6 +248,9 @@ protected void shadeQuad(MutableQuadViewImpl quad, LightMode lightMode, boolean public void bufferDefaultModel(BakedModel model, @Nullable BlockState state) { MutableQuadViewImpl editorQuad = this.editorQuad; + // Per-submodel state pushed by FFAPI's multipart mixin, falls through to the root context otherwise. + final SodiumModelData currentData = this.currentModelData(); + final TriState aoOverride = this.useAO; // If there is no transform, we can check the culling face once for all the quads, // and we don't need to check for transforms per-quad. @@ -229,10 +260,12 @@ public void bufferDefaultModel(BakedModel model, @Nullable BlockState state) { final Direction cullFace = ModelHelper.faceFromIndex(i); RandomSource random = this.randomSupplier.get(); - AmbientOcclusionMode ao = PlatformBlockAccess.getInstance().usesAmbientOcclusion(model, state, modelData, type, slice, pos); + AmbientOcclusionMode ao = applyAOOverride( + PlatformBlockAccess.getInstance().usesAmbientOcclusion(model, state, currentData, type, slice, pos), + aoOverride); if (noTransform) { if (!this.isFaceCulled(cullFace)) { - final List quads = PlatformModelAccess.getInstance().getQuads(level, pos, model, state, cullFace, random, type, modelData); + final List quads = PlatformModelAccess.getInstance().getQuads(level, pos, model, state, cullFace, random, type, currentData); final int count = quads.size(); for (int j = 0; j < count; j++) { @@ -245,7 +278,7 @@ public void bufferDefaultModel(BakedModel model, @Nullable BlockState state) { } } } else { - final List quads = PlatformModelAccess.getInstance().getQuads(level, pos, model, state, cullFace, random, type, modelData); + final List quads = PlatformModelAccess.getInstance().getQuads(level, pos, model, state, cullFace, random, type, currentData); final int count = quads.size(); for (int j = 0; j < count; j++) { diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index cbaba51297..5c21b6decd 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -9,14 +9,6 @@ base { } repositories { - maven("https://maven.pkg.github.com/ims212/Forge_Fabric_API") { - credentials { - username = "IMS212" - // Read only token - password = "ghp_" + "DEuGv0Z56vnSOYKLCXdsS9svK4nb9K39C1Hn" - } - } - maven("https://maven.su5ed.dev/releases") maven("https://maven.neoforged.net/releases/") } @@ -54,7 +46,7 @@ dependencies { } addEmbeddedFabricModule("org.sinytra.forgified-fabric-api:fabric-api-base:0.4.42+d1308ded19") - addEmbeddedFabricModule("org.sinytra.forgified-fabric-api:fabric-renderer-api-v1:3.4.0+9c40919e19") + addEmbeddedFabricModule("org.sinytra.forgified-fabric-api:fabric-renderer-api-v1:3.4.1+9125b6dc19") addEmbeddedFabricModule("org.sinytra.forgified-fabric-api:fabric-rendering-data-attachment-v1:0.3.48+73761d2e19") addEmbeddedFabricModule("org.sinytra.forgified-fabric-api:fabric-block-view-api-v2:1.0.10+9afaaf8c19") diff --git a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AbstractBlockRenderContextMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AbstractBlockRenderContextMixin.java index ed7d5a42ac..52574202b7 100644 --- a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AbstractBlockRenderContextMixin.java +++ b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AbstractBlockRenderContextMixin.java @@ -3,11 +3,21 @@ import net.caffeinemc.mods.sodium.client.render.frapi.render.AbstractBlockRenderContext; import net.caffeinemc.mods.sodium.client.services.SodiumModelData; import net.fabricmc.fabric.api.renderer.v1.render.RenderContext; +import net.fabricmc.fabric.api.util.TriState; import net.minecraft.client.renderer.RenderType; import net.neoforged.neoforge.client.model.data.ModelData; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import java.util.Deque; + +/** + * Self-mixin that implements {@link RenderContext} with NeoForge's {@link ModelData} based on + * {@link SodiumModelData} state in {@link AbstractBlockRenderContext}. + * + * See #3517. + */ @Mixin(AbstractBlockRenderContext.class) public abstract class AbstractBlockRenderContextMixin implements RenderContext { @Shadow @@ -16,13 +26,42 @@ public abstract class AbstractBlockRenderContextMixin implements RenderContext { @Shadow protected SodiumModelData modelData; + @Shadow + @Final + protected Deque modelDataStack; + + @Shadow + protected TriState useAO; + @Override public ModelData getModelData() { - return (ModelData) (Object) this.modelData; + SodiumModelData top = this.modelDataStack.peek(); + return (ModelData) (Object) (top != null ? top : this.modelData); } @Override public RenderType getRenderType() { return type; } + + @Override + public void pushModelData(ModelData modelData) { + // ModelData implements SodiumModelData via ModelDataMixin + this.modelDataStack.push((SodiumModelData) (Object) modelData); + } + + @Override + public void popModelData() { + this.modelDataStack.pop(); + } + + @Override + public TriState usesAmbientOcclusion() { + return this.useAO; + } + + @Override + public void setUsesAmbientOcclusion(TriState state) { + this.useAO = state; + } } From 2bea172ba20211b0c1a1873a9c0f4623cdc8feb6 Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 5 May 2026 22:22:48 +0200 Subject: [PATCH 182/215] Add manual way of publishing tags to caffeinemc maven to work around intermittent github downtimes causing missed publishing --- .github/workflows/publish-maven.yml | 47 +++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/publish-maven.yml diff --git a/.github/workflows/publish-maven.yml b/.github/workflows/publish-maven.yml new file mode 100644 index 0000000000..6580cbb219 --- /dev/null +++ b/.github/workflows/publish-maven.yml @@ -0,0 +1,47 @@ +# Used when a developer decides to publish a release to the caffeinemc maven +# This is primarily for when we manually need to publish a release because for some reason publishing broke +name: Release CaffeineMC Maven + +on: [ workflow_dispatch ] + +jobs: + build: + runs-on: ubuntu-latest + environment: + name: prod + + steps: + - name: Extract current branch name + shell: bash + # bash pattern expansion to grab branch name without slashes + run: ref="${GITHUB_REF#refs/heads/}" && echo "branch=${ref////-}" >> $GITHUB_OUTPUT + id: ref + + - name: Checkout sources + uses: actions/checkout@v6 + + - uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-read-only: true + + - name: Execute Gradle build + run: ./gradlew build -Pbuild.release=true + + - name: Upload artifacts + uses: actions/upload-artifact@v7 + with: + name: sodium-artifacts-${{ steps.ref.outputs.ref }} + path: build/mods/*.jar + + - name: Publish Tag Release to CaffeineMC Maven + env: + ORG_GRADLE_PROJECT_caffeineMCMavenUsername: ${{ secrets.CAFFEINEMC_MAVEN_USERNAME }} + ORG_GRADLE_PROJECT_caffeineMCMavenPassword: ${{ secrets.CAFFEINEMC_MAVEN_PASSWORD }} + if: ${{ env.ORG_GRADLE_PROJECT_caffeineMCMavenUsername != '' && env.ORG_GRADLE_PROJECT_caffeineMCMavenPassword != '' }} + run: ./gradlew publishAllPublicationsToCaffeineMCRepository -Pbuild.release=true From 3c608e21de076076d2ab87580d5d897ccb7d01e7 Mon Sep 17 00:00:00 2001 From: douira Date: Tue, 5 May 2026 00:38:09 +0200 Subject: [PATCH 183/215] Avoid retaining large allocations created when heavy quad splitting takes place --- .../translucent_sorting/data/DynamicBSPData.java | 13 ++++++++++++- .../translucent_sorting/data/TranslucentData.java | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java index 51aa594387..f0a9601d25 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java @@ -22,13 +22,15 @@ public class DynamicBSPData extends DynamicData { private final int indexQuadCount; private final BSPNode rootNode; private final int generation; - private final UpdatedQuadsList updatedQuadsList; // TODO: delete reference after mesh task is done since this won't be needed anymore after that + private UpdatedQuadsList updatedQuadsList; + private final boolean neededQuadSplitting; private DynamicBSPData(SectionPos sectionPos, int inputQuadCount, BSPResult result, Vector3dc initialCameraPos, int generation) { super(sectionPos, inputQuadCount, result, initialCameraPos); this.rootNode = result.getRootNode(); this.generation = generation; this.updatedQuadsList = result.getUpdatedQuadsList(); + this.neededQuadSplitting = this.updatedQuadsList != null; if (this.updatedQuadsList != null) { this.indexQuadCount = this.updatedQuadsList.getIndexQuadCount(); @@ -61,6 +63,10 @@ public int getIndexQuadCount() { @Override public DynamicSorter getSorter() { + // release references to the modified quad list, + // since we sort during the meshing task for the first time (in particular, when there was a non-null updated quad list) + this.updatedQuadsList = null; + return new DynamicBSPSorter(this.getIndexQuadCount()); // index quad count } @@ -69,6 +75,11 @@ public UpdatedQuadsList getUpdatedQuads() { return this.updatedQuadsList; } + @Override + public boolean meshesWereModified() { + return this.neededQuadSplitting; + } + public static DynamicBSPData fromMesh(CombinedCameraPos cameraPos, TQuad[] quads, SectionPos sectionPos, TranslucentData oldData, QuadSplittingMode quadSplittingMode) { BSPNode oldRoot = null; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TranslucentData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TranslucentData.java index 6e1718ce75..635201bd0f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TranslucentData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/TranslucentData.java @@ -33,7 +33,7 @@ public UpdatedQuadsList getUpdatedQuads() { } public boolean meshesWereModified() { - return this.getUpdatedQuads() != null; + return false; } /** From 18e74c4301d97f09f15a4e2a65989a3e878ccda9 Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 7 May 2026 00:00:37 +0200 Subject: [PATCH 184/215] Disable BSP Node reuse when quads were split Fixes #3617 --- .../chunk/translucent_sorting/bsp_tree/BSPNode.java | 4 ++-- .../translucent_sorting/bsp_tree/BSPWorkspace.java | 10 ++++++++-- .../bsp_tree/InnerPartitionBSPNode.java | 2 +- .../chunk/translucent_sorting/data/DynamicBSPData.java | 8 ++++++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java index e0664e8634..a199c17a14 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPNode.java @@ -32,13 +32,13 @@ public void collectSortedQuads(NativeBuffer nativeBuffer, Vector3fc cameraPos) { } public static BSPResult buildBSP(TQuad[] quads, SectionPos sectionPos, BSPNode oldRoot, - boolean prepareNodeReuse, QuadSplittingMode quadSplittingMode) { + boolean prepareNodeReuse, boolean allowNodeReuse, QuadSplittingMode quadSplittingMode) { // throw if there's too many quads InnerPartitionBSPNode.validateQuadCount(quads.length); // create a workspace and then the nodes figure out the recursive building. // throws if the BSP can't be built, null if none is necessary - var workspace = new BSPWorkspace(quads, sectionPos, prepareNodeReuse, quadSplittingMode); + var workspace = new BSPWorkspace(quads, sectionPos, prepareNodeReuse, allowNodeReuse, quadSplittingMode); // initialize the indexes to all quads int[] initialIndexes = new int[quads.length]; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPWorkspace.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPWorkspace.java index f7c9affd8e..fae176ab61 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPWorkspace.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPWorkspace.java @@ -21,7 +21,8 @@ class BSPWorkspace extends ObjectArrayList { final BSPResult result = new BSPResult(); private final SectionPos sectionPos; - final boolean prepareNodeReuse; + boolean prepareNodeReuse; + final boolean allowNodeReuse; final boolean quantizeTriggerNormals; private int quadCount; @@ -29,10 +30,11 @@ class BSPWorkspace extends ObjectArrayList { private IntArrayList availableQuadIndexes; private UpdatedQuadsList updatedQuads; - BSPWorkspace(TQuad[] quads, SectionPos sectionPos, boolean prepareNodeReuse, QuadSplittingMode quadSplittingMode) { + BSPWorkspace(TQuad[] quads, SectionPos sectionPos, boolean prepareNodeReuse, boolean allowNodeReuse, QuadSplittingMode quadSplittingMode) { super(quads); this.sectionPos = sectionPos; this.prepareNodeReuse = prepareNodeReuse; + this.allowNodeReuse = allowNodeReuse; this.quantizeTriggerNormals = quadSplittingMode.quantizeTriggerNormals(); this.quadCount = quads.length; @@ -65,6 +67,10 @@ private void registerQuadUpdate(FullTQuad quad) { } this.updatedQuads.add(quad); } + + // don't attempt any node reuse preparation if any quads were split, + // already prepared node reuse will simply be ignored + this.prepareNodeReuse = false; } public UpdatedQuadsList getFinalizedUpdatedQuads() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java index 7462eb0e54..66fe1588cd 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/InnerPartitionBSPNode.java @@ -153,7 +153,7 @@ boolean hasFixedOffset() { } static InnerPartitionBSPNode attemptNodeReuse(BSPWorkspace workspace, IntArrayList newIndexes, InnerPartitionBSPNode oldNode) { - if (oldNode == null) { + if (oldNode == null || !workspace.allowNodeReuse) { return null; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java index f0a9601d25..ed2e9c64fb 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicBSPData.java @@ -85,15 +85,19 @@ public static DynamicBSPData fromMesh(CombinedCameraPos cameraPos, TQuad[] quads BSPNode oldRoot = null; int generation = 0; boolean prepareNodeReuse = false; + boolean allowNodeReuse = false; if (oldData instanceof DynamicBSPData oldBSPData) { generation = oldBSPData.generation + 1; oldRoot = oldBSPData.rootNode; + // disallow making use of node reuse if quad splitting ended up being needed when the tree was originally built + allowNodeReuse = !oldBSPData.neededQuadSplitting; + // only enable partial updates after a certain number of generations // (times the section has been built) - prepareNodeReuse = generation >= NODE_REUSE_MIN_GENERATION; + prepareNodeReuse = allowNodeReuse && generation >= NODE_REUSE_MIN_GENERATION; } - var result = BSPNode.buildBSP(quads, sectionPos, oldRoot, prepareNodeReuse, quadSplittingMode); + var result = BSPNode.buildBSP(quads, sectionPos, oldRoot, prepareNodeReuse, allowNodeReuse, quadSplittingMode); var dynamicData = new DynamicBSPData(sectionPos, quads.length, result, cameraPos.getAbsoluteCameraPos(), generation); From 7b53f55d2c667690f520ab8a2d6d4d912b07410a Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 7 May 2026 01:13:02 +0200 Subject: [PATCH 185/215] Fix wrong applicability test in BaseBiForest that would cause wrong rendering at render distances >32 --- .../mods/sodium/client/render/chunk/tree/BaseBiForest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseBiForest.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseBiForest.java index ddbfe1ae05..9fa4828d9d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseBiForest.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/BaseBiForest.java @@ -4,6 +4,7 @@ public abstract class BaseBiForest extends BaseForest { private static final int SECONDARY_TREE_OFFSET_XZ = 4; + private static final int MAX_BUILD_DISTANCE = 16 * 65 / 2; // radius of 32 chunk render distance protected final T mainTree; protected T secondaryTree; @@ -48,7 +49,8 @@ public int getPresence(int x, int y, int z) { } public static boolean checkApplicable(float buildDistance, Level level) { - if (buildDistance / 16.0f > 64.0f) { + int buildDistanceInt = (int) Math.ceil(buildDistance); + if (buildDistanceInt > MAX_BUILD_DISTANCE) { return false; } From 1396042af89f9c675c32d4bf9d1e97397deeb168 Mon Sep 17 00:00:00 2001 From: douira Date: Fri, 8 May 2026 20:32:57 +0200 Subject: [PATCH 186/215] Significantly reduce memory consumption on the direct sorting fallback path We now throw away full quad data if we've fully fallen back to the direct sorting path --- .../data/DynamicTopoData.java | 74 ++++++++++++++----- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java index 204fa0b7a9..548d2eb51e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/data/DynamicTopoData.java @@ -10,6 +10,7 @@ import java.nio.IntBuffer; import java.util.function.IntConsumer; +import java.util.function.Supplier; /** * Performs dynamic topo sorting and falls back to distance sorting as @@ -34,26 +35,40 @@ public class DynamicTopoData extends DynamicData { private static final int PATIENT_TOPO_ATTEMPTS = 5; private static final int REGULAR_TOPO_ATTEMPTS = 2; - private boolean GFNITrigger = true; - private boolean directTrigger = false; + private boolean GFNITrigger; + private boolean directTrigger; private int consecutiveTopoSortFailures = 0; private double directTriggerKey = -1; private boolean pendingTriggerIsDirect; - private final TQuad[] quads; - private final Object2ReferenceMap distancesByNormal; + private TQuad[] quads; + private Vector3fc[] centroids; + private Object2ReferenceMap distancesByNormal; private DynamicTopoData(SectionPos sectionPos, TQuad[] quads, GeometryPlanes geometryPlanes, Vector3dc initialCameraPos, - Object2ReferenceMap distancesByNormal) { + Supplier> distancesByNormal) { super(sectionPos, quads.length, geometryPlanes, initialCameraPos); - this.quads = quads; - this.distancesByNormal = distancesByNormal; if (this.getInputQuadCount() > MAX_TOPO_SORT_QUADS) { this.directTrigger = true; this.GFNITrigger = false; + + this.computeCentroids(quads); + } else { + this.directTrigger = false; + this.GFNITrigger = true; + + this.quads = quads; + this.distancesByNormal = distancesByNormal.get(); + } + } + + private void computeCentroids(TQuad[] quads) { + this.centroids = new Vector3fc[quads.length]; + for (int i = 0; i < quads.length; i++) { + this.centroids[i] = quads[i].getCenter(); } } @@ -85,6 +100,7 @@ public boolean isMatchingSorter(DynamicTopoSorter sorter) { public boolean checkAndApplyGFNITriggerOff(DynamicTopoSorter sorter) { if (this.GFNITrigger && !sorter.GFNITrigger) { this.GFNITrigger = false; + this.checkDirectSortingFallback(); return true; } return false; @@ -118,6 +134,18 @@ private void copyStateFrom(DynamicTopoSorter sorter) { this.GFNITrigger = sorter.GFNITrigger; this.directTrigger = sorter.directTrigger; this.consecutiveTopoSortFailures = sorter.consecutiveTopoSortFailuresNew; + + this.checkDirectSortingFallback(); + } + + private void checkDirectSortingFallback() { + // once the GFNI trigger is turned off, the topo sort data is never used again, so it can be freed to save memory. + if (!this.GFNITrigger && this.quads != null) { + this.computeCentroids(this.quads); + + this.quads = null; + this.distancesByNormal = null; + } } @Override @@ -205,7 +233,7 @@ void writeSort(CombinedCameraPos cameraPos, boolean initial) { if (this.directTrigger) { indexBuffer.rewind(); - distanceSortDirect(indexBuffer, DynamicTopoData.this.quads, cameraPos.getRelativeCameraPos()); + distanceSortDirect(indexBuffer, DynamicTopoData.this.centroids, DynamicTopoData.this.quads, cameraPos.getRelativeCameraPos()); } if (initial) { @@ -218,31 +246,41 @@ void writeSort(CombinedCameraPos cameraPos, boolean initial) { * Sorts the given quads by descending center distance to the camera and writes * the resulting order to the given index buffer. */ - static void distanceSortDirect(IntBuffer indexBuffer, TQuad[] quads, Vector3fc cameraPos) { - if (quads.length <= 1) { + static void distanceSortDirect(IntBuffer indexBuffer, Vector3fc[] centroids, TQuad[] quads, Vector3fc cameraPos) { + int count; + if (centroids != null) { + count = centroids.length; + } else { + count = quads.length; + } + + if (count <= 1) { // Avoid allocations when there is nothing to sort. TranslucentData.writeQuadVertexIndexes(indexBuffer, 0); } else { - final var keys = new int[quads.length]; - final var perm = new int[quads.length]; + final var keys = new int[count]; + final var perm = new int[count]; - for (int idx = 0; idx < quads.length; idx++) { - var centroid = quads[idx].getCenter(); + for (int idx = 0; idx < count; idx++) { + Vector3fc centroid; + if (centroids != null) { + centroid = centroids[idx]; + } else { + centroid = quads[idx].getCenter(); + } keys[idx] = ~Float.floatToRawIntBits(centroid.distanceSquared(cameraPos)); perm[idx] = idx; } RadixSort.sortIndirect(perm, keys, false); - for (int idx = 0; idx < quads.length; idx++) { + for (int idx = 0; idx < count; idx++) { TranslucentData.writeQuadVertexIndexes(indexBuffer, perm[idx]); } } } public static DynamicTopoData fromMesh(CombinedCameraPos cameraPos, TQuad[] quads, SectionPos sectionPos, GeometryPlanes geometryPlanes) { - var distancesByNormal = geometryPlanes.prepareAndGetDistances(); - - return new DynamicTopoData(sectionPos, quads, geometryPlanes, cameraPos.getAbsoluteCameraPos(), distancesByNormal); + return new DynamicTopoData(sectionPos, quads, geometryPlanes, cameraPos.getAbsoluteCameraPos(), geometryPlanes::prepareAndGetDistances); } } From a2a74f0e23dd3d4dfcf0d2e50188e10fed709f28 Mon Sep 17 00:00:00 2001 From: GeForceLegend <41003546+GeForceLegend@users.noreply.github.com> Date: Sat, 9 May 2026 06:08:21 +0800 Subject: [PATCH 187/215] Fixed #3603 (incorrect AO and sky light level around light sources) (#3631) --- .../model/light/data/LightDataAccess.java | 8 +---- .../client/model/light/smooth/AoFaceData.java | 34 +++++++++++++++---- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/data/LightDataAccess.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/data/LightDataAccess.java index 02c13470ae..d78b2b5efa 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/data/LightDataAccess.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/data/LightDataAccess.java @@ -88,13 +88,7 @@ protected int compute(int x, int y, int z) { } } - // FIX: Do not apply AO from blocks that emit light - float ao; - if (lu == 0) { - ao = state.getShadeBrightness(level, pos); - } else { - ao = 1.0f; - } + float ao = state.getShadeBrightness(level, pos); return packFC(fc) | packFO(fo) | packOP(op) | packEM(em) | packAO(ao) | packLU(lu) | packSL(sl) | packBL(bl); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/AoFaceData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/AoFaceData.java index 2fca84b4d6..3ef10e5a1e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/AoFaceData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/AoFaceData.java @@ -237,16 +237,38 @@ private static int packLight(float sl, float bl) { private static int calculateCornerBrightness(int a, int b, int c, int d, boolean aem, boolean bem, boolean cem, boolean dem) { // FIX: Normalize corner vectors correctly to the minimum non-zero value between each one to prevent // strange issues - if ((a == 0) || (b == 0) || (c == 0) || (d == 0)) { + int ab = a & 0xFF; + int bb = b & 0xFF; + int cb = c & 0xFF; + int db = d & 0xFF; + if ((ab == 0) || (bb == 0) || (cb == 0) || (db == 0)) { // Find the minimum value between all corners - final int min = minNonZero(minNonZero(a, b), minNonZero(c, d)); + final int min = minNonZero(minNonZero(ab, bb), minNonZero(cb, db)); // Normalize the corner values - a = Math.max(a, min); - b = Math.max(b, min); - c = Math.max(c, min); - d = Math.max(d, min); + ab = Math.max(ab, min); + bb = Math.max(bb, min); + cb = Math.max(cb, min); + db = Math.max(db, min); } + int as = a & 0xFF0000; + int bs = b & 0xFF0000; + int cs = c & 0xFF0000; + int ds = d & 0xFF0000; + if ((as == 0) || (bs == 0) || (cs == 0) || (ds == 0)) { + // Find the minimum value between all corners + final int min = minNonZero(minNonZero(as, bs), minNonZero(cs, ds)); + + // Normalize the corner values + as = Math.max(as, min); + bs = Math.max(bs, min); + cs = Math.max(cs, min); + ds = Math.max(ds, min); + } + a = ab | as; + b = bb | bs; + c = cb | cs; + d = db | ds; // FIX: Apply the fullbright lightmap from emissive blocks at the very end so it cannot influence // the minimum lightmap and produce incorrect results (for example, sculk sensors in a dark room) From 6befd2043bc00d0a0ec757156b553220d74c019a Mon Sep 17 00:00:00 2001 From: douira Date: Sat, 9 May 2026 00:10:08 +0200 Subject: [PATCH 188/215] Sort Entity Quads by Closest Point (#3635) --- .../client/gui/SodiumConfigBuilder.java | 11 ++++ .../mods/sodium/client/gui/SodiumOptions.java | 2 + .../client/util/sorting/VertexSorters.java | 63 +++++++++++++++++++ .../resources/assets/sodium/lang/en_us.json | 2 + 4 files changed, 78 insertions(+) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java index 4723bfdb96..b2a30bc833 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java @@ -415,6 +415,17 @@ private OptionPageBuilder buildQualityPage(ConfigBuilder builder) { .setFlags(OptionFlag.REQUIRES_ASSET_RELOAD) ) ); + qualityPage.addOptionGroup(builder.createOptionGroup() + .addOption( + builder.createBooleanOption(ResourceLocation.parse("sodium:quality.closest_point_entity_sort")) + .setStorageHandler(this.sodiumStorage) + .setName(Component.translatable("sodium.options.closest_point_entity_sort.name")) + .setTooltip(Component.translatable("sodium.options.closest_point_entity_sort.tooltip")) + .setImpact(OptionImpact.MEDIUM) + .setDefaultValue(DEFAULTS.quality.useClosestPointEntitySort) + .setBinding(value -> this.sodiumOpts.quality.useClosestPointEntitySort = value, () -> this.sodiumOpts.quality.useClosestPointEntitySort) + ) + ); return qualityPage; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptions.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptions.java index 5674f58330..7e03aef09e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptions.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptions.java @@ -66,6 +66,8 @@ public static class QualitySettings { public LeavesQuality leavesQuality = LeavesQuality.DEFAULT; public boolean enableVignette = true; + + public boolean useClosestPointEntitySort = false; } public static class NotificationSettings { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java index 46f13ce057..9a40932a1e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/sorting/VertexSorters.java @@ -1,9 +1,11 @@ package net.caffeinemc.mods.sodium.client.util.sorting; import com.mojang.blaze3d.vertex.VertexSorting; +import net.caffeinemc.mods.sodium.client.SodiumClientMod; import net.caffeinemc.mods.sodium.client.util.MathUtil; import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.NotNull; +import org.joml.Intersectionf; import org.joml.Vector3f; import org.lwjgl.system.MemoryUtil; @@ -100,6 +102,18 @@ public static int[] sort(ByteBuffer buffer, int vertexCount, int vertexStride, V Validate.isTrue(buffer.remaining() >= vertexStride * vertexCount, "Vertex buffer is not large enough to contain all vertices"); + if (SodiumClientMod.options().quality.useClosestPointEntitySort) { + if (sorting instanceof VertexSorters.SortByDistanceToPoint pointMetric) { + return sortWithPerspective(buffer, vertexCount, vertexStride, pointMetric, pointMetric.x, pointMetric.y, pointMetric.z); + } else if (sorting instanceof VertexSorters.SortByDistanceToOrigin) { + return sortWithPerspective(buffer, vertexCount, vertexStride, sorting, 0.0f, 0.0f, 0.0f); + } + } + + return sortWithCentroid(buffer, vertexCount, vertexStride, sorting); + } + + public static int[] sortWithCentroid(ByteBuffer buffer, int vertexCount, int vertexStride, VertexSortingExtended sorting) { long pVertex0 = MemoryUtil.memAddress(buffer); long pVertex2 = MemoryUtil.memAddress(buffer, vertexStride * 2); @@ -138,4 +152,53 @@ public static int[] sort(ByteBuffer buffer, int vertexCount, int vertexStride, V return perm; } + + public static int[] sortWithPerspective(ByteBuffer buffer, int vertexCount, int vertexStride, VertexSortingExtended metric, float refX, float refY, float refZ) { + long pVertex0 = MemoryUtil.memAddress(buffer); + long pVertex1 = MemoryUtil.memAddress(buffer, vertexStride); + long pVertex2 = MemoryUtil.memAddress(buffer, vertexStride * 2); + + int primitiveCount = vertexCount / 4; + int primitiveStride = vertexStride * 4; + + final int[] keys = new int[primitiveCount]; + final int[] perm = new int[primitiveCount]; + + final var scratch = new Vector3f(); + + for (int primitiveId = 0; primitiveId < primitiveCount; primitiveId++) { + // instead of calculating the centroid, calculate the closest point on the quad (assuming it's flat and rectangular) to the reference, which may not be the centroid + float v0x = MemoryUtil.memGetFloat(pVertex0 + 0L); + float v0y = MemoryUtil.memGetFloat(pVertex0 + 4L); + float v0z = MemoryUtil.memGetFloat(pVertex0 + 8L); + + float v1x = MemoryUtil.memGetFloat(pVertex1 + 0L); + float v1y = MemoryUtil.memGetFloat(pVertex1 + 4L); + float v1z = MemoryUtil.memGetFloat(pVertex1 + 8L); + + float v2x = MemoryUtil.memGetFloat(pVertex2 + 0L); + float v2y = MemoryUtil.memGetFloat(pVertex2 + 4L); + float v2z = MemoryUtil.memGetFloat(pVertex2 + 8L); + + // this method requires the first point to be between the second and third in the rectangle + Intersectionf.findClosestPointOnRectangle( + v1x, v1y, v1z, + v0x, v0y, v0z, + v2x, v2y, v2z, + refX, refY, refZ, + scratch); + + // The sign bit of the metric is negated as we need back-to-front (descending) ordering. + keys[primitiveId] = MathUtil.floatToComparableInt(-metric.applyMetric(scratch.x, scratch.y, scratch.z)); + perm[primitiveId] = primitiveId; + + pVertex0 += primitiveStride; + pVertex1 += primitiveStride; + pVertex2 += primitiveStride; + } + + RadixSort.sortIndirect(perm, keys, true); + + return perm; + } } diff --git a/common/src/main/resources/assets/sodium/lang/en_us.json b/common/src/main/resources/assets/sodium/lang/en_us.json index 5544489865..18c29b74ae 100644 --- a/common/src/main/resources/assets/sodium/lang/en_us.json +++ b/common/src/main/resources/assets/sodium/lang/en_us.json @@ -65,6 +65,8 @@ "sodium.options.quad_splitting.tooltip": "Uses quad splitting to make translucent blocks (not entities or items) look correct through translucency sorting even if they are intersecting or have weird shapes, such as waterlogged stained glass panes. In \"Safe\" mode there's a limit on how much geometry can be generated in extreme cases to prevent crashes or performance degradation. This limit is disabled in \"Unlimited\" mode.", "sodium.options.quad_splitting.safe": "Safe", "sodium.options.quad_splitting.unlimited": "Unlimited", + "sodium.options.closest_point_entity_sort.name" : "Closest-Point Entity Sorting", + "sodium.options.closest_point_entity_sort.tooltip" : "Sorts the geometry of entities by the closest point to the camera. This improves rendering accuracy for translucent quads with different sizes, at a small CPU cost.", "sodium.options.use_no_error_context.name": "Use No Error Context", "sodium.options.use_no_error_context.tooltip": "When enabled, the OpenGL context will be created with error checking disabled. This slightly improves rendering performance, but it can make debugging sudden unexplained crashes much harder.", "sodium.options.buttons.undo": "Undo", From f43442ed1d05d4e3d37e6d3220e0a9bae9d3015e Mon Sep 17 00:00:00 2001 From: GeForceLegend <41003546+geforcelegend@users.noreply.github.com> Date: Sun, 10 May 2026 22:38:33 +0200 Subject: [PATCH 189/215] Change fullbright blocks to only emit block light and no sky light (#3640) --- .../sodium/client/model/light/smooth/AoFaceData.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/AoFaceData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/AoFaceData.java index 3ef10e5a1e..b2e29daa7e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/AoFaceData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/AoFaceData.java @@ -273,16 +273,20 @@ private static int calculateCornerBrightness(int a, int b, int c, int d, boolean // FIX: Apply the fullbright lightmap from emissive blocks at the very end so it cannot influence // the minimum lightmap and produce incorrect results (for example, sculk sensors in a dark room) if (aem) { - a = LightTexture.FULL_BRIGHT; + a &= 0xFF0000; + a |= 0xF0; } if (bem) { - b = LightTexture.FULL_BRIGHT; + b &= 0xFF0000; + b |= 0xF0; } if (cem) { - c = LightTexture.FULL_BRIGHT; + c &= 0xFF0000; + c |= 0xF0; } if (dem) { - d = LightTexture.FULL_BRIGHT; + d &= 0xFF0000; + d |= 0xF0; } return ((a + b + c + d) >> 2) & 0xFF00FF; From 9a706a5a4c00f3242b35982a3e32ac62a068bd28 Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 11 May 2026 02:24:35 +0200 Subject: [PATCH 190/215] Bump version to 0.8.12-beta.2 --- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 3b37071f01..1db08540f6 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -10,7 +10,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ - val MOD_VERSION: String = "0.8.11" + val MOD_VERSION: String = "0.8.12-beta.2" val RELEASE_TAG: String = "mc$MINECRAFT_VERSION-$MOD_VERSION" From 0857bf9dd8795318be4a5f1e53384875513f18fd Mon Sep 17 00:00:00 2001 From: douira Date: Wed, 13 May 2026 20:13:00 +0200 Subject: [PATCH 191/215] Bump version to 0.8.12-alpha.1 and update changelog --- CHANGELOG.md | 14 +++++++++++++- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1df59e9278..ad2c90e5db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,4 +4,16 @@ Everything above the line is ignored and not included in the changelog. Everything below will be in the changelog on GitHub, Modrinth and CurseForge. ---------- -Sodium [SodiumVersion]() fixes some bugs and updates to the latest version of NeoForge. +Sodium [SodiumVersion]() is a backport of modern Sodium 0.8.12 to Minecraft [MCVersion](). This experiment has graduated to alpha status. The degree to which we commit to supporting this branch will be determined by testing and by how well it works and integrates with the ecosystem. The quality of this version is expected to further improve it gets used, we discover quirks, and receive feedback. + +- Significantly improved the performance of rendering the world (up to +115%) on some computers. +- Greatly improved the rendering of transparent objects with complex models, especially when submerged in water. +- Lots and lots of improvements to the user experience in the Video Settings menu. +- Reduced latency and micro-stutter when updating chunks in the world. +- Slightly faster entity rendering, especially for transparent mobs and particles. +- Improvements for hardware and mod compatibility. +- ...And many more bug fixes and improvements... + +It includes the backport of our Config API and other conventions that will hopefully make it easier for mods to interact with Sodium across multiple versions. This release series doesn't get released at the same cadence as our current releases for Minecraft 26.1 and 1.21.11, and doesn't follow the same alpha/beta numbering. + +Please participate in testing this release, coordinating on mod compatibility, and giving feedback on [our discord server](https://caffeinemc.net/discord). \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 1db08540f6..8df4746d5a 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -10,7 +10,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ - val MOD_VERSION: String = "0.8.12-beta.2" + val MOD_VERSION: String = "0.8.12-alpha.1" val RELEASE_TAG: String = "mc$MINECRAFT_VERSION-$MOD_VERSION" From f0f9c7b4f104276457612a2d64b0e55fbe03a76e Mon Sep 17 00:00:00 2001 From: IThundxr Date: Wed, 13 May 2026 18:20:36 -0400 Subject: [PATCH 192/215] Fix BufferBuilderMixin conflicting with Iris' MixinBufferBuilder_SeparateAo when requireOverwriteAnnotations is enabled (#3658) --- .../intrinsics/BufferBuilderAccessor.java | 11 +++ .../intrinsics/BufferBuilderMixin.java | 77 ------------------ .../intrinsics/VertexConsumerMixin.java | 80 +++++++++++++++++++ .../main/resources/sodium-common.mixins.json | 3 +- 4 files changed, 93 insertions(+), 78 deletions(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/BufferBuilderAccessor.java delete mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/BufferBuilderMixin.java create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/VertexConsumerMixin.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/BufferBuilderAccessor.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/BufferBuilderAccessor.java new file mode 100644 index 0000000000..f96041566e --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/BufferBuilderAccessor.java @@ -0,0 +1,11 @@ +package net.caffeinemc.mods.sodium.mixin.features.render.immediate.buffer_builder.intrinsics; + +import com.mojang.blaze3d.vertex.BufferBuilder; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(BufferBuilder.class) +public interface BufferBuilderAccessor { + @Accessor("fastFormat") + boolean sodium$fastFormat(); +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/BufferBuilderMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/BufferBuilderMixin.java deleted file mode 100644 index 9232a508e5..0000000000 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/BufferBuilderMixin.java +++ /dev/null @@ -1,77 +0,0 @@ -package net.caffeinemc.mods.sodium.mixin.features.render.immediate.buffer_builder.intrinsics; - -import com.mojang.blaze3d.vertex.BufferBuilder; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; -import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; -import net.caffeinemc.mods.sodium.api.util.ColorABGR; -import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; -import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadView; -import net.caffeinemc.mods.sodium.client.render.immediate.model.BakedModelEncoder; -import net.minecraft.client.renderer.block.model.BakedQuad; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Shadow; - -@SuppressWarnings({ "SameParameterValue" }) -@Mixin(BufferBuilder.class) -public abstract class BufferBuilderMixin implements VertexConsumer { - @Shadow - @Final - private boolean fastFormat; - - @Override - public void putBulkData(PoseStack.Pose matrices, BakedQuad bakedQuad, float r, float g, float b, float a, int light, int overlay) { - if (!this.fastFormat) { - VertexConsumer.super.putBulkData(matrices, bakedQuad, r, g, b, a, light, overlay); - - if (bakedQuad.getSprite() != null) { - SpriteUtil.INSTANCE.markSpriteActive(bakedQuad.getSprite()); - } - - return; - } - - if (bakedQuad.getVertices().length < 32) { - return; // we do not accept quads with less than 4 properly sized vertices - } - - VertexBufferWriter writer = VertexBufferWriter.of(this); - - ModelQuadView quad = (ModelQuadView) bakedQuad; - - int color = ColorABGR.pack(r, g, b, a); - BakedModelEncoder.writeQuadVertices(writer, matrices, quad, color, light, overlay, false); - - if (quad.getSprite() != null) { - SpriteUtil.INSTANCE.markSpriteActive(quad.getSprite()); - } - } - - @Override - public void putBulkData(PoseStack.Pose matrices, BakedQuad bakedQuad, float[] brightnessTable, float r, float g, float b, float a, int[] light, int overlay, boolean colorize) { - if (!this.fastFormat) { - VertexConsumer.super.putBulkData(matrices, bakedQuad, brightnessTable, r, g, b, a, light, overlay, colorize); - - if (bakedQuad.getSprite() != null) { - SpriteUtil.INSTANCE.markSpriteActive(bakedQuad.getSprite()); - } - - return; - } - - if (bakedQuad.getVertices().length < 32) { - return; // we do not accept quads with less than 4 properly sized vertices - } - - VertexBufferWriter writer = VertexBufferWriter.of(this); - - ModelQuadView quad = (ModelQuadView) bakedQuad; - - BakedModelEncoder.writeQuadVertices(writer, matrices, quad, r, g, b, a, brightnessTable, colorize, light, overlay); - - if (quad.getSprite() != null) { - SpriteUtil.INSTANCE.markSpriteActive(quad.getSprite()); - } - } -} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/VertexConsumerMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/VertexConsumerMixin.java new file mode 100644 index 0000000000..22867435ec --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/immediate/buffer_builder/intrinsics/VertexConsumerMixin.java @@ -0,0 +1,80 @@ +package net.caffeinemc.mods.sodium.mixin.features.render.immediate.buffer_builder.intrinsics; + +import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; +import net.caffeinemc.mods.sodium.api.util.ColorABGR; +import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; +import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadView; +import net.caffeinemc.mods.sodium.client.render.immediate.model.BakedModelEncoder; +import net.minecraft.client.renderer.block.model.BakedQuad; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(VertexConsumer.class) +public interface VertexConsumerMixin { + @WrapMethod(method = "putBulkData(Lcom/mojang/blaze3d/vertex/PoseStack$Pose;Lnet/minecraft/client/renderer/block/model/BakedQuad;FFFFII)V") + default void sodium$modifyPutBulkData(PoseStack.Pose pose, BakedQuad bakedQuad, float red, float green, float blue, float alpha, int packedLight, int packedOverlay, Operation original) { + if ((Object) this instanceof BufferBuilder bufferBuilder) { + if (!((BufferBuilderAccessor) bufferBuilder).sodium$fastFormat()) { + original.call(pose, bakedQuad, red, green, blue, alpha, packedLight, packedOverlay); + + if (bakedQuad.getSprite() != null) { + SpriteUtil.INSTANCE.markSpriteActive(bakedQuad.getSprite()); + } + + return; + } + + if (bakedQuad.getVertices().length < 32) { + return; // we do not accept quads with less than 4 properly sized vertices + } + + VertexBufferWriter writer = VertexBufferWriter.of((VertexConsumer) this); + + ModelQuadView quad = (ModelQuadView) bakedQuad; + + int color = ColorABGR.pack(red, green, blue, alpha); + BakedModelEncoder.writeQuadVertices(writer, pose, quad, color, packedLight, packedOverlay, false); + + if (quad.getSprite() != null) { + SpriteUtil.INSTANCE.markSpriteActive(quad.getSprite()); + } + } else { + original.call(pose, bakedQuad, red, green, blue, alpha, packedLight, packedOverlay); + } + } + + @WrapMethod(method = "putBulkData(Lcom/mojang/blaze3d/vertex/PoseStack$Pose;Lnet/minecraft/client/renderer/block/model/BakedQuad;[FFFFF[IIZ)V") + default void sodium$modifyPutBulkData(PoseStack.Pose pose, BakedQuad bakedQuad, float[] brightness, float red, float green, float blue, float alpha, int[] lightmap, int packedOverlay, boolean readAlpha, Operation original) { + if ((Object) this instanceof BufferBuilder bufferBuilder) { + if (!((BufferBuilderAccessor) bufferBuilder).sodium$fastFormat()) { + original.call(pose, bakedQuad, brightness, red, green, blue, alpha, lightmap, packedOverlay, readAlpha); + + if (bakedQuad.getSprite() != null) { + SpriteUtil.INSTANCE.markSpriteActive(bakedQuad.getSprite()); + } + + return; + } + + if (bakedQuad.getVertices().length < 32) { + return; // we do not accept quads with less than 4 properly sized vertices + } + + VertexBufferWriter writer = VertexBufferWriter.of((VertexConsumer) this); + + ModelQuadView quad = (ModelQuadView) bakedQuad; + + BakedModelEncoder.writeQuadVertices(writer, pose, quad, red, green, blue, alpha, brightness, readAlpha, lightmap, packedOverlay); + + if (quad.getSprite() != null) { + SpriteUtil.INSTANCE.markSpriteActive(quad.getSprite()); + } + } else { + original.call(pose, bakedQuad, brightness, red, green, blue, alpha, lightmap, packedOverlay, readAlpha); + } + } +} diff --git a/common/src/main/resources/sodium-common.mixins.json b/common/src/main/resources/sodium-common.mixins.json index 061c044ab8..59a2423df1 100644 --- a/common/src/main/resources/sodium-common.mixins.json +++ b/common/src/main/resources/sodium-common.mixins.json @@ -58,7 +58,8 @@ "features.render.gui.font.BakedGlyphMixin", "features.render.gui.outlines.LevelRendererMixin", "features.render.immediate.DirectionMixin", - "features.render.immediate.buffer_builder.intrinsics.BufferBuilderMixin", + "features.render.immediate.buffer_builder.intrinsics.BufferBuilderAccessor", + "features.render.immediate.buffer_builder.intrinsics.VertexConsumerMixin", "features.render.immediate.buffer_builder.sorting.MeshDataAccessor", "features.render.immediate.buffer_builder.sorting.MultiBufferSourceMixin", "features.render.immediate.buffer_builder.sorting.VertexSortingMixin", From c07130f8e71f529e0edf06c819b854c83e77136b Mon Sep 17 00:00:00 2001 From: douira Date: Wed, 13 May 2026 22:31:17 +0200 Subject: [PATCH 193/215] Potentially fix issues with publishing by making the buildscript more similar to that used in 1.21.11 --- fabric/build.gradle.kts | 78 +++++++++++++++++++++++++++++++++++++- neoforge/build.gradle.kts | 79 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 155 insertions(+), 2 deletions(-) diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts index 1ff8f5ee9b..f8270d88d5 100644 --- a/fabric/build.gradle.kts +++ b/fabric/build.gradle.kts @@ -1,3 +1,6 @@ +import net.fabricmc.loom.task.RemapJarTask +import net.fabricmc.loom.task.RemapSourcesJarTask + plugins { id("multiloader-platform") @@ -8,18 +11,29 @@ base { archivesName = "sodium-fabric" } +val configurationApiModJava: Configuration = configurations.create("apiJava") { + isCanBeResolved = true +} + val configurationCommonModJava: Configuration = configurations.create("commonJava") { isCanBeResolved = true } + +val configurationApiModSources: Configuration = configurations.create("apiSources") { + isCanBeResolved = true +} + val configurationCommonModResources: Configuration = configurations.create("commonResources") { isCanBeResolved = true } dependencies { configurationCommonModJava(project(path = ":common", configuration = "commonMainJava")) - configurationCommonModJava(project(path = ":common", configuration = "commonApiJava")) + configurationApiModJava(project(path = ":common", configuration = "commonApiJava")) configurationCommonModJava(project(path = ":common", configuration = "commonBootJava")) + configurationApiModSources(project(path = ":common", configuration = "commonApiSources")) + configurationCommonModResources(project(path = ":common", configuration = "commonMainResources")) configurationCommonModResources(project(path = ":common", configuration = "commonApiResources")) configurationCommonModResources(project(path = ":common", configuration = "commonBootResources")) @@ -28,7 +42,9 @@ dependencies { sourceSets.apply { main { compileClasspath += configurationCommonModJava + compileClasspath += configurationApiModJava runtimeClasspath += configurationCommonModJava + runtimeClasspath += configurationApiModJava } } @@ -80,6 +96,38 @@ loom { tasks { jar { from(configurationCommonModJava) + from(configurationApiModJava) + } + + val apiJar = register("apiJar") { + archiveClassifier.set("api-dev") + from(configurationApiModJava) + from(sourceSets.main.get().resources) + destinationDirectory.set(file(project.layout.buildDirectory).resolve("devlibs")) + } + + val apiSourcesJar = register("apiSourcesJar") { + archiveClassifier.set("api-sources-dev") + from(configurationApiModSources) + from(sourceSets.main.get().resources) + destinationDirectory.set(file(project.layout.buildDirectory).resolve("devlibs")) + } + + register("remapApiJar") { + dependsOn("apiJar") + archiveClassifier.set("api") + nestedJars.unset() + destinationDirectory.set(file(rootProject.layout.buildDirectory).resolve("api")) + + inputFile.set(apiJar.flatMap { it.archiveFile }) + } + + register("remapApiSourcesJar") { + dependsOn("apiSourcesJar") + archiveClassifier.set("api-sources") + destinationDirectory.set(file(rootProject.layout.buildDirectory).resolve("api-sources")) + + inputFile.set(apiSourcesJar.flatMap { it.archiveFile }) } remapJar { @@ -89,4 +137,32 @@ tasks { processResources { from(configurationCommonModResources) } +} + +publishing { + publications { + create("maven") { + groupId = project.group as String + artifactId = rootProject.name + "-" + project.name + version = version + + from(components["java"]) + } + + create("mavenApi") { + groupId = project.group as String + artifactId = rootProject.name + "-" + project.name + "-api" + version = version + + artifact(tasks.named("remapApiJar")) { + classifier = null + } + + artifact(tasks.named("remapApiSourcesJar")) { + classifier = "sources" + } + + pom.packaging = "jar" + } + } } \ No newline at end of file diff --git a/neoforge/build.gradle.kts b/neoforge/build.gradle.kts index 5c21b6decd..967654a59f 100644 --- a/neoforge/build.gradle.kts +++ b/neoforge/build.gradle.kts @@ -20,6 +20,12 @@ sourceSets { val configurationCommonModJava: Configuration = configurations.create("commonModJava") { isCanBeResolved = true } +val configurationCommonApiJava: Configuration = configurations.create("commonApiJava") { + isCanBeResolved = true +} +val configurationCommonApiSources: Configuration = configurations.create("apiSources") { + isCanBeResolved = true +} val configurationCommonModResources: Configuration = configurations.create("commonModResources") { isCanBeResolved = true } @@ -33,13 +39,17 @@ val configurationCommonServiceResources: Configuration = configurations.create(" dependencies { configurationCommonModJava(project(path = ":common", configuration = "commonMainJava")) - configurationCommonModJava(project(path = ":common", configuration = "commonApiJava")) + configurationCommonApiJava(project(path = ":common", configuration = "commonApiJava")) configurationCommonServiceJava(project(path = ":common", configuration = "commonBootJava")) + configurationCommonApiSources(project(path = ":common", configuration = "commonApiSources")) + configurationCommonModResources(project(path = ":common", configuration = "commonMainResources")) configurationCommonModResources(project(path = ":common", configuration = "commonApiResources")) configurationCommonServiceResources(project(path = ":common", configuration = "commonBootResources")) + configurationCommonApiSources(project(path = ":common", configuration = "commonApiSources")) + fun addEmbeddedFabricModule(dependency: String) { dependencies.implementation(dependency) dependencies.jarJar(dependency) @@ -55,6 +65,7 @@ dependencies { val modJar = tasks.register("modJar") { from(configurationCommonModJava) + from(configurationCommonApiJava) from(configurationCommonModResources) from(sourceSets["mod"].output) @@ -68,6 +79,30 @@ val modJar = tasks.register("modJar") { archiveClassifier = "mod" } +val apiJar = tasks.register("apiJar") { + from(configurationCommonApiJava) + + from(rootDir.resolve("LICENSE.md")) + + archiveClassifier = "api" + + destinationDirectory.set(file(rootProject.layout.buildDirectory).resolve("api")) +} + +val apiSourcesJar = tasks.register("apiSourcesJar") { + from(configurationCommonApiSources) + + from(rootDir.resolve("LICENSE.md")) + + archiveClassifier = "api-sources" + + destinationDirectory.set(file(rootProject.layout.buildDirectory).resolve("api-sources")) +} + +tasks.jar { + dependsOn(apiJar) +} + val configurationMod: Configuration = configurations.create("mod") { isCanBeConsumed = true isCanBeResolved = true @@ -88,7 +123,9 @@ sourceSets { runtimeClasspath = sourceSets["main"].runtimeClasspath compileClasspath += configurationCommonModJava + compileClasspath += configurationCommonApiJava runtimeClasspath += configurationCommonModJava + runtimeClasspath += configurationCommonApiJava } } @@ -148,3 +185,43 @@ tasks { } } } + +publishing { + publications { + create("maven") { + groupId = project.group as String + artifactId = rootProject.name + "-" + project.name + version = version + + from(components["java"]) + } + + create("mavenApi") { + groupId = project.group as String + artifactId = rootProject.name + "-" + project.name + "-api" + version = version + + artifact(apiJar) { + classifier = null + } + + artifact(apiSourcesJar) { + classifier = "sources" + } + + pom.packaging = "jar" + } + + create("mavenMod") { + groupId = project.group as String + artifactId = rootProject.name + "-" + project.name + "-mod" + version = version + + artifact(modJar) { + classifier = null + } + + pom.packaging = "jar" + } + } +} From bcb7e95a21dc56030838e3a841d805d5b459cb8a Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 14 May 2026 01:27:22 +0200 Subject: [PATCH 194/215] Bump version to 0.8.12-alpha.2, update and improve changelog --- CHANGELOG.md | 18 +++++++++++++++--- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad2c90e5db..74b7ba23e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ Everything above the line is ignored and not included in the changelog. Everything below will be in the changelog on GitHub, Modrinth and CurseForge. ---------- -Sodium [SodiumVersion]() is a backport of modern Sodium 0.8.12 to Minecraft [MCVersion](). This experiment has graduated to alpha status. The degree to which we commit to supporting this branch will be determined by testing and by how well it works and integrates with the ecosystem. The quality of this version is expected to further improve it gets used, we discover quirks, and receive feedback. +### Overview +Sodium [SodiumVersion]() is a backport of modern Sodium 0.8 to Minecraft [MCVersion](). This experiment has graduated to alpha status. The degree to which we commit to supporting this branch will be determined by testing and by how well it works and integrates with the ecosystem. The quality of this version is expected to further improve it gets used, we discover quirks, and receive feedback. - Significantly improved the performance of rendering the world (up to +115%) on some computers. - Greatly improved the rendering of transparent objects with complex models, especially when submerged in water. @@ -14,6 +15,17 @@ Sodium [SodiumVersion]() is a backport of modern Sodium 0.8.12 to Minecraft [MCV - Improvements for hardware and mod compatibility. - ...And many more bug fixes and improvements... -It includes the backport of our Config API and other conventions that will hopefully make it easier for mods to interact with Sodium across multiple versions. This release series doesn't get released at the same cadence as our current releases for Minecraft 26.1 and 1.21.11, and doesn't follow the same alpha/beta numbering. +### Using and Testing This Release +It includes the backport of our Config API and other conventions that will hopefully make it easier for mods to interact with Sodium across multiple versions. This release series doesn't get released at the same cadence as our current releases for Minecraft 26.1 and 1.21.11, and doesn't follow the same alpha/beta numbering. Mod developers can find our artifacts, such as the Config API, on [our Maven repository](https://maven.caffeinemc.net/). -Please participate in testing this release, coordinating on mod compatibility, and giving feedback on [our discord server](https://caffeinemc.net/discord). \ No newline at end of file +Please participate in testing this release, coordinating on mod compatibility, and giving feedback on [our discord server](https://caffeinemc.net/discord). + +Known incompatibilities at the time of release: +- Iris +- Voxy +- More culling, sodiumleafculling +- Create Aeronautics, Sable + +### Alpha Series Changelog +- Fix BufferBuilderMixin conflicting with Iris' MixinBufferBuilder_SeparateAo when requireOverwriteAnnotations is enabled ([#3658](https://github.com/CaffeineMC/sodium/pull/3658)) +- Potentially fix issues with publishing by making the buildscript more similar to that used in 1.21.11 diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 8df4746d5a..24292d7c6b 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -10,7 +10,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ - val MOD_VERSION: String = "0.8.12-alpha.1" + val MOD_VERSION: String = "0.8.12-alpha.2" val RELEASE_TAG: String = "mc$MINECRAFT_VERSION-$MOD_VERSION" From 5548e5a3b15bd809dcef1d3457bf719015c4ef59 Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 14 May 2026 01:38:49 +0200 Subject: [PATCH 195/215] Fix remapping jar task on fabric to fix the release --- build.gradle.kts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index fb1b8fe2cc..c156f03ee4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -58,11 +58,12 @@ fun me.modmuss50.mpp.ModPublishExtension.setupFor(loaderName: String, releasePla val loaderLowercase = loaderName.lowercase(Locale.ROOT) if (releasePlatform == "both" || releasePlatform == loaderLowercase) { - val jar = project(":$loaderLowercase").tasks.named("jar").get().archiveFile + val taskName = if (loaderLowercase == "fabric") "remapJar" else "jar" + val jar = project(":$loaderLowercase").tasks.getByName(taskName).outputs.files.singleFile val releaseTitle = "Sodium ${BuildConfig.MOD_VERSION} for $loaderName ${BuildConfig.MINECRAFT_VERSION}" val releaseVersion = "${BuildConfig.RELEASE_TAG}-$loaderLowercase" - + curseforge("curseforge$loaderName") { from(curseforgeOptions) From dc752ae8b01e8acd774e60f3a3229b9f5fa52d39 Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 14 May 2026 01:39:18 +0200 Subject: [PATCH 196/215] Bump version to 0.8.12-alpha.3 --- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 24292d7c6b..532d237210 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -10,7 +10,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ - val MOD_VERSION: String = "0.8.12-alpha.2" + val MOD_VERSION: String = "0.8.12-alpha.3" val RELEASE_TAG: String = "mc$MINECRAFT_VERSION-$MOD_VERSION" From fafd53af59f68a05f1f6e30b016a798b39802343 Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 14 May 2026 17:08:51 +0200 Subject: [PATCH 197/215] Revert "Update texture light coords math to match Vanilla (#3311)" This reverts commit 57da1974abbd1884e0637669b740bd2734843962. This commit was wrongly cherry-picked into this branch. --- .../render/chunk/vertex/format/impl/CompactChunkVertex.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 15aee0854c..ab191b9934 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 @@ -107,8 +107,8 @@ private static int encodeTexture(float center, float x) { } private static int encodeLight(int light) { - int sky = Mth.clamp(((light >>> 16) & 0xFF) + 8, 8, 248); - int block = Mth.clamp(((light >>> 0) & 0xFF) + 8, 8, 248); + int sky = Mth.clamp((light >>> 16) & 0xFF, 8, 248); + int block = Mth.clamp((light >>> 0) & 0xFF, 8, 248); return (block << 0) | (sky << 8); } From 022e076568048bd9060773d4879666371d120b68 Mon Sep 17 00:00:00 2001 From: douira Date: Thu, 14 May 2026 17:48:18 +0200 Subject: [PATCH 198/215] Fix the corrupted config screen to show up properly --- .../mods/sodium/client/gui/SodiumOptions.java | 3 +- .../gui/screen/ConfigCorruptedScreen.java | 43 ++++++++----------- .../resources/assets/sodium/lang/en_us.json | 5 ++- 3 files changed, 24 insertions(+), 27 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptions.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptions.java index 7e03aef09e..ee0e1ca508 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptions.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumOptions.java @@ -153,7 +153,8 @@ private static Path getConfigPath() { public static void writeToDisk(SodiumOptions config) throws IOException { if (config.isReadOnly()) { - throw new IllegalStateException("Config file is read-only"); + // throws an IOException so that it is caught correctly when trying to save the config when it's locked + throw new IOException("Config file is read-only"); } Path path = getConfigPath(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/screen/ConfigCorruptedScreen.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/screen/ConfigCorruptedScreen.java index 6f868e41b5..16f1231acc 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/screen/ConfigCorruptedScreen.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/screen/ConfigCorruptedScreen.java @@ -3,34 +3,20 @@ import net.caffeinemc.mods.sodium.client.SodiumClientMod; import net.caffeinemc.mods.sodium.client.console.Console; import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; +import net.caffeinemc.mods.sodium.client.gui.Colors; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; import org.jetbrains.annotations.Nullable; import java.util.Arrays; -import java.util.List; +import java.util.Iterator; import java.util.function.Function; -import java.util.stream.Collectors; public class ConfigCorruptedScreen extends Screen { - private static final String TEXT_BODY_RAW = """ - A problem occurred while trying to load the configuration file. This - can happen when the file has been corrupted on disk, or when trying - to manually edit the file by hand. - - If you continue, the configuration file will be reset back to known-good - defaults, and you will lose any changes that have since been made to your - Video Settings. - - More information about the error can be found in the log file. - """; - - private static final List TEXT_BODY = Arrays.stream(TEXT_BODY_RAW.split("\n")) - .map(Component::literal) - .collect(Collectors.toList()); private static final int BUTTON_WIDTH = 140; private static final int BUTTON_HEIGHT = 20; @@ -41,7 +27,7 @@ public class ConfigCorruptedScreen extends Screen { private final Function nextScreen; public ConfigCorruptedScreen(@Nullable Screen prevScreen, @Nullable Function nextScreen) { - super(Component.literal("Sodium failed to load the configuration file")); + super(Component.translatable("sodium.console.corrupt_config.console.title")); this.prevScreen = prevScreen; this.nextScreen = nextScreen; @@ -53,8 +39,8 @@ protected void init() { int buttonY = this.height - SCREEN_PADDING - BUTTON_HEIGHT; - this.addRenderableWidget(Button.builder(Component.literal("Continue"), (btn) -> { - Console.instance().logMessage(MessageLevel.INFO, "sodium.console.config_file_was_reset", true, 3.0); + this.addRenderableWidget(Button.builder(Component.translatable("gui.continue"), (btn) -> { + Console.instance().logMessage(MessageLevel.INFO, "sodium.console.corrupt_config.console.config_file_was_reset", true, 3.0); SodiumClientMod.restoreDefaultOptions(); Minecraft.getInstance().setScreen(this.nextScreen.apply(this.prevScreen)); @@ -69,15 +55,22 @@ protected void init() { public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { super.render(graphics, mouseX, mouseY, delta); - graphics.drawString(this.font, Component.literal("Sodium Renderer"), 32, 32, 0xffffff); - graphics.drawString(this.font, Component.literal("Could not load the configuration file"), 32, 48, 0xff0000); + graphics.drawString(this.font, Component.literal("Sodium Renderer"), 32, 32, Colors.FOREGROUND); + graphics.drawString(this.font, Component.translatable("sodium.console.corrupt_config.message.title"), 32, 48, 0xFFFF0000); - for (int i = 0; i < TEXT_BODY.size(); i++) { - if (TEXT_BODY.get(i).getString().isEmpty()) { + var lines = Arrays.stream(Component.translatable("sodium.console.corrupt_config.message.body").getString().split("\n")) + .map(Component::literal); + + var i = 0; + for (Iterator it = lines.iterator(); it.hasNext(); ) { + var line = it.next(); + i++; + + if (line.getString().isEmpty()) { continue; } - graphics.drawString(this.font, TEXT_BODY.get(i), 32, 68 + (i * 12), 0xffffff); + graphics.drawString(this.font, line, 32, 68 + (i * 12), Colors.FOREGROUND); } } } diff --git a/common/src/main/resources/assets/sodium/lang/en_us.json b/common/src/main/resources/assets/sodium/lang/en_us.json index 18c29b74ae..0991d8f1ee 100644 --- a/common/src/main/resources/assets/sodium/lang/en_us.json +++ b/common/src/main/resources/assets/sodium/lang/en_us.json @@ -78,7 +78,10 @@ "sodium.console.core_shaders_warn": "The following resource packs may be incompatible with Sodium:", "sodium.console.core_shaders_info": "Check the game log for detailed information.", "sodium.console.config_not_loaded": "The configuration file for Sodium has been corrupted, or is currently unreadable. Some options have been temporarily reset to their defaults. Please open the Video Settings screen to resolve this problem.", - "sodium.console.config_file_was_reset": "The config file has been reset to known-good defaults.", + "sodium.console.corrupt_config.console.config_file_was_reset": "The config file has been reset to known-good defaults.", + "sodium.console.corrupt_config.console.title": "Sodium failed to load the configuration file", + "sodium.console.corrupt_config.message.title": "Could not load the configuration file", + "sodium.console.corrupt_config.message.body": "A problem occurred while trying to load the configuration file. This\ncan happen when the file has been corrupted on disk, or when trying\nto manually edit the file by hand.\n\nIf you continue, the configuration file will be reset back to known-good\ndefaults, and you will lose any changes that have since been made to your\nVideo Settings.\n\nMore information about the error can be found in the log file.", "sodium.options.search": "Search", "sodium.options.search.hint": "Search...", "sodium.options.open_external_page_button": "Open" From d95c66f9738bba8d376e32ae6af3055452f1238a Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 25 May 2026 15:14:40 +0200 Subject: [PATCH 199/215] Bump version to 0.8.12-alpha.4 and update changelog --- CHANGELOG.md | 18 ++++++++++++------ buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74b7ba23e9..7b856bd156 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,14 +18,20 @@ Sodium [SodiumVersion]() is a backport of modern Sodium 0.8 to Minecraft [MCVers ### Using and Testing This Release It includes the backport of our Config API and other conventions that will hopefully make it easier for mods to interact with Sodium across multiple versions. This release series doesn't get released at the same cadence as our current releases for Minecraft 26.1 and 1.21.11, and doesn't follow the same alpha/beta numbering. Mod developers can find our artifacts, such as the Config API, on [our Maven repository](https://maven.caffeinemc.net/). -Please participate in testing this release, coordinating on mod compatibility, and giving feedback on [our discord server](https://caffeinemc.net/discord). +Please participate in testing this release, coordinating on mod compatibility, and giving feedback on [our discord server](https://caffeinemc.net/discord). In the thread in #testing-builds we have more information. Report any issues you may have in the thread. -Known incompatibilities at the time of release: -- Iris -- Voxy -- More culling, sodiumleafculling -- Create Aeronautics, Sable +Known incompatibilities at the time of this release: +- sodiumleafculling does not work +- C2ME's latest version works +- Create Aeronautics does not work +- Veil has an update that works, Sable has a patch that works but no update +- Iris has a test build on discord that works +- More culling's latest version works +- Voxy does not work +- EBE animations don’t work ### Alpha Series Changelog - Fix BufferBuilderMixin conflicting with Iris' MixinBufferBuilder_SeparateAo when requireOverwriteAnnotations is enabled ([#3658](https://github.com/CaffeineMC/sodium/pull/3658)) - Potentially fix issues with publishing by making the buildscript more similar to that used in 1.21.11 +- Fix the corrupted config screen to show up properly +- Revert "Update texture light coords math to match Vanilla (#3311)" as this was mistakenly applied to 1.21.1 diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 532d237210..039f1ffd19 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -10,7 +10,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ - val MOD_VERSION: String = "0.8.12-alpha.3" + val MOD_VERSION: String = "0.8.12-alpha.4" val RELEASE_TAG: String = "mc$MINECRAFT_VERSION-$MOD_VERSION" From cae2ffc12f7f172e9b6c5c673f754f9483f2f97f Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 31 May 2026 03:53:22 +0200 Subject: [PATCH 200/215] Implement percentile fps display --- .../client/util/FrameTimeStatistics.java | 147 ++++++++++++++++++ .../sodium/mixin/core/MinecraftMixin.java | 13 ++ .../hooks/debug/DebugScreenOverlayMixin.java | 45 ++++++ 3 files changed, 205 insertions(+) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/util/FrameTimeStatistics.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/FrameTimeStatistics.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/FrameTimeStatistics.java new file mode 100644 index 0000000000..95f283632e --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/FrameTimeStatistics.java @@ -0,0 +1,147 @@ +package net.caffeinemc.mods.sodium.client.util; + +import it.unimi.dsi.fastutil.longs.LongComparator; +import it.unimi.dsi.fastutil.longs.LongComparators; +import it.unimi.dsi.fastutil.longs.LongHeaps; +import it.unimi.dsi.fastutil.objects.Reference2LongArrayMap; + +import java.util.Arrays; +import java.util.Comparator; + +// Maintains a ring buffer of frame durations populated from DebugScreenOverlay#logFrameDuration. We keep our own buffer because vanilla's FpsAccumulator only retains 240 samples, which gives only a fractional sample in the p99.9 tail. +public final class FrameTimeStatistics { + private static final int SAMPLE_COUNT = 1000; + + public record Percentile(String name, int window, float p) { + } + + private static final Percentile[] PERCENTILES = new Percentile[] { + new Percentile("p50", 200, 0.5f), + new Percentile("p98", Integer.MAX_VALUE, 0.98f), + new Percentile("p99.5",Integer.MAX_VALUE, 0.995f) + }; + + // PERCENTILES sorted (window asc, p desc) so each step of compute() either keeps the heap window or extends it with older sample, and within an equal-window run popDownTo only shrinks the heap. + private static final Percentile[] CALCULATION_ORDER = computeCalculationOrder(); + + public static final FrameTimeStatistics INSTANCE = new FrameTimeStatistics(); + + private static final LongComparator HEAP_COMPARATOR = LongComparators.OPPOSITE_COMPARATOR; + + private final long[] samples = new long[SAMPLE_COUNT]; + private int writeIndex = 0; + private int sampleSize = 0; + + private long[] heap; + private volatile Reference2LongArrayMap cached; + + private FrameTimeStatistics() { + } + + public Reference2LongArrayMap get() { + if (this.cached == null) { + this.cached = this.compute(); + } + return this.cached; + } + + // called from the render thread for every frame whose duration vanilla logs + public void logSample(long frameDuration) { + this.samples[this.writeIndex] = frameDuration; + this.writeIndex = (this.writeIndex + 1) % SAMPLE_COUNT; + if (this.sampleSize < SAMPLE_COUNT) { + this.sampleSize++; + } + } + + public void invalidate() { + this.cached = null; + } + + private Reference2LongArrayMap compute() { + int n = this.sampleSize; + if (n <= 0) { + return null; + } + + if (this.heap == null) { + this.heap = new long[SAMPLE_COUNT]; + } + var heap = this.heap; + + // pre-populate so iteration order in the result map matches the display order in PERCENTILES + var results = new Reference2LongArrayMap(PERCENTILES.length); + for (Percentile percentile : PERCENTILES) { + results.put(percentile, -1L); + } + + // currentWindow is how many samples we've copied from the ring buffer into our calculation array so far + int currentWindow = 0; + int heapSize = 0; + for (var percentile : CALCULATION_ORDER) { + int window = Math.min(percentile.window(), n); + + if (window > currentWindow) { + // append the next-older block of samples and re-heapify the full window; popped entries from the previous group rejoin the heap automatically + this.copyMostRecentSamples(heap, currentWindow, window - currentWindow); + LongHeaps.makeHeap(heap, window, HEAP_COMPARATOR); + currentWindow = window; + heapSize = window; + } + + heapSize = popDownTo(heap, heapSize, window - rankFromTop(percentile.p(), window)); + results.put(percentile, heap[0]); + } + return results; + } + + // copy ring-buffer samples into dest[destOffset..destOffset + count), where destOffset is the index of the first sample expressed as "k-th most recent" (0 = newest). One arraycopy if the source range doesn't wrap, two if it does. The order within dest is irrelevant because the consumer rebuilds the heap. + private void copyMostRecentSamples(long[] dest, int destOffset, int count) { + if (count <= 0) { + return; + } + int srcStart = Math.floorMod(this.writeIndex - destOffset - count, SAMPLE_COUNT); + int firstChunk = Math.min(count, SAMPLE_COUNT - srcStart); + System.arraycopy(this.samples, srcStart, dest, destOffset, firstChunk); + if (firstChunk < count) { + System.arraycopy(this.samples, 0, dest, destOffset + firstChunk, count - firstChunk); + } + } + + private static Percentile[] computeCalculationOrder() { + var order = Arrays.copyOf(PERCENTILES, PERCENTILES.length); + + // sort by window ascending, p descending + var byWindowThenPDesc = Comparator + .comparingInt(Percentile::window) + .thenComparing(Percentile::p, Comparator.reverseOrder()); + + Arrays.sort(order, byWindowThenPDesc); + + return order; + } + + // index of the p-quantile in a descending sequence of length n + private static int rankFromTop(double p, int n) { + int rank = (int) Math.floor((1.0 - p) * n); + if (rank < 0) { + return 0; + } + if (rank >= n) { + return n - 1; + } + return rank; + } + + // pop maxes (swap with tail, sift down) until the heap shrinks to targetSize + private static int popDownTo(long[] heap, int heapSize, int targetSize) { + while (heapSize > targetSize) { + heapSize--; + heap[0] = heap[heapSize]; + if (heapSize > 0) { + LongHeaps.downHeap(heap, heapSize, 0, HEAP_COMPARATOR); + } + } + return heapSize; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/MinecraftMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/MinecraftMixin.java index 578ace0ae4..632dc0901e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/MinecraftMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/MinecraftMixin.java @@ -4,10 +4,12 @@ import net.caffeinemc.mods.sodium.client.SodiumClientMod; import net.caffeinemc.mods.sodium.client.checks.ResourcePackScanner; import net.caffeinemc.mods.sodium.client.config.ConfigManager; +import net.caffeinemc.mods.sodium.client.util.FrameTimeStatistics; import net.minecraft.client.Minecraft; import net.minecraft.server.packs.resources.ReloadableResourceManager; import net.minecraft.util.profiling.ProfilerFiller; import org.lwjgl.opengl.GL32C; +import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -90,4 +92,15 @@ private void postInit(CallbackInfoReturnable cir) { private void postResourceReload(CallbackInfoReturnable> cir) { ResourcePackScanner.checkIfCoreShaderLoaded(this.resourceManager); } + + /** + * hook the vanilla fps update to update our fps display only when it does too, once a second + */ + @Inject( + method = "runTick", + at = @At(value = "FIELD", target = "Lnet/minecraft/client/Minecraft;fps:I", opcode = Opcodes.PUTSTATIC, shift = At.Shift.AFTER) + ) + private void sodium$updatePercentileCache(boolean advanceGameTime, CallbackInfo ci) { + FrameTimeStatistics.INSTANCE.invalidate(); + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/hooks/debug/DebugScreenOverlayMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/hooks/debug/DebugScreenOverlayMixin.java index eedb7302f9..1d32083b34 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/hooks/debug/DebugScreenOverlayMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/gui/hooks/debug/DebugScreenOverlayMixin.java @@ -1,8 +1,10 @@ package net.caffeinemc.mods.sodium.mixin.features.gui.hooks.debug; import com.google.common.collect.Lists; +import com.llamalad7.mixinextras.injector.ModifyReturnValue; import net.caffeinemc.mods.sodium.client.SodiumClientMod; import net.caffeinemc.mods.sodium.client.render.SodiumWorldRenderer; +import net.caffeinemc.mods.sodium.client.util.FrameTimeStatistics; import net.caffeinemc.mods.sodium.client.util.MathUtil; import net.caffeinemc.mods.sodium.client.util.NativeBuffer; import net.minecraft.ChatFormatting; @@ -10,10 +12,13 @@ import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.lang.management.ManagementFactory; import java.util.ArrayList; +import java.util.List; @Mixin(DebugScreenOverlay.class) public abstract class DebugScreenOverlayMixin { @@ -67,4 +72,44 @@ private static String getNativeMemoryString() { private static long getNativeMemoryUsage() { return ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage().getUsed() + NativeBuffer.getTotalAllocated(); } + + /** + * Maintain our own frame-time ring buffer (with more samples than vanilla's FPS + * graph storage) by capturing every frame duration that vanilla logs. + */ + @Inject(method = "logFrameDuration", at = @At("HEAD")) + private void sodium$captureFrameDuration(long frameDuration, CallbackInfo ci) { + FrameTimeStatistics.INSTANCE.logSample(frameDuration); + } + + @ModifyReturnValue(method = "getGameInformation", at = @At("RETURN")) + private List sodium$insertFpsPercentiles(List lines) { + var results = FrameTimeStatistics.INSTANCE.get(); + if (results == null || results.isEmpty()) { + return lines; + } + + var sb = new StringBuilder(); + for (var entry : results.reference2LongEntrySet()) { + if (!sb.isEmpty()) { + sb.append(' '); + } + long ns = entry.getLongValue(); + sb.append(ChatFormatting.GRAY) + .append(entry.getKey().name()).append('=') + .append(ChatFormatting.RESET) + .append(sodium$nanosToFps(ns)); + } + sb.append(ChatFormatting.GRAY).append(" fps"); + + // put it right after the fps line, which vanilla always emits as the second entry + lines.add(2, sb.toString()); + + return lines; + } + + @Unique + private static long sodium$nanosToFps(long ns) { + return ns > 0L ? Math.round(1.0e9 / ns) : 0L; + } } From 693bfed5fffe3ab6e9aff83fe62f8383d27724ea Mon Sep 17 00:00:00 2001 From: 2No2Name <50278648+2No2Name@users.noreply.github.com> Date: Sun, 31 May 2026 14:50:44 +0200 Subject: [PATCH 201/215] Fix crash when using `-Dmixin.debug=true` (#3689) Fixes: https://github.com/CaffeineMC/sodium/issues/3688 --- .../mods/sodium/mixin/core/render/world/LevelRendererMixin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/world/LevelRendererMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/world/LevelRendererMixin.java index 75f229c2be..82b994bcbe 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/world/LevelRendererMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/world/LevelRendererMixin.java @@ -214,7 +214,7 @@ private void onRenderBlockEntities(DeltaTracker deltaTracker, boolean bl, Camera // Exclusive to NeoForge, allow to fail. @SuppressWarnings("all") - @Inject(method = "iterateVisibleBlockEntities", at = @At("HEAD"), cancellable = true, require = 0) + @Inject(method = "iterateVisibleBlockEntities", at = @At("HEAD"), cancellable = true, expect = 0, require = 0) public void replaceBlockEntityIteration(Consumer blockEntityConsumer, CallbackInfo ci) { ci.cancel(); From 9ba24ee553124e6e9a7bd8df9bbaa0eb70abd1fc Mon Sep 17 00:00:00 2001 From: MoePus <547007249@qq.com> Date: Fri, 12 Jun 2026 07:20:20 +0800 Subject: [PATCH 202/215] Check vertex format before using fast path (#3716) --- .../api/vertex/buffer/VertexBufferWriter.java | 28 +++++++++++++++++++ .../render/frapi/render/QuadEncoder.java | 2 +- .../render/vertex/VertexConsumerUtils.java | 20 ++++++++++++- .../vertex/buffer/BufferBuilderExtension.java | 11 ++++++++ .../consumer/BufferBuilderMixin.java | 5 ++++ .../consumer/EntityOutlineGeneratorMixin.java | 5 ++++ .../SheetedDecalTextureGeneratorMixin.java | 5 ++++ .../SpriteCoordinateExpanderMixin.java | 5 ++++ .../consumer/VertexMultiConsumerMixin.java | 22 +++++++++++++++ .../features/render/entity/CubeMixin.java | 3 +- .../shadows/EntityRenderDispatcherMixin.java | 2 +- .../render/gui/font/BakedGlyphMixin.java | 2 +- .../gui/outlines/LevelRendererMixin.java | 2 +- .../render/model/item/ItemRendererMixin.java | 3 +- .../particle/SingleQuadParticleMixin.java | 4 +-- .../model/block/ModelBlockRendererMixin.java | 3 +- .../model/block/ModelBlockRendererMixin.java | 3 +- 17 files changed, 114 insertions(+), 11 deletions(-) diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/buffer/VertexBufferWriter.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/buffer/VertexBufferWriter.java index 002a2bd986..b50a500cbe 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/buffer/VertexBufferWriter.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/vertex/buffer/VertexBufferWriter.java @@ -38,6 +38,24 @@ static VertexBufferWriter tryOf(VertexConsumer consumer) { return null; } + /** + * Converts a {@link VertexConsumer} into a {@link VertexBufferWriter} if possible and only if it can use optimized + * writes with the requested vertex format. + * + * @param consumer The vertex consumer to create a writer for + * @param format The vertex format that will be written + * @return An implementation of {@link VertexBufferWriter}, or null if the vertex consumer does not support optimized + * writes for the requested vertex format + */ + @Nullable + static VertexBufferWriter tryOf(VertexConsumer consumer, VertexFormat format) { + if (consumer instanceof VertexBufferWriter writer && writer.canUseIntrinsics(format)) { + return writer; + } + + return null; + } + private static RuntimeException createUnsupportedVertexConsumerThrowable(VertexConsumer consumer) { var clazz = consumer.getClass(); var name = clazz.getName(); @@ -73,6 +91,16 @@ default boolean canUseIntrinsics() { return true; } + /** + * Checks whether this writer can use optimized writes for vertices with the specified format. + * + * @param format The vertex format that will be written + * @return true if this writer and any nested consumers can use intrinsics for the vertex format + */ + default boolean canUseIntrinsics(VertexFormat format) { + return this.canUseIntrinsics(); + } + /** * Creates a copy of the source data and pushes it into the specified {@param writer}. This is useful for when * you need to use to re-use the source data after the call, and do not want the {@param writer} to modify diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/QuadEncoder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/QuadEncoder.java index 8925908a4b..fa38ca0f92 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/QuadEncoder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/QuadEncoder.java @@ -16,7 +16,7 @@ public class QuadEncoder { public static void writeQuadVertices(MutableQuadViewImpl quad, VertexConsumer vertexConsumer, int overlay, Matrix4f matPosition, boolean trustedNormals, Matrix3f matNormal) { - VertexBufferWriter writer = VertexConsumerUtils.convertOrLog(vertexConsumer); + VertexBufferWriter writer = VertexConsumerUtils.convertOrLog(vertexConsumer, EntityVertex.FORMAT); if (writer != null) { writeQuadVertices(quad, writer, overlay, matPosition, trustedNormals, matNormal); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/vertex/VertexConsumerUtils.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/vertex/VertexConsumerUtils.java index 978da79efd..35411e9f64 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/vertex/VertexConsumerUtils.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/vertex/VertexConsumerUtils.java @@ -1,8 +1,8 @@ package net.caffeinemc.mods.sodium.client.render.vertex; import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.blaze3d.vertex.VertexFormat; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; - import javax.annotation.Nullable; public class VertexConsumerUtils { @@ -21,4 +21,22 @@ public class VertexConsumerUtils { return writer; } + + /** + * Attempt to convert a {@link VertexConsumer} into a {@link VertexBufferWriter} which can use optimized writes for + * the specified {@link VertexFormat}. If this fails, return null and log a message. + * + * @param consumer the consumer to convert + * @param format the vertex format that will be written + * @return a {@link VertexBufferWriter}, or null if the consumer cannot use optimized writes for the specified format + */ + public static @Nullable VertexBufferWriter convertOrLog(VertexConsumer consumer, VertexFormat format) { + VertexBufferWriter writer = VertexBufferWriter.tryOf(consumer, format); + + if (writer == null) { + VertexConsumerTracker.logBadConsumer(consumer); + } + + return writer; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/vertex/buffer/BufferBuilderExtension.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/vertex/buffer/BufferBuilderExtension.java index 40af81b01b..d780fef3fd 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/vertex/buffer/BufferBuilderExtension.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/vertex/buffer/BufferBuilderExtension.java @@ -1,7 +1,18 @@ package net.caffeinemc.mods.sodium.client.render.vertex.buffer; +import com.mojang.blaze3d.vertex.VertexFormat; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; public interface BufferBuilderExtension extends VertexBufferWriter { void sodium$duplicateVertex(); + + /** + * @return the vertex format this buffer builder is currently configured to write + */ + VertexFormat sodium$getVertexFormat(); + + @Override + default boolean canUseIntrinsics(VertexFormat format) { + return this.sodium$getVertexFormat() == format; + } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/BufferBuilderMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/BufferBuilderMixin.java index ec198c1731..8a93c2f67b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/BufferBuilderMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/BufferBuilderMixin.java @@ -48,6 +48,11 @@ public abstract class BufferBuilderMixin implements VertexBufferWriter, BufferBu this.vertices++; } + @Override + public VertexFormat sodium$getVertexFormat() { + return this.format; + } + @Override public void push(MemoryStack stack, long src, int count, VertexFormat format) { var length = count * this.vertexSize; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/EntityOutlineGeneratorMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/EntityOutlineGeneratorMixin.java index 015277efce..a4664e270c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/EntityOutlineGeneratorMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/EntityOutlineGeneratorMixin.java @@ -37,6 +37,11 @@ public boolean canUseIntrinsics() { return this.canUseIntrinsics; } + @Override + public boolean canUseIntrinsics(VertexFormat format) { + return this.canUseIntrinsics && VertexBufferWriter.tryOf(this.delegate, format) != null; + } + @Override public void push(MemoryStack stack, long ptr, int count, VertexFormat format) { transform(ptr, count, format, diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/SheetedDecalTextureGeneratorMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/SheetedDecalTextureGeneratorMixin.java index b08314a264..84a47942bb 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/SheetedDecalTextureGeneratorMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/SheetedDecalTextureGeneratorMixin.java @@ -55,6 +55,11 @@ public boolean canUseIntrinsics() { return this.canUseIntrinsics; } + @Override + public boolean canUseIntrinsics(VertexFormat format) { + return this.canUseIntrinsics && VertexBufferWriter.tryOf(this.delegate, format) != null; + } + @Override public void push(MemoryStack stack, long ptr, int count, VertexFormat format) { transform(ptr, count, format, diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/SpriteCoordinateExpanderMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/SpriteCoordinateExpanderMixin.java index a5791813ed..66a0848355 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/SpriteCoordinateExpanderMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/SpriteCoordinateExpanderMixin.java @@ -47,6 +47,11 @@ public boolean canUseIntrinsics() { return this.canUseIntrinsics; } + @Override + public boolean canUseIntrinsics(VertexFormat format) { + return this.canUseIntrinsics && VertexBufferWriter.tryOf(this.delegate, format) != null; + } + @Override public void push(MemoryStack stack, final long ptr, int count, VertexFormat format) { transform(ptr, count, format, diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/VertexMultiConsumerMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/VertexMultiConsumerMixin.java index 551492f3ea..6df4d5b477 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/VertexMultiConsumerMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/VertexMultiConsumerMixin.java @@ -37,6 +37,13 @@ public boolean canUseIntrinsics() { return this.canUseIntrinsics; } + @Override + public boolean canUseIntrinsics(VertexFormat format) { + return this.canUseIntrinsics && + VertexBufferWriter.tryOf(this.first, format) != null && + VertexBufferWriter.tryOf(this.second, format) != null; + } + @Override public void push(MemoryStack stack, long ptr, int count, VertexFormat format) { VertexBufferWriter.copyInto(VertexBufferWriter.of(this.first), stack, ptr, count, format); @@ -74,6 +81,21 @@ public boolean canUseIntrinsics() { return this.canUseIntrinsics; } + @Override + public boolean canUseIntrinsics(VertexFormat format) { + if (!this.canUseIntrinsics) { + return false; + } + + for (var delegate : this.delegates) { + if (VertexBufferWriter.tryOf(delegate, format) == null) { + return false; + } + } + + return true; + } + @Override public void push(MemoryStack stack, long ptr, int count, VertexFormat format) { for (var delegate : this.delegates) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/entity/CubeMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/entity/CubeMixin.java index d1590c9c14..6a793362e7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/entity/CubeMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/entity/CubeMixin.java @@ -4,6 +4,7 @@ import com.mojang.blaze3d.vertex.VertexConsumer; import net.caffeinemc.mods.sodium.api.util.ColorARGB; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; +import net.caffeinemc.mods.sodium.api.vertex.format.common.EntityVertex; import net.caffeinemc.mods.sodium.client.render.immediate.model.EntityRenderer; import net.caffeinemc.mods.sodium.client.render.immediate.model.ModelCuboid; import net.caffeinemc.mods.sodium.client.render.vertex.VertexConsumerUtils; @@ -38,7 +39,7 @@ private void onInit(ModelPart.Cube instance, float value, int u, int v, float x, @Inject(method = "compile", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/vertex/PoseStack$Pose;pose()Lorg/joml/Matrix4f;"), cancellable = true) private void onCompile(PoseStack.Pose pose, VertexConsumer buffer, int light, int overlay, int color, CallbackInfo ci) { - VertexBufferWriter writer = VertexConsumerUtils.convertOrLog(buffer); + VertexBufferWriter writer = VertexConsumerUtils.convertOrLog(buffer, EntityVertex.FORMAT); if (writer == null) { return; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/entity/shadows/EntityRenderDispatcherMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/entity/shadows/EntityRenderDispatcherMixin.java index 7f96b527b3..178f8990c3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/entity/shadows/EntityRenderDispatcherMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/entity/shadows/EntityRenderDispatcherMixin.java @@ -37,7 +37,7 @@ public class EntityRenderDispatcherMixin { */ @Inject(method = "renderBlockShadow", at = @At("HEAD"), cancellable = true) private static void renderShadowPartFast(PoseStack.Pose matrices, VertexConsumer vertices, ChunkAccess chunk, LevelReader level, BlockPos pos, double x, double y, double z, float radius, float opacity, CallbackInfo ci) { - var writer = VertexConsumerUtils.convertOrLog(vertices); + var writer = VertexConsumerUtils.convertOrLog(vertices, EntityVertex.FORMAT); if (writer == null) { return; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/gui/font/BakedGlyphMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/gui/font/BakedGlyphMixin.java index 08e138f1e0..96f9fb006b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/gui/font/BakedGlyphMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/gui/font/BakedGlyphMixin.java @@ -53,7 +53,7 @@ public class BakedGlyphMixin { */ @Inject(method = "render", at = @At("HEAD"), cancellable = true) private void drawFast(boolean italic, float x, float y, Matrix4f matrix, VertexConsumer vertexConsumer, float red, float green, float blue, float alpha, int light, CallbackInfo ci) { - var writer = VertexConsumerUtils.convertOrLog(vertexConsumer); + var writer = VertexConsumerUtils.convertOrLog(vertexConsumer, GlyphVertex.FORMAT); if (writer == null) { return; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/gui/outlines/LevelRendererMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/gui/outlines/LevelRendererMixin.java index e78a553301..106e28a386 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/gui/outlines/LevelRendererMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/gui/outlines/LevelRendererMixin.java @@ -29,7 +29,7 @@ public class LevelRendererMixin { private static void drawBoxFast(PoseStack matrices, VertexConsumer vertexConsumer, double x1, double y1, double z1, double x2, double y2, double z2, float red, float green, float blue, float alpha, float xAxisRed, float yAxisGreen, float zAxisBlue, CallbackInfo ci) { - var writer = VertexConsumerUtils.convertOrLog(vertexConsumer); + var writer = VertexConsumerUtils.convertOrLog(vertexConsumer, LineVertex.FORMAT); if (writer == null) { return; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/item/ItemRendererMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/item/ItemRendererMixin.java index fc3576589e..8a764860ec 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/item/ItemRendererMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/item/ItemRendererMixin.java @@ -7,6 +7,7 @@ import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.caffeinemc.mods.sodium.api.util.ColorARGB; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; +import net.caffeinemc.mods.sodium.api.vertex.format.common.EntityVertex; import net.caffeinemc.mods.sodium.client.model.quad.BakedQuadView; import net.caffeinemc.mods.sodium.client.render.immediate.model.BakedModelEncoder; import net.caffeinemc.mods.sodium.client.render.vertex.VertexConsumerUtils; @@ -59,7 +60,7 @@ private Direction[] renderModelFastDirections(Operation original) */ @WrapOperation(method = "renderModelLists", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/entity/ItemRenderer;renderQuadList(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/blaze3d/vertex/VertexConsumer;Ljava/util/List;Lnet/minecraft/world/item/ItemStack;II)V")) private void renderModelFast(ItemRenderer itemRenderer, PoseStack poseStack, VertexConsumer vertexConsumer, List quads, ItemStack itemStack, int light, int overlay, Operation original) { - var writer = VertexConsumerUtils.convertOrLog(vertexConsumer); + var writer = VertexConsumerUtils.convertOrLog(vertexConsumer, EntityVertex.FORMAT); if (writer == null) { original.call(itemRenderer, poseStack, vertexConsumer, quads, itemStack, light, overlay); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/particle/SingleQuadParticleMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/particle/SingleQuadParticleMixin.java index 413613d00c..8957e71146 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/particle/SingleQuadParticleMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/particle/SingleQuadParticleMixin.java @@ -55,7 +55,7 @@ protected SingleQuadParticleMixin(ClientLevel level, double x, double y, double */ @Inject(method = "render(Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/client/Camera;F)V", at = @At("HEAD"), cancellable = true) protected void render(VertexConsumer vertexConsumer, Camera camera, float tickDelta, CallbackInfo ci) { - final var writer = VertexConsumerUtils.convertOrLog(vertexConsumer); + final var writer = VertexConsumerUtils.convertOrLog(vertexConsumer, ParticleVertex.FORMAT); if (writer == null) { return; @@ -100,7 +100,7 @@ protected void render(VertexConsumer vertexConsumer, Camera camera, float tickDe */ @Inject(method = "renderRotatedQuad(Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/client/Camera;Lorg/joml/Quaternionf;F)V", at = @At("HEAD"), cancellable = true) protected void renderRotatedQuad(VertexConsumer vertexConsumer, Camera camera, Quaternionf quaternion, float tickDelta, CallbackInfo ci) { - final var writer = VertexConsumerUtils.convertOrLog(vertexConsumer); + final var writer = VertexConsumerUtils.convertOrLog(vertexConsumer, ParticleVertex.FORMAT); if (writer == null) { return; diff --git a/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java b/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java index 636c9974eb..4dae9e1cea 100644 --- a/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java +++ b/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java @@ -5,6 +5,7 @@ import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.caffeinemc.mods.sodium.api.util.ColorABGR; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; +import net.caffeinemc.mods.sodium.api.vertex.format.common.EntityVertex; import net.caffeinemc.mods.sodium.client.model.quad.BakedQuadView; import net.caffeinemc.mods.sodium.client.render.immediate.model.BakedModelEncoder; import net.caffeinemc.mods.sodium.client.render.vertex.VertexConsumerUtils; @@ -58,7 +59,7 @@ private static void renderQuads(PoseStack.Pose matrices, VertexBufferWriter writ */ @Inject(method = "renderModel", at = @At("HEAD"), cancellable = true) private void renderFast(PoseStack.Pose entry, VertexConsumer vertexConsumer, BlockState blockState, BakedModel bakedModel, float red, float green, float blue, int light, int overlay, CallbackInfo ci) { - var writer = VertexConsumerUtils.convertOrLog(vertexConsumer); + var writer = VertexConsumerUtils.convertOrLog(vertexConsumer, EntityVertex.FORMAT); if (writer == null) { return; } diff --git a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java index c93024b0fd..02595a1463 100644 --- a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java +++ b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/render/model/block/ModelBlockRendererMixin.java @@ -5,6 +5,7 @@ import net.caffeinemc.mods.sodium.api.texture.SpriteUtil; import net.caffeinemc.mods.sodium.api.util.ColorABGR; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; +import net.caffeinemc.mods.sodium.api.vertex.format.common.EntityVertex; import net.caffeinemc.mods.sodium.client.model.quad.BakedQuadView; import net.caffeinemc.mods.sodium.client.render.immediate.model.BakedModelEncoder; import net.caffeinemc.mods.sodium.client.render.vertex.VertexConsumerUtils; @@ -60,7 +61,7 @@ private static void renderQuads(PoseStack.Pose matrices, VertexBufferWriter writ */ @Inject(method = "renderModel(Lcom/mojang/blaze3d/vertex/PoseStack$Pose;Lcom/mojang/blaze3d/vertex/VertexConsumer;Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/client/resources/model/BakedModel;FFFIILnet/neoforged/neoforge/client/model/data/ModelData;Lnet/minecraft/client/renderer/RenderType;)V", at = @At("HEAD"), cancellable = true) private void renderFast(PoseStack.Pose entry, VertexConsumer vertexConsumer, BlockState blockState, BakedModel bakedModel, float red, float green, float blue, int light, int overlay, ModelData modelData, RenderType renderType, CallbackInfo ci) { - var writer = VertexConsumerUtils.convertOrLog(vertexConsumer); + var writer = VertexConsumerUtils.convertOrLog(vertexConsumer, EntityVertex.FORMAT); if (writer == null) { return; } From 1c2b9d79619123dbd33620d593467173bc868568 Mon Sep 17 00:00:00 2001 From: douira Date: Wed, 10 Jun 2026 14:28:02 +0200 Subject: [PATCH 203/215] Only make environment changes if the early window will create a gl context early (#3697) --- .../mods/sodium/service/SodiumWorkarounds.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/neoforge/src/main/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java b/neoforge/src/main/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java index 577ae48187..d66993ebc9 100644 --- a/neoforge/src/main/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java +++ b/neoforge/src/main/java/net/caffeinemc/mods/sodium/service/SodiumWorkarounds.java @@ -3,8 +3,9 @@ import net.caffeinemc.mods.sodium.client.compatibility.checks.PreLaunchChecks; import net.caffeinemc.mods.sodium.client.compatibility.environment.probe.GraphicsAdapterProbe; import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds; -import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; import net.caffeinemc.mods.sodium.client.compatibility.workarounds.amd.AmdWorkarounds; +import net.caffeinemc.mods.sodium.client.compatibility.workarounds.nvidia.NvidiaWorkarounds; +import net.neoforged.fml.loading.FMLConfig; import net.neoforged.neoforgespi.earlywindow.GraphicsBootstrapper; public class SodiumWorkarounds implements GraphicsBootstrapper { @@ -19,8 +20,11 @@ public void bootstrap(String[] arguments) { GraphicsAdapterProbe.findAdapters(); Workarounds.init(); - // Context creation happens earlier on NeoForge, so we need to apply this now - NvidiaWorkarounds.applyEnvironmentChanges(); - AmdWorkarounds.applyEnvironmentChanges(); + // When early window control is disabled, NeoForge creates no early GL context, so context creation happens later in Window#createGlfwWindow. We want to avoid doing the workarounds twice if the context is going to be created later and thus our mixin to Window#createGlfwWindow runs that also applies them. + // See https://github.com/CaffeineMC/sodium/issues/3664 for more details. + if (FMLConfig.getBoolConfigValue(FMLConfig.ConfigValue.EARLY_WINDOW_CONTROL)) { + NvidiaWorkarounds.applyEnvironmentChanges(); + AmdWorkarounds.applyEnvironmentChanges(); + } } } From 49927dcf313c0adbebfdf57bee059c1fd0861d4b Mon Sep 17 00:00:00 2001 From: douira Date: Fri, 12 Jun 2026 02:29:08 +0200 Subject: [PATCH 204/215] Allow picking which platforms to publish to in the release workflow --- .github/workflows/publish-release.yml | 11 +++++-- build.gradle.kts | 45 ++++++++++++++++----------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 7fbe0020d6..c16fc4e82d 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -1,6 +1,6 @@ # Used when a developer decides to publish a release # This makes use of caching for faster builds and uploads the resulting artifacts -name: Release GH+MR+CF +name: Release on Platforms permissions: contents: write # Needed to publish a GitHub release @@ -14,6 +14,13 @@ on: - both - fabric - neoforge + destination: + type: choice + description: Destination + options: + - GH+MR+CF + - GH+MR + - GH jobs: build: @@ -55,7 +62,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CURSEFORGE_API_KEY: ${{ secrets.CURSEFORGE_API_KEY }} MODRINTH_API_KEY: ${{ secrets.MODRINTH_API_KEY }} - run: ./gradlew publishMods -Pbuild.release=true -Pbuild.release.platform=${{ inputs.platform }} + run: ./gradlew publishMods -Pbuild.release=true -Pbuild.release.platform=${{ inputs.platform }} -Pbuild.release.destination=${{ inputs.destination }} - name: Publish Tag Release to CaffeineMC Maven env: diff --git a/build.gradle.kts b/build.gradle.kts index c156f03ee4..7f89a9f9be 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,6 +16,11 @@ gradle.projectsEvaluated { val releasePlatform: String = project.providers.gradleProperty("build.release.platform").orNull ?: return@publishMods println("build.release.platform must be defined (expected: both, fabric, neoforge)") + val releaseDestination: String = project.providers.gradleProperty("build.release.destination").orNull + ?: return@publishMods println("build.release.destination must be defined (expected: GH+MR+CF, GH+MR, GH)") + val publishModrinth = releaseDestination.contains("MR") + val publishCurseforge = releaseDestination.contains("CF") + val modVersion = BuildConfig.createVersionString(project); type = when { @@ -37,8 +42,8 @@ gradle.projectsEvaluated { minecraftVersions.add(BuildConfig.MINECRAFT_VERSION) } - setupFor("Fabric", releasePlatform, curseforgeShared, modrinthShared) - setupFor("NeoForge", releasePlatform, curseforgeShared, modrinthShared) + setupFor("Fabric", releasePlatform, publishCurseforge, publishModrinth, curseforgeShared, modrinthShared) + setupFor("NeoForge", releasePlatform, publishCurseforge, publishModrinth, curseforgeShared, modrinthShared) github { accessToken = project.providers.environmentVariable("GITHUB_TOKEN") @@ -54,7 +59,7 @@ gradle.projectsEvaluated { } } -fun me.modmuss50.mpp.ModPublishExtension.setupFor(loaderName: String, releasePlatform: String, curseforgeOptions: Provider, modrinthOptions: Provider) { +fun me.modmuss50.mpp.ModPublishExtension.setupFor(loaderName: String, releasePlatform: String, publishCurseforge: Boolean, publishModrinth: Boolean, curseforgeOptions: Provider, modrinthOptions: Provider) { val loaderLowercase = loaderName.lowercase(Locale.ROOT) if (releasePlatform == "both" || releasePlatform == loaderLowercase) { @@ -64,25 +69,29 @@ fun me.modmuss50.mpp.ModPublishExtension.setupFor(loaderName: String, releasePla val releaseTitle = "Sodium ${BuildConfig.MOD_VERSION} for $loaderName ${BuildConfig.MINECRAFT_VERSION}" val releaseVersion = "${BuildConfig.RELEASE_TAG}-$loaderLowercase" - curseforge("curseforge$loaderName") { - from(curseforgeOptions) - - file.set(jar) - displayName = releaseTitle - version = releaseVersion - modLoaders.add(loaderLowercase) + if (publishCurseforge) { + curseforge("curseforge$loaderName") { + from(curseforgeOptions) + + file.set(jar) + displayName = releaseTitle + version = releaseVersion + modLoaders.add(loaderLowercase) - clientRequired = true - serverRequired = false + clientRequired = true + serverRequired = false + } } - modrinth("modrinth$loaderName") { - from(modrinthOptions) + if (publishModrinth) { + modrinth("modrinth$loaderName") { + from(modrinthOptions) - file.set(jar) - displayName = releaseTitle - version = releaseVersion - modLoaders.add(loaderLowercase) + file.set(jar) + displayName = releaseTitle + version = releaseVersion + modLoaders.add(loaderLowercase) + } } } } \ No newline at end of file From 6d3ea953e6b81df76246eac0cb46fa3e3e6d36e6 Mon Sep 17 00:00:00 2001 From: douira Date: Fri, 12 Jun 2026 02:36:06 +0200 Subject: [PATCH 205/215] Improve the presentation and wording of some video options (#3700) --- .../sodium/client/gui/SodiumConfigBuilder.java | 10 +++++++--- .../mods/sodium/client/gui/options/Toggle.java | 17 +++++++++++++++++ .../translucent_sorting/QuadSplittingMode.java | 2 +- .../resources/assets/sodium/lang/en_us.json | 11 +++++++---- 4 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/Toggle.java diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java index b2a30bc833..3be982f492 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/SodiumConfigBuilder.java @@ -15,6 +15,7 @@ import net.caffeinemc.mods.sodium.client.compatibility.workarounds.Workarounds; import net.caffeinemc.mods.sodium.client.gl.arena.staging.MappedStagingBuffer; import net.caffeinemc.mods.sodium.client.gl.device.RenderDevice; +import net.caffeinemc.mods.sodium.client.gui.options.Toggle; import net.caffeinemc.mods.sodium.client.gui.options.control.ControlValueFormatterImpls; import net.caffeinemc.mods.sodium.client.render.chunk.DeferMode; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.QuadSplittingMode; @@ -417,13 +418,16 @@ private OptionPageBuilder buildQualityPage(ConfigBuilder builder) { ); qualityPage.addOptionGroup(builder.createOptionGroup() .addOption( - builder.createBooleanOption(ResourceLocation.parse("sodium:quality.closest_point_entity_sort")) + builder.createEnumOption(ResourceLocation.parse("sodium:quality.closest_point_entity_sort"), Toggle.class) .setStorageHandler(this.sodiumStorage) .setName(Component.translatable("sodium.options.closest_point_entity_sort.name")) .setTooltip(Component.translatable("sodium.options.closest_point_entity_sort.tooltip")) + .setElementNameProvider(EnumOptionBuilder.nameProviderFrom( + Component.translatable("sodium.options.closest_point_entity_sort.default"), + Component.translatable("sodium.options.closest_point_entity_sort.enhanced"))) .setImpact(OptionImpact.MEDIUM) - .setDefaultValue(DEFAULTS.quality.useClosestPointEntitySort) - .setBinding(value -> this.sodiumOpts.quality.useClosestPointEntitySort = value, () -> this.sodiumOpts.quality.useClosestPointEntitySort) + .setDefaultValue(Toggle.fromBoolean(DEFAULTS.quality.useClosestPointEntitySort)) + .setBinding(value -> this.sodiumOpts.quality.useClosestPointEntitySort = value.toBoolean(), () -> Toggle.fromBoolean(this.sodiumOpts.quality.useClosestPointEntitySort)) ) ); return qualityPage; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/Toggle.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/Toggle.java new file mode 100644 index 0000000000..e1b7fadbbc --- /dev/null +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/Toggle.java @@ -0,0 +1,17 @@ +package net.caffeinemc.mods.sodium.client.gui.options; + +/** + * Two-state enum used to present boolean-backed options as enum controls in the options screen. + */ +public enum Toggle { + OFF, + ON; + + public static Toggle fromBoolean(boolean value) { + return value ? ON : OFF; + } + + public boolean toBoolean() { + return this == ON; + } +} diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/QuadSplittingMode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/QuadSplittingMode.java index 481606b4a1..3b896d82f7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/QuadSplittingMode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/QuadSplittingMode.java @@ -5,7 +5,7 @@ import net.minecraft.util.Mth; public enum QuadSplittingMode implements TextProvider { - OFF("/", 1.0f, true, "options.off"), + OFF("/", 1.0f, true, "sodium.options.quad_splitting.off"), SAFE("S", 2.0f, true, "sodium.options.quad_splitting.safe"), UNLIMITED("U", Float.POSITIVE_INFINITY, false, "sodium.options.quad_splitting.unlimited"); diff --git a/common/src/main/resources/assets/sodium/lang/en_us.json b/common/src/main/resources/assets/sodium/lang/en_us.json index 0991d8f1ee..3694f6b6b4 100644 --- a/common/src/main/resources/assets/sodium/lang/en_us.json +++ b/common/src/main/resources/assets/sodium/lang/en_us.json @@ -61,12 +61,15 @@ "sodium.options.defer_chunk_updates.always": "Deferred", "sodium.options.defer_chunk_updates.one_frame": "Soon", "sodium.options.defer_chunk_updates.zero_frames": "Immediate", - "sodium.options.quad_splitting.name": "Perfect Translucency", - "sodium.options.quad_splitting.tooltip": "Uses quad splitting to make translucent blocks (not entities or items) look correct through translucency sorting even if they are intersecting or have weird shapes, such as waterlogged stained glass panes. In \"Safe\" mode there's a limit on how much geometry can be generated in extreme cases to prevent crashes or performance degradation. This limit is disabled in \"Unlimited\" mode.", + "sodium.options.quad_splitting.name": "Block Transparency Limits", + "sodium.options.quad_splitting.tooltip": "Determines what approach is used to make translucent blocks (not entities or items) look correct even when they intersect or have unusual shapes, such as waterlogged stained glass panes. \"Basic\" disables this feature and such blocks may render incorrectly. \"Safe\" limits the amount of newly generated geometry to prevent instability in extreme cases, while \"Unlimited\" imposes no limit.", + "sodium.options.quad_splitting.off": "Basic", "sodium.options.quad_splitting.safe": "Safe", "sodium.options.quad_splitting.unlimited": "Unlimited", - "sodium.options.closest_point_entity_sort.name" : "Closest-Point Entity Sorting", - "sodium.options.closest_point_entity_sort.tooltip" : "Sorts the geometry of entities by the closest point to the camera. This improves rendering accuracy for translucent quads with different sizes, at a small CPU cost.", + "sodium.options.closest_point_entity_sort.name" : "Entity Sorting", + "sodium.options.closest_point_entity_sort.tooltip" : "Controls how the translucent geometry of entities is sorted. \"Enhanced\" sorts by the closest point to the camera, which improves rendering accuracy for translucent quads with different sizes, at a small CPU cost. \"Default\" uses the standard sorting.", + "sodium.options.closest_point_entity_sort.default" : "Default", + "sodium.options.closest_point_entity_sort.enhanced" : "Enhanced", "sodium.options.use_no_error_context.name": "Use No Error Context", "sodium.options.use_no_error_context.tooltip": "When enabled, the OpenGL context will be created with error checking disabled. This slightly improves rendering performance, but it can make debugging sudden unexplained crashes much harder.", "sodium.options.buttons.undo": "Undo", From 88a6403a13ddc21db2a8c87d01001441c55ec17e Mon Sep 17 00:00:00 2001 From: douira Date: Fri, 12 Jun 2026 12:01:02 +0200 Subject: [PATCH 206/215] use shorter pre-release version format to stay within version string length limit --- buildSrc/src/main/kotlin/BuildConfig.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index 039f1ffd19..fa16f676ac 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -12,7 +12,12 @@ object BuildConfig { // https://semver.org/ val MOD_VERSION: String = "0.8.12-alpha.4" - val RELEASE_TAG: String = "mc$MINECRAFT_VERSION-$MOD_VERSION" + val MINECRAFT_VERSION_SHORT: String = MINECRAFT_VERSION + .replace("-snapshot-", "s") + .replace("-pre-", "p") + .replace("-rc-", "r") + + val RELEASE_TAG: String = "mc$MINECRAFT_VERSION_SHORT-$MOD_VERSION" val CURSEFORGE_PROJECT_ID = "394468" val MODRINTH_PROJECT_ID = "AANobbMI" @@ -30,7 +35,7 @@ object BuildConfig { builder.append("-SNAPSHOT") } - builder.append("+mc").append(MINECRAFT_VERSION) + builder.append("+mc").append(MINECRAFT_VERSION_SHORT) if (!isReleaseBuild) { if (buildId != null) { From 309c6916cdc193a412c55706d228ef2cb1dbc7b8 Mon Sep 17 00:00:00 2001 From: douira Date: Sat, 13 Jun 2026 15:18:45 +0200 Subject: [PATCH 207/215] Bump version to 0.8.12-beta.1, update changelog, and set iris breaks clause --- CHANGELOG.md | 18 ++++++++++++------ buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- fabric/src/main/resources/fabric.mod.json | 2 +- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b856bd156..64e055ee4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,13 +21,12 @@ It includes the backport of our Config API and other conventions that will hopef Please participate in testing this release, coordinating on mod compatibility, and giving feedback on [our discord server](https://caffeinemc.net/discord). In the thread in #testing-builds we have more information. Report any issues you may have in the thread. Known incompatibilities at the time of this release: -- sodiumleafculling does not work -- C2ME's latest version works -- Create Aeronautics does not work -- Veil has an update that works, Sable has a patch that works but no update -- Iris has a test build on discord that works -- More culling's latest version works +- Create Aeronautics works as of 1.3.0 +- Sable works as of 2.0.0 +- Veil works as of 4.1.2 +- Iris works as of 1.8.13 (unreleased on platforms at the time of this release) - Voxy does not work +- sodiumleafculling does not work - EBE animations don’t work ### Alpha Series Changelog @@ -35,3 +34,10 @@ Known incompatibilities at the time of this release: - Potentially fix issues with publishing by making the buildscript more similar to that used in 1.21.11 - Fix the corrupted config screen to show up properly - Revert "Update texture light coords math to match Vanilla (#3311)" as this was mistakenly applied to 1.21.1 + +### Beta Series Changelog +- Added the display of fps percentiles. This gives a more accurate idea of the typical frame rate and lets you identify how smooth it is. +- Fix crash when using `-Dmixin.debug=true` ([#3689](https://github.com/CaffeineMC/sodium/pull/3689)) +- Added checks to prevent crashes when using the vertex writing fast path ([#3716](https://github.com/CaffeineMC/sodium/pull/3716)) +- Only make environment changes if the early window will create a gl context early ([#3697](https://github.com/CaffeineMC/sodium/pull/3697)) +- Improve the presentation and wording of some video options ([#3700](https://github.com/CaffeineMC/sodium/pull/3700)) \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index fa16f676ac..be34858cdd 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -10,7 +10,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ - val MOD_VERSION: String = "0.8.12-alpha.4" + val MOD_VERSION: String = "0.8.12-beta.1" val MINECRAFT_VERSION_SHORT: String = MINECRAFT_VERSION .replace("-snapshot-", "s") diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index 05f43d540e..dacdf14955 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -94,7 +94,7 @@ "custom_hud" : "<3.4.2", "farsight" : "<=1.21-4.3", "iceberg" : "<1.2.7", - "iris" : "<1.8.7", + "iris" : "<1.8.13", "movingelevators" : "<=1.4.7", "notenoughcrashes" : "<4.4.8", "noxesium" : "<2.3.3", From 43b54de8974dd6db6b839d5e66c86fd882d6b5b1 Mon Sep 17 00:00:00 2001 From: douira Date: Sat, 13 Jun 2026 15:31:05 +0200 Subject: [PATCH 208/215] Fix incorrect wording in the changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64e055ee4f..d53472a949 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ Everything above the line is ignored and not included in the changelog. Everythi changelog on GitHub, Modrinth and CurseForge. ---------- ### Overview -Sodium [SodiumVersion]() is a backport of modern Sodium 0.8 to Minecraft [MCVersion](). This experiment has graduated to alpha status. The degree to which we commit to supporting this branch will be determined by testing and by how well it works and integrates with the ecosystem. The quality of this version is expected to further improve it gets used, we discover quirks, and receive feedback. +Sodium [SodiumVersion]() is a backport of modern Sodium 0.8 to Minecraft [MCVersion](). - Significantly improved the performance of rendering the world (up to +115%) on some computers. - Greatly improved the rendering of transparent objects with complex models, especially when submerged in water. From 7b83b500918299a3964c7874459520ddd1838dea Mon Sep 17 00:00:00 2001 From: PentBeear <43016309+PentBeear@users.noreply.github.com> Date: Sun, 14 Jun 2026 09:45:58 -0500 Subject: [PATCH 209/215] Fixes the fabric version of sodium overriding all fluid BlockTintSources while rendering (#3729) --- .../caffeinemc/mods/sodium/fabric/render/FluidRendererImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FluidRendererImpl.java b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FluidRendererImpl.java index dee26d14e1..e79fe293c9 100644 --- a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FluidRendererImpl.java +++ b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FluidRendererImpl.java @@ -126,7 +126,7 @@ public void clear() { public ColorProvider getColorProvider(Fluid fluid) { var override = this.colorProviderRegistry.getColorProvider(fluid); - if (!hasModOverride && override != null) { + if (hasModOverride && override != null) { return override; } From 840d307d66c0f271d03be90bf0eee095e65a4fda Mon Sep 17 00:00:00 2001 From: douira Date: Sun, 14 Jun 2026 23:46:10 +0200 Subject: [PATCH 210/215] Fix block tinting by not incorrectly converting to the wrong color format in QuadViewImpl. --- .../mods/sodium/client/render/frapi/mesh/QuadViewImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java index ac2823862d..f3fcc57af7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java @@ -299,7 +299,7 @@ public float getZ(int idx) { @Override public int getColor(int idx) { - return ColorHelper.toVanillaColor(color(idx)); + return color(idx); } @Override From d544a169d3c89682b95c133600fd6482d5d5a89e Mon Sep 17 00:00:00 2001 From: JellySquid <1363084+jellysquid3@users.noreply.github.com> Date: Thu, 18 Jun 2026 05:17:26 -0500 Subject: [PATCH 211/215] Update fabric.mod.json to add known incompatible mods Some mods with mixins to our code do not specify a version constraint on Sodium. To help users with difficult crashes, add an inverted constraint on our side to make sure only compatible versions are used. Other mods are either fully deprecated by the APIs introduced in Sodium 0.8.x, or have otherwise become unmaintained. Users should remove these mods or find a suitable replacement. --- fabric/src/main/resources/fabric.mod.json | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/fabric/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json index dacdf14955..8c5872c288 100644 --- a/fabric/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -88,7 +88,7 @@ "ocrenderfix_sodium" : "*", "betterfpsdist" : "<=1.21-4.5", "bobby" : "<5.2.4", - "chunksfadein" : "<2.0.2-1.21.2", + "chunksfadein" : "<3.0.22-1.21", "cull-less-leaves" : "<=1.3.0", "cullleaves" : "<=3.4.0-fabric", "custom_hud" : "<3.4.2", @@ -99,11 +99,16 @@ "notenoughcrashes" : "<4.4.8", "noxesium" : "<2.3.3", "reeses-sodium-options" : "<1.8.0", - "sodium-extra" : "<0.6.0", - "sspb" : "<4.0.0", - "moreculling": "<1.0.4", + "sodium-extra" : "<0.8.7", + "sspb" : "<4.1.0", + "moreculling": "<1.0.8", "simply-no-shading": "<7.6.2", - "betterend": "<=21.0.11" + "betterend": "<=21.0.11", + "sodiumoptionsapi": "*", + "sodiumoptionsmodcompat": "*", + "sodiumextras": "*", + "sodiumdynamiclights": "*", + "sodiumleafculling": "*" }, "provides" : [ "indium" From f214a22d147f618203412816a518b1a1e1f60a38 Mon Sep 17 00:00:00 2001 From: Yao Chung Hu <30311066+flashyreese@users.noreply.github.com> Date: Mon, 22 Jun 2026 14:27:57 +0200 Subject: [PATCH 212/215] Skip rendering regions without resources (#3752) --- .../render/chunk/DefaultChunkRenderer.java | 18 ++++++++++-------- .../render/chunk/region/RenderRegion.java | 1 + 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java index 1b629f7e3f..55ac9c9273 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/DefaultChunkRenderer.java @@ -68,6 +68,12 @@ public void render(ChunkRenderMatrices matrices, continue; } + var resources = region.getResources(); + if (resources == null) { + region.clearCachedBatchFor(renderPass); + continue; + } + var batch = region.getCachedBatch(renderPass); if (!batch.isFilled) { fillCommandBuffer(batch, region, storage, renderList, camera, renderPass, useBlockFaceCulling, useIndexedTessellation); @@ -86,9 +92,9 @@ public void render(ChunkRenderMatrices matrices, GlTessellation tessellation; if (useIndexedTessellation) { - tessellation = this.prepareIndexedTessellation(commandList, region); + tessellation = this.prepareIndexedTessellation(commandList, resources); } else { - tessellation = this.prepareTessellation(commandList, region); + tessellation = this.prepareTessellation(commandList, resources); } setModelMatrixUniforms(shader, region, camera); @@ -314,9 +320,7 @@ private static float getCameraTranslation(int chunkBlockPos, int cameraBlockPos, return (chunkBlockPos - cameraBlockPos) - cameraPos; } - private GlTessellation prepareTessellation(CommandList commandList, RenderRegion region) { - var resources = region.getResources(); - + private GlTessellation prepareTessellation(CommandList commandList, RenderRegion.DeviceResources resources) { GlTessellation tessellation = resources.getTessellation(); if (tessellation == null) { tessellation = this.createRegionTessellation(commandList, resources, true); @@ -326,9 +330,7 @@ private GlTessellation prepareTessellation(CommandList commandList, RenderRegion return tessellation; } - private GlTessellation prepareIndexedTessellation(CommandList commandList, RenderRegion region) { - var resources = region.getResources(); - + private GlTessellation prepareIndexedTessellation(CommandList commandList, RenderRegion.DeviceResources resources) { GlTessellation tessellation = resources.getIndexedTessellation(); if (tessellation == null) { tessellation = this.createRegionTessellation(commandList, resources, false); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java index 12da495bd1..0d1f2be7f4 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/region/RenderRegion.java @@ -244,6 +244,7 @@ public void update(CommandList commandList) { if (this.resources != null && this.resources.shouldDelete()) { this.resources.delete(commandList); this.resources = null; + this.clearAllCachedBatches(); } } From ec15afd7c85f850552577739eb30858faea1e592 Mon Sep 17 00:00:00 2001 From: douira Date: Wed, 24 Jun 2026 17:44:27 +0200 Subject: [PATCH 213/215] Update changelog and bump version to 0.8.12-beta.2 --- CHANGELOG.md | 3 ++- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d53472a949..002e994535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,4 +40,5 @@ Known incompatibilities at the time of this release: - Fix crash when using `-Dmixin.debug=true` ([#3689](https://github.com/CaffeineMC/sodium/pull/3689)) - Added checks to prevent crashes when using the vertex writing fast path ([#3716](https://github.com/CaffeineMC/sodium/pull/3716)) - Only make environment changes if the early window will create a gl context early ([#3697](https://github.com/CaffeineMC/sodium/pull/3697)) -- Improve the presentation and wording of some video options ([#3700](https://github.com/CaffeineMC/sodium/pull/3700)) \ No newline at end of file +- Improve the presentation and wording of some video options ([#3700](https://github.com/CaffeineMC/sodium/pull/3700)) +- Fix crash "getResources is null" ([#3752](https://github.com/CaffeineMC/sodium/pull/3752)) diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index be34858cdd..cf4ce3186d 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -10,7 +10,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ - val MOD_VERSION: String = "0.8.12-beta.1" + val MOD_VERSION: String = "0.8.12-beta.2" val MINECRAFT_VERSION_SHORT: String = MINECRAFT_VERSION .replace("-snapshot-", "s") From ade3786b2d9814edfda629ac54f8f5d44e519df0 Mon Sep 17 00:00:00 2001 From: douira Date: Sat, 27 Jun 2026 15:47:58 +0200 Subject: [PATCH 214/215] Cleanup imports and apply .this qualifier where necessary --- .../blockentity/BlockEntityRenderHandler.java | 2 - .../api/config/option/SteppedValidator.java | 2 +- .../client/checks/ResourcePackScanner.java | 2 - .../config/builder/EnumOptionBuilderImpl.java | 6 +- .../ExternalButtonOptionBuilderImpl.java | 2 +- .../builder/IntegerOptionBuilderImpl.java | 4 +- .../config/builder/OptionBuilderImpl.java | 4 +- .../builder/StatefulOptionBuilderImpl.java | 16 +- .../builder/StaticOptionBuilderImpl.java | 2 +- .../client/config/structure/Config.java | 10 +- .../sodium/client/gl/arena/GlBufferArena.java | 4 +- .../client/gl/attribute/GlVertexFormat.java | 2 +- .../client/gl/device/GLRenderDevice.java | 6 +- .../sodium/client/gl/shader/ShaderParser.java | 12 +- .../client/gui/FullscreenResolutionRange.java | 2 +- .../mods/sodium/client/gui/GUIScaleRange.java | 2 +- .../client/gui/VideoSettingsScreen.java | 14 +- .../gui/options/control/ControlElement.java | 4 +- .../gui/options/control/CyclingControl.java | 6 +- .../control/ExternalButtonControl.java | 2 +- .../gui/options/control/SliderControl.java | 4 +- .../control/StatefulControlElement.java | 2 +- .../gui/options/control/TickBoxControl.java | 6 +- .../client/gui/prompt/ScreenPrompt.java | 2 +- .../gui/widgets/CenteredFlatWidget.java | 6 +- .../client/gui/widgets/FlatButtonWidget.java | 4 +- .../client/gui/widgets/OptionListWidget.java | 2 +- .../client/model/light/LightPipeline.java | 1 - .../model/light/flat/FlatLightPipeline.java | 5 +- .../client/model/light/smooth/AoFaceData.java | 1 - .../light/smooth/SmoothLightPipeline.java | 16 +- .../sodium/client/model/quad/ModelQuad.java | 2 +- .../client/model/quad/ModelQuadView.java | 30 ++-- .../chunk/BlockEntityRenderHandlerImpl.java | 2 - .../render/chunk/ExtendedBlockEntityType.java | 2 - .../render/chunk/RenderSectionManager.java | 10 +- .../buffers/BakedChunkModelBuilder.java | 4 +- .../compile/buffers/ChunkVertexConsumer.java | 22 +-- .../chunk/compile/estimation/Estimator.java | 4 +- .../estimation/MeshTaskSizeEstimator.java | 1 - .../chunk/compile/pipeline/BlockRenderer.java | 22 +-- .../pipeline/DefaultFluidRenderer.java | 2 +- .../chunk/compile/pipeline/FluidRenderer.java | 2 - .../tasks/ChunkBuilderMeshingTask.java | 8 +- .../TranslucentGeometryCollector.java | 4 +- .../bsp_tree/BSPSortState.java | 6 +- .../trigger/DirectTriggers.java | 12 +- .../trigger/NormalList.java | 1 - .../client/render/chunk/tree/Forest.java | 2 +- .../render/chunk/tree/TraversableTree.java | 8 +- .../client/render/frapi/SodiumRenderer.java | 6 +- .../frapi/material/MaterialFinderImpl.java | 20 +-- .../frapi/material/MaterialViewImpl.java | 14 +- .../frapi/material/RenderMaterialImpl.java | 2 +- .../render/frapi/mesh/MeshBuilderImpl.java | 44 +++--- .../client/render/frapi/mesh/MeshImpl.java | 4 +- .../frapi/mesh/MutableQuadViewImpl.java | 128 ++++++++-------- .../render/frapi/mesh/QuadViewImpl.java | 126 +++++++-------- .../render/AbstractBlockRenderContext.java | 28 ++-- .../frapi/render/AbstractRenderContext.java | 30 ++-- .../frapi/render/ItemRenderContext.java | 144 +++++++++--------- .../render/NonTerrainBlockRenderContext.java | 8 +- .../viewport/frustum/SimpleFrustum.java | 94 ++++++------ .../services/SodiumModelDataContainer.java | 6 +- .../util/interval_tree/DoubleInterval.java | 34 ++--- .../client/util/interval_tree/Interval.java | 22 +-- .../client/util/interval_tree/TreeNode.java | 8 +- .../client/world/biome/LevelColorCache.java | 2 +- .../world/cloned/ChunkRenderContext.java | 3 +- .../world/cloned/ClonedChunkSection.java | 6 +- .../mods/sodium/mixin/SodiumMixinPlugin.java | 4 +- .../core/render/BlockEntityTypeMixin.java | 10 +- .../consumer/BufferBuilderMixin.java | 2 +- .../consumer/VertexMultiConsumerMixin.java | 4 +- .../render/entity/ModelPartMixin.java | 11 +- .../render/frapi/ItemRendererMixin.java | 4 +- .../render/frapi/ModelBlockRendererMixin.java | 4 +- .../render/world/sky/LevelRendererMixin.java | 1 - .../shader/uniform/ShaderInstanceMixin.java | 4 +- .../textures/scan/SpriteContentsMixin.java | 2 +- .../fabric/block/FabricBlockAccess.java | 3 +- .../fabric/level/FabricLevelAccess.java | 16 -- .../fabric/render/FluidRendererImpl.java | 16 +- .../features/world/biome/BiomeMixin.java | 8 +- .../model/MultiPartBakedModelMixin.java | 4 +- .../features/world/biome/BiomeMixin.java | 8 +- .../AbstractBlockRenderContextMixin.java | 2 +- .../platform/neoforge/LevelSliceMixin.java | 6 +- .../neoforge/render/FluidRendererImpl.java | 6 +- 89 files changed, 547 insertions(+), 594 deletions(-) diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderHandler.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderHandler.java index b5de855a17..5a1e7054af 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderHandler.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/blockentity/BlockEntityRenderHandler.java @@ -1,7 +1,5 @@ package net.caffeinemc.mods.sodium.api.blockentity; -import java.util.function.Predicate; - import net.caffeinemc.mods.sodium.api.internal.DependencyInjection; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; diff --git a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/SteppedValidator.java b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/SteppedValidator.java index d038304c8f..9bbc5db47d 100644 --- a/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/SteppedValidator.java +++ b/common/src/api/java/net/caffeinemc/mods/sodium/api/config/option/SteppedValidator.java @@ -38,7 +38,7 @@ default boolean isValueValid(int value) { @Override default Integer getValidatedValue(Integer value, Supplier defaultValueSupplier) { - if (isValueValid(value)) { + if (this.isValueValid(value)) { return value; } return defaultValueSupplier.get(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/ResourcePackScanner.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/ResourcePackScanner.java index ff6be4cb8c..b4ca154078 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/ResourcePackScanner.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/checks/ResourcePackScanner.java @@ -2,8 +2,6 @@ import net.caffeinemc.mods.sodium.client.console.Console; import net.caffeinemc.mods.sodium.client.console.message.MessageLevel; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.FilePackResources; import net.minecraft.server.packs.PackResources; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java index 30cddd225f..0b20c553f0 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/EnumOptionBuilderImpl.java @@ -82,15 +82,15 @@ Collection getDependencies() { } Class getEnumClass() { - return getFirstNotNull(this.enumClass, EnumOption::getEnumClass); + return this.getFirstNotNull(this.enumClass, EnumOption::getEnumClass); } DependentValue> getAllowedValues() { - return getFirstNotNull(this.allowedValues, EnumOption::getAllowedValues); + return this.getFirstNotNull(this.allowedValues, EnumOption::getAllowedValues); } Function getElementNameProvider() { - return getFirstNotNull(this.elementNameProvider, EnumOption::getElementNameProvider); + return this.getFirstNotNull(this.elementNameProvider, EnumOption::getElementNameProvider); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java index f1a80d2b5d..ef74b272ec 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/ExternalButtonOptionBuilderImpl.java @@ -38,7 +38,7 @@ Class getOptionClass() { } Consumer getCurrentScreenConsumer() { - return getFirstNotNull(this.currentScreenConsumer, ExternalButtonOption::getCurrentScreenConsumer); + return this.getFirstNotNull(this.currentScreenConsumer, ExternalButtonOption::getCurrentScreenConsumer); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java index bad96cbf60..f2c2873836 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/IntegerOptionBuilderImpl.java @@ -67,11 +67,11 @@ Class getOptionClass() { } DependentValue getValidatorProvider() { - return getFirstNotNull(this.validatorProvider, IntegerOption::getValidatorProvider); + return this.getFirstNotNull(this.validatorProvider, IntegerOption::getValidatorProvider); } ControlValueFormatter getValueFormatter() { - return getFirstNotNull(this.valueFormatter, IntegerOption::getValueFormatter); + return this.getFirstNotNull(this.valueFormatter, IntegerOption::getValueFormatter); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java index 72d281f8a3..9a63823704 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/OptionBuilderImpl.java @@ -70,11 +70,11 @@ public V getFirstNotNull(V overlayValue, Function extractor) { } Component getName() { - return getFirstNotNull(this.name, Option::getName); + return this.getFirstNotNull(this.name, Option::getName); } DependentValue getEnabled() { - return getFirstNotNull(this.enabled, Option::getEnabled); + return this.getFirstNotNull(this.enabled, Option::getEnabled); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java index 17c5a3ecd6..8781b43ef0 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StatefulOptionBuilderImpl.java @@ -53,35 +53,35 @@ Collection getDependencies() { } StorageEventHandler getStorage() { - return getFirstNotNull(this.storage, StatefulOption::getStorage); + return this.getFirstNotNull(this.storage, StatefulOption::getStorage); } Function getTooltipProvider() { - return getFirstNotNull(this.tooltipProvider, StatefulOption::getTooltipProvider); + return this.getFirstNotNull(this.tooltipProvider, StatefulOption::getTooltipProvider); } OptionImpact getImpact() { - return getFirstNotNull(this.impact, StatefulOption::getImpact); + return this.getFirstNotNull(this.impact, StatefulOption::getImpact); } Set getFlags() { - return getFirstNotNull(this.flags, StatefulOption::getFlags); + return this.getFirstNotNull(this.flags, StatefulOption::getFlags); } DependentValue getDefaultValue() { - return getFirstNotNull(this.defaultValue, StatefulOption::getDefaultValue); + return this.getFirstNotNull(this.defaultValue, StatefulOption::getDefaultValue); } Boolean getControlHiddenWhenDisabled() { - return getFirstNotNull(this.controlHiddenWhenDisabled, StatefulOption::getControlHiddenWhenDisabled); + return this.getFirstNotNull(this.controlHiddenWhenDisabled, StatefulOption::getControlHiddenWhenDisabled); } OptionBinding getBinding() { - return getFirstNotNull(this.binding, StatefulOption::getBinding); + return this.getFirstNotNull(this.binding, StatefulOption::getBinding); } Consumer getApplyHook() { - return getFirstNotNull(this.applyHook, StatefulOption::getApplyHook); + return this.getFirstNotNull(this.applyHook, StatefulOption::getApplyHook); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StaticOptionBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StaticOptionBuilderImpl.java index 4faa28a809..9df7e8ba52 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StaticOptionBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/builder/StaticOptionBuilderImpl.java @@ -20,7 +20,7 @@ void validateData() { } Component getTooltip() { - return getFirstNotNull(this.tooltip, StaticOption::getTooltip); + return this.getFirstNotNull(this.tooltip, StaticOption::getTooltip); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java index 72288cf19e..e0fe1c2633 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/config/structure/Config.java @@ -41,7 +41,7 @@ public Config(List modOptions) { for (var option : this.options.values()) { option.loadValueInitial(); } - resetAllOptionsFromBindings(); + this.resetAllOptionsFromBindings(); } private void registerSearchIndex() { @@ -120,7 +120,7 @@ private void applyOptionChanges() { // apply override to option if it exists if (override != null) { var replacement = override.change(); - exchangeOption(options, i, replacement, option); + this.exchangeOption(options, i, replacement, option); } } } @@ -141,7 +141,7 @@ private void applyOptionChanges() { var change = overlay.change(); try { var overlaidOption = change.buildWithBaseOption(option); - exchangeOption(options, i, overlaidOption, option); + this.exchangeOption(options, i, overlaidOption, option); } catch (Exception e) { throw new IllegalArgumentException("Failed to apply overlay from '" + overlay.source() + "' to option '" + option.id + "'", e); } @@ -291,7 +291,7 @@ public void applyAllOptions() { if (flags == null) { return; } - processFlags(flags); + this.processFlags(flags); } public void applyOption(ResourceLocation id) { @@ -307,7 +307,7 @@ public void applyOption(ResourceLocation id) { if (flags == null) { return; } - processFlags(flags); + this.processFlags(flags); } public boolean anyOptionChanged() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java index 2b5df04128..b3e5360f6a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/arena/GlBufferArena.java @@ -345,7 +345,7 @@ public boolean upload(CommandList commandList, Stream stream, flo // If we weren't able to upload some buffers, they will have been left behind in the queue if (!queue.isEmpty()) { // resize to the new estimated capacity - this.resize(commandList, estimateNewCapacity(regionFillFractionInv, queue)); + this.resize(commandList, this.estimateNewCapacity(regionFillFractionInv, queue)); // Try again to upload any buffers that failed last time this.tryUploads(commandList, queue); @@ -361,7 +361,7 @@ public boolean upload(CommandList commandList, Stream stream, flo private long estimateNewCapacity(float regionFillFractionInv, List queue) { // Calculate the amount of memory needed for the remaining uploads - long requiredTotalSize = getRequiredTotalSize(queue); + long requiredTotalSize = this.getRequiredTotalSize(queue); int newSegmentCount = this.segmentCount + queue.size(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/attribute/GlVertexFormat.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/attribute/GlVertexFormat.java index de24d23e1a..3034f1e257 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/attribute/GlVertexFormat.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/attribute/GlVertexFormat.java @@ -55,7 +55,7 @@ public String toString() { } public GlVertexAttributeBinding[] getShaderBindings() { - return bindings; + return this.bindings; } public static class Builder { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java index 958b504c86..424bc24d9f 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/device/GLRenderDevice.java @@ -25,7 +25,7 @@ public class GLRenderDevice implements RenderDevice { @Override public CommandList createCommandList() { - GLRenderDevice.this.checkDeviceActive(); + this.checkDeviceActive(); return this.commandList; } @@ -224,7 +224,7 @@ public GlBufferMapping mapBuffer(GlBuffer buffer, long offset, long length, Enum @Override public void unmap(GlBufferMapping map) { - checkMapDisposed(map); + this.checkMapDisposed(map); GlBuffer buffer = map.getBufferObject(); @@ -237,7 +237,7 @@ public void unmap(GlBufferMapping map) { @Override public void flushMappedRange(GlBufferMapping map, int offset, int length) { - checkMapDisposed(map); + this.checkMapDisposed(map); GlBuffer buffer = map.getBufferObject(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderParser.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderParser.java index 4b35d936d6..cbf85d2f07 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderParser.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gl/shader/ShaderParser.java @@ -39,16 +39,16 @@ public void parseShader(String name, String src) { lineNumber++; if (line.startsWith("#version")) { this.lines.add(line); - this.lines.add(lineDirectiveFor(name, lineNumber)); + this.lines.add(this.lineDirectiveFor(name, lineNumber)); } else if (line.startsWith("#import")) { // add the original import statement as a comment for reference this.lines.add("// START " + line); - processImport(line); + this.processImport(line); // reset the line directive to the current file this.lines.add("// END " + line); - this.lines.add(lineDirectiveFor(name, lineNumber)); + this.lines.add(this.lineDirectiveFor(name, lineNumber)); } else { this.lines.add(line); } @@ -70,13 +70,13 @@ private String lineDirectiveFor(String name, int line) { } private void processImport(String line) { - ResourceLocation name = parseImport(line); + ResourceLocation name = this.parseImport(line); // mark the start of the imported file var nameString = name.toString(); - this.lines.add(lineDirectiveFor(nameString, 0)); + this.lines.add(this.lineDirectiveFor(nameString, 0)); - parseShader(nameString, ShaderLoader.getShaderSource(name)); + this.parseShader(nameString, ShaderLoader.getShaderSource(name)); } private static final Pattern IMPORT_PATTERN = Pattern.compile("#import <(?.*):(?.*)>"); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/FullscreenResolutionRange.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/FullscreenResolutionRange.java index afcb996f78..3a801c48a5 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/FullscreenResolutionRange.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/FullscreenResolutionRange.java @@ -29,6 +29,6 @@ public int step() { @Override public boolean isValueValid(int value) { - return value >= min(); + return value >= this.min(); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/GUIScaleRange.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/GUIScaleRange.java index c8e5ac4ba4..54c7b7a0d7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/GUIScaleRange.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/GUIScaleRange.java @@ -20,6 +20,6 @@ public int step() { @Override public boolean isValueValid(int value) { - return value >= min(); + return value >= this.min(); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java index 5fd8691b37..269192d2c2 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/VideoSettingsScreen.java @@ -180,7 +180,7 @@ private void rebuild() { int topBarHeight = Layout.BUTTON_SHORT; this.searchWidget = new SearchWidget(this::onSearchResults, new Dim2i(x, y, w, topBarHeight)); - int topBarClear = topBarHeight + ifInsetY(Layout.INNER_MARGIN); + int topBarClear = topBarHeight + this.ifInsetY(Layout.INNER_MARGIN); this.pageList = new PageListWidget(new Dim2i(x, y + topBarClear, Layout.PAGE_LIST_WIDTH, h - topBarClear), this); this.addRenderableWidget(this.pageList); @@ -206,18 +206,18 @@ private void rebuild() { this.pageList.getLimitX(), y + topBarHeight + Layout.INNER_MARGIN, Layout.OPTION_WIDTH + Layout.OPTION_LIST_SCROLLBAR_OFFSET + Layout.SCROLLBAR_WIDTH, - h - topBarHeight - (reserveBottomSpace ? (Layout.INNER_MARGIN * 2 + Layout.BUTTON_SHORT) : Layout.INNER_MARGIN) - ifNotInsetY(Layout.INNER_MARGIN) + h - topBarHeight - (reserveBottomSpace ? (Layout.INNER_MARGIN * 2 + Layout.BUTTON_SHORT) : Layout.INNER_MARGIN) - this.ifNotInsetY(Layout.INNER_MARGIN) ); this.optionList = new OptionListWidget(this, optionListDim, this::onSectionFocused); this.addRenderableWidget(this.optionList); - var tooltipAreaY = y + topBarHeight + ifInsetY(Layout.TOOLTIP_OUTER_MARGIN); + var tooltipAreaY = y + topBarHeight + this.ifInsetY(Layout.TOOLTIP_OUTER_MARGIN); this.tooltip.setTooltipArea( new Dim2i( this.optionList.getLimitX(), tooltipAreaY, - this.getLimitX() - this.optionList.getLimitX() - ifNotInsetX(Layout.TOOLTIP_OUTER_MARGIN), - this.getLimitY() - tooltipAreaY - ifNotInsetY(Layout.TOOLTIP_OUTER_MARGIN) + this.getLimitX() - this.optionList.getLimitX() - this.ifNotInsetX(Layout.TOOLTIP_OUTER_MARGIN), + this.getLimitY() - tooltipAreaY - this.ifNotInsetY(Layout.TOOLTIP_OUTER_MARGIN) ) ); } @@ -225,8 +225,8 @@ private void rebuild() { private void rebuildActionButtons(boolean stackVertically) { int buttonW = Layout.BUTTON_LONG; int buttonH = Layout.BUTTON_SHORT; - int closeX = this.getLimitX() - buttonW - ifNotInsetX(Layout.INNER_MARGIN); - int closeY = this.getLimitY() - (ifNotInsetY(Layout.INNER_MARGIN) + buttonH); + int closeX = this.getLimitX() - buttonW - this.ifNotInsetX(Layout.INNER_MARGIN); + int closeY = this.getLimitY() - (this.ifNotInsetY(Layout.INNER_MARGIN) + buttonH); int dx = stackVertically ? 0 : -(Layout.INNER_MARGIN + buttonW); int dy = stackVertically ? -(Layout.INNER_MARGIN + buttonH) : 0; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java index 2d6102718c..3f56b0b9ae 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ControlElement.java @@ -41,7 +41,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { name = name + " *"; } - name = truncateLabelToFit(name); + name = this.truncateLabelToFit(name); String label; if (this.getOption().isEnabled()) { @@ -71,7 +71,7 @@ protected MutableComponent formatDisabledControlValue(Component value) { } protected String truncateLabelToFit(String name) { - return truncateTextToFit(name, this.getWidth() - this.getContentWidth() - Layout.OPTION_LABEL_END_PADDING); + return this.truncateTextToFit(name, this.getWidth() - this.getContentWidth() - Layout.OPTION_LABEL_END_PADDING); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java index eb25e37530..cb1095fe99 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/CyclingControl.java @@ -75,7 +75,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { if (this.isResetOverlayActive()) return false; if (this.option.isEnabled() && button == 0 && this.isMouseOver(mouseX, mouseY)) { - cycleControl(Screen.hasShiftDown()); + this.cycleControl(Screen.hasShiftDown()); return true; } @@ -84,10 +84,10 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (!isFocused()) return false; + if (!this.isFocused()) return false; if (CommonInputs.selected(keyCode)) { - cycleControl(Screen.hasShiftDown()); + this.cycleControl(Screen.hasShiftDown()); return true; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ExternalButtonControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ExternalButtonControl.java index e481b393dd..a9447fb569 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ExternalButtonControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/ExternalButtonControl.java @@ -101,7 +101,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (!isFocused()) return false; + if (!this.isFocused()) return false; if (CommonInputs.selected(keyCode)) { this.openScreen(this.screen); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java index c022fd6eb1..3e8d0e521c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/SliderControl.java @@ -158,7 +158,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { public boolean mouseReleased(double mouseX, double mouseY, int button) { if (this.option.isEnabled() && button == 0 && this.sliderHeld) { this.sliderHeld = false; - playClickSound(); + this.playClickSound(); return true; } @@ -190,7 +190,7 @@ public void setValue(double newThumbPosition) { @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (!isFocused()) return false; + if (!this.isFocused()) return false; var range = this.option.getSteppedValidator(); var isLeft = keyCode == InputConstants.KEY_LEFT; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/StatefulControlElement.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/StatefulControlElement.java index 679ef33f90..acd7c51f48 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/StatefulControlElement.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/StatefulControlElement.java @@ -39,6 +39,6 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { @Override protected String truncateLabelToFit(String name) { int rightReserve = this.isResetOverlayActive() ? this.resetButton.getWidth() : this.getContentWidth() + Layout.OPTION_LABEL_END_PADDING; - return truncateTextToFit(name, this.getWidth() - rightReserve); + return this.truncateTextToFit(name, this.getWidth() - rightReserve); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java index e64e4148fc..2d641aa76c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/options/control/TickBoxControl.java @@ -98,7 +98,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { if (this.isResetOverlayActive()) return false; if (this.option.isEnabled() && button == 0 && this.isMouseOver(mouseX, mouseY)) { - toggleControl(); + this.toggleControl(); return true; } @@ -107,10 +107,10 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { @Override public boolean keyPressed(int keyCode, int scanCode, int modifiers) { - if (!isFocused()) return false; + if (!this.isFocused()) return false; if (CommonInputs.selected(keyCode)) { - toggleControl(); + this.toggleControl(); return true; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/prompt/ScreenPrompt.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/prompt/ScreenPrompt.java index 9c10fcb663..9b0e978d11 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/prompt/ScreenPrompt.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/prompt/ScreenPrompt.java @@ -97,7 +97,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { textY += Layout.TEXT_PARAGRAPH_SPACING; } - for (var button : getWidgets()) { + for (var button : this.getWidgets()) { button.render(graphics, mouseX, mouseY, delta); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/CenteredFlatWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/CenteredFlatWidget.java index 885c6f5840..c8b5f98343 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/CenteredFlatWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/CenteredFlatWidget.java @@ -44,7 +44,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { this.hovered = this.isMouseOver(mouseX, mouseY); int backgroundColor = this.hovered ? this.theme.bgHighlight : (this.selected ? this.theme.bgDefault : this.theme.bgInactive); - int textColor = this.selected || !this.isSelectable ? this.theme.themeLighter : this.hovered ? this.theme.theme : theme.themeDarker; + int textColor = this.selected || !this.isSelectable ? this.theme.themeLighter : this.hovered ? this.theme.theme : this.theme.themeDarker; int x1 = this.getX(); int y1 = this.getY(); @@ -98,7 +98,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { } if (button == 0 && this.isMouseOver(mouseX, mouseY)) { - doAction(); + this.doAction(); return true; } @@ -112,7 +112,7 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { return false; if (CommonInputs.selected(keyCode)) { - doAction(); + this.doAction(); return true; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/FlatButtonWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/FlatButtonWidget.java index 02343160ea..21dd458d95 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/FlatButtonWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/FlatButtonWidget.java @@ -100,7 +100,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { } if (button == 0 && this.isMouseOver(mouseX, mouseY)) { - doAction(); + this.doAction(); return true; } @@ -114,7 +114,7 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { return false; if (CommonInputs.selected(keyCode)) { - doAction(); + this.doAction(); return true; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java index c5347917f9..a7a8a7e808 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/gui/widgets/OptionListWidget.java @@ -266,7 +266,7 @@ protected int rightReservedWidth() { } protected String truncateLabelToFit(String name, int padding) { - return truncateTextToFit(name, this.getWidth() - padding - this.rightReservedWidth()); + return this.truncateTextToFit(name, this.getWidth() - padding - this.rightReservedWidth()); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/LightPipeline.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/LightPipeline.java index a4c9e8c268..e2ecdddaec 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/LightPipeline.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/LightPipeline.java @@ -4,7 +4,6 @@ import net.caffeinemc.mods.sodium.client.model.quad.ModelQuadView; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; -import net.minecraft.world.level.material.FluidState; /** * Light pipelines allow model quads for any location in the level to be lit using various backends, including fluids diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/flat/FlatLightPipeline.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/flat/FlatLightPipeline.java index 314605fb05..589697e5b1 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/flat/FlatLightPipeline.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/flat/FlatLightPipeline.java @@ -12,7 +12,6 @@ import net.minecraft.core.Direction; import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.FluidState; import java.util.Arrays; @@ -38,14 +37,14 @@ public void calculate(ModelQuadView quad, BlockPos pos, QuadLightData out, Direc // To match vanilla behavior, use the cull face if it exists/is available if (cullFace != null) { - lightmap = getOffsetLightmap(pos, cullFace); + lightmap = this.getOffsetLightmap(pos, cullFace); Arrays.fill(out.br, this.lightCache.getLevel().getShade(lightFace, shade)); } else { int flags = quad.getFlags(); // If the face is aligned, use the light data above it // To match vanilla behavior, also treat the face as aligned if it is parallel and the block state is a full cube if ((flags & ModelQuadFlags.IS_ALIGNED) != 0 || ((flags & ModelQuadFlags.IS_PARALLEL) != 0 && unpackFC(this.lightCache.get(pos)))) { - lightmap = getOffsetLightmap(pos, lightFace); + lightmap = this.getOffsetLightmap(pos, lightFace); Arrays.fill(out.br, this.lightCache.getLevel().getShade(lightFace, shade)); } else { lightmap = getEmissiveLightmap(this.lightCache.get(pos)); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/AoFaceData.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/AoFaceData.java index b2e29daa7e..a7f413b8cb 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/AoFaceData.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/AoFaceData.java @@ -1,7 +1,6 @@ package net.caffeinemc.mods.sodium.client.model.light.smooth; import net.caffeinemc.mods.sodium.client.model.light.data.LightDataAccess; -import net.minecraft.client.renderer.LightTexture; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java index e3e98689f2..570a2fd70d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/light/smooth/SmoothLightPipeline.java @@ -232,13 +232,13 @@ private AoFaceData gatherInsetFace(ModelQuadView quad, BlockPos blockPos, int ve final float w1 = AoNeighborInfo.get(lightFace).getDepth(quad.getX(vertexIndex), quad.getY(vertexIndex), quad.getZ(vertexIndex)); if (Mth.equal(w1, 0)) { - return getCachedFaceData(blockPos, lightFace, true, shade); + return this.getCachedFaceData(blockPos, lightFace, true, shade); } else if (Mth.equal(w1, 1)) { - return getCachedFaceData(blockPos, lightFace, false, shade); + return this.getCachedFaceData(blockPos, lightFace, false, shade); } else { - tmpFace.reset(); + this.tmpFace.reset(); final float w0 = 1 - w1; - return AoFaceData.weightedMean(getCachedFaceData(blockPos, lightFace, true, shade), w0, getCachedFaceData(blockPos, lightFace, false, shade), w1, tmpFace); + return AoFaceData.weightedMean(this.getCachedFaceData(blockPos, lightFace, true, shade), w0, this.getCachedFaceData(blockPos, lightFace, false, shade), w1, this.tmpFace); } } @@ -253,7 +253,7 @@ private void applyIrregularFace(BlockPos blockPos, ModelQuadView quad, QuadLight for (int i = 0; i < 4; i++) { // TODO: Avoid this if the accurate normal is the face normal - Vector3f normal = NormI8.unpack(quad.getAccurateNormal(i), vertexNormal); + Vector3f normal = NormI8.unpack(quad.getAccurateNormal(i), this.vertexNormal); float ao = 0, sky = 0, block = 0, maxAo = 0; float maxSky = 0, maxBlock = 0; @@ -261,7 +261,7 @@ private void applyIrregularFace(BlockPos blockPos, ModelQuadView quad, QuadLight if (!Mth.equal(0f, x)) { final Direction face = x > 0 ? Direction.EAST : Direction.WEST; - final AoFaceData fd = gatherInsetFace(quad, blockPos, i, face, shade); + final AoFaceData fd = this.gatherInsetFace(quad, blockPos, i, face, shade); AoNeighborInfo.get(face).calculateCornerWeights(quad.getX(i), quad.getY(i), quad.getZ(i), w); final float n = x * x; final float a = fd.getBlendedShade(w) * this.getAmbientBrightness(face, shade); @@ -279,7 +279,7 @@ private void applyIrregularFace(BlockPos blockPos, ModelQuadView quad, QuadLight if (!Mth.equal(0f, y)) { final Direction face = y > 0 ? Direction.UP : Direction.DOWN; - final AoFaceData fd = gatherInsetFace(quad, blockPos, i, face, shade); + final AoFaceData fd = this.gatherInsetFace(quad, blockPos, i, face, shade); AoNeighborInfo.get(face).calculateCornerWeights(quad.getX(i), quad.getY(i), quad.getZ(i), w); final float n = y * y; final float a = fd.getBlendedShade(w) * this.getAmbientBrightness(face, shade); @@ -297,7 +297,7 @@ private void applyIrregularFace(BlockPos blockPos, ModelQuadView quad, QuadLight if (!Mth.equal(0f, z)) { final Direction face = z > 0 ? Direction.SOUTH : Direction.NORTH; - final AoFaceData fd = gatherInsetFace(quad, blockPos, i, face, shade); + final AoFaceData fd = this.gatherInsetFace(quad, blockPos, i, face, shade); AoNeighborInfo.get(face).calculateCornerWeights(quad.getX(i), quad.getY(i), quad.getZ(i), w); final float n = z * z; final float a = fd.getBlendedShade(w) * this.getAmbientBrightness(face, shade); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuad.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuad.java index d5f650313d..ef526b6a28 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuad.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuad.java @@ -125,7 +125,7 @@ public int getVertexNormal(int idx) { @Override public int getFaceNormal() { - return faceNormal; + return this.faceNormal; } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuadView.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuadView.java index 08369fb0af..49ebf030cb 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuadView.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/model/quad/ModelQuadView.java @@ -1,7 +1,7 @@ package net.caffeinemc.mods.sodium.client.model.quad; -import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFlags; import net.caffeinemc.mods.sodium.api.util.NormI8; +import net.caffeinemc.mods.sodium.client.model.quad.properties.ModelQuadFlags; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.core.Direction; @@ -79,21 +79,21 @@ default boolean hasColor() { } default int calculateNormal() { - final float x0 = getX(0); - final float y0 = getY(0); - final float z0 = getZ(0); + final float x0 = this.getX(0); + final float y0 = this.getY(0); + final float z0 = this.getZ(0); - final float x1 = getX(1); - final float y1 = getY(1); - final float z1 = getZ(1); + final float x1 = this.getX(1); + final float y1 = this.getY(1); + final float z1 = this.getZ(1); - final float x2 = getX(2); - final float y2 = getY(2); - final float z2 = getZ(2); + final float x2 = this.getX(2); + final float y2 = this.getY(2); + final float z2 = this.getZ(2); - final float x3 = getX(3); - final float y3 = getY(3); - final float z3 = getZ(3); + final float x3 = this.getX(3); + final float y3 = this.getY(3); + final float z3 = this.getZ(3); final float dx0 = x2 - x0; final float dy0 = y2 - y0; @@ -123,8 +123,8 @@ default int calculateNormal() { * @return the per-vertex normal if it is set, otherwise the face normal. */ default int getAccurateNormal(int i) { - int normal = getVertexNormal(i); + int normal = this.getVertexNormal(i); - return normal == 0 ? getFaceNormal() : normal; + return normal == 0 ? this.getFaceNormal() : normal; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/BlockEntityRenderHandlerImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/BlockEntityRenderHandlerImpl.java index 56f323d708..e1809eabcb 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/BlockEntityRenderHandlerImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/BlockEntityRenderHandlerImpl.java @@ -1,7 +1,5 @@ package net.caffeinemc.mods.sodium.client.render.chunk; -import java.util.function.Predicate; - import net.caffeinemc.mods.sodium.api.blockentity.BlockEntityRenderHandler; import net.caffeinemc.mods.sodium.api.blockentity.BlockEntityRenderPredicate; import net.minecraft.world.level.block.entity.BlockEntity; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ExtendedBlockEntityType.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ExtendedBlockEntityType.java index c74e5712a6..bca68c35da 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ExtendedBlockEntityType.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/ExtendedBlockEntityType.java @@ -6,8 +6,6 @@ import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntityType; -import java.util.function.Predicate; - @SuppressWarnings("unchecked") public interface ExtendedBlockEntityType { BlockEntityRenderPredicate[] sodium$getRenderPredicates(); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java index 17c7c6d387..03b3c458d9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/RenderSectionManager.java @@ -593,10 +593,10 @@ public void updateChunks(boolean updateImmediately) { private void submitSectionTasks( ChunkJobCollector importantCollector, ChunkJobCollector semiImportantCollector, ChunkJobCollector deferredCollector, UploadResourceBudget uploadBudget) { - submitSectionTasks(importantCollector, uploadBudget, TaskQueueType.ZERO_FRAME_DEFER); - submitSectionTasks(semiImportantCollector, uploadBudget, TaskQueueType.ONE_FRAME_DEFER); - submitSectionTasks(deferredCollector, uploadBudget, TaskQueueType.ALWAYS_DEFER); - submitSectionTasks(deferredCollector, uploadBudget, TaskQueueType.INITIAL_BUILD); + this.submitSectionTasks(importantCollector, uploadBudget, TaskQueueType.ZERO_FRAME_DEFER); + this.submitSectionTasks(semiImportantCollector, uploadBudget, TaskQueueType.ONE_FRAME_DEFER); + this.submitSectionTasks(deferredCollector, uploadBudget, TaskQueueType.ALWAYS_DEFER); + this.submitSectionTasks(deferredCollector, uploadBudget, TaskQueueType.INITIAL_BUILD); } private void submitSectionTasks(ChunkJobCollector collector, UploadResourceBudget uploadBudget, TaskQueueType queueType) { @@ -615,7 +615,7 @@ private void submitSectionTasks(ChunkJobCollector collector, UploadResourceBudge // sections for which there's a currently running task. var pendingUpdate = section.getPendingUpdate(); if (pendingUpdate != 0) { - submitSectionTask(collector, section, pendingUpdate, uploadBudget, queueType == TaskQueueType.ZERO_FRAME_DEFER); + this.submitSectionTask(collector, section, pendingUpdate, uploadBudget, queueType == TaskQueueType.ZERO_FRAME_DEFER); } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/BakedChunkModelBuilder.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/BakedChunkModelBuilder.java index 3c1b798305..cee91b9a6e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/BakedChunkModelBuilder.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/BakedChunkModelBuilder.java @@ -31,8 +31,8 @@ public void addSprite(@NotNull TextureAtlasSprite sprite) { @Override public VertexConsumer asFallbackVertexConsumer(Material material, TranslucentGeometryCollector collector) { - fallbackVertexConsumer.setData(material, collector); - return fallbackVertexConsumer; + this.fallbackVertexConsumer.setData(material, collector); + return this.fallbackVertexConsumer; } public void destroy() { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/ChunkVertexConsumer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/ChunkVertexConsumer.java index ede996847e..5cfccb288c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/ChunkVertexConsumer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/buffers/ChunkVertexConsumer.java @@ -45,7 +45,7 @@ public void setData(Material material, TranslucentGeometryCollector collector) { vertex.z = z; vertex.ao = 1.0f; this.writtenAttributes |= ATTRIBUTE_POSITION_BIT; - return potentiallyEndVertex(); + return this.potentiallyEndVertex(); } // Writing color ignores alpha since alpha is used as a color multiplier by Sodium. @@ -54,7 +54,7 @@ public void setData(Material material, TranslucentGeometryCollector collector) { ChunkVertexEncoder.Vertex vertex = this.vertices[this.vertexIndex]; vertex.color = ColorABGR.pack(red, green, blue, alpha); this.writtenAttributes |= ATTRIBUTE_COLOR_BIT; - return potentiallyEndVertex(); + return this.potentiallyEndVertex(); } @Override @@ -62,7 +62,7 @@ public void setData(Material material, TranslucentGeometryCollector collector) { ChunkVertexEncoder.Vertex vertex = this.vertices[this.vertexIndex]; vertex.color = ColorABGR.pack(red, green, blue, alpha); this.writtenAttributes |= ATTRIBUTE_COLOR_BIT; - return potentiallyEndVertex(); + return this.potentiallyEndVertex(); } @Override @@ -70,7 +70,7 @@ public void setData(Material material, TranslucentGeometryCollector collector) { ChunkVertexEncoder.Vertex vertex = this.vertices[this.vertexIndex]; vertex.color = ColorARGB.toABGR(argb); this.writtenAttributes |= ATTRIBUTE_COLOR_BIT; - return potentiallyEndVertex(); + return this.potentiallyEndVertex(); } @Override @@ -79,18 +79,18 @@ public void setData(Material material, TranslucentGeometryCollector collector) { vertex.u = u; vertex.v = v; this.writtenAttributes |= ATTRIBUTE_TEXTURE_BIT; - return potentiallyEndVertex(); + return this.potentiallyEndVertex(); } // Overlay is ignored for chunk geometry. @Override public @NotNull VertexConsumer setUv1(int u, int v) { - return potentiallyEndVertex(); + return this.potentiallyEndVertex(); } @Override public @NotNull VertexConsumer setOverlay(int uv) { - return potentiallyEndVertex(); + return this.potentiallyEndVertex(); } @Override @@ -98,7 +98,7 @@ public void setData(Material material, TranslucentGeometryCollector collector) { ChunkVertexEncoder.Vertex vertex = this.vertices[this.vertexIndex]; vertex.light = ((v & 0xFFFF) << 16) | (u & 0xFFFF); this.writtenAttributes |= ATTRIBUTE_LIGHT_BIT; - return potentiallyEndVertex(); + return this.potentiallyEndVertex(); } @Override @@ -106,13 +106,13 @@ public void setData(Material material, TranslucentGeometryCollector collector) { ChunkVertexEncoder.Vertex vertex = this.vertices[this.vertexIndex]; vertex.light = uv; this.writtenAttributes |= ATTRIBUTE_LIGHT_BIT; - return potentiallyEndVertex(); + return this.potentiallyEndVertex(); } @Override public @NotNull VertexConsumer setNormal(float x, float y, float z) { this.writtenAttributes |= ATTRIBUTE_NORMAL_BIT; - return potentiallyEndVertex(); + return this.potentiallyEndVertex(); } public VertexConsumer potentiallyEndVertex() { @@ -124,7 +124,7 @@ public VertexConsumer potentiallyEndVertex() { this.writtenAttributes = 0; if (this.vertexIndex == 4) { - int normal = calculateNormal(); + int normal = this.calculateNormal(); ModelQuadFacing cullFace = ModelQuadFacing.fromPackedNormal(normal); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Estimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Estimator.java index c7a475075d..2f87b0e5f7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Estimator.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/Estimator.java @@ -19,8 +19,8 @@ public abstract class Estimator< TInput, TOutput, TModel extends Estimator.Model> { - protected final Map models = createMap(); - protected final Map batches = createMap(); + protected final Map models = this.createMap(); + protected final Map batches = this.createMap(); protected interface DataBatch { void addDataPoint(TBatchPoint input); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshTaskSizeEstimator.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshTaskSizeEstimator.java index 82aac38a10..16e803e3a3 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshTaskSizeEstimator.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/estimation/MeshTaskSizeEstimator.java @@ -2,7 +2,6 @@ import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection; import net.caffeinemc.mods.sodium.client.render.chunk.region.RenderRegion; -import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientLevel; import java.util.EnumMap; 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 243d3a29b7..71055e2130 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 @@ -94,14 +94,14 @@ public void renderModel(BakedModel model, BlockState state, BlockPos pos, BlockP this.colorProvider = this.colorProviderRegistry.getColorProvider(state.getBlock()); - type = ItemBlockRenderTypes.getChunkRenderType(state); + this.type = ItemBlockRenderTypes.getChunkRenderType(state); this.prepareCulling(true); this.prepareAoInfo(model.useAmbientOcclusion()); - modelData = PlatformModelAccess.getInstance().getModelData(slice, model, state, pos, slice.getPlatformModelData(pos)); + this.modelData = PlatformModelAccess.getInstance().getModelData(this.slice, model, state, pos, this.slice.getPlatformModelData(pos)); - Iterable renderTypes = PlatformModelAccess.getInstance().getModelRenderTypes(level, model, state, pos, random, modelData); + Iterable renderTypes = PlatformModelAccess.getInstance().getModelRenderTypes(this.level, model, state, pos, this.random, this.modelData); this.allowDowngrade = true; Iterator it = renderTypes.iterator(); @@ -119,8 +119,8 @@ public void renderModel(BakedModel model, BlockState state, BlockPos pos, BlockP ((FabricBakedModel) model).emitBlockQuads(this.level, state, pos, this.randomSupplier, this); } - type = null; - modelData = SodiumModelData.EMPTY; + this.type = null; + this.modelData = SodiumModelData.EMPTY; } /** @@ -144,9 +144,9 @@ protected void processQuad(MutableQuadViewImpl quad) { final BlendMode blendMode = mat.blendMode(); if (blendMode == BlendMode.DEFAULT) { - material = DefaultMaterials.forRenderLayer(type); + material = DefaultMaterials.forRenderLayer(this.type); } else { - material = DefaultMaterials.forRenderLayer(blendMode.blockRenderLayer == null ? type : blendMode.blockRenderLayer); + material = DefaultMaterials.forRenderLayer(blendMode.blockRenderLayer == null ? this.type : blendMode.blockRenderLayer); } this.colorizeQuad(quad, colorIndex); @@ -160,7 +160,7 @@ private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) { if (colorProvider != null) { int[] vertexColors = this.vertexColors; - colorProvider.getColors(this.slice, this.pos, this.scratchPos, this.state, quad, vertexColors, slice.hasBiomeBlend()); + colorProvider.getColors(this.slice, this.pos, this.scratchPos, this.state, quad, vertexColors, this.slice.hasBiomeBlend()); for (int i = 0; i < 4; i++) { quad.color(i, ColorMixer.mulComponentWise(vertexColors[i], quad.color(i))); @@ -200,7 +200,7 @@ private void bufferQuad(MutableQuadViewImpl quad, float[] brightnesses, Material // attempt render pass downgrade if possible var pass = material.pass; - var downgradedPass = attemptPassDowngrade(atlasSprite, pass); + var downgradedPass = this.attemptPassDowngrade(atlasSprite, pass); if (downgradedPass != null) { pass = downgradedPass; } @@ -246,7 +246,7 @@ private boolean validateQuadUVs(TextureAtlasSprite atlasSprite) { } private @Nullable TerrainRenderPass attemptPassDowngrade(TextureAtlasSprite sprite, TerrainRenderPass pass) { - if (!allowDowngrade || Workarounds.isWorkaroundEnabled(Workarounds.Reference.INTEL_DEPTH_BUFFER_COMPARISON_UNRELIABLE)) { + if (!this.allowDowngrade || Workarounds.isWorkaroundEnabled(Workarounds.Reference.INTEL_DEPTH_BUFFER_COMPARISON_UNRELIABLE)) { return null; } @@ -263,7 +263,7 @@ private boolean validateQuadUVs(TextureAtlasSprite atlasSprite) { } if (attemptDowngrade) { - attemptDowngrade = validateQuadUVs(sprite); + attemptDowngrade = this.validateQuadUVs(sprite); } if (attemptDowngrade) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java index 6df1c9ebca..249378d4f0 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/DefaultFluidRenderer.java @@ -377,7 +377,7 @@ private void updateQuad(ModelQuadViewMutable quad, LevelSlice level, BlockPos po lighter.calculate(quad, pos, light, null, dir, false, false); - colorProvider.getColors(level, pos, scratchPos, fluidState, quad, this.quadColors, level.hasBiomeBlend()); + colorProvider.getColors(level, pos, this.scratchPos, fluidState, quad, this.quadColors, level.hasBiomeBlend()); // multiply the per-vertex color against the combined brightness // the combined brightness is the per-vertex brightness multiplied by the block's brightness diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/FluidRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/FluidRenderer.java index 5a9a16766d..b28456aa08 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/FluidRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/pipeline/FluidRenderer.java @@ -1,7 +1,5 @@ package net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline; -import net.caffeinemc.mods.sodium.client.model.color.ColorProviderRegistry; -import net.caffeinemc.mods.sodium.client.model.light.LightPipelineProvider; import net.caffeinemc.mods.sodium.client.render.chunk.compile.ChunkBuildBuffers; import net.caffeinemc.mods.sodium.client.render.chunk.translucent_sorting.TranslucentGeometryCollector; import net.caffeinemc.mods.sodium.client.world.LevelSlice; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java index 97a359fe54..d6e167744a 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/compile/tasks/ChunkBuilderMeshingTask.java @@ -144,10 +144,10 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke } } catch (ReportedException ex) { // Propagate existing crashes (add context) - throw fillCrashInfo(ex.getReport(), slice, blockPos); + throw this.fillCrashInfo(ex.getReport(), slice, blockPos); } catch (Exception ex) { // Create a new crash report for other exceptions (e.g. thrown in getQuads) - throw fillCrashInfo(CrashReport.forThrowable(ex, "Encountered exception while building chunk meshes"), slice, blockPos); + throw this.fillCrashInfo(CrashReport.forThrowable(ex, "Encountered exception while building chunk meshes"), slice, blockPos); } PlatformLevelRenderHooks.INSTANCE.runChunkMeshAppenders(this.renderContext.getRenderers(), type -> buffers.get(DefaultMaterials.forRenderLayer(type)).asFallbackVertexConsumer(DefaultMaterials.forRenderLayer(type), collector), @@ -181,7 +181,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke translucentData = collector.getTranslucentData(oldData, this); } catch (Exception ex) { // Create a new crash report for exceptions thrown during sort preparation - throw fillCrashInfo(CrashReport.forThrowable(ex, "Encountered exception while preparing for translucency sorting"), slice, null); + throw this.fillCrashInfo(CrashReport.forThrowable(ex, "Encountered exception while preparing for translucency sorting"), slice, null); } reuseUploadedData = !this.forceSort && translucentData == oldData; } @@ -226,7 +226,7 @@ public ChunkBuildOutput execute(ChunkBuildContext buildContext, CancellationToke output.setSorter(sorter); } catch (Exception ex) { // Create a new crash report for exceptions thrown during sorting - throw fillCrashInfo(CrashReport.forThrowable(ex, "Encountered exception while writing index buffer for translucent geometry"), slice, null); + throw this.fillCrashInfo(CrashReport.forThrowable(ex, "Encountered exception while writing index buffer for translucent geometry"), slice, null); } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java index c4865c7212..50b4a9b1f7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/TranslucentGeometryCollector.java @@ -261,7 +261,7 @@ private SortType sortTypeHeuristic() { } int alignedNormalCount = Integer.bitCount(this.alignedFacingBitmap); - int planeCount = getPlaneCount(alignedNormalCount); + int planeCount = this.getPlaneCount(alignedNormalCount); int unalignedNormalCount = this.untrackedUnalignedNormalCount; if (this.unalignedANormal != -1) { @@ -390,7 +390,7 @@ public SortType finishRendering() { } this.quadLists = null; // they're not needed anymore - this.sortType = filterSortType(sortTypeHeuristic(), this.sortBehavior); + this.sortType = filterSortType(this.sortTypeHeuristic(), this.sortBehavior); return this.sortType; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPSortState.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPSortState.java index e5d54689dc..eb9a87115d 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPSortState.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/bsp_tree/BSPSortState.java @@ -54,10 +54,10 @@ private void checkModificationCounter(int reduceBy) { void writeIndex(int index) { if (this.indexMap != null) { TranslucentData.writeQuadVertexIndexes(this.indexBuffer, this.indexMap[index]); - checkModificationCounter(1); + this.checkModificationCounter(1); } else if (this.fixedIndexOffset != NO_FIXED_OFFSET) { TranslucentData.writeQuadVertexIndexes(this.indexBuffer, this.fixedIndexOffset + index); - checkModificationCounter(1); + this.checkModificationCounter(1); } else { TranslucentData.writeQuadVertexIndexes(this.indexBuffer, index); } @@ -312,7 +312,7 @@ void writeIndexes(int[] indexes) { // check if the index modification session is over. this is very important or // there's an exception if (useIndexMap || useFixedIndexOffset) { - checkModificationCounter(valueCount); + this.checkModificationCounter(valueCount); } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/DirectTriggers.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/DirectTriggers.java index de8d651c48..bde5812092 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/DirectTriggers.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/DirectTriggers.java @@ -87,9 +87,9 @@ private static class DirectTriggerData { Vector3dc getSectionCenter() { if (this.sectionCenter == null) { this.sectionCenter = new Vector3d( - sectionPos.minBlockX() + 8, - sectionPos.minBlockY() + 8, - sectionPos.minBlockZ() + 8); + this.sectionPos.minBlockX() + 8, + this.sectionPos.minBlockY() + 8, + this.sectionPos.minBlockZ() + 8); } return this.sectionCenter; } @@ -110,16 +110,16 @@ Vector3dc getSectionCenter() { * section. */ double getSectionCenterTriggerCameraDist() { - return Math.sqrt(getSectionCenterDistSquared(this.triggerCameraPos)); + return Math.sqrt(this.getSectionCenterDistSquared(this.triggerCameraPos)); } double getSectionCenterDistSquared(Vector3dc vector) { - Vector3dc sectionCenter = getSectionCenter(); + Vector3dc sectionCenter = this.getSectionCenter(); return sectionCenter.distanceSquared(vector); } boolean isAngleTriggering(Vector3dc vector) { - return getSectionCenterDistSquared(vector) > SECTION_CENTER_DIST_SQUARED; + return this.getSectionCenterDistSquared(vector) > SECTION_CENTER_DIST_SQUARED; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalList.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalList.java index 4ce9902f94..7a5e086710 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalList.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/translucent_sorting/trigger/NormalList.java @@ -11,7 +11,6 @@ import net.caffeinemc.mods.sodium.client.util.interval_tree.Interval; import net.caffeinemc.mods.sodium.client.util.interval_tree.Interval.Bounded; import net.caffeinemc.mods.sodium.client.util.interval_tree.IntervalTree; -import org.joml.Vector3dc; import org.joml.Vector3fc; import java.util.Collection; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/Forest.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/Forest.java index 9fbbd3b471..766e3e0687 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/Forest.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/Forest.java @@ -6,7 +6,7 @@ public interface Forest { void add(int x, int y, int z); default void add(RenderSection section) { - add(section.getChunkX(), section.getChunkY(), section.getChunkZ()); + this.add(section.getChunkX(), section.getChunkY(), section.getChunkZ()); } int getPresence(int x, int y, int z); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableTree.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableTree.java index 65a7777307..3e4f32b392 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableTree.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/tree/TraversableTree.java @@ -72,7 +72,7 @@ public void traverse(CoordinateSectionVisitor visitor, Viewport viewport, float // everything is already inside the distance limit if the build distance is smaller var initialInside = this.distanceLimit >= buildDistance ? INSIDE_DISTANCE : 0; - this.traverse(getChildOrderModulator(0, 0, 0, 1 << 5), 0, 5, initialInside); + this.traverse(this.getChildOrderModulator(0, 0, 0, 1 << 5), 0, 5, initialInside); this.visitor = null; this.viewport = null; @@ -104,7 +104,7 @@ void traverse(int orderModulator, int nodeOrigin, int level, int inside) { int y = deinterleave6(sectionOrigin >> 1) + this.offsetY; int z = deinterleave6(sectionOrigin >> 2) + this.offsetZ; - if (inside == FULLY_INSIDE || testLeafNode(x, y, z, inside)) { + if (inside == FULLY_INSIDE || this.testLeafNode(x, y, z, inside)) { this.visitor.visit(x, y, z); } } @@ -170,7 +170,7 @@ void testChild(int childOrigin, int childHalfDim, int level, int inside) { // immediately traverse if fully inside if (inside == FULLY_INSIDE) { level--; - this.traverse(getChildOrderModulator(x, y, z, 1 << level), childOrigin, level, inside); + this.traverse(this.getChildOrderModulator(x, y, z, 1 << level), childOrigin, level, inside); return; } @@ -218,7 +218,7 @@ void testChild(int childOrigin, int childHalfDim, int level, int inside) { if (visible) { level--; - this.traverse(getChildOrderModulator(x, y, z, 1 << level), childOrigin, level, inside); + this.traverse(this.getChildOrderModulator(x, y, z, 1 << level), childOrigin, level, inside); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/SodiumRenderer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/SodiumRenderer.java index 7b39a52063..e2ff77cea7 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/SodiumRenderer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/SodiumRenderer.java @@ -55,15 +55,15 @@ public MaterialFinder materialFinder() { @Override public RenderMaterial materialById(ResourceLocation id) { - return materialMap.get(id); + return this.materialMap.get(id); } @Override public boolean registerMaterial(ResourceLocation id, RenderMaterial material) { - if (materialMap.containsKey(id)) return false; + if (this.materialMap.containsKey(id)) return false; // cast to prevent acceptance of impostor implementations - materialMap.put(id, (RenderMaterialImpl) material); + this.materialMap.put(id, (RenderMaterialImpl) material); return true; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialFinderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialFinderImpl.java index 44a5b97f83..f0df79faaf 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialFinderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialFinderImpl.java @@ -44,25 +44,25 @@ public MaterialFinderImpl() { public MaterialFinder blendMode(BlendMode blendMode) { Objects.requireNonNull(blendMode, "BlendMode may not be null"); - bits = (bits & ~BLEND_MODE_MASK) | (blendMode.ordinal() << BLEND_MODE_BIT_OFFSET); + this.bits = (this.bits & ~BLEND_MODE_MASK) | (blendMode.ordinal() << BLEND_MODE_BIT_OFFSET); return this; } @Override public MaterialFinder disableColorIndex(boolean disable) { - bits = disable ? (bits | COLOR_DISABLE_FLAG) : (bits & ~COLOR_DISABLE_FLAG); + this.bits = disable ? (this.bits | COLOR_DISABLE_FLAG) : (this.bits & ~COLOR_DISABLE_FLAG); return this; } @Override public MaterialFinder emissive(boolean isEmissive) { - bits = isEmissive ? (bits | EMISSIVE_FLAG) : (bits & ~EMISSIVE_FLAG); + this.bits = isEmissive ? (this.bits | EMISSIVE_FLAG) : (this.bits & ~EMISSIVE_FLAG); return this; } @Override public MaterialFinder disableDiffuse(boolean disable) { - bits = disable ? (bits | DIFFUSE_FLAG) : (bits & ~DIFFUSE_FLAG); + this.bits = disable ? (this.bits | DIFFUSE_FLAG) : (this.bits & ~DIFFUSE_FLAG); return this; } @@ -70,7 +70,7 @@ public MaterialFinder disableDiffuse(boolean disable) { public MaterialFinder ambientOcclusion(TriState mode) { Objects.requireNonNull(mode, "ambient occlusion TriState may not be null"); - bits = (bits & ~AO_MASK) | (mode.ordinal() << AO_BIT_OFFSET); + this.bits = (this.bits & ~AO_MASK) | (mode.ordinal() << AO_BIT_OFFSET); return this; } @@ -78,7 +78,7 @@ public MaterialFinder ambientOcclusion(TriState mode) { public MaterialFinder glint(TriState mode) { Objects.requireNonNull(mode, "glint TriState may not be null"); - bits = (bits & ~GLINT_MASK) | (mode.ordinal() << GLINT_BIT_OFFSET); + this.bits = (this.bits & ~GLINT_MASK) | (mode.ordinal() << GLINT_BIT_OFFSET); return this; } @@ -86,24 +86,24 @@ public MaterialFinder glint(TriState mode) { public MaterialFinder shadeMode(ShadeMode mode) { Objects.requireNonNull(mode, "ShadeMode may not be null"); - bits = (bits & ~SHADE_MODE_MASK) | (mode.ordinal() << SHADE_MODE_BIT_OFFSET); + this.bits = (this.bits & ~SHADE_MODE_MASK) | (mode.ordinal() << SHADE_MODE_BIT_OFFSET); return this; } @Override public MaterialFinder copyFrom(MaterialView material) { - bits = ((MaterialViewImpl) material).bits; + this.bits = ((MaterialViewImpl) material).bits; return this; } @Override public MaterialFinder clear() { - bits = defaultBits; + this.bits = defaultBits; return this; } @Override public RenderMaterial find() { - return RenderMaterialImpl.byIndex(bits); + return RenderMaterialImpl.byIndex(this.bits); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialViewImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialViewImpl.java index e6a4ea1e2e..3cc1cce7c9 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialViewImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/MaterialViewImpl.java @@ -85,36 +85,36 @@ protected MaterialViewImpl(int bits) { @Override public BlendMode blendMode() { - return BLEND_MODES[(bits & BLEND_MODE_MASK) >>> BLEND_MODE_BIT_OFFSET]; + return BLEND_MODES[(this.bits & BLEND_MODE_MASK) >>> BLEND_MODE_BIT_OFFSET]; } @Override public boolean disableColorIndex() { - return (bits & COLOR_DISABLE_FLAG) != 0; + return (this.bits & COLOR_DISABLE_FLAG) != 0; } @Override public boolean emissive() { - return (bits & EMISSIVE_FLAG) != 0; + return (this.bits & EMISSIVE_FLAG) != 0; } @Override public boolean disableDiffuse() { - return (bits & DIFFUSE_FLAG) != 0; + return (this.bits & DIFFUSE_FLAG) != 0; } @Override public TriState ambientOcclusion() { - return TRI_STATES[(bits & AO_MASK) >>> AO_BIT_OFFSET]; + return TRI_STATES[(this.bits & AO_MASK) >>> AO_BIT_OFFSET]; } @Override public TriState glint() { - return TRI_STATES[(bits & GLINT_MASK) >>> GLINT_BIT_OFFSET]; + return TRI_STATES[(this.bits & GLINT_MASK) >>> GLINT_BIT_OFFSET]; } @Override public ShadeMode shadeMode() { - return SHADE_MODES[(bits & SHADE_MODE_MASK) >>> SHADE_MODE_BIT_OFFSET]; + return SHADE_MODES[(this.bits & SHADE_MODE_MASK) >>> SHADE_MODE_BIT_OFFSET]; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/RenderMaterialImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/RenderMaterialImpl.java index 91a202828a..df529670ca 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/RenderMaterialImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/material/RenderMaterialImpl.java @@ -36,7 +36,7 @@ private RenderMaterialImpl(int bits) { } public int index() { - return bits; + return this.bits; } public static RenderMaterialImpl byIndex(int index) { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshBuilderImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshBuilderImpl.java index 34e69c8b1f..b94a1e3d15 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshBuilderImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshBuilderImpl.java @@ -30,39 +30,39 @@ public class MeshBuilderImpl implements MeshBuilder { private int[] data = new int[256]; private int index = 0; - private int limit = data.length; + private int limit = this.data.length; private final Maker maker = new Maker(); public MeshBuilderImpl() { - ensureCapacity(EncodingFormat.TOTAL_STRIDE); - maker.data = data; - maker.baseIndex = index; - maker.clear(); + this.ensureCapacity(EncodingFormat.TOTAL_STRIDE); + this.maker.data = this.data; + this.maker.baseIndex = this.index; + this.maker.clear(); } protected void ensureCapacity(int stride) { - if (stride > limit - index) { - limit *= 2; - final int[] bigger = new int[limit]; - System.arraycopy(data, 0, bigger, 0, index); - data = bigger; - maker.data = data; + if (stride > this.limit - this.index) { + this.limit *= 2; + final int[] bigger = new int[this.limit]; + System.arraycopy(this.data, 0, bigger, 0, this.index); + this.data = bigger; + this.maker.data = this.data; } } @Override public QuadEmitter getEmitter() { - maker.clear(); - return maker; + this.maker.clear(); + return this.maker; } @Override public Mesh build() { - final int[] packed = new int[index]; - System.arraycopy(data, 0, packed, 0, index); - index = 0; - maker.baseIndex = index; - maker.clear(); + final int[] packed = new int[this.index]; + System.arraycopy(this.data, 0, packed, 0, this.index); + this.index = 0; + this.maker.baseIndex = this.index; + this.maker.clear(); return new MeshImpl(packed); } @@ -75,10 +75,10 @@ public Mesh build() { private class Maker extends MutableQuadViewImpl { @Override public void emitDirectly() { - computeGeometry(); - index += EncodingFormat.TOTAL_STRIDE; - ensureCapacity(EncodingFormat.TOTAL_STRIDE); - baseIndex = index; + this.computeGeometry(); + MeshBuilderImpl.this.index += EncodingFormat.TOTAL_STRIDE; + MeshBuilderImpl.this.ensureCapacity(EncodingFormat.TOTAL_STRIDE); + this.baseIndex = MeshBuilderImpl.this.index; } } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshImpl.java index 60fa624c90..84f5679914 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MeshImpl.java @@ -38,7 +38,7 @@ public class MeshImpl implements Mesh { @Override public void forEach(Consumer consumer) { - forEach(consumer, cursorPool.get()); + this.forEach(consumer, this.cursorPool.get()); } /** @@ -47,7 +47,7 @@ public void forEach(Consumer consumer) { * Also means renderer can hold final references to quad buffers. */ void forEach(Consumer consumer, QuadViewImpl cursor) { - final int limit = data.length; + final int limit = this.data.length; int index = 0; cursor.data = this.data; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MutableQuadViewImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MutableQuadViewImpl.java index 28f51af336..3a50a5f762 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MutableQuadViewImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/MutableQuadViewImpl.java @@ -50,87 +50,87 @@ public abstract class MutableQuadViewImpl extends QuadViewImpl implements QuadEm @Nullable public TextureAtlasSprite cachedSprite() { - return cachedSprite; + return this.cachedSprite; } public void cachedSprite(@Nullable TextureAtlasSprite sprite) { - cachedSprite = sprite; + this.cachedSprite = sprite; } public TextureAtlasSprite sprite(SpriteFinder finder) { - TextureAtlasSprite sprite = cachedSprite; + TextureAtlasSprite sprite = this.cachedSprite; if (sprite == null) { - cachedSprite = sprite = finder.find(this); + this.cachedSprite = sprite = finder.find(this); } return sprite; } public void clear() { - System.arraycopy(EMPTY, 0, data, baseIndex, EncodingFormat.TOTAL_STRIDE); - isGeometryInvalid = true; - nominalFace = null; - normalFlags(0); - tag(0); - colorIndex(-1); - cullFace(null); - material(SodiumRenderer.STANDARD_MATERIAL); - cachedSprite(null); + System.arraycopy(EMPTY, 0, this.data, this.baseIndex, EncodingFormat.TOTAL_STRIDE); + this.isGeometryInvalid = true; + this.nominalFace = null; + this.normalFlags(0); + this.tag(0); + this.colorIndex(-1); + this.cullFace(null); + this.material(SodiumRenderer.STANDARD_MATERIAL); + this.cachedSprite(null); } @Override public void load() { super.load(); - cachedSprite(null); + this.cachedSprite(null); } @Override public MutableQuadViewImpl pos(int vertexIndex, float x, float y, float z) { - final int index = baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X; - data[index] = Float.floatToRawIntBits(x); - data[index + 1] = Float.floatToRawIntBits(y); - data[index + 2] = Float.floatToRawIntBits(z); - isGeometryInvalid = true; + final int index = this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X; + this.data[index] = Float.floatToRawIntBits(x); + this.data[index + 1] = Float.floatToRawIntBits(y); + this.data[index + 2] = Float.floatToRawIntBits(z); + this.isGeometryInvalid = true; return this; } @Override public MutableQuadViewImpl color(int vertexIndex, int color) { - data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_COLOR] = color; + this.data[this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_COLOR] = color; return this; } @Override public MutableQuadViewImpl uv(int vertexIndex, float u, float v) { - final int i = baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_U; - data[i] = Float.floatToRawIntBits(u); - data[i + 1] = Float.floatToRawIntBits(v); - cachedSprite(null); + final int i = this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_U; + this.data[i] = Float.floatToRawIntBits(u); + this.data[i + 1] = Float.floatToRawIntBits(v); + this.cachedSprite(null); return this; } @Override public MutableQuadViewImpl spriteBake(TextureAtlasSprite sprite, int bakeFlags) { TextureHelper.bakeSprite(this, sprite, bakeFlags); - cachedSprite(sprite); + this.cachedSprite(sprite); return this; } @Override public MutableQuadViewImpl lightmap(int vertexIndex, int lightmap) { - data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_LIGHTMAP] = lightmap; + this.data[this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_LIGHTMAP] = lightmap; return this; } protected void normalFlags(int flags) { - data[baseIndex + HEADER_BITS] = EncodingFormat.normalFlags(data[baseIndex + HEADER_BITS], flags); + this.data[this.baseIndex + HEADER_BITS] = EncodingFormat.normalFlags(this.data[this.baseIndex + HEADER_BITS], flags); } @Override public MutableQuadViewImpl normal(int vertexIndex, float x, float y, float z) { - normalFlags(normalFlags() | (1 << vertexIndex)); - data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_NORMAL] = NormI8.pack(x, y, z); + this.normalFlags(this.normalFlags() | (1 << vertexIndex)); + this.data[this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_NORMAL] = NormI8.pack(x, y, z); return this; } @@ -142,27 +142,27 @@ public final void populateMissingNormals() { if (normalFlags == 0b1111) return; - final int packedFaceNormal = packedFaceNormal(); + final int packedFaceNormal = this.packedFaceNormal(); for (int v = 0; v < 4; v++) { if ((normalFlags & (1 << v)) == 0) { - data[baseIndex + v * VERTEX_STRIDE + VERTEX_NORMAL] = packedFaceNormal; + this.data[this.baseIndex + v * VERTEX_STRIDE + VERTEX_NORMAL] = packedFaceNormal; } } - normalFlags(0b1111); + this.normalFlags(0b1111); } @Override public final MutableQuadViewImpl cullFace(@Nullable Direction face) { - data[baseIndex + HEADER_BITS] = EncodingFormat.cullFace(data[baseIndex + HEADER_BITS], face); - nominalFace(face); + this.data[this.baseIndex + HEADER_BITS] = EncodingFormat.cullFace(this.data[this.baseIndex + HEADER_BITS], face); + this.nominalFace(face); return this; } @Override public final MutableQuadViewImpl nominalFace(@Nullable Direction face) { - nominalFace = face; + this.nominalFace = face; return this; } @@ -172,19 +172,19 @@ public final MutableQuadViewImpl material(RenderMaterial material) { material = SodiumRenderer.STANDARD_MATERIAL; } - data[baseIndex + HEADER_BITS] = EncodingFormat.material(data[baseIndex + HEADER_BITS], (RenderMaterialImpl) material); + this.data[this.baseIndex + HEADER_BITS] = EncodingFormat.material(this.data[this.baseIndex + HEADER_BITS], (RenderMaterialImpl) material); return this; } @Override public final MutableQuadViewImpl colorIndex(int colorIndex) { - data[baseIndex + HEADER_COLOR_INDEX] = colorIndex; + this.data[this.baseIndex + HEADER_COLOR_INDEX] = colorIndex; return this; } @Override public final MutableQuadViewImpl tag(int tag) { - data[baseIndex + HEADER_TAG] = tag; + this.data[this.baseIndex + HEADER_TAG] = tag; return this; } @@ -193,15 +193,15 @@ public MutableQuadViewImpl copyFrom(QuadView quad) { final QuadViewImpl q = (QuadViewImpl) quad; q.computeGeometry(); - System.arraycopy(q.data, q.baseIndex, data, baseIndex, EncodingFormat.TOTAL_STRIDE); - faceNormal.set(q.faceNormal); - nominalFace = q.nominalFace; - isGeometryInvalid = false; + System.arraycopy(q.data, q.baseIndex, this.data, this.baseIndex, EncodingFormat.TOTAL_STRIDE); + this.faceNormal.set(q.faceNormal); + this.nominalFace = q.nominalFace; + this.isGeometryInvalid = false; if (quad instanceof MutableQuadViewImpl mutableQuad) { - cachedSprite(mutableQuad.cachedSprite()); + this.cachedSprite(mutableQuad.cachedSprite()); } else { - cachedSprite(null); + this.cachedSprite(null); } return this; @@ -213,30 +213,30 @@ public MutableQuadViewImpl copyFrom(QuadView quad) { * Only use this if you are also setting the geometry and sprite. */ private void fromVanillaInternal(int[] quadData, int startIndex) { - System.arraycopy(quadData, startIndex, data, baseIndex + HEADER_STRIDE, QuadView.VANILLA_QUAD_STRIDE); + System.arraycopy(quadData, startIndex, this.data, this.baseIndex + HEADER_STRIDE, QuadView.VANILLA_QUAD_STRIDE); - int colorIndex = baseIndex + VERTEX_COLOR; + int colorIndex = this.baseIndex + VERTEX_COLOR; for (int i = 0; i < 4; i++) { - data[colorIndex] = ColorHelper.fromVanillaColor(data[colorIndex]); + this.data[colorIndex] = ColorHelper.fromVanillaColor(this.data[colorIndex]); colorIndex += VERTEX_STRIDE; } } @Override public final MutableQuadViewImpl fromVanilla(int[] quadData, int startIndex) { - fromVanillaInternal(quadData, startIndex); - isGeometryInvalid = true; - cachedSprite(null); + this.fromVanillaInternal(quadData, startIndex); + this.isGeometryInvalid = true; + this.cachedSprite(null); return this; } @Override public final MutableQuadViewImpl fromVanilla(BakedQuad quad, RenderMaterial material, @Nullable Direction cullFace) { - fromVanillaInternal(quad.getVertices(), 0); - data[baseIndex + HEADER_BITS] = EncodingFormat.cullFace(0, cullFace); - nominalFace(quad.getDirection()); - colorIndex(quad.getTintIndex()); + this.fromVanillaInternal(quad.getVertices(), 0); + this.data[this.baseIndex + HEADER_BITS] = EncodingFormat.cullFace(0, cullFace); + this.nominalFace(quad.getDirection()); + this.colorIndex(quad.getTintIndex()); // TODO: Is this the same as hasShade? if (!((BakedQuadView) quad).hasShade()) { @@ -247,19 +247,19 @@ public final MutableQuadViewImpl fromVanilla(BakedQuad quad, RenderMaterial mate material = RenderMaterialImpl.setAmbientOcclusion((RenderMaterialImpl) material, TriState.FALSE); } - material(material); - tag(0); + this.material(material); + this.tag(0); // Copy geometry cached inside the quad BakedQuadView bakedView = (BakedQuadView) quad; - NormI8.unpack(bakedView.getFaceNormal(), faceNormal); - data[baseIndex + HEADER_FACE_NORMAL] = bakedView.getFaceNormal(); - int headerBits = EncodingFormat.lightFace(data[baseIndex + HEADER_BITS], bakedView.getLightFace()); + NormI8.unpack(bakedView.getFaceNormal(), this.faceNormal); + this.data[this.baseIndex + HEADER_FACE_NORMAL] = bakedView.getFaceNormal(); + int headerBits = EncodingFormat.lightFace(this.data[this.baseIndex + HEADER_BITS], bakedView.getLightFace()); headerBits = EncodingFormat.normalFace(headerBits, bakedView.getNormalFace()); - data[baseIndex + HEADER_BITS] = EncodingFormat.geometryFlags(headerBits, bakedView.getFlags()); - isGeometryInvalid = false; + this.data[this.baseIndex + HEADER_BITS] = EncodingFormat.geometryFlags(headerBits, bakedView.getFlags()); + this.isGeometryInvalid = false; - cachedSprite(quad.getSprite()); + this.cachedSprite(quad.getSprite()); return this; } @@ -271,8 +271,8 @@ public final MutableQuadViewImpl fromVanilla(BakedQuad quad, RenderMaterial mate @Override public final MutableQuadViewImpl emit() { - emitDirectly(); - clear(); + this.emitDirectly(); + this.clear(); return this; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java index f3fcc57af7..21daee8872 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/mesh/QuadViewImpl.java @@ -57,59 +57,59 @@ public class QuadViewImpl implements QuadView, ModelQuadView { * The encoded data must contain valid computed geometry. */ public void load() { - isGeometryInvalid = false; - nominalFace = lightFace(); - NormI8.unpack(packedFaceNormal(), faceNormal); + this.isGeometryInvalid = false; + this.nominalFace = this.lightFace(); + NormI8.unpack(this.packedFaceNormal(), this.faceNormal); } protected void computeGeometry() { - if (isGeometryInvalid) { - isGeometryInvalid = false; + if (this.isGeometryInvalid) { + this.isGeometryInvalid = false; - NormalHelper.computeFaceNormal(faceNormal, this); - int packedFaceNormal = NormI8.pack(faceNormal); - data[baseIndex + HEADER_FACE_NORMAL] = packedFaceNormal; + NormalHelper.computeFaceNormal(this.faceNormal, this); + int packedFaceNormal = NormI8.pack(this.faceNormal); + this.data[this.baseIndex + HEADER_FACE_NORMAL] = packedFaceNormal; // depends on face normal Direction lightFace = GeometryHelper.lightFace(this); - data[baseIndex + HEADER_BITS] = EncodingFormat.lightFace(data[baseIndex + HEADER_BITS], lightFace); + this.data[this.baseIndex + HEADER_BITS] = EncodingFormat.lightFace(this.data[this.baseIndex + HEADER_BITS], lightFace); // depends on face normal - data[baseIndex + HEADER_BITS] = EncodingFormat.normalFace(data[baseIndex + HEADER_BITS], ModelQuadFacing.fromPackedNormal(packedFaceNormal)); + this.data[this.baseIndex + HEADER_BITS] = EncodingFormat.normalFace(this.data[this.baseIndex + HEADER_BITS], ModelQuadFacing.fromPackedNormal(packedFaceNormal)); // depends on light face - data[baseIndex + HEADER_BITS] = EncodingFormat.geometryFlags(data[baseIndex + HEADER_BITS], ModelQuadFlags.getQuadFlags(this, lightFace)); + this.data[this.baseIndex + HEADER_BITS] = EncodingFormat.geometryFlags(this.data[this.baseIndex + HEADER_BITS], ModelQuadFlags.getQuadFlags(this, lightFace)); } } /** gets flags used for lighting - lazily computed via {@link ModelQuadFlags#getQuadFlags}. */ public int geometryFlags() { - computeGeometry(); - return EncodingFormat.geometryFlags(data[baseIndex + HEADER_BITS]); + this.computeGeometry(); + return EncodingFormat.geometryFlags(this.data[this.baseIndex + HEADER_BITS]); } public boolean hasShade() { - return !material().disableDiffuse(); + return !this.material().disableDiffuse(); } @Override public float x(int vertexIndex) { - return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X]); + return Float.intBitsToFloat(this.data[this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X]); } @Override public float y(int vertexIndex) { - return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_Y]); + return Float.intBitsToFloat(this.data[this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_Y]); } @Override public float z(int vertexIndex) { - return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_Z]); + return Float.intBitsToFloat(this.data[this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_Z]); } @Override public float posByIndex(int vertexIndex, int coordinateIndex) { - return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X + coordinateIndex]); + return Float.intBitsToFloat(this.data[this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X + coordinateIndex]); } @Override @@ -118,24 +118,24 @@ public Vector3f copyPos(int vertexIndex, @Nullable Vector3f target) { target = new Vector3f(); } - final int index = baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X; - target.set(Float.intBitsToFloat(data[index]), Float.intBitsToFloat(data[index + 1]), Float.intBitsToFloat(data[index + 2])); + final int index = this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_X; + target.set(Float.intBitsToFloat(this.data[index]), Float.intBitsToFloat(this.data[index + 1]), Float.intBitsToFloat(this.data[index + 2])); return target; } @Override public int color(int vertexIndex) { - return data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_COLOR]; + return this.data[this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_COLOR]; } @Override public float u(int vertexIndex) { - return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_U]); + return Float.intBitsToFloat(this.data[this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_U]); } @Override public float v(int vertexIndex) { - return Float.intBitsToFloat(data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_V]); + return Float.intBitsToFloat(this.data[this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_V]); } @Override @@ -144,70 +144,70 @@ public Vector2f copyUv(int vertexIndex, @Nullable Vector2f target) { target = new Vector2f(); } - final int index = baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_U; - target.set(Float.intBitsToFloat(data[index]), Float.intBitsToFloat(data[index + 1])); + final int index = this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_U; + target.set(Float.intBitsToFloat(this.data[index]), Float.intBitsToFloat(this.data[index + 1])); return target; } @Override public int lightmap(int vertexIndex) { - return data[baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_LIGHTMAP]; + return this.data[this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_LIGHTMAP]; } public int normalFlags() { - return EncodingFormat.normalFlags(data[baseIndex + HEADER_BITS]); + return EncodingFormat.normalFlags(this.data[this.baseIndex + HEADER_BITS]); } @Override public boolean hasNormal(int vertexIndex) { - return (normalFlags() & (1 << vertexIndex)) != 0; + return (this.normalFlags() & (1 << vertexIndex)) != 0; } /** True if any vertex normal has been set. */ public boolean hasVertexNormals() { - return normalFlags() != 0; + return this.normalFlags() != 0; } /** True if all vertex normals have been set. */ public boolean hasAllVertexNormals() { - return (normalFlags() & 0b1111) == 0b1111; + return (this.normalFlags() & 0b1111) == 0b1111; } protected final int normalIndex(int vertexIndex) { - return baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_NORMAL; + return this.baseIndex + vertexIndex * VERTEX_STRIDE + VERTEX_NORMAL; } /** * This method will only return a meaningful value if {@link #hasNormal} returns {@code true} for the same vertex index. */ public int packedNormal(int vertexIndex) { - return data[normalIndex(vertexIndex)]; + return this.data[this.normalIndex(vertexIndex)]; } @Override public float normalX(int vertexIndex) { - return hasNormal(vertexIndex) ? NormI8.unpackX(data[normalIndex(vertexIndex)]) : Float.NaN; + return this.hasNormal(vertexIndex) ? NormI8.unpackX(this.data[this.normalIndex(vertexIndex)]) : Float.NaN; } @Override public float normalY(int vertexIndex) { - return hasNormal(vertexIndex) ? NormI8.unpackY(data[normalIndex(vertexIndex)]) : Float.NaN; + return this.hasNormal(vertexIndex) ? NormI8.unpackY(this.data[this.normalIndex(vertexIndex)]) : Float.NaN; } @Override public float normalZ(int vertexIndex) { - return hasNormal(vertexIndex) ? NormI8.unpackZ(data[normalIndex(vertexIndex)]) : Float.NaN; + return this.hasNormal(vertexIndex) ? NormI8.unpackZ(this.data[this.normalIndex(vertexIndex)]) : Float.NaN; } @Override @Nullable public Vector3f copyNormal(int vertexIndex, @Nullable Vector3f target) { - if (hasNormal(vertexIndex)) { + if (this.hasNormal(vertexIndex)) { if (target == null) { target = new Vector3f(); } - final int normal = data[normalIndex(vertexIndex)]; + final int normal = this.data[this.normalIndex(vertexIndex)]; NormI8.unpack(normal, target); return target; } else { @@ -218,56 +218,56 @@ public Vector3f copyNormal(int vertexIndex, @Nullable Vector3f target) { @Override @Nullable public final Direction cullFace() { - return EncodingFormat.cullFace(data[baseIndex + HEADER_BITS]); + return EncodingFormat.cullFace(this.data[this.baseIndex + HEADER_BITS]); } @Override @NotNull public final Direction lightFace() { - computeGeometry(); - return EncodingFormat.lightFace(data[baseIndex + HEADER_BITS]); + this.computeGeometry(); + return EncodingFormat.lightFace(this.data[this.baseIndex + HEADER_BITS]); } public final ModelQuadFacing normalFace() { - computeGeometry(); - return EncodingFormat.normalFace(data[baseIndex + HEADER_BITS]); + this.computeGeometry(); + return EncodingFormat.normalFace(this.data[this.baseIndex + HEADER_BITS]); } @Override @Nullable public final Direction nominalFace() { - return nominalFace; + return this.nominalFace; } public final int packedFaceNormal() { - computeGeometry(); - return data[baseIndex + HEADER_FACE_NORMAL]; + this.computeGeometry(); + return this.data[this.baseIndex + HEADER_FACE_NORMAL]; } @Override public final Vector3f faceNormal() { - computeGeometry(); - return faceNormal; + this.computeGeometry(); + return this.faceNormal; } @Override public final RenderMaterialImpl material() { - return EncodingFormat.material(data[baseIndex + HEADER_BITS]); + return EncodingFormat.material(this.data[this.baseIndex + HEADER_BITS]); } @Override public final int colorIndex() { - return data[baseIndex + HEADER_COLOR_INDEX]; + return this.data[this.baseIndex + HEADER_COLOR_INDEX]; } @Override public final int tag() { - return data[baseIndex + HEADER_TAG]; + return this.data[this.baseIndex + HEADER_TAG]; } @Override public final void toVanilla(int[] target, int targetIndex) { - System.arraycopy(data, baseIndex + HEADER_STRIDE, target, targetIndex, QUAD_STRIDE); + System.arraycopy(this.data, this.baseIndex + HEADER_STRIDE, target, targetIndex, QUAD_STRIDE); // The color is the fourth integer in each vertex. // EncodingFormat.VERTEX_COLOR is not used because it also @@ -284,52 +284,52 @@ public final void toVanilla(int[] target, int targetIndex) { @Override public float getX(int idx) { - return x(idx); + return this.x(idx); } @Override public float getY(int idx) { - return y(idx); + return this.y(idx); } @Override public float getZ(int idx) { - return z(idx); + return this.z(idx); } @Override public int getColor(int idx) { - return color(idx); + return this.color(idx); } @Override public float getTexU(int idx) { - return u(idx); + return this.u(idx); } @Override public float getTexV(int idx) { - return v(idx); + return this.v(idx); } @Override public int getVertexNormal(int idx) { - return data[normalIndex(idx)]; + return this.data[this.normalIndex(idx)]; } @Override public int getFaceNormal() { - return packedFaceNormal(); + return this.packedFaceNormal(); } @Override public int getLight(int idx) { - return lightmap(idx); + return this.lightmap(idx); } @Override public int getColorIndex() { - return material().disableColorIndex() ? -1 : colorIndex(); + return this.material().disableColorIndex() ? -1 : this.colorIndex(); } @Override @@ -339,11 +339,11 @@ public TextureAtlasSprite getSprite() { @Override public Direction getLightFace() { - return lightFace(); + return this.lightFace(); } @Override public int getFlags() { - return geometryFlags(); + return this.geometryFlags(); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractBlockRenderContext.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractBlockRenderContext.java index f736dc8435..90a4448829 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractBlockRenderContext.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractBlockRenderContext.java @@ -63,16 +63,16 @@ public abstract class AbstractBlockRenderContext extends AbstractRenderContext { private final MutableQuadViewImpl editorQuad = new MutableQuadViewImpl() { { - data = new int[EncodingFormat.TOTAL_STRIDE]; - clear(); + this.data = new int[EncodingFormat.TOTAL_STRIDE]; + this.clear(); } @Override public void emitDirectly() { - if (type == null) { + if (AbstractBlockRenderContext.this.type == null) { throw new IllegalStateException("No render type is set but an FRAPI object was asked to render!"); } - renderQuad(this); + AbstractBlockRenderContext.this.renderQuad(this); } }; @@ -127,8 +127,8 @@ public void emitDirectly() { protected RandomSource random; protected long randomSeed; protected final Supplier randomSupplier = () -> { - random.setSeed(randomSeed); - return random; + this.random.setSeed(this.randomSeed); + return this.random; }; /** @@ -186,7 +186,6 @@ private static AmbientOcclusionMode applyAOOverride(AmbientOcclusionMode mode, T }; } - @SuppressWarnings("removal") @Deprecated @Override public BakedModelConsumer bakedModelConsumer() { @@ -223,7 +222,7 @@ protected void prepareCulling(boolean enableCulling) { protected void prepareAoInfo(boolean modelAo) { this.useAmbientOcclusion = Minecraft.useAmbientOcclusion(); // Ignore the incorrect IDEA warning here. - this.defaultLightMode = this.useAmbientOcclusion && modelAo && PlatformBlockAccess.getInstance().getLightEmission(state, level, pos) == 0 ? LightMode.SMOOTH : LightMode.FLAT; + this.defaultLightMode = this.useAmbientOcclusion && modelAo && PlatformBlockAccess.getInstance().getLightEmission(this.state, this.level, this.pos) == 0 ? LightMode.SMOOTH : LightMode.FLAT; } protected void shadeQuad(MutableQuadViewImpl quad, LightMode lightMode, boolean emissive, ShadeMode shadeMode) { @@ -261,16 +260,16 @@ public void bufferDefaultModel(BakedModel model, @Nullable BlockState state) { RandomSource random = this.randomSupplier.get(); AmbientOcclusionMode ao = applyAOOverride( - PlatformBlockAccess.getInstance().usesAmbientOcclusion(model, state, currentData, type, slice, pos), + PlatformBlockAccess.getInstance().usesAmbientOcclusion(model, state, currentData, this.type, this.slice, this.pos), aoOverride); if (noTransform) { if (!this.isFaceCulled(cullFace)) { - final List quads = PlatformModelAccess.getInstance().getQuads(level, pos, model, state, cullFace, random, type, currentData); + final List quads = PlatformModelAccess.getInstance().getQuads(this.level, this.pos, model, state, cullFace, random, this.type, currentData); final int count = quads.size(); for (int j = 0; j < count; j++) { final BakedQuad q = quads.get(j); - editorQuad.fromVanilla(q, (type == RenderType.tripwire() || type == RenderType.translucent()) ? TRANSLUCENT_MATERIAL : STANDARD_MATERIALS[ao.ordinal()], cullFace); + editorQuad.fromVanilla(q, (this.type == RenderType.tripwire() || this.type == RenderType.translucent()) ? TRANSLUCENT_MATERIAL : STANDARD_MATERIALS[ao.ordinal()], cullFace); // Call processQuad instead of emit for efficiency // (avoid unnecessarily clearing data, trying to apply transforms, and performing cull check again) @@ -278,12 +277,12 @@ public void bufferDefaultModel(BakedModel model, @Nullable BlockState state) { } } } else { - final List quads = PlatformModelAccess.getInstance().getQuads(level, pos, model, state, cullFace, random, type, currentData); + final List quads = PlatformModelAccess.getInstance().getQuads(this.level, this.pos, model, state, cullFace, random, this.type, currentData); final int count = quads.size(); for (int j = 0; j < count; j++) { final BakedQuad q = quads.get(j); - editorQuad.fromVanilla(q, (type == RenderType.tripwire() || type == RenderType.translucent()) ? TRANSLUCENT_MATERIAL : STANDARD_MATERIALS[ao.ordinal()], cullFace); + editorQuad.fromVanilla(q, (this.type == RenderType.tripwire() || this.type == RenderType.translucent()) ? TRANSLUCENT_MATERIAL : STANDARD_MATERIALS[ao.ordinal()], cullFace); // Call renderQuad instead of emit for efficiency // (avoid unnecessarily clearing data) this.renderQuad(editorQuad); @@ -294,12 +293,11 @@ public void bufferDefaultModel(BakedModel model, @Nullable BlockState state) { editorQuad.clear(); } - @SuppressWarnings("removal") @Deprecated private class BakedModelConsumerImpl implements BakedModelConsumer { @Override public void accept(BakedModel model) { - accept(model, AbstractBlockRenderContext.this.state); + this.accept(model, AbstractBlockRenderContext.this.state); } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractRenderContext.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractRenderContext.java index 6074c497cf..1473970372 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractRenderContext.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/AbstractRenderContext.java @@ -29,10 +29,10 @@ public abstract class AbstractRenderContext implements RenderContext { private QuadTransform activeTransform = NO_TRANSFORM; private final ObjectArrayList transformStack = new ObjectArrayList<>(); private final QuadTransform stackTransform = q -> { - int i = transformStack.size() - 1; + int i = this.transformStack.size() - 1; while (i >= 0) { - if (!transformStack.get(i--).transform(q)) { + if (!this.transformStack.get(i--).transform(q)) { return false; } } @@ -44,12 +44,12 @@ public abstract class AbstractRenderContext implements RenderContext { private final Consumer meshConsumer = mesh -> mesh.outputTo(getEmitter()); protected final boolean transform(MutableQuadView q) { - return activeTransform.transform(q); + return this.activeTransform.transform(q); } @Override public boolean hasTransform() { - return activeTransform != NO_TRANSFORM; + return this.activeTransform != NO_TRANSFORM; } @Override @@ -58,23 +58,23 @@ public void pushTransform(QuadTransform transform) { throw new NullPointerException("Renderer received null QuadTransform."); } - transformStack.push(transform); + this.transformStack.push(transform); - if (transformStack.size() == 1) { - activeTransform = transform; - } else if (transformStack.size() == 2) { - activeTransform = stackTransform; + if (this.transformStack.size() == 1) { + this.activeTransform = transform; + } else if (this.transformStack.size() == 2) { + this.activeTransform = this.stackTransform; } } @Override public void popTransform() { - transformStack.pop(); + this.transformStack.pop(); - if (transformStack.isEmpty()) { - activeTransform = NO_TRANSFORM; - } else if (transformStack.size() == 1) { - activeTransform = transformStack.get(0); + if (this.transformStack.isEmpty()) { + this.activeTransform = NO_TRANSFORM; + } else if (this.transformStack.size() == 1) { + this.activeTransform = this.transformStack.get(0); } } @@ -82,6 +82,6 @@ public void popTransform() { @Deprecated @Override public Consumer meshConsumer() { - return meshConsumer; + return this.meshConsumer; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java index f2fe41f95b..cd82833739 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/ItemRenderContext.java @@ -60,13 +60,13 @@ public class ItemRenderContext extends AbstractRenderContext { private final MutableQuadViewImpl editorQuad = new MutableQuadViewImpl() { { - data = new int[EncodingFormat.TOTAL_STRIDE]; - clear(); + this.data = new int[EncodingFormat.TOTAL_STRIDE]; + this.clear(); } @Override public void emitDirectly() { - renderQuad(this); + ItemRenderContext.this.renderQuad(this); } }; @@ -78,8 +78,8 @@ public void emitDirectly() { private final RandomSource random = new SingleThreadedRandomSource(ITEM_RANDOM_SEED); private final Supplier randomSupplier = () -> { - random.setSeed(ITEM_RANDOM_SEED); - return random; + this.random.setSeed(ITEM_RANDOM_SEED); + return this.random; }; private ItemStack itemStack; @@ -111,8 +111,8 @@ public ItemRenderContext(ItemColors colorMap, VanillaModelBufferer vanillaBuffer @Override public QuadEmitter getEmitter() { - editorQuad.clear(); - return editorQuad; + this.editorQuad.clear(); + return this.editorQuad; } @Override @@ -122,85 +122,84 @@ public boolean isFaceCulled(@Nullable Direction face) { @Override public ItemDisplayContext itemTransformationMode() { - return transformMode; + return this.transformMode; } - @SuppressWarnings("removal") @Deprecated @Override public BakedModelConsumer bakedModelConsumer() { - return vanillaModelConsumer; + return this.vanillaModelConsumer; } public void renderModel(ItemStack itemStack, ItemDisplayContext transformMode, boolean invert, PoseStack poseStack, MultiBufferSource bufferSource, int lightmap, int overlay, BakedModel model) { this.itemStack = itemStack; this.transformMode = transformMode; this.poseStack = poseStack; - matPosition = poseStack.last().pose(); - trustedNormals = poseStack.last().trustedNormals; - matNormal = poseStack.last().normal(); + this.matPosition = poseStack.last().pose(); + this.trustedNormals = poseStack.last().trustedNormals; + this.matNormal = poseStack.last().normal(); this.bufferSource = bufferSource; this.lightmap = lightmap; this.overlay = overlay; - computeOutputInfo(); + this.computeOutputInfo(); - ((FabricBakedModel) model).emitItemQuads(itemStack, randomSupplier, this); + ((FabricBakedModel) model).emitItemQuads(itemStack, this.randomSupplier, this); this.itemStack = null; this.poseStack = null; this.bufferSource = null; - dynamicDisplayGlintEntry = null; - translucentVertexConsumer = null; - cutoutVertexConsumer = null; - translucentGlintVertexConsumer = null; - cutoutGlintVertexConsumer = null; - defaultVertexConsumer = null; + this.dynamicDisplayGlintEntry = null; + this.translucentVertexConsumer = null; + this.cutoutVertexConsumer = null; + this.translucentGlintVertexConsumer = null; + this.cutoutGlintVertexConsumer = null; + this.defaultVertexConsumer = null; } private void computeOutputInfo() { - isDefaultTranslucent = true; - isTranslucentDirect = true; + this.isDefaultTranslucent = true; + this.isTranslucentDirect = true; - Item item = itemStack.getItem(); + Item item = this.itemStack.getItem(); if (item instanceof BlockItem blockItem) { BlockState state = blockItem.getBlock().defaultBlockState(); RenderType renderType = ItemBlockRenderTypes.getChunkRenderType(state); if (renderType != RenderType.translucent()) { - isDefaultTranslucent = false; + this.isDefaultTranslucent = false; } - if (transformMode != ItemDisplayContext.GUI && !transformMode.firstPerson()) { - isTranslucentDirect = false; + if (this.transformMode != ItemDisplayContext.GUI && !this.transformMode.firstPerson()) { + this.isTranslucentDirect = false; } } - isDefaultGlint = itemStack.hasFoil(); - isGlintDynamicDisplay = ItemRendererAccessor.sodium$hasAnimatedTexture(itemStack); + this.isDefaultGlint = this.itemStack.hasFoil(); + this.isGlintDynamicDisplay = ItemRendererAccessor.sodium$hasAnimatedTexture(this.itemStack); - defaultVertexConsumer = getVertexConsumer(BlendMode.DEFAULT, TriState.DEFAULT); + this.defaultVertexConsumer = this.getVertexConsumer(BlendMode.DEFAULT, TriState.DEFAULT); } private void renderQuad(MutableQuadViewImpl quad) { - if (!transform(quad)) { + if (!this.transform(quad)) { return; } final RenderMaterial mat = quad.material(); final int colorIndex = mat.disableColorIndex() ? -1 : quad.colorIndex(); final boolean emissive = mat.emissive(); - final VertexConsumer vertexConsumer = getVertexConsumer(mat.blendMode(), mat.glint()); + final VertexConsumer vertexConsumer = this.getVertexConsumer(mat.blendMode(), mat.glint()); - colorizeQuad(quad, colorIndex); - shadeQuad(quad, emissive); - bufferQuad(quad, vertexConsumer); + this.colorizeQuad(quad, colorIndex); + this.shadeQuad(quad, emissive); + this.bufferQuad(quad, vertexConsumer); } private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) { if (colorIndex != -1) { - final int itemColor = colorMap.getColor(itemStack, colorIndex); + final int itemColor = this.colorMap.getColor(this.itemStack, colorIndex); for (int i = 0; i < 4; i++) { quad.color(i, ColorMixer.mulComponentWise(itemColor, quad.color(i))); @@ -223,7 +222,7 @@ private void shadeQuad(MutableQuadViewImpl quad, boolean emissive) { } private void bufferQuad(MutableQuadViewImpl quad, VertexConsumer vertexConsumer) { - QuadEncoder.writeQuadVertices(quad, vertexConsumer, overlay, matPosition, trustedNormals, matNormal); + QuadEncoder.writeQuadVertices(quad, vertexConsumer, this.overlay, this.matPosition, this.trustedNormals, this.matNormal); var sprite = quad.sprite(SpriteFinderCache.forBlockAtlas()); if (sprite != null) { SpriteUtil.INSTANCE.markSpriteActive(sprite); @@ -240,103 +239,102 @@ private VertexConsumer getVertexConsumer(BlendMode blendMode, TriState glintMode boolean glint; if (blendMode == BlendMode.DEFAULT) { - translucent = isDefaultTranslucent; + translucent = this.isDefaultTranslucent; } else { translucent = blendMode == BlendMode.TRANSLUCENT; } if (glintMode == TriState.DEFAULT) { - glint = isDefaultGlint; + glint = this.isDefaultGlint; } else { glint = glintMode == TriState.TRUE; } if (translucent) { if (glint) { - if (translucentGlintVertexConsumer == null) { - translucentGlintVertexConsumer = createTranslucentVertexConsumer(true); + if (this.translucentGlintVertexConsumer == null) { + this.translucentGlintVertexConsumer = this.createTranslucentVertexConsumer(true); } - return translucentGlintVertexConsumer; + return this.translucentGlintVertexConsumer; } else { - if (translucentVertexConsumer == null) { - translucentVertexConsumer = createTranslucentVertexConsumer(false); + if (this.translucentVertexConsumer == null) { + this.translucentVertexConsumer = this.createTranslucentVertexConsumer(false); } - return translucentVertexConsumer; + return this.translucentVertexConsumer; } } else { if (glint) { - if (cutoutGlintVertexConsumer == null) { - cutoutGlintVertexConsumer = createCutoutVertexConsumer(true); + if (this.cutoutGlintVertexConsumer == null) { + this.cutoutGlintVertexConsumer = this.createCutoutVertexConsumer(true); } - return cutoutGlintVertexConsumer; + return this.cutoutGlintVertexConsumer; } else { - if (cutoutVertexConsumer == null) { - cutoutVertexConsumer = createCutoutVertexConsumer(false); + if (this.cutoutVertexConsumer == null) { + this.cutoutVertexConsumer = this.createCutoutVertexConsumer(false); } - return cutoutVertexConsumer; + return this.cutoutVertexConsumer; } } } private VertexConsumer createTranslucentVertexConsumer(boolean glint) { - if (glint && isGlintDynamicDisplay) { - return createDynamicDisplayGlintVertexConsumer(Minecraft.useShaderTransparency() && !isTranslucentDirect ? Sheets.translucentItemSheet() : Sheets.translucentCullBlockSheet()); + if (glint && this.isGlintDynamicDisplay) { + return this.createDynamicDisplayGlintVertexConsumer(Minecraft.useShaderTransparency() && !this.isTranslucentDirect ? Sheets.translucentItemSheet() : Sheets.translucentCullBlockSheet()); } - if (isTranslucentDirect) { - return ItemRenderer.getFoilBufferDirect(bufferSource, Sheets.translucentCullBlockSheet(), true, glint); + if (this.isTranslucentDirect) { + return ItemRenderer.getFoilBufferDirect(this.bufferSource, Sheets.translucentCullBlockSheet(), true, glint); } else if (Minecraft.useShaderTransparency()) { - return ItemRenderer.getFoilBuffer(bufferSource, Sheets.translucentItemSheet(), true, glint); + return ItemRenderer.getFoilBuffer(this.bufferSource, Sheets.translucentItemSheet(), true, glint); } else { - return ItemRenderer.getFoilBuffer(bufferSource, Sheets.translucentItemSheet(), true, glint); + return ItemRenderer.getFoilBuffer(this.bufferSource, Sheets.translucentItemSheet(), true, glint); } } private VertexConsumer createCutoutVertexConsumer(boolean glint) { - if (glint && isGlintDynamicDisplay) { - return createDynamicDisplayGlintVertexConsumer(Sheets.cutoutBlockSheet()); + if (glint && this.isGlintDynamicDisplay) { + return this.createDynamicDisplayGlintVertexConsumer(Sheets.cutoutBlockSheet()); } - return ItemRenderer.getFoilBufferDirect(bufferSource, Sheets.cutoutBlockSheet(), true, glint); + return ItemRenderer.getFoilBufferDirect(this.bufferSource, Sheets.cutoutBlockSheet(), true, glint); } private VertexConsumer createDynamicDisplayGlintVertexConsumer(RenderType type) { - if (dynamicDisplayGlintEntry == null) { - dynamicDisplayGlintEntry = poseStack.last().copy(); + if (this.dynamicDisplayGlintEntry == null) { + this.dynamicDisplayGlintEntry = this.poseStack.last().copy(); - if (transformMode == ItemDisplayContext.GUI) { - MatrixUtil.mulComponentWise(dynamicDisplayGlintEntry.pose(), 0.5F); - } else if (transformMode.firstPerson()) { - MatrixUtil.mulComponentWise(dynamicDisplayGlintEntry.pose(), 0.75F); + if (this.transformMode == ItemDisplayContext.GUI) { + MatrixUtil.mulComponentWise(this.dynamicDisplayGlintEntry.pose(), 0.5F); + } else if (this.transformMode.firstPerson()) { + MatrixUtil.mulComponentWise(this.dynamicDisplayGlintEntry.pose(), 0.75F); } } - return ItemRenderer.getCompassFoilBuffer(bufferSource, type, dynamicDisplayGlintEntry); + return ItemRenderer.getCompassFoilBuffer(this.bufferSource, type, this.dynamicDisplayGlintEntry); } public void bufferDefaultModel(BakedModel model, @Nullable BlockState state) { - if (hasTransform() || vanillaBufferer == null) { - VanillaModelEncoder.emitItemQuads(model, state, randomSupplier, ItemRenderContext.this); + if (this.hasTransform() || this.vanillaBufferer == null) { + VanillaModelEncoder.emitItemQuads(model, state, this.randomSupplier, this); } else { - vanillaBufferer.accept(model, itemStack, lightmap, overlay, poseStack, defaultVertexConsumer); + this.vanillaBufferer.accept(model, this.itemStack, this.lightmap, this.overlay, this.poseStack, this.defaultVertexConsumer); } } - @SuppressWarnings("removal") @Deprecated private class BakedModelConsumerImpl implements BakedModelConsumer { @Override public void accept(BakedModel model) { - accept(model, null); + this.accept(model, null); } @Override public void accept(BakedModel model, @Nullable BlockState state) { - bufferDefaultModel(model, state); + ItemRenderContext.this.bufferDefaultModel(model, state); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java index 3e9e938cce..9741cdadce 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/frapi/render/NonTerrainBlockRenderContext.java @@ -100,9 +100,9 @@ protected void processQuad(MutableQuadViewImpl quad) { } final boolean emissive = mat.emissive(); - colorizeQuad(quad, colorIndex); - shadeQuad(quad, lightMode, emissive, shadeMode); - bufferQuad(quad); + this.colorizeQuad(quad, colorIndex); + this.shadeQuad(quad, lightMode, emissive, shadeMode); + this.bufferQuad(quad); } private void colorizeQuad(MutableQuadViewImpl quad, int colorIndex) { @@ -127,7 +127,7 @@ protected void shadeQuad(MutableQuadViewImpl quad, LightMode lightMode, boolean } private void bufferQuad(MutableQuadViewImpl quad) { - QuadEncoder.writeQuadVertices(quad, vertexConsumer, overlay, matPosition, trustedNormals, matNormal); + QuadEncoder.writeQuadVertices(quad, this.vertexConsumer, this.overlay, this.matPosition, this.trustedNormals, this.matNormal); var sprite = quad.sprite(SpriteFinderCache.forBlockAtlas()); if (sprite != null) { SpriteUtil.INSTANCE.markSpriteActive(sprite); diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java index 21b886322b..196f2b10c0 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/render/viewport/frustum/SimpleFrustum.java @@ -38,53 +38,53 @@ public SimpleFrustum(FrustumIntersection frustumIntersection) { throw new RuntimeException("Failed to access planes field in FrustumIntersection", e); } - nxX = planes[0].x; - nxY = planes[0].y; - nxZ = planes[0].z; - pxX = planes[1].x; - pxY = planes[1].y; - pxZ = planes[1].z; - nyX = planes[2].x; - nyY = planes[2].y; - nyZ = planes[2].z; - pyX = planes[3].x; - pyY = planes[3].y; - pyZ = planes[3].z; - nzX = planes[4].x; - nzY = planes[4].y; - nzZ = planes[4].z; - pzX = planes[5].x; - pzY = planes[5].y; - pzZ = planes[5].z; + this.nxX = planes[0].x; + this.nxY = planes[0].y; + this.nxZ = planes[0].z; + this.pxX = planes[1].x; + this.pxY = planes[1].y; + this.pxZ = planes[1].z; + this.nyX = planes[2].x; + this.nyY = planes[2].y; + this.nyZ = planes[2].z; + this.pyX = planes[3].x; + this.pyY = planes[3].y; + this.pyZ = planes[3].z; + this.nzX = planes[4].x; + this.nzY = planes[4].y; + this.nzZ = planes[4].z; + this.pzX = planes[5].x; + this.pzY = planes[5].y; + this.pzZ = planes[5].z; final float size = Viewport.CHUNK_SECTION_PADDED_RADIUS; - negNxW = -(planes[0].w + nxX * (nxX < 0 ? -size : size) + - nxY * (nxY < 0 ? -size : size) + - nxZ * (nxZ < 0 ? -size : size)); - negPxW = -(planes[1].w + pxX * (pxX < 0 ? -size : size) + - pxY * (pxY < 0 ? -size : size) + - pxZ * (pxZ < 0 ? -size : size)); - negNyW = -(planes[2].w + nyX * (nyX < 0 ? -size : size) + - nyY * (nyY < 0 ? -size : size) + - nyZ * (nyZ < 0 ? -size : size)); - negPyW = -(planes[3].w + pyX * (pyX < 0 ? -size : size) + - pyY * (pyY < 0 ? -size : size) + - pyZ * (pyZ < 0 ? -size : size)); - negNzW = -(planes[4].w + nzX * (nzX < 0 ? -size : size) + - nzY * (nzY < 0 ? -size : size) + - nzZ * (nzZ < 0 ? -size : size)); - negPzW = -(planes[5].w + pzX * (pzX < 0 ? -size : size) + - pzY * (pzY < 0 ? -size : size) + - pzZ * (pzZ < 0 ? -size : size)); + this.negNxW = -(planes[0].w + this.nxX * (this.nxX < 0 ? -size : size) + + this.nxY * (this.nxY < 0 ? -size : size) + + this.nxZ * (this.nxZ < 0 ? -size : size)); + this.negPxW = -(planes[1].w + this.pxX * (this.pxX < 0 ? -size : size) + + this.pxY * (this.pxY < 0 ? -size : size) + + this.pxZ * (this.pxZ < 0 ? -size : size)); + this.negNyW = -(planes[2].w + this.nyX * (this.nyX < 0 ? -size : size) + + this.nyY * (this.nyY < 0 ? -size : size) + + this.nyZ * (this.nyZ < 0 ? -size : size)); + this.negPyW = -(planes[3].w + this.pyX * (this.pyX < 0 ? -size : size) + + this.pyY * (this.pyY < 0 ? -size : size) + + this.pyZ * (this.pyZ < 0 ? -size : size)); + this.negNzW = -(planes[4].w + this.nzX * (this.nzX < 0 ? -size : size) + + this.nzY * (this.nzY < 0 ? -size : size) + + this.nzZ * (this.nzZ < 0 ? -size : size)); + this.negPzW = -(planes[5].w + this.pzX * (this.pzX < 0 ? -size : size) + + this.pzY * (this.pzY < 0 ? -size : size) + + this.pzZ * (this.pzZ < 0 ? -size : size)); } public boolean testSection(float x, float y, float z) { // Skip far plane checks because it has been ensured by searchDistance and isWithinRenderDistance check in OcclusionCuller - return nxX * x + nxY * y + nxZ * z >= negNxW && - pxX * x + pxY * y + pxZ * z >= negPxW && - nyX * x + nyY * y + nyZ * z >= negNyW && - pyX * x + pyY * y + pyZ * z >= negPyW && - nzX * x + nzY * y + nzZ * z >= negNzW; + return this.nxX * x + this.nxY * y + this.nxZ * z >= this.negNxW && + this.pxX * x + this.pxY * y + this.pxZ * z >= this.negPxW && + this.nyX * x + this.nyY * y + this.nyZ * z >= this.negNyW && + this.pyX * x + this.pyY * y + this.pyZ * z >= this.negPyW && + this.nzX * x + this.nzY * y + this.nzZ * z >= this.negNzW; } public boolean testSectionExpanded(float floatOriginX, float floatOriginY, float floatOriginZ, float extend) { @@ -95,12 +95,12 @@ public boolean testSectionExpanded(float floatOriginX, float floatOriginY, float float minZ = floatOriginZ - extend; float maxZ = floatOriginZ + extend; - return nxX * (nxX < 0 ? minX : maxX) + nxY * (nxY < 0 ? minY : maxY) + nxZ * (nxZ < 0 ? minZ : maxZ) >= negNxW && - pxX * (pxX < 0 ? minX : maxX) + pxY * (pxY < 0 ? minY : maxY) + pxZ * (pxZ < 0 ? minZ : maxZ) >= negPxW && - nyX * (nyX < 0 ? minX : maxX) + nyY * (nyY < 0 ? minY : maxY) + nyZ * (nyZ < 0 ? minZ : maxZ) >= negNyW && - pyX * (pyX < 0 ? minX : maxX) + pyY * (pyY < 0 ? minY : maxY) + pyZ * (pyZ < 0 ? minZ : maxZ) >= negPyW && - nzX * (nzX < 0 ? minX : maxX) + nzY * (nzY < 0 ? minY : maxY) + nzZ * (nzZ < 0 ? minZ : maxZ) >= negNzW && - pzX * (pzX < 0 ? minX : maxX) + pzY * (pzY < 0 ? minY : maxY) + pzZ * (pzZ < 0 ? minZ : maxZ) >= negPzW; + return this.nxX * (this.nxX < 0 ? minX : maxX) + this.nxY * (this.nxY < 0 ? minY : maxY) + this.nxZ * (this.nxZ < 0 ? minZ : maxZ) >= this.negNxW && + this.pxX * (this.pxX < 0 ? minX : maxX) + this.pxY * (this.pxY < 0 ? minY : maxY) + this.pxZ * (this.pxZ < 0 ? minZ : maxZ) >= this.negPxW && + this.nyX * (this.nyX < 0 ? minX : maxX) + this.nyY * (this.nyY < 0 ? minY : maxY) + this.nyZ * (this.nyZ < 0 ? minZ : maxZ) >= this.negNyW && + this.pyX * (this.pyX < 0 ? minX : maxX) + this.pyY * (this.pyY < 0 ? minY : maxY) + this.pyZ * (this.pyZ < 0 ? minZ : maxZ) >= this.negPyW && + this.nzX * (this.nzX < 0 ? minX : maxX) + this.nzY * (this.nzY < 0 ? minY : maxY) + this.nzZ * (this.nzZ < 0 ? minZ : maxZ) >= this.negNzW && + this.pzX * (this.pzX < 0 ? minX : maxX) + this.pzY * (this.pzY < 0 ? minY : maxY) + this.pzZ * (this.pzZ < 0 ? minZ : maxZ) >= this.negPzW; } @Override diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/services/SodiumModelDataContainer.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/services/SodiumModelDataContainer.java index acd20d8f67..a3c592a40b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/services/SodiumModelDataContainer.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/services/SodiumModelDataContainer.java @@ -3,8 +3,6 @@ import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import net.minecraft.core.BlockPos; -import java.util.Map; - /** * A container that holds the platform's model data. */ @@ -18,10 +16,10 @@ public SodiumModelDataContainer(Long2ObjectMap modelDataMap) { } public SodiumModelData getModelData(BlockPos pos) { - return modelDataMap.getOrDefault(pos.asLong(), SodiumModelData.EMPTY); + return this.modelDataMap.getOrDefault(pos.asLong(), SodiumModelData.EMPTY); } public boolean isEmpty() { - return isEmpty; + return this.isEmpty; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/DoubleInterval.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/DoubleInterval.java index e5c60f3852..1d04236853 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/DoubleInterval.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/DoubleInterval.java @@ -92,14 +92,14 @@ protected Interval create() { */ @Override public boolean isEmpty() { - if (getStart() != null && getStart().isNaN()) + if (this.getStart() != null && this.getStart().isNaN()) return true; - if (getEnd() != null && getEnd().isNaN()) + if (this.getEnd() != null && this.getEnd().isNaN()) return true; - if (getStart() != null && getEnd() != null) { - if (getStart() == Double.POSITIVE_INFINITY && getEnd() == Double.POSITIVE_INFINITY) + if (this.getStart() != null && this.getEnd() != null) { + if (this.getStart() == Double.POSITIVE_INFINITY && this.getEnd() == Double.POSITIVE_INFINITY) return true; - if (getStart() == Double.NEGATIVE_INFINITY && getEnd() == Double.NEGATIVE_INFINITY) + if (this.getStart() == Double.NEGATIVE_INFINITY && this.getEnd() == Double.NEGATIVE_INFINITY) return true; } return super.isEmpty(); @@ -123,24 +123,24 @@ public boolean isEmpty() { */ @Override public Double getMidpoint() { - if (isEmpty()) + if (this.isEmpty()) return null; // Handle null values - if (getStart() == null && getEnd() == null) + if (this.getStart() == null && this.getEnd() == null) return 0.0; - if (getStart() == null) - return getEnd() - OFFSET; - if (getEnd() == null) - return getStart() + OFFSET; + if (this.getStart() == null) + return this.getEnd() - OFFSET; + if (this.getEnd() == null) + return this.getStart() + OFFSET; // Now we are sure there are no more null values involved - if (getStart() == Double.NEGATIVE_INFINITY && getEnd() == Double.POSITIVE_INFINITY) + if (this.getStart() == Double.NEGATIVE_INFINITY && this.getEnd() == Double.POSITIVE_INFINITY) return 0.0; - if (getStart() == Double.NEGATIVE_INFINITY) - return getEnd() - OFFSET; - if (getEnd() == Double.POSITIVE_INFINITY) - return getStart() + OFFSET; - return getStart() + (getEnd() - getStart()) / 2; + if (this.getStart() == Double.NEGATIVE_INFINITY) + return this.getEnd() - OFFSET; + if (this.getEnd() == Double.POSITIVE_INFINITY) + return this.getStart() + OFFSET; + return this.getStart() + (this.getEnd() - this.getStart()) / 2; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/Interval.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/Interval.java index 3c677ed151..3ca8ed67cd 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/Interval.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/Interval.java @@ -244,7 +244,7 @@ public boolean isEmpty() { * @return The newly created interval. */ protected Interval create(T start, boolean isStartInclusive, T end, boolean isEndInclusive) { - Interval interval = create(); + Interval interval = this.create(); interval.start = start; interval.isStartInclusive = isStartInclusive; interval.end = end; @@ -287,7 +287,7 @@ public boolean isEndInclusive() { * @return {@code true}, if the current interval contains the {@code query} point or false otherwise. */ public boolean contains(T query) { - if (isEmpty() || query == null) { + if (this.isEmpty() || query == null) { return false; } @@ -308,7 +308,7 @@ public boolean contains(T query) { * @return The intersection of the current interval wih the {@code other} interval. */ public Interval getIntersection(Interval other) { - if (other == null || isEmpty() || other.isEmpty()) + if (other == null || this.isEmpty() || other.isEmpty()) return null; // Make sure that the one with the smaller starting point gets intersected with the other. // If necessary, swap the intervals @@ -352,7 +352,7 @@ public Interval getIntersection(Interval other) { isNewEndInclusive = other.isEndInclusive; } } - Interval intersection = create(newStart, isNewStartInclusive, newEnd, isNewEndInclusive); + Interval intersection = this.create(newStart, isNewStartInclusive, newEnd, isNewEndInclusive); return intersection.isEmpty() ? null : intersection; } @@ -368,7 +368,7 @@ public Interval getIntersection(Interval other) { public boolean intersects(Interval query) { if (query == null) return false; - Interval intersection = getIntersection(query); + Interval intersection = this.getIntersection(query); return intersection != null; } @@ -391,7 +391,7 @@ public boolean isRightOf(T point, boolean inclusive) { int compare = point.compareTo(this.start); if (compare != 0) return compare < 0; - return !isStartInclusive() || !inclusive; + return !this.isStartInclusive() || !inclusive; } /** @@ -406,7 +406,7 @@ public boolean isRightOf(T point, boolean inclusive) { * interval, or {@code false} instead. */ public boolean isRightOf(T point) { - return isRightOf(point, true); + return this.isRightOf(point, true); } /** @@ -424,7 +424,7 @@ public boolean isRightOf(T point) { public boolean isRightOf(Interval other) { if (other == null || other.isEmpty()) return false; - return isRightOf(other.end, other.isEndInclusive()); + return this.isRightOf(other.end, other.isEndInclusive()); } /** @@ -446,7 +446,7 @@ public boolean isLeftOf(T point, boolean inclusive) { int compare = point.compareTo(this.end); if (compare != 0) return compare > 0; - return !isEndInclusive() || !inclusive; + return !this.isEndInclusive() || !inclusive; } /** @@ -461,7 +461,7 @@ public boolean isLeftOf(T point, boolean inclusive) { * interval, or {@code false} instead. */ public boolean isLeftOf(T point) { - return isLeftOf(point, true); + return this.isLeftOf(point, true); } /** @@ -479,7 +479,7 @@ public boolean isLeftOf(T point) { public boolean isLeftOf(Interval other) { if (other == null || other.isEmpty()) return false; - return isLeftOf(other.start, other.isStartInclusive()); + return this.isLeftOf(other.start, other.isStartInclusive()); } /** diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/TreeNode.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/TreeNode.java index 6845489a1c..cd4c7b7b91 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/TreeNode.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/util/interval_tree/TreeNode.java @@ -224,17 +224,17 @@ private TreeNode balanceOut() { // The tree is right-heavy. if (height(this.right.left) > height(this.right.right)) { this.right = this.right.rightRotate(); - return leftRotate(); + return this.leftRotate(); } else { - return leftRotate(); + return this.leftRotate(); } } else if (balance > 1) { // The tree is left-heavy. if (height(this.left.right) > height(this.left.left)) { this.left = this.left.leftRotate(); - return rightRotate(); + return this.rightRotate(); } else - return rightRotate(); + return this.rightRotate(); } else { // The tree is already balanced. return this; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java index a245f6f14d..582f62dd85 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/biome/LevelColorCache.java @@ -110,7 +110,7 @@ private void updateColorBuffers(int relY, ColorResolver resolver, Slice slice) { } public int getBlendRadius() { - return blendRadius; + return this.blendRadius; } private static class Slice { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ChunkRenderContext.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ChunkRenderContext.java index 0242748dcc..9f31b4b998 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ChunkRenderContext.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ChunkRenderContext.java @@ -1,6 +1,5 @@ package net.caffeinemc.mods.sodium.client.world.cloned; -import net.caffeinemc.mods.sodium.client.services.SodiumModelDataContainer; import net.minecraft.core.SectionPos; import net.minecraft.world.level.levelgen.structure.BoundingBox; @@ -32,6 +31,6 @@ public BoundingBox getVolume() { } public List getRenderers() { - return renderers; + return this.renderers; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ClonedChunkSection.java b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ClonedChunkSection.java index b1eff5852c..2c0035407e 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ClonedChunkSection.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/client/world/cloned/ClonedChunkSection.java @@ -52,7 +52,7 @@ public ClonedChunkSection(Level level, LevelChunk chunk, @Nullable LevelChunkSec Int2ReferenceMap blockEntityMap = null; Int2ReferenceMap blockEntityRenderDataMap = null; SodiumModelDataContainer modelMap = PlatformModelAccess.getInstance().getModelDataContainer(level, pos); - auxLightManager = PlatformLevelAccess.INSTANCE.getLightManager(chunk, pos); + this.auxLightManager = PlatformLevelAccess.INSTANCE.getLightManager(chunk, pos); if (section != null) { if (!section.hasOnlyAir()) { @@ -248,7 +248,7 @@ public SectionPos getPosition() { } public SodiumModelDataContainer getModelMap() { - return modelMap; + return this.modelMap; } public @Nullable DataLayer getLightArray(LightLayer lightType) { @@ -264,6 +264,6 @@ public void setLastUsedTimestamp(long timestamp) { } public SodiumAuxiliaryLightManager getAuxLightManager() { - return auxLightManager; + return this.auxLightManager; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/SodiumMixinPlugin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/SodiumMixinPlugin.java index 091a950c4d..e5101ceb74 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/SodiumMixinPlugin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/SodiumMixinPlugin.java @@ -30,7 +30,7 @@ public void onLoad(String mixinPackage) { this.dependencyResolutionFailed = PlatformRuntimeInformation.getInstance().isModInLoadingList("embeddium"); - if (dependencyResolutionFailed) { + if (this.dependencyResolutionFailed) { this.logger.error("Not applying any Sodium mixins; dependency resolution has failed."); } @@ -45,7 +45,7 @@ public String getRefMapperConfig() { @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { - if (dependencyResolutionFailed) { + if (this.dependencyResolutionFailed) { return false; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/BlockEntityTypeMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/BlockEntityTypeMixin.java index 63709d4ce4..ce89c509c2 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/BlockEntityTypeMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/BlockEntityTypeMixin.java @@ -1,7 +1,5 @@ package net.caffeinemc.mods.sodium.mixin.core.render; -import java.util.function.Predicate; - import net.caffeinemc.mods.sodium.api.blockentity.BlockEntityRenderPredicate; import net.caffeinemc.mods.sodium.client.render.chunk.ExtendedBlockEntityType; import net.minecraft.world.level.block.entity.BlockEntity; @@ -17,23 +15,23 @@ public class BlockEntityTypeMixin implements ExtendedBloc @Override public BlockEntityRenderPredicate[] sodium$getRenderPredicates() { - return sodium$renderPredicates; + return this.sodium$renderPredicates; } @Override public void sodium$addRenderPredicate(BlockEntityRenderPredicate predicate) { - sodium$renderPredicates = ArrayUtils.add(sodium$renderPredicates, predicate); + this.sodium$renderPredicates = ArrayUtils.add(this.sodium$renderPredicates, predicate); } @Override public boolean sodium$removeRenderPredicate(BlockEntityRenderPredicate predicate) { - int index = ArrayUtils.indexOf(sodium$renderPredicates, predicate); + int index = ArrayUtils.indexOf(this.sodium$renderPredicates, predicate); if (index == ArrayUtils.INDEX_NOT_FOUND) { return false; } - sodium$renderPredicates = ArrayUtils.remove(sodium$renderPredicates, index); + this.sodium$renderPredicates = ArrayUtils.remove(this.sodium$renderPredicates, index); return true; } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/BufferBuilderMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/BufferBuilderMixin.java index 8a93c2f67b..c1e87ecf6c 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/BufferBuilderMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/BufferBuilderMixin.java @@ -71,7 +71,7 @@ public void push(MemoryStack stack, long src, int count, VertexFormat format) { } this.vertices += count; - this.vertexPointer = (dst + length) - vertexSize; + this.vertexPointer = (dst + length) - this.vertexSize; this.elementsToFill = 0; } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/VertexMultiConsumerMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/VertexMultiConsumerMixin.java index 6df4d5b477..3849caf352 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/VertexMultiConsumerMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/core/render/immediate/consumer/VertexMultiConsumerMixin.java @@ -1,8 +1,8 @@ package net.caffeinemc.mods.sodium.mixin.core.render.immediate.consumer; -import com.mojang.blaze3d.vertex.VertexFormat; import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.blaze3d.vertex.VertexFormat; import net.caffeinemc.mods.sodium.api.vertex.buffer.VertexBufferWriter; import org.lwjgl.system.MemoryStack; import org.spongepowered.asm.mixin.Final; @@ -62,7 +62,7 @@ public static class MultipleMixin implements VertexBufferWriter { @Inject(method = "", at = @At("RETURN")) private void checkFullStatus(CallbackInfo ci) { - this.canUseIntrinsics = allDelegatesSupportIntrinsics(); + this.canUseIntrinsics = this.allDelegatesSupportIntrinsics(); } @Unique diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/entity/ModelPartMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/entity/ModelPartMixin.java index e5a20fa916..08c31f6f13 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/entity/ModelPartMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/entity/ModelPartMixin.java @@ -1,14 +1,11 @@ package net.caffeinemc.mods.sodium.mixin.features.render.entity; -import net.caffeinemc.mods.sodium.client.render.immediate.model.EntityRenderer; +import com.mojang.blaze3d.vertex.PoseStack; import net.caffeinemc.mods.sodium.api.math.MatrixHelper; import net.minecraft.client.model.geom.ModelPart; -import org.spongepowered.asm.mixin.*; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.Shadow; @Mixin(ModelPart.class) public class ModelPartMixin { diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/frapi/ItemRendererMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/frapi/ItemRendererMixin.java index a9d7ab6b83..834ddec25b 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/frapi/ItemRendererMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/frapi/ItemRendererMixin.java @@ -50,12 +50,12 @@ public abstract class ItemRendererMixin { private final ItemRenderContext.VanillaModelBufferer vanillaBufferer = this::renderModelLists; @Unique - private final ThreadLocal contexts = ThreadLocal.withInitial(() -> new ItemRenderContext(itemColors, vanillaBufferer)); + private final ThreadLocal contexts = ThreadLocal.withInitial(() -> new ItemRenderContext(this.itemColors, this.vanillaBufferer)); @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/BakedModel;isCustomRenderer()Z"), cancellable = true) private void beforeRenderItem(ItemStack stack, ItemDisplayContext transformMode, boolean invert, PoseStack matrixStack, MultiBufferSource vertexConsumerProvider, int light, int overlay, BakedModel model, CallbackInfo ci) { if (!((FabricBakedModel) model).isVanillaAdapter()) { - contexts.get().renderModel(stack, transformMode, invert, matrixStack, vertexConsumerProvider, light, overlay, model); + this.contexts.get().renderModel(stack, transformMode, invert, matrixStack, vertexConsumerProvider, light, overlay, model); matrixStack.popPose(); ci.cancel(); } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/frapi/ModelBlockRendererMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/frapi/ModelBlockRendererMixin.java index e231e6497a..a0dd842782 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/frapi/ModelBlockRendererMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/frapi/ModelBlockRendererMixin.java @@ -45,12 +45,12 @@ public abstract class ModelBlockRendererMixin { private BlockColors blockColors; @Unique - private final ThreadLocal contexts = ThreadLocal.withInitial(() -> new NonTerrainBlockRenderContext(blockColors)); + private final ThreadLocal contexts = ThreadLocal.withInitial(() -> new NonTerrainBlockRenderContext(this.blockColors)); @Inject(method = "tesselateBlock", at = @At("HEAD"), cancellable = true) private void onRender(BlockAndTintGetter blockView, BakedModel model, BlockState state, BlockPos pos, PoseStack matrix, VertexConsumer buffer, boolean cull, RandomSource rand, long seed, int overlay, CallbackInfo ci) { if (!((FabricBakedModel) model).isVanillaAdapter()) { - contexts.get().renderModel(blockView, model, state, pos, matrix, buffer, cull, rand, seed, overlay); + this.contexts.get().renderModel(blockView, model, state, pos, matrix, buffer, cull, rand, seed, overlay); ci.cancel(); } } diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/world/sky/LevelRendererMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/world/sky/LevelRendererMixin.java index 4edcbc806d..11e29c7adc 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/world/sky/LevelRendererMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/render/world/sky/LevelRendererMixin.java @@ -1,6 +1,5 @@ package net.caffeinemc.mods.sodium.mixin.features.render.world.sky; -import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.Camera; import net.minecraft.client.renderer.FogRenderer; import net.minecraft.client.renderer.LevelRenderer; diff --git a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/shader/uniform/ShaderInstanceMixin.java b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/shader/uniform/ShaderInstanceMixin.java index 4c37086f9f..1ea5656385 100644 --- a/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/shader/uniform/ShaderInstanceMixin.java +++ b/common/src/main/java/net/caffeinemc/mods/sodium/mixin/features/shader/uniform/ShaderInstanceMixin.java @@ -1,9 +1,9 @@ package net.caffeinemc.mods.sodium.mixin.features.shader.uniform; import com.mojang.blaze3d.shaders.Uniform; -import com.mojang.blaze3d.vertex.VertexFormat; import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import net.minecraft.client.renderer.ShaderInstance; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -14,8 +14,6 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import java.util.List; -import net.minecraft.client.renderer.ShaderInstance; -import net.minecraft.server.packs.resources.ResourceProvider; /** * On the NVIDIA drivers (and maybe some others), the OpenGL submission thread requires expensive state synchronization 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 db106e65e4..686b21e199 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 @@ -35,7 +35,7 @@ public class SpriteContentsMixin implements SpriteContentsExtension { */ @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) { - scanSpriteContents(nativeImage); + this.scanSpriteContents(nativeImage); original.call(instance, nativeImage); } diff --git a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/block/FabricBlockAccess.java b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/block/FabricBlockAccess.java index 0fd7c13935..8d16c82968 100644 --- a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/block/FabricBlockAccess.java +++ b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/block/FabricBlockAccess.java @@ -6,7 +6,6 @@ import net.caffeinemc.mods.sodium.client.services.PlatformBlockAccess; import net.caffeinemc.mods.sodium.client.services.SodiumModelData; import net.fabricmc.fabric.api.client.render.fluid.v1.FluidRenderHandlerRegistry; -import net.fabricmc.fabric.api.util.TriState; import net.minecraft.client.player.LocalPlayer; import net.minecraft.client.renderer.RenderType; import net.minecraft.client.resources.model.BakedModel; @@ -78,7 +77,7 @@ public boolean platformHasBlockData() { @Override public float getNormalVectorShade(ModelQuadView quad, BlockAndTintGetter level, boolean shade) { - return normalShade(level, NormI8.unpackX(quad.getFaceNormal()), NormI8.unpackY(quad.getFaceNormal()), NormI8.unpackZ(quad.getFaceNormal()), shade); + return this.normalShade(level, NormI8.unpackX(quad.getFaceNormal()), NormI8.unpackY(quad.getFaceNormal()), NormI8.unpackZ(quad.getFaceNormal()), shade); } @Override diff --git a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/level/FabricLevelAccess.java b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/level/FabricLevelAccess.java index 7f5fedf7c3..970963ea18 100644 --- a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/level/FabricLevelAccess.java +++ b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/level/FabricLevelAccess.java @@ -1,27 +1,11 @@ package net.caffeinemc.mods.sodium.fabric.level; -import com.mojang.blaze3d.vertex.VertexConsumer; -import net.caffeinemc.mods.sodium.client.model.color.ColorProviderRegistry; -import net.caffeinemc.mods.sodium.client.model.light.LightPipelineProvider; -import net.caffeinemc.mods.sodium.client.render.chunk.compile.pipeline.FluidRenderer; import net.caffeinemc.mods.sodium.client.services.PlatformLevelAccess; -import net.caffeinemc.mods.sodium.client.world.LevelSlice; import net.caffeinemc.mods.sodium.client.world.SodiumAuxiliaryLightManager; -import net.caffeinemc.mods.sodium.fabric.render.FluidRendererImpl; -import net.minecraft.client.Camera; -import net.minecraft.client.renderer.LevelRenderer; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.culling.Frustum; -import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; -import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.chunk.LevelChunk; import org.jetbrains.annotations.Nullable; -import org.joml.Matrix4f; - -import java.util.List; -import java.util.function.Function; public class FabricLevelAccess implements PlatformLevelAccess { @Override diff --git a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FluidRendererImpl.java b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FluidRendererImpl.java index e79fe293c9..f24d0e815d 100644 --- a/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FluidRendererImpl.java +++ b/fabric/src/main/java/net/caffeinemc/mods/sodium/fabric/render/FluidRendererImpl.java @@ -33,8 +33,8 @@ public class FluidRendererImpl extends FluidRenderer { public FluidRendererImpl(ColorProviderRegistry colorProviderRegistry, LightPipelineProvider lighters) { this.colorProviderRegistry = colorProviderRegistry; - defaultRenderer = new DefaultFluidRenderer(lighters); - defaultContext = new DefaultRenderContext(); + this.defaultRenderer = new DefaultFluidRenderer(lighters); + this.defaultContext = new DefaultRenderContext(); } public void render(LevelSlice level, BlockState blockState, FluidState fluidState, BlockPos blockPos, BlockPos offset, TranslucentGeometryCollector collector, ChunkBuildBuffers buffers) { @@ -71,12 +71,12 @@ public void render(LevelSlice level, BlockState blockState, FluidState fluidStat // To allow invoking this method from the injector, where there is no local Sodium context, the renderer and // parameters are bundled into a DefaultRenderContext which is stored in a ThreadLocal. - defaultContext.setUp(this.colorProviderRegistry, this.defaultRenderer, level, blockState, fluidState, blockPos, offset, collector, meshBuilder, material, handler, hasModOverride); + this.defaultContext.setUp(this.colorProviderRegistry, this.defaultRenderer, level, blockState, fluidState, blockPos, offset, collector, meshBuilder, material, handler, hasModOverride); try { - FluidRendering.render(handler, level, blockPos, meshBuilder.asFallbackVertexConsumer(material, collector), blockState, fluidState, defaultContext); + FluidRendering.render(handler, level, blockPos, meshBuilder.asFallbackVertexConsumer(material, collector), blockState, fluidState, this.defaultContext); } finally { - defaultContext.clear(); + this.defaultContext.clear(); } } @@ -126,17 +126,17 @@ public void clear() { public ColorProvider getColorProvider(Fluid fluid) { var override = this.colorProviderRegistry.getColorProvider(fluid); - if (hasModOverride && override != null) { + if (this.hasModOverride && override != null) { return override; } - return FabricColorProviders.adapt(handler); + return FabricColorProviders.adapt(this.handler); } @Override public void render(FluidRenderHandler handler, BlockAndTintGetter world, BlockPos pos, VertexConsumer vertexConsumer, BlockState blockState, FluidState fluidState) { this.renderer.render(this.level, this.blockState, this.fluidState, this.blockPos, this.offset, this.collector, this.meshBuilder, this.material, - getColorProvider(fluidState.getType()), handler.getFluidSprites(this.level, this.blockPos, this.fluidState)); + this.getColorProvider(fluidState.getType()), handler.getFluidSprites(this.level, this.blockPos, this.fluidState)); } } diff --git a/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/features/world/biome/BiomeMixin.java b/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/features/world/biome/BiomeMixin.java index 05c12f8caa..fb05e59cc0 100644 --- a/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/features/world/biome/BiomeMixin.java +++ b/fabric/src/main/java/net/caffeinemc/mods/sodium/mixin/features/world/biome/BiomeMixin.java @@ -38,12 +38,12 @@ public abstract class BiomeMixin { @Inject(method = "", at = @At("RETURN")) private void onInit(CallbackInfo ci) { - setupColors(); + this.setupColors(); } @Unique private void setupColors() { - this.cachedSpecialEffects = specialEffects; + this.cachedSpecialEffects = this.specialEffects; var grassColor = this.cachedSpecialEffects.getGrassColorOverride(); @@ -73,7 +73,7 @@ private void setupColors() { @Overwrite public int getGrassColor(double x, double z) { if (this.specialEffects != this.cachedSpecialEffects) { - setupColors(); + this.setupColors(); } int color; @@ -100,7 +100,7 @@ public int getGrassColor(double x, double z) { @Overwrite public int getFoliageColor() { if (this.specialEffects != this.cachedSpecialEffects) { - setupColors(); + this.setupColors(); } int color; diff --git a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/model/MultiPartBakedModelMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/model/MultiPartBakedModelMixin.java index b625502653..e613d6aad4 100644 --- a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/model/MultiPartBakedModelMixin.java +++ b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/model/MultiPartBakedModelMixin.java @@ -89,7 +89,7 @@ public List getQuads(@Nullable BlockState state, @Nullable Direction for (BakedModel model : models) { random.setSeed(seed); - if (canSkipRenderTypeCheck || renderType == null || model.getRenderTypes(state, random, modelData).contains(renderType)) { + if (this.canSkipRenderTypeCheck || renderType == null || model.getRenderTypes(state, random, modelData).contains(renderType)) { quads.addAll(model.getQuads(state, direction, random, MultipartModelData.resolve(modelData, model), renderType)); } } @@ -105,7 +105,7 @@ public List getQuads(@Nullable BlockState state, @Nullable Direction public ChunkRenderTypeSet getRenderTypes(@NotNull BlockState state, @NotNull RandomSource random, @NotNull ModelData data) { long seed = random.nextLong(); - if (canSkipRenderTypeCheck) { + if (this.canSkipRenderTypeCheck) { return ItemBlockRenderTypes.getRenderLayers(state); } diff --git a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/world/biome/BiomeMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/world/biome/BiomeMixin.java index cea4041f18..2cd742769a 100644 --- a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/world/biome/BiomeMixin.java +++ b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/features/world/biome/BiomeMixin.java @@ -38,12 +38,12 @@ public abstract class BiomeMixin { @Inject(method = "", at = @At("RETURN")) private void onInit(CallbackInfo ci) { - setupColors(); + this.setupColors(); } @Unique private void setupColors() { - this.cachedSpecialEffects = getModifiedSpecialEffects(); + this.cachedSpecialEffects = this.getModifiedSpecialEffects(); var grassColor = this.cachedSpecialEffects.getGrassColorOverride(); @@ -73,7 +73,7 @@ private void setupColors() { @Overwrite public int getGrassColor(double x, double z) { if (this.getModifiedSpecialEffects() != this.cachedSpecialEffects) { - setupColors(); + this.setupColors(); } int color; @@ -100,7 +100,7 @@ public int getGrassColor(double x, double z) { @Overwrite public int getFoliageColor() { if (this.getModifiedSpecialEffects() != this.cachedSpecialEffects) { - setupColors(); + this.setupColors(); } int color; diff --git a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AbstractBlockRenderContextMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AbstractBlockRenderContextMixin.java index 52574202b7..96767e6927 100644 --- a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AbstractBlockRenderContextMixin.java +++ b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/AbstractBlockRenderContextMixin.java @@ -41,7 +41,7 @@ public ModelData getModelData() { @Override public RenderType getRenderType() { - return type; + return this.type; } @Override diff --git a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/LevelSliceMixin.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/LevelSliceMixin.java index 42e8f89b9a..2be4524fcd 100644 --- a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/LevelSliceMixin.java +++ b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/mixin/platform/neoforge/LevelSliceMixin.java @@ -43,7 +43,7 @@ public SodiumModelData getPlatformModelData(BlockPos pos) { @Override public ModelData getModelData(BlockPos pos) { - SodiumModelData modelData = getPlatformModelData(pos); + SodiumModelData modelData = this.getPlatformModelData(pos); return modelData != null ? (ModelData) (Object) modelData : null; } @@ -52,7 +52,7 @@ public ModelData getModelData(BlockPos pos) { int relChunkX = pos.x - (this.originBlockX >> 4); int relChunkZ = pos.z - (this.originBlockZ >> 4); - return (AuxiliaryLightManager) auxLightManager[getLocalSectionIndex(relChunkX, 0, relChunkZ)]; + return (AuxiliaryLightManager) this.auxLightManager[getLocalSectionIndex(relChunkX, 0, relChunkZ)]; } @Override @@ -61,7 +61,7 @@ public ModelData getModelData(BlockPos pos) { int relBlockY = pos.getY() - this.originBlockY; int relBlockZ = pos.getZ() - this.originBlockZ; - return (AuxiliaryLightManager) auxLightManager[getLocalSectionIndex(relBlockX >> 4, relBlockY >> 4, relBlockZ >> 4)]; + return (AuxiliaryLightManager) this.auxLightManager[getLocalSectionIndex(relBlockX >> 4, relBlockY >> 4, relBlockZ >> 4)]; } @Override diff --git a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/FluidRendererImpl.java b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/FluidRendererImpl.java index d73cc8aaab..d3e59fe310 100644 --- a/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/FluidRendererImpl.java +++ b/neoforge/src/mod/java/net/caffeinemc/mods/sodium/neoforge/render/FluidRendererImpl.java @@ -30,7 +30,7 @@ public class FluidRendererImpl extends FluidRenderer { public FluidRendererImpl(ColorProviderRegistry colorProviderRegistry, LightPipelineProvider lighters) { this.colorProviderRegistry = colorProviderRegistry; - defaultRenderer = new DefaultFluidRenderer(lighters); + this.defaultRenderer = new DefaultFluidRenderer(lighters); } public void render(LevelSlice level, BlockState blockState, FluidState fluidState, BlockPos blockPos, BlockPos offset, TranslucentGeometryCollector collector, ChunkBuildBuffers buffers) { @@ -119,12 +119,12 @@ public ColorProvider getColorProvider(Fluid fluid) { return override; } - return ForgeColorProviders.adapt(handler); + return ForgeColorProviders.adapt(this.handler); } public void render() { this.renderer.render(this.level, this.blockState, this.fluidState, this.blockPos, this.offset, this.collector, this.meshBuilder, this.material, - getColorProvider(fluidState.getType()), FluidSpriteCache.getFluidSprites(level, blockPos, fluidState)); + this.getColorProvider(this.fluidState.getType()), FluidSpriteCache.getFluidSprites(this.level, this.blockPos, this.fluidState)); } } From 53306ac4db8f9fae1655c81539ffcd79e4afc4fb Mon Sep 17 00:00:00 2001 From: douira Date: Mon, 6 Jul 2026 19:10:27 +0200 Subject: [PATCH 215/215] Update changelog and bump version to 0.8.12 --- CHANGELOG.md | 28 ++++++------------------- buildSrc/src/main/kotlin/BuildConfig.kt | 2 +- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 002e994535..8db93dd5e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,30 +15,14 @@ Sodium [SodiumVersion]() is a backport of modern Sodium 0.8 to Minecraft [MCVers - Improvements for hardware and mod compatibility. - ...And many more bug fixes and improvements... -### Using and Testing This Release -It includes the backport of our Config API and other conventions that will hopefully make it easier for mods to interact with Sodium across multiple versions. This release series doesn't get released at the same cadence as our current releases for Minecraft 26.1 and 1.21.11, and doesn't follow the same alpha/beta numbering. Mod developers can find our artifacts, such as the Config API, on [our Maven repository](https://maven.caffeinemc.net/). +### Using and Building on This Release +It includes the backport of our Config API and other conventions that will hopefully make it easier for mods to interact with Sodium across multiple versions. This release series doesn't get released at the same cadence as our current releases for Minecraft 26.2 and 26.1, and doesn't follow the same alpha/beta numbering. Mod developers can find our artifacts, such as the Config API, on [our Maven repository](https://maven.caffeinemc.net/). -Please participate in testing this release, coordinating on mod compatibility, and giving feedback on [our discord server](https://caffeinemc.net/discord). In the thread in #testing-builds we have more information. Report any issues you may have in the thread. - -Known incompatibilities at the time of this release: +Mod compatibility: - Create Aeronautics works as of 1.3.0 - Sable works as of 2.0.0 - Veil works as of 4.1.2 -- Iris works as of 1.8.13 (unreleased on platforms at the time of this release) -- Voxy does not work -- sodiumleafculling does not work -- EBE animations don’t work - -### Alpha Series Changelog -- Fix BufferBuilderMixin conflicting with Iris' MixinBufferBuilder_SeparateAo when requireOverwriteAnnotations is enabled ([#3658](https://github.com/CaffeineMC/sodium/pull/3658)) -- Potentially fix issues with publishing by making the buildscript more similar to that used in 1.21.11 -- Fix the corrupted config screen to show up properly -- Revert "Update texture light coords math to match Vanilla (#3311)" as this was mistakenly applied to 1.21.1 +- Iris works as of 1.8.13 +- Not compatible with any version of "Sodium Options API", which is not affiliated with us, and any mods that depend on it. -### Beta Series Changelog -- Added the display of fps percentiles. This gives a more accurate idea of the typical frame rate and lets you identify how smooth it is. -- Fix crash when using `-Dmixin.debug=true` ([#3689](https://github.com/CaffeineMC/sodium/pull/3689)) -- Added checks to prevent crashes when using the vertex writing fast path ([#3716](https://github.com/CaffeineMC/sodium/pull/3716)) -- Only make environment changes if the early window will create a gl context early ([#3697](https://github.com/CaffeineMC/sodium/pull/3697)) -- Improve the presentation and wording of some video options ([#3700](https://github.com/CaffeineMC/sodium/pull/3700)) -- Fix crash "getResources is null" ([#3752](https://github.com/CaffeineMC/sodium/pull/3752)) +Some other mods may still be incompatible with this release. If your modpack doesn't work with this release, downgrade to the version of Sodium it was built with and choose appropriate versions of other mods. New or updated modpacks are encouraged to use the latest version of Sodium and compatible mods. \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/BuildConfig.kt b/buildSrc/src/main/kotlin/BuildConfig.kt index cf4ce3186d..1a94f09ba8 100644 --- a/buildSrc/src/main/kotlin/BuildConfig.kt +++ b/buildSrc/src/main/kotlin/BuildConfig.kt @@ -10,7 +10,7 @@ object BuildConfig { val PARCHMENT_VERSION: String? = "2024.11.17" // https://semver.org/ - val MOD_VERSION: String = "0.8.12-beta.2" + val MOD_VERSION: String = "0.8.12" val MINECRAFT_VERSION_SHORT: String = MINECRAFT_VERSION .replace("-snapshot-", "s")