From 4e00934353837d912861ec60ff4052f20211faeb Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 28 Sep 2026 21:35:23 +0200 Subject: [PATCH] Blur each glass strength separately on 1.21.11 Every blur strength in use gets its own copy of the game, blurred with vanilla's blur at that radius instead of the animated menu blur. Co-Authored-By: Claude Opus 5.5 --- common | 2 +- .../mixins/render/GameRendererMixin.java | 5 +- .../impl/provider/SaturnProviderImpl.java | 8 -- .../saturnclient/impl/ui/GlassRenderer.java | 87 +++++++++++++++---- .../saturnclient/impl/ui/RenderScopeImpl.java | 15 ++-- 5 files changed, 80 insertions(+), 37 deletions(-) diff --git a/common b/common index 4d9c629..7bc3081 160000 --- a/common +++ b/common @@ -1 +1 @@ -Subproject commit 4d9c629eaf4f5d315d772d7e07336ff237369390 +Subproject commit 7bc308155e882743badc71e8465ec8e913022c0a diff --git a/src/main/java/org/saturnclient/impl/features/mixins/render/GameRendererMixin.java b/src/main/java/org/saturnclient/impl/features/mixins/render/GameRendererMixin.java index 324fe01..be65e23 100644 --- a/src/main/java/org/saturnclient/impl/features/mixins/render/GameRendererMixin.java +++ b/src/main/java/org/saturnclient/impl/features/mixins/render/GameRendererMixin.java @@ -49,13 +49,12 @@ public class GameRendererMixin { //? if >=1.21.6 { // From 1.21.6 the menu blur's strength is the vanilla setting, read here every frame. While a - // Saturn screen is open, it's Saturn's background blur instead, faded in with the screen. Glass - // uses the same blur, so this holds even when the screen doesn't blur its whole background. + // Saturn screen is open, it's Saturn's background blur instead, faded in with the screen. @Redirect(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/option/GameOptions;getMenuBackgroundBlurrinessValue()I")) private int useSaturnBlur(GameOptions options) { if (MinecraftClient.getInstance().currentScreen instanceof SaturnScreenFabric saturnScreen) { - return saturnScreen.screen.getBlurRadius(); + return saturnScreen.screen.getBackgroundBlur(); } return options.getMenuBackgroundBlurrinessValue(); diff --git a/src/main/java/org/saturnclient/impl/provider/SaturnProviderImpl.java b/src/main/java/org/saturnclient/impl/provider/SaturnProviderImpl.java index f468ed4..fb9b976 100644 --- a/src/main/java/org/saturnclient/impl/provider/SaturnProviderImpl.java +++ b/src/main/java/org/saturnclient/impl/provider/SaturnProviderImpl.java @@ -76,14 +76,6 @@ public class SaturnProviderImpl implements SaturnProvider { return (IdentifierRef) (Object) playerSkin.get(); } - @Override - public boolean supportsGlass() { - //? if >=1.21.11 { - return true; - //?} else - //return false; - } - @Override public String getModVersion() { return FabricLoader.getInstance().getModContainer(SaturnClient.MOD_ID) diff --git a/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java b/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java index 1c2f589..cee6ba6 100644 --- a/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java +++ b/src/main/java/org/saturnclient/impl/ui/GlassRenderer.java @@ -1,28 +1,38 @@ package org.saturnclient.impl.ui; //? if >=1.21.11 { +import java.io.Reader; +import java.util.HashMap; +import java.util.Map; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.mojang.blaze3d.pipeline.RenderPipeline; import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.textures.FilterMode; import com.mojang.blaze3d.textures.GpuTextureView; +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.TextureSetup; import net.minecraft.client.util.Pool; import net.minecraft.util.Identifier; -// Glass: a copy of the game behind the screen, blurred with vanilla's blur (as strong as Saturn's -// background blur), drawn through each panel's rounded rectangle texture (shaders/core/glass.fsh). -// The copy is made while the screen builds its GUI, before the GUI is drawn, so it holds the world -// or panorama alone. -// TODO: glass before 1.21.11 (#23). Those versions blur the whole background instead. +// 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")) @@ -30,23 +40,29 @@ public final class GlassRenderer { .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 Identifier BLUR = Identifier.ofVanilla("post_effect/blur.json"); + private static final ProjectionMatrix2 PROJECTION = new ProjectionMatrix2("saturn glass", 0.1f, 1000f, false); + // For the blurs' intermediate targets, freed once unused for a few frames private static final Pool POOL = new Pool(3); - private static SimpleFramebuffer backdrop; + // By blur strength. A blur that failed to load is kept as null, so it isn't retried every frame. + private static final Map BLURS = new HashMap<>(); + private static final Map BACKDROPS = new HashMap<>(); private GlassRenderer() { } - // Copies the main framebuffer and blurs the copy. Call once a frame, before drawing glass. - public static void captureBackdrop() { + // Copies the main framebuffer 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 main = SaturnClient.client.getFramebuffer(); int width = main.textureWidth; int height = main.textureHeight; + SimpleFramebuffer backdrop = BACKDROPS.get(blur); if (backdrop == null) { - backdrop = new SimpleFramebuffer("Saturn glass", width, height, false); + backdrop = new SimpleFramebuffer("Saturn glass " + blur, width, height, false); + BACKDROPS.put(blur, backdrop); } else if (backdrop.textureWidth != width || backdrop.textureHeight != height) { backdrop.resize(width, height); } @@ -54,18 +70,53 @@ public final class GlassRenderer { RenderSystem.getDevice().createCommandEncoder().copyTextureToTexture( main.getColorAttachment(), backdrop.getColorAttachment(), 0, 0, 0, 0, 0, width, height); - PostEffectProcessor blur = SaturnClient.client.getShaderLoader().loadPostEffect(BLUR, - DefaultFramebufferSet.MAIN_ONLY); - if (blur != null) { - blur.render(backdrop, POOL); + PostEffectProcessor processor = getBlur(blur); + if (processor != null) { + processor.render(backdrop, POOL); } POOL.decrementLifespan(); } - // The mask, then the blurred copy, as the glass pipeline samples them - public static TextureSetup textures(GpuTextureView mask, GpuSampler maskSampler) { - return TextureSetup.of(mask, maskSampler, backdrop.getColorAttachmentView(), + // 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)); } + + private static PostEffectProcessor getBlur(int blur) { + if (!BLURS.containsKey(blur)) { + BLURS.put(blur, loadBlur(blur)); + } + return BLURS.get(blur); + } + + // Vanilla's blur, with its radius set to blur. Vanilla's own leaves it at 0, which makes the shader + // read the menu blur setting (Saturn's animated background blur while a Saturn screen is open). + private static PostEffectProcessor loadBlur(int blur) { + try (Reader reader = SaturnClient.client.getResourceManager().getResourceOrThrow(BLUR).getReader()) { + JsonElement json = JsonParser.parseReader(reader); + setRadius(json, blur); + + PostEffectPipeline pipeline = PostEffectPipeline.CODEC.parse(JsonOps.INSTANCE, json).getOrThrow(); + return PostEffectProcessor.parseEffect(pipeline, SaturnClient.client.getTextureManager(), + DefaultFramebufferSet.MAIN_ONLY, Identifier.of("saturnclient", "glass_blur_" + blur), PROJECTION); + } catch (Exception e) { + SaturnClient.LOGGER.error("Couldn't load the glass blur", e); + return null; + } + } + + // Sets every "Radius" uniform in the effect + private static void setRadius(JsonElement json, int radius) { + if (json.isJsonArray()) { + json.getAsJsonArray().forEach(element -> setRadius(element, radius)); + } else if (json.isJsonObject()) { + JsonObject object = json.getAsJsonObject(); + if (object.has("name") && object.get("name").getAsString().equals("Radius")) { + object.addProperty("value", (float) radius); + } + object.entrySet().forEach(entry -> setRadius(entry.getValue(), radius)); + } + } } //?} diff --git a/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java b/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java index 5c8b123..d9004bc 100644 --- a/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java +++ b/src/main/java/org/saturnclient/impl/ui/RenderScopeImpl.java @@ -5,7 +5,9 @@ package org.saturnclient.impl.ui; //? if >=1.21.6 { import java.util.ArrayDeque; import java.util.Deque; +import java.util.HashSet; import java.util.Objects; +import java.util.Set; import org.jetbrains.annotations.Nullable; import org.joml.Matrix3x2f; @@ -62,8 +64,8 @@ public class RenderScopeImpl implements RenderScope { private ScissorStack scissorStack; private int opacity = 0xFF << 24; //? if >=1.21.11 { - // Whether the glass backdrop was made for this scope's frame - private boolean backdropCaptured = false; + // The glass blurs made for this scope's frame + private final Set capturedBlurs = new HashSet<>(); //?} public RenderScopeImpl(DrawContext context) { @@ -236,20 +238,19 @@ public class RenderScopeImpl implements RenderScope { //? if >=1.21.11 { @Override - public void drawGlass(IdentifierRef mask, int x, int y, int width, int height) { + 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 (!backdropCaptured) { - GlassRenderer.captureBackdrop(); - backdropCaptured = true; + if (capturedBlurs.add(blur)) { + GlassRenderer.captureBackdrop(blur); } AbstractTexture maskTexture = SaturnClient.client.getTextureManager().getTexture((Identifier) (Object) mask); this.state.addSimpleElement(new TexturedQuadGuiElementRenderState( GlassRenderer.PIPELINE, - GlassRenderer.textures(maskTexture.getGlTextureView(), maskTexture.getSampler()), + GlassRenderer.textures(maskTexture.getGlTextureView(), maskTexture.getSampler(), blur), new Matrix3x2f(this.matrices.stack), x, y, x + width, y + height, 0, 1, 0, 1, color, this.scissorStack.peekLast()));