Add GUI scale and glass on every version #40

Merged
selimaj-dev merged 11 commits from glass-blur into master 2026-09-28 20:02:50 +00:00
5 changed files with 80 additions and 37 deletions
Showing only changes of commit 4e00934353 - Show all commits
+1 -1
Submodule common updated: 4d9c629eaf...7bc308155e
@@ -49,13 +49,12 @@ public class GameRendererMixin {
//? if >=1.21.6 { //? if >=1.21.6 {
// From 1.21.6 the menu blur's strength is the vanilla setting, read here every frame. While a // 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 // Saturn screen is open, it's Saturn's background blur instead, faded in with the screen.
// uses the same blur, so this holds even when the screen doesn't blur its whole background.
@Redirect(method = "render", at = @At(value = "INVOKE", @Redirect(method = "render", at = @At(value = "INVOKE",
target = "Lnet/minecraft/client/option/GameOptions;getMenuBackgroundBlurrinessValue()I")) target = "Lnet/minecraft/client/option/GameOptions;getMenuBackgroundBlurrinessValue()I"))
private int useSaturnBlur(GameOptions options) { private int useSaturnBlur(GameOptions options) {
if (MinecraftClient.getInstance().currentScreen instanceof SaturnScreenFabric saturnScreen) { if (MinecraftClient.getInstance().currentScreen instanceof SaturnScreenFabric saturnScreen) {
return saturnScreen.screen.getBlurRadius(); return saturnScreen.screen.getBackgroundBlur();
} }
return options.getMenuBackgroundBlurrinessValue(); return options.getMenuBackgroundBlurrinessValue();
@@ -76,14 +76,6 @@ public class SaturnProviderImpl implements SaturnProvider {
return (IdentifierRef) (Object) playerSkin.get(); return (IdentifierRef) (Object) playerSkin.get();
} }
@Override
public boolean supportsGlass() {
//? if >=1.21.11 {
return true;
//?} else
//return false;
}
@Override @Override
public String getModVersion() { public String getModVersion() {
return FabricLoader.getInstance().getModContainer(SaturnClient.MOD_ID) return FabricLoader.getInstance().getModContainer(SaturnClient.MOD_ID)
@@ -1,28 +1,38 @@
package org.saturnclient.impl.ui; package org.saturnclient.impl.ui;
//? if >=1.21.11 { //? 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.pipeline.RenderPipeline;
import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.textures.FilterMode; import com.mojang.blaze3d.textures.FilterMode;
import com.mojang.blaze3d.textures.GpuTextureView; import com.mojang.blaze3d.textures.GpuTextureView;
import com.mojang.serialization.JsonOps;
import org.saturnclient.saturnclient.SaturnClient; import org.saturnclient.saturnclient.SaturnClient;
import net.minecraft.client.gl.Framebuffer; import net.minecraft.client.gl.Framebuffer;
import net.minecraft.client.gl.GpuSampler; import net.minecraft.client.gl.GpuSampler;
import net.minecraft.client.gl.PostEffectPipeline;
import net.minecraft.client.gl.PostEffectProcessor; import net.minecraft.client.gl.PostEffectProcessor;
import net.minecraft.client.gl.RenderPipelines; import net.minecraft.client.gl.RenderPipelines;
import net.minecraft.client.gl.SimpleFramebuffer; import net.minecraft.client.gl.SimpleFramebuffer;
import net.minecraft.client.render.DefaultFramebufferSet; import net.minecraft.client.render.DefaultFramebufferSet;
import net.minecraft.client.render.ProjectionMatrix2;
import net.minecraft.client.texture.TextureSetup; import net.minecraft.client.texture.TextureSetup;
import net.minecraft.client.util.Pool; import net.minecraft.client.util.Pool;
import net.minecraft.util.Identifier; import net.minecraft.util.Identifier;
// Glass: a copy of the game behind the screen, blurred with vanilla's blur (as strong as Saturn's // Glass: a copy of the game behind the screen, blurred, drawn through each panel's rounded rectangle
// background blur), drawn through each panel's rounded rectangle texture (shaders/core/glass.fsh). // texture (shaders/core/glass.fsh). There's a copy for each blur strength in use (menus and widgets
// The copy is made while the screen builds its GUI, before the GUI is drawn, so it holds the world // have their own), made while the screen builds its GUI, before the GUI is drawn, so it holds the
// or panorama alone. // world or panorama alone, without the screen's own animated background blur.
// TODO: glass before 1.21.11 (#23). Those versions blur the whole background instead. // TODO: glass before 1.21.11 (#23). Those versions draw translucent panels without it.
public final class GlassRenderer { public final class GlassRenderer {
public static final RenderPipeline PIPELINE = RenderPipeline.builder(RenderPipelines.POSITION_TEX_COLOR_SNIPPET) public static final RenderPipeline PIPELINE = RenderPipeline.builder(RenderPipelines.POSITION_TEX_COLOR_SNIPPET)
.withLocation(Identifier.of("saturnclient", "pipeline/glass")) .withLocation(Identifier.of("saturnclient", "pipeline/glass"))
@@ -30,23 +40,29 @@ public final class GlassRenderer {
.withSampler("Sampler1") .withSampler("Sampler1")
.build(); .build();
private static final Identifier BLUR = Identifier.ofVanilla("blur"); private static final Identifier BLUR = Identifier.ofVanilla("post_effect/blur.json");
// For the blur's intermediate target, freed once unused for a few frames 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 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<Integer, PostEffectProcessor> BLURS = new HashMap<>();
private static final Map<Integer, SimpleFramebuffer> BACKDROPS = new HashMap<>();
private GlassRenderer() { private GlassRenderer() {
} }
// Copies the main framebuffer and blurs the copy. Call once a frame, before drawing glass. // Copies the main framebuffer and blurs the copy by blur. Call once a frame for each blur, before
public static void captureBackdrop() { // drawing glass with it.
public static void captureBackdrop(int blur) {
Framebuffer main = SaturnClient.client.getFramebuffer(); Framebuffer main = SaturnClient.client.getFramebuffer();
int width = main.textureWidth; int width = main.textureWidth;
int height = main.textureHeight; int height = main.textureHeight;
SimpleFramebuffer backdrop = BACKDROPS.get(blur);
if (backdrop == null) { 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) { } else if (backdrop.textureWidth != width || backdrop.textureHeight != height) {
backdrop.resize(width, height); backdrop.resize(width, height);
} }
@@ -54,18 +70,53 @@ public final class GlassRenderer {
RenderSystem.getDevice().createCommandEncoder().copyTextureToTexture( RenderSystem.getDevice().createCommandEncoder().copyTextureToTexture(
main.getColorAttachment(), backdrop.getColorAttachment(), 0, 0, 0, 0, 0, width, height); main.getColorAttachment(), backdrop.getColorAttachment(), 0, 0, 0, 0, 0, width, height);
PostEffectProcessor blur = SaturnClient.client.getShaderLoader().loadPostEffect(BLUR, PostEffectProcessor processor = getBlur(blur);
DefaultFramebufferSet.MAIN_ONLY); if (processor != null) {
if (blur != null) { processor.render(backdrop, POOL);
blur.render(backdrop, POOL);
} }
POOL.decrementLifespan(); POOL.decrementLifespan();
} }
// The mask, then the blurred copy, as the glass pipeline samples them // The mask, then the copy blurred by blur, as the glass pipeline samples them
public static TextureSetup textures(GpuTextureView mask, GpuSampler maskSampler) { public static TextureSetup textures(GpuTextureView mask, GpuSampler maskSampler, int blur) {
return TextureSetup.of(mask, maskSampler, backdrop.getColorAttachmentView(), return TextureSetup.of(mask, maskSampler, BACKDROPS.get(blur).getColorAttachmentView(),
RenderSystem.getSamplerCache().get(FilterMode.LINEAR)); 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));
}
}
} }
//?} //?}
@@ -5,7 +5,9 @@ package org.saturnclient.impl.ui;
//? if >=1.21.6 { //? if >=1.21.6 {
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import java.util.HashSet;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.joml.Matrix3x2f; import org.joml.Matrix3x2f;
@@ -62,8 +64,8 @@ public class RenderScopeImpl implements RenderScope {
private ScissorStack scissorStack; private ScissorStack scissorStack;
private int opacity = 0xFF << 24; private int opacity = 0xFF << 24;
//? if >=1.21.11 { //? if >=1.21.11 {
// Whether the glass backdrop was made for this scope's frame // The glass blurs made for this scope's frame
private boolean backdropCaptured = false; private final Set<Integer> capturedBlurs = new HashSet<>();
//?} //?}
public RenderScopeImpl(DrawContext context) { public RenderScopeImpl(DrawContext context) {
@@ -236,20 +238,19 @@ public class RenderScopeImpl implements RenderScope {
//? if >=1.21.11 { //? if >=1.21.11 {
@Override @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); int color = getColor(0xFFFFFFFF);
if (mask == null || color >>> 24 == 0) if (mask == null || color >>> 24 == 0)
return; return;
if (!backdropCaptured) { if (capturedBlurs.add(blur)) {
GlassRenderer.captureBackdrop(); GlassRenderer.captureBackdrop(blur);
backdropCaptured = true;
} }
AbstractTexture maskTexture = SaturnClient.client.getTextureManager().getTexture((Identifier) (Object) mask); AbstractTexture maskTexture = SaturnClient.client.getTextureManager().getTexture((Identifier) (Object) mask);
this.state.addSimpleElement(new TexturedQuadGuiElementRenderState( this.state.addSimpleElement(new TexturedQuadGuiElementRenderState(
GlassRenderer.PIPELINE, GlassRenderer.PIPELINE,
GlassRenderer.textures(maskTexture.getGlTextureView(), maskTexture.getSampler()), GlassRenderer.textures(maskTexture.getGlTextureView(), maskTexture.getSampler(), blur),
new Matrix3x2f(this.matrices.stack), new Matrix3x2f(this.matrices.stack),
x, y, x + width, y + height, 0, 1, 0, 1, color, x, y, x + width, y + height, 0, 1, 0, 1, color,
this.scissorStack.peekLast())); this.scissorStack.peekLast()));