From 2b5bcf4098e0ed77d6f0660922251aa103743fa9 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 28 Sep 2026 21:55:47 +0200 Subject: [PATCH] Draw glass on every version 1.21.6 to 1.21.10 draw glass as 1.21.11 does, without samplers, and the glass shader imports vanilla's transforms block, whose layout differs between them. 1.21.4 and 1.21.5 draw straight into the framebuffer, so the game is saved before the screen blurs it, and glass is a render layer: with a pipeline on 1.21.5 and a shader program on 1.21.4. Co-Authored-By: Claude Opus 5.5 --- common | 2 +- .../saturnclient/impl/ui/GlassRenderer.java | 229 +++++++++++++++++- .../saturnclient/impl/ui/RenderScopeImpl.java | 38 ++- .../impl/ui/SaturnScreenFabric.java | 4 + .../saturnclient/shaders/core/glass.fsh | 11 +- .../saturnclient/shaders/core/glass.json | 13 + .../shaders/core/glass_pre_1_21_6.fsh | 27 +++ 7 files changed, 297 insertions(+), 27 deletions(-) create mode 100644 src/main/resources/assets/saturnclient/shaders/core/glass.json create mode 100644 src/main/resources/assets/saturnclient/shaders/core/glass_pre_1_21_6.fsh diff --git a/common b/common index 7bc3081..524fc36 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 7bc308155e882743badc71e8465ec8e913022c0a +Subproject commit 524fc36bf333f37796516e267e5e8de8d5030f7e diff --git a/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java b/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java index cee6ba6..f552152 100644 --- a/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java +++ b/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java @@ -1,6 +1,20 @@ package org.saturnclient.impl.ui; -//? if >=1.21.11 { +// Glass: a copy of the game behind the screen, blurred, drawn through each panel's rounded rectangle +// texture as a mask. There's a copy for each blur strength in use (menus and widgets have their own), +// blurred with vanilla's blur at that radius, so it holds the world or panorama without the screen's +// own animated background blur. +// +// The GUI rewrites split this into three implementations: +// - 1.21.6+: the GUI is drawn after the screen builds it, so the copy is taken from the main +// framebuffer while the screen builds its GUI, and glass is a GUI element with its own pipeline +// (shaders/core/glass.fsh). The blur is vanilla's effect rebuilt with the radius set. +// - 1.21.5: the screen draws straight into the framebuffer, blurring it first, so the game is saved +// before the blur (saveBackground). Glass is a render layer with its own pipeline +// (shaders/core/glass_pre_1_21_6.fsh), and the blur takes the radius as it runs. +// - 1.21.4: as 1.21.5, with GL framebuffers copied by blitting, and a shader program +// (shaders/core/glass.json) instead of a pipeline. +//? if >=1.21.6 { import java.io.Reader; import java.util.HashMap; import java.util.Map; @@ -17,22 +31,17 @@ import com.mojang.serialization.JsonOps; import org.saturnclient.saturnclient.SaturnClient; import net.minecraft.client.gl.Framebuffer; -import net.minecraft.client.gl.GpuSampler; import net.minecraft.client.gl.PostEffectPipeline; import net.minecraft.client.gl.PostEffectProcessor; import net.minecraft.client.gl.RenderPipelines; import net.minecraft.client.gl.SimpleFramebuffer; import net.minecraft.client.render.DefaultFramebufferSet; import net.minecraft.client.render.ProjectionMatrix2; +import net.minecraft.client.texture.AbstractTexture; import net.minecraft.client.texture.TextureSetup; import net.minecraft.client.util.Pool; import net.minecraft.util.Identifier; -// Glass: a copy of the game behind the screen, blurred, drawn through each panel's rounded rectangle -// texture (shaders/core/glass.fsh). There's a copy for each blur strength in use (menus and widgets -// have their own), made while the screen builds its GUI, before the GUI is drawn, so it holds the -// world or panorama alone, without the screen's own animated background blur. -// TODO: glass before 1.21.11 (#23). Those versions draw translucent panels without it. public final class GlassRenderer { public static final RenderPipeline PIPELINE = RenderPipeline.builder(RenderPipelines.POSITION_TEX_COLOR_SNIPPET) .withLocation(Identifier.of("saturnclient", "pipeline/glass")) @@ -52,8 +61,8 @@ public final class GlassRenderer { private GlassRenderer() { } - // Copies the main framebuffer and blurs the copy by blur. Call once a frame for each blur, before - // drawing glass with it. + // Copies the main framebuffer and blurs the copy by blur. Call once a frame for each blur, while + // the screen builds its GUI, before drawing glass with it. public static void captureBackdrop(int blur) { Framebuffer main = SaturnClient.client.getFramebuffer(); int width = main.textureWidth; @@ -66,6 +75,8 @@ public final class GlassRenderer { } else if (backdrop.textureWidth != width || backdrop.textureHeight != height) { backdrop.resize(width, height); } + //? if <1.21.11 + //backdrop.getColorAttachment().setTextureFilter(FilterMode.LINEAR, false); RenderSystem.getDevice().createCommandEncoder().copyTextureToTexture( main.getColorAttachment(), backdrop.getColorAttachment(), 0, 0, 0, 0, 0, width, height); @@ -78,9 +89,12 @@ public final class GlassRenderer { } // The mask, then the copy blurred by blur, as the glass pipeline samples them - public static TextureSetup textures(GpuTextureView mask, GpuSampler maskSampler, int blur) { - return TextureSetup.of(mask, maskSampler, BACKDROPS.get(blur).getColorAttachmentView(), - RenderSystem.getSamplerCache().get(FilterMode.LINEAR)); + public static TextureSetup textures(AbstractTexture mask, int blur) { + GpuTextureView backdrop = BACKDROPS.get(blur).getColorAttachmentView(); + //? if >=1.21.11 + return TextureSetup.of(mask.getGlTextureView(), mask.getSampler(), backdrop, RenderSystem.getSamplerCache().get(FilterMode.LINEAR)); + //? if <1.21.11 + //return TextureSetup.of(mask.getGlTextureView(), backdrop); } private static PostEffectProcessor getBlur(int blur) { @@ -119,4 +133,193 @@ public final class GlassRenderer { } } } -//?} +//?} else if >=1.21.5 { +/*import java.util.HashMap; +import java.util.Map; + +import com.mojang.blaze3d.pipeline.RenderPipeline; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.textures.FilterMode; + +import org.saturnclient.saturnclient.SaturnClient; + +import net.minecraft.client.gl.Framebuffer; +import net.minecraft.client.gl.PostEffectProcessor; +import net.minecraft.client.gl.RenderPipelines; +import net.minecraft.client.gl.SimpleFramebuffer; +import net.minecraft.client.render.DefaultFramebufferSet; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.RenderPhase; +import net.minecraft.client.util.Pool; +import net.minecraft.util.Identifier; + +public final class GlassRenderer { + private static final RenderPipeline PIPELINE = RenderPipeline.builder(RenderPipelines.POSITION_TEX_COLOR_SNIPPET) + .withLocation(Identifier.of("saturnclient", "pipeline/glass")) + .withFragmentShader(Identifier.of("saturnclient", "core/glass_pre_1_21_6")) + .withSampler("Sampler1") + .build(); + + private static final Identifier BLUR = Identifier.ofVanilla("blur"); + // For the blur's intermediate target, freed once unused for a few frames + private static final Pool POOL = new Pool(3); + + private static final Map BACKDROPS = new HashMap<>(); + // By mask and blur + private static final Map LAYERS = new HashMap<>(); + // The game behind the screen before the screen blurs it, which glass is blurred from + private static SimpleFramebuffer background; + + private GlassRenderer() { + } + + // Saves the main framebuffer for glass. Call each frame before the screen blurs its background. + public static void saveBackground() { + background = copy(SaturnClient.client.getFramebuffer(), background, "Saturn glass background"); + } + + // Copies the saved background and blurs the copy by blur. Call once a frame for each blur, before + // drawing glass with it. + public static void captureBackdrop(int blur) { + Framebuffer from = background != null ? background : SaturnClient.client.getFramebuffer(); + SimpleFramebuffer backdrop = copy(from, BACKDROPS.get(blur), "Saturn glass " + blur); + BACKDROPS.put(blur, backdrop); + + PostEffectProcessor processor = SaturnClient.client.getShaderLoader().loadPostEffect(BLUR, + DefaultFramebufferSet.MAIN_ONLY); + if (processor != null) { + processor.render(backdrop, POOL, pass -> pass.setUniform("Radius", (float) blur)); + } + POOL.decrementLifespan(); + } + + // Draws glass through mask with the copy blurred by blur + public static RenderLayer getLayer(Identifier mask, int blur) { + return LAYERS.computeIfAbsent(mask + " " + blur, key -> RenderLayer.of("saturn_glass", 786432, PIPELINE, + RenderLayer.MultiPhaseParameters.builder() + .texture(new RenderPhase.TextureBase(() -> { + RenderSystem.setShaderTexture(0, + SaturnClient.client.getTextureManager().getTexture(mask).getGlTexture()); + RenderSystem.setShaderTexture(1, BACKDROPS.get(blur).getColorAttachment()); + }, () -> { + })) + .build(false))); + } + + // Copies from into to, made or resized to match + private static SimpleFramebuffer copy(Framebuffer from, SimpleFramebuffer to, String name) { + int width = from.textureWidth; + int height = from.textureHeight; + + if (to == null) { + to = new SimpleFramebuffer(name, width, height, false); + } else if (to.textureWidth != width || to.textureHeight != height) { + to.resize(width, height); + } + to.getColorAttachment().setTextureFilter(FilterMode.LINEAR, false); + + RenderSystem.getDevice().createCommandEncoder().copyTextureToTexture( + from.getColorAttachment(), to.getColorAttachment(), 0, 0, 0, 0, 0, width, height); + return to; + } +} +*///?} else { +/*import java.util.HashMap; +import java.util.Map; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL30; + +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; + +import org.saturnclient.saturnclient.SaturnClient; + +import net.minecraft.client.gl.Defines; +import net.minecraft.client.gl.Framebuffer; +import net.minecraft.client.gl.PostEffectProcessor; +import net.minecraft.client.gl.ShaderProgramKey; +import net.minecraft.client.gl.SimpleFramebuffer; +import net.minecraft.client.render.DefaultFramebufferSet; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.RenderPhase; +import net.minecraft.client.render.VertexFormat; +import net.minecraft.client.render.VertexFormats; +import net.minecraft.client.util.Pool; +import net.minecraft.util.Identifier; + +public final class GlassRenderer { + private static final ShaderProgramKey PROGRAM = new ShaderProgramKey(Identifier.of("saturnclient", "core/glass"), + VertexFormats.POSITION_TEXTURE_COLOR, Defines.EMPTY); + + private static final Identifier BLUR = Identifier.ofVanilla("blur"); + // For the blur's intermediate target, freed once unused for a few frames + private static final Pool POOL = new Pool(3); + + private static final Map BACKDROPS = new HashMap<>(); + // By mask and blur + private static final Map LAYERS = new HashMap<>(); + // The game behind the screen before the screen blurs it, which glass is blurred from + private static SimpleFramebuffer background; + + private GlassRenderer() { + } + + // Saves the main framebuffer for glass. Call each frame before the screen blurs its background. + public static void saveBackground() { + background = copy(SaturnClient.client.getFramebuffer(), background); + } + + // Copies the saved background and blurs the copy by blur. Call once a frame for each blur, before + // drawing glass with it. + public static void captureBackdrop(int blur) { + Framebuffer from = background != null ? background : SaturnClient.client.getFramebuffer(); + SimpleFramebuffer backdrop = copy(from, BACKDROPS.get(blur)); + BACKDROPS.put(blur, backdrop); + + PostEffectProcessor processor = SaturnClient.client.getShaderLoader().loadPostEffect(BLUR, + DefaultFramebufferSet.MAIN_ONLY); + if (processor != null) { + processor.setUniforms("Radius", blur); + processor.render(backdrop, POOL); + } + POOL.decrementLifespan(); + SaturnClient.client.getFramebuffer().beginWrite(false); + } + + // Draws glass through mask with the copy blurred by blur + public static RenderLayer getLayer(Identifier mask, int blur) { + return LAYERS.computeIfAbsent(mask + " " + blur, key -> RenderLayer.of("saturn_glass", + VertexFormats.POSITION_TEXTURE_COLOR, VertexFormat.DrawMode.QUADS, 786432, + RenderLayer.MultiPhaseParameters.builder() + .texture(new RenderPhase.TextureBase(() -> { + RenderSystem.setShaderTexture(0, mask); + RenderSystem.setShaderTexture(1, BACKDROPS.get(blur).getColorAttachment()); + }, () -> { + })) + .program(new RenderPhase.ShaderProgram(PROGRAM)) + .transparency(RenderPhase.TRANSLUCENT_TRANSPARENCY) + .build(false))); + } + + // Copies from into to, made or resized to match + private static SimpleFramebuffer copy(Framebuffer from, SimpleFramebuffer to) { + int width = from.textureWidth; + int height = from.textureHeight; + + if (to == null) { + to = new SimpleFramebuffer(width, height, false); + } else if (to.textureWidth != width || to.textureHeight != height) { + to.resize(width, height); + } + to.setTexFilter(GL11.GL_LINEAR); + + GlStateManager._glBindFramebuffer(GL30.GL_READ_FRAMEBUFFER, from.fbo); + GlStateManager._glBindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, to.fbo); + GlStateManager._glBlitFrameBuffer(0, 0, width, height, 0, 0, width, height, GL11.GL_COLOR_BUFFER_BIT, + GL11.GL_NEAREST); + SaturnClient.client.getFramebuffer().beginWrite(false); + return to; + } +} +*///?} diff --git a/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java b/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java index d9004bc..3398516 100644 --- a/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java +++ b/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java @@ -42,7 +42,6 @@ import net.minecraft.client.render.entity.state.EntityRenderState; import net.minecraft.client.render.item.KeyedItemRenderState; //?} else //import net.minecraft.client.render.item.ItemRenderState; -//? if >=1.21.11 import net.minecraft.client.texture.AbstractTexture; import net.minecraft.client.texture.Sprite; import net.minecraft.client.texture.TextureSetup; @@ -63,10 +62,8 @@ public class RenderScopeImpl implements RenderScope { private ScissorStack scissorStack; private int opacity = 0xFF << 24; - //? if >=1.21.11 { // The glass blurs made for this scope's frame private final Set capturedBlurs = new HashSet<>(); - //?} public RenderScopeImpl(DrawContext context) { this(context.getMatrices(), ((DrawContextAccessor) (Object) context).getState()); @@ -236,7 +233,6 @@ public class RenderScopeImpl implements RenderScope { this.scissorStack.peekLast())); } - //? if >=1.21.11 { @Override public void drawGlass(IdentifierRef mask, int x, int y, int width, int height, int blur) { int color = getColor(0xFFFFFFFF); @@ -250,12 +246,11 @@ public class RenderScopeImpl implements RenderScope { AbstractTexture maskTexture = SaturnClient.client.getTextureManager().getTexture((Identifier) (Object) mask); this.state.addSimpleElement(new TexturedQuadGuiElementRenderState( GlassRenderer.PIPELINE, - GlassRenderer.textures(maskTexture.getGlTextureView(), maskTexture.getSampler(), blur), + GlassRenderer.textures(maskTexture, blur), new Matrix3x2f(this.matrices.stack), x, y, x + width, y + height, 0, 1, 0, 1, color, this.scissorStack.peekLast())); } - //?} // Scissor management @Override @@ -395,7 +390,9 @@ public class RenderScopeImpl implements RenderScope { //?} else { /*import java.util.ArrayDeque; import java.util.Deque; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import java.util.function.Consumer; import org.jetbrains.annotations.Nullable; @@ -448,6 +445,8 @@ public class RenderScopeImpl implements RenderScope { public VertexConsumerProvider.Immediate vertexConsumers; private ScissorStack scissorStack = new ScissorStack(); private int opacity = 255 << 24; + // The glass blurs made for this scope's frame + private final Set capturedBlurs = new HashSet<>(); private final ItemRenderState itemRenderState; // ---------------------------- @@ -687,6 +686,33 @@ public class RenderScopeImpl implements RenderScope { matrices.pop(); } + @Override + public void drawGlass(IdentifierRef mask, int x, int y, int width, int height, int blur) { + int color = getColor(0xFFFFFFFF); + if (mask == null || color >>> 24 == 0) + return; + + if (capturedBlurs.add(blur)) { + GlassRenderer.captureBackdrop(blur); + } + + // In quarter units, as drawTexturedQuad draws + int x1 = x * 4; + int x2 = (x + width) * 4; + int y1 = y * 4; + int y2 = (y + height) * 4; + + matrices.push(); + matrices.scale(0.25f, 0.25f, 1.0f); + Matrix4f matrix4f = matrices.peek().getPositionMatrix(); + VertexConsumer vertexConsumer = vertexConsumers.getBuffer(GlassRenderer.getLayer((Identifier) (Object) mask, blur)); + vertexConsumer.vertex(matrix4f, x1, y1, 0).texture(0, 0).color(color); + vertexConsumer.vertex(matrix4f, x1, y2, 0).texture(0, 1).color(color); + vertexConsumer.vertex(matrix4f, x2, y2, 0).texture(1, 1).color(color); + vertexConsumer.vertex(matrix4f, x2, y1, 0).texture(1, 0).color(color); + matrices.pop(); + } + // ---------------------------- // Items // ---------------------------- diff --git a/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java b/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java index bfaa041..7f17d03 100644 --- a/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java +++ b/src/main/java/org/saturnclient/impl/ui/SaturnScreenFabric.java @@ -97,6 +97,10 @@ public class SaturnScreenFabric extends Screen implements ScreenProvider { ROTATING_PANORAMA_RENDERER.render(context, this.width, this.height, screen.backgroundOpacity, delta); } + // Glass is blurred from the game as it is before this screen blurs it. + context.draw(); + GlassRenderer.saveBackground(); + PostEffectProcessor postEffectProcessor = this.client.getShaderLoader().loadPostEffect( Identifier.ofVanilla("blur"), DefaultFramebufferSet.MAIN_ONLY); diff --git a/src/main/resources/assets/saturnclient/shaders/core/glass.fsh b/src/main/resources/assets/saturnclient/shaders/core/glass.fsh index fe6f02b..7d5ee7e 100644 --- a/src/main/resources/assets/saturnclient/shaders/core/glass.fsh +++ b/src/main/resources/assets/saturnclient/shaders/core/glass.fsh @@ -1,13 +1,10 @@ #version 330 -// Saturn's glass: the blurred game behind the screen, cut to a panel's shape. +// Saturn's glass: the blurred game behind the screen, cut to a panel's shape. For 1.21.6 and +// later; glass_pre_1_21_6.fsh (with glass.json, the program 1.21.4 loads) is for older versions. -layout(std140) uniform DynamicTransforms { - mat4 ModelViewMat; - vec4 ColorModulator; - vec3 ModelOffset; - mat4 TextureMat; -}; +// Vanilla's own, since its layout changes between versions (1.21.11 dropped LineWidth) +#moj_import // The panel's rounded rectangle, the same texture its background is drawn with uniform sampler2D Sampler0; diff --git a/src/main/resources/assets/saturnclient/shaders/core/glass.json b/src/main/resources/assets/saturnclient/shaders/core/glass.json new file mode 100644 index 0000000..6aaac85 --- /dev/null +++ b/src/main/resources/assets/saturnclient/shaders/core/glass.json @@ -0,0 +1,13 @@ +{ + "vertex": "minecraft:core/position_tex_color", + "fragment": "saturnclient:core/glass_pre_1_21_6", + "samplers": [ + { "name": "Sampler0" }, + { "name": "Sampler1" } + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] } + ] +} diff --git a/src/main/resources/assets/saturnclient/shaders/core/glass_pre_1_21_6.fsh b/src/main/resources/assets/saturnclient/shaders/core/glass_pre_1_21_6.fsh new file mode 100644 index 0000000..e529808 --- /dev/null +++ b/src/main/resources/assets/saturnclient/shaders/core/glass_pre_1_21_6.fsh @@ -0,0 +1,27 @@ +#version 150 + +// Saturn's glass (see glass.fsh), for 1.21.4 and 1.21.5, which set uniforms one by one instead of in +// blocks. + +uniform vec4 ColorModulator; + +// The panel's rounded rectangle, the same texture its background is drawn with +uniform sampler2D Sampler0; +// The game behind the screen, blurred, the size of the framebuffer +uniform sampler2D Sampler1; + +in vec2 texCoord0; +in vec4 vertexColor; + +out vec4 fragColor; + +void main() { + float mask = texture(Sampler0, texCoord0).a * vertexColor.a; + if (mask == 0.0) { + discard; + } + + // The blurred copy lines up with the framebuffer being drawn to. + vec2 screenUv = gl_FragCoord.xy / vec2(textureSize(Sampler1, 0)); + fragColor = vec4(texture(Sampler1, screenUv).rgb, mask) * ColorModulator; +}