From 05e7b951c49c66d455d78904f782c759127d0db4 Mon Sep 17 00:00:00 2001 From: gnilobaiter <104412752+gnilobaiter@users.noreply.github.com> Date: Wed, 30 Sep 2026 16:27:13 +0300 Subject: [PATCH] Fix: Clear stale Skyboxify sky layers to avoid frametime spikes (#108) Skyboxify queues its sky layers every frame and only clears the queue when the vanilla sky pass renders. When the sky pass is skipped (e.g. underwater) the queue grows by a few layers per frame and is drawn all at once when the sky returns. With custom sky packs and a high render scale this shows up as a large frametime spike when surfacing. Clear the queue after the level has rendered via a reflective, optional compat hook. It is a no-op when Skyboxify is absent or the queue is already empty. --- .../compat/skyboxify/SkyboxifyCompat.java | 71 +++++++++++++++++++ .../renderscale/mixin/MixinGameRenderer.java | 1 + 2 files changed, 72 insertions(+) create mode 100644 src/main/java/dev/zelo/renderscale/compat/skyboxify/SkyboxifyCompat.java diff --git a/src/main/java/dev/zelo/renderscale/compat/skyboxify/SkyboxifyCompat.java b/src/main/java/dev/zelo/renderscale/compat/skyboxify/SkyboxifyCompat.java new file mode 100644 index 0000000..d053c75 --- /dev/null +++ b/src/main/java/dev/zelo/renderscale/compat/skyboxify/SkyboxifyCompat.java @@ -0,0 +1,71 @@ +package dev.zelo.renderscale.compat.skyboxify; + +import dev.zelo.renderscale.Constants; +import net.minecraft.client.Minecraft; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * Skyboxify queues its sky layers every frame during extraction, but only clears the queue when the vanilla sky + * pass actually renders. When vanilla skips the sky (e.g. underwater), the queue keeps growing and is drawn all at + * once when the sky comes back, causing a large frametime spike that scales with the render resolution. + * Clearing the queue after the level has rendered is a no-op in normal frames. + *
+ * Everything is reflective so Skyboxify stays an optional dependency. + */ +public final class SkyboxifyCompat { + private static boolean initialized; + private static Field skyRendererField; + private static Field featureRendererField; + private static Method clearMethod; + + private SkyboxifyCompat() { + } + + public static void clearStaleSkySubmits() { + if (!initialized) { + initialized = true; + init(); + } + if (clearMethod == null) return; + + try { + Object skyRenderer = skyRendererField.get(Minecraft.getInstance().levelRenderer); + if (skyRenderer == null) return; + Object featureRenderer = featureRendererField.get(skyRenderer); + if (featureRenderer != null) clearMethod.invoke(featureRenderer); + } catch (ReflectiveOperationException | RuntimeException e) { + clearMethod = null; + Constants.LOG.warn("Skyboxify compatibility disabled", e); + } + } + + private static void init() { + try { + Class> featureRenderer = Class.forName("btw.lowercase.skyboxify.skybox.renderer.FeatureRenderer"); + Class> skyRenderer = Class.forName("net.minecraft.client.renderer.SkyRenderer"); + + Field levelRendererSky = null; + for (Field field : Minecraft.getInstance().levelRenderer.getClass().getDeclaredFields()) { + if (field.getType() == skyRenderer) { + levelRendererSky = field; + break; + } + } + if (levelRendererSky == null) return; + + Field renderer = skyRenderer.getDeclaredField("skyboxify$skyFeatureRenderer"); + Method clear = featureRenderer.getMethod("clear"); + levelRendererSky.setAccessible(true); + renderer.setAccessible(true); + + skyRendererField = levelRendererSky; + featureRendererField = renderer; + clearMethod = clear; + Constants.LOG.info("Skyboxify detected: stale sky layers will be cleared each frame"); + } catch (ReflectiveOperationException | LinkageError ignored) { + // Skyboxify is not installed or has a different layout + } + } +} diff --git a/src/main/java/dev/zelo/renderscale/mixin/MixinGameRenderer.java b/src/main/java/dev/zelo/renderscale/mixin/MixinGameRenderer.java index 28c1149..81ff4d6 100644 --- a/src/main/java/dev/zelo/renderscale/mixin/MixinGameRenderer.java +++ b/src/main/java/dev/zelo/renderscale/mixin/MixinGameRenderer.java @@ -86,5 +86,6 @@ private void handBack(CallbackInfo callbackInfo) { windowState.guiScale = renderScale$guiScale; //?} RenderScale.getInstance().setShouldScale(false); + dev.zelo.renderscale.compat.skyboxify.SkyboxifyCompat.clearStaleSkySubmits(); } }