Properly reset/flush state for crash interceptors (#2133)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-09-25 20:43:05 -07:00
1 parent 888a4de0c9
commit 1babfd968c
15 files changed
+271 -33

No files matched your search

@@ -19,6 +19,7 @@ import org.lwjgl.opengl.GL43;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Arrays;
public final class GLDebug {
@@ -586,6 +587,13 @@ public final class GLDebug {
}
}
public static void resetGroupStack() {
if (debugState instanceof KHRDebugState khr) {
khr.depth = 0;
Arrays.fill(khr.groupStack, null);
}
}
public static void debugMessage(String message) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.debugMessage(message);
@@ -116,6 +116,7 @@ import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -470,7 +471,7 @@ public class GLStateManager {
public static void reset() {
final GLContextState glCtx = ctx();
runningSplash = true;
DisplayListManager.abortCompilation();
glCtx.attribDepth = 0;
for (int i = 0; i < glCtx.attribSets.length; i++) {
glCtx.attribSets[i] = null;
@@ -484,8 +485,40 @@ public class GLStateManager {
((CowStateStack<?>) stack).drain();
}
Arrays.fill(glCtx.savedMvGen, 0);
Arrays.fill(glCtx.savedMvLinearGen, 0);
Arrays.fill(glCtx.savedProjGen, 0);
Arrays.fill(glCtx.savedTexMatGen, 0);
Arrays.fill(glCtx.savedLightingGen, 0);
Arrays.fill(glCtx.savedFragmentGen, 0);
Arrays.fill(glCtx.savedColorGen, 0);
Arrays.fill(glCtx.savedNormalGen, 0);
Arrays.fill(glCtx.savedTexCoordGen, 0);
glCtx.modelViewMatrix.clear();
glCtx.projectionMatrix.clear();
glCtx.mvGeneration++;
glCtx.mvLinearGeneration++;
glCtx.projGeneration++;
glCtx.texMatrixGeneration++;
glCtx.lightingGeneration++;
glCtx.colorGeneration++;
glCtx.texGenGeneration++;
glCtx.clipPlaneGeneration++;
glCtx.fragmentGeneration++;
glCtx.programGeneration++;
ShaderManager.bumpNormalGeneration();
ShaderManager.bumpTexCoordGeneration();
glCtx.textures.texMatIdentityGen++;
glCtx.drawFramebuffer = 0;
glCtx.readFramebuffer = 0;
glCtx.drawFramebufferGeneration++;
if (RENDER_BACKEND != null) {
RENDER_BACKEND.invalidateStateMirror();
replayStateToBackend();
}
}
/**
@@ -3934,6 +3967,18 @@ public class GLStateManager {
final int sampler = samplerUnits.get(i);
if (sampler != 0) RENDER_BACKEND.bindSampler(i, sampler);
}
for (int i = 0; i <= glCtx.maxBoundTextureUnit; i++) {
final TextureBinding b = glCtx.textures.getTextureUnitBindings(i);
RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + i);
RENDER_BACKEND.bindTexture(b.getTarget() == 0 ? GL11.GL_TEXTURE_2D : b.getTarget(), b.getBinding());
}
RENDER_BACKEND.activeTexture(GL13.GL_TEXTURE0 + getActiveTextureUnit());
RENDER_BACKEND.bindVertexArray(glCtx.boundVAO != 0 ? glCtx.boundVAO : defaultVAO);
RENDER_BACKEND.useProgram(glCtx.activeProgram);
ShaderManager.invalidateBoundProgram();
RENDER_BACKEND.bindFramebuffer(GL30.GL_DRAW_FRAMEBUFFER, glCtx.drawFramebuffer);
RENDER_BACKEND.bindFramebuffer(GL30.GL_READ_FRAMEBUFFER, glCtx.readFramebuffer);
}
private static void applyPolygonOffset(GLContextState glCtx) {
@@ -242,6 +242,8 @@ public abstract class RenderBackend {
}
}
public void invalidateStateMirror() {}
public void onFrameEnd() {}
/** Fired by lwjgl3ify Display before a mutation that recreates the swapchain. */
@@ -121,6 +121,12 @@ public final class ShaderManager {
lastBoundProgramId = GLStateManager.getActiveProgram();
}
public static void invalidateBoundProgram() {
final ShaderManager sm = Holder.INSTANCE;
sm.lastBoundProgramId = -1;
sm.currentProgram = null;
}
public void deactivate() {
active = false;
currentProgram = null;
@@ -196,6 +196,10 @@ public class SDLGPURenderBackend extends RenderBackend {
for (ContextState st : registeredStates.snapshot()) st.pipeline.markInputDirty();
}
@Override public void invalidateStateMirror() {
s().invalidateMirror();
}
public static final int CTR_SLOT_WRITES = 0;
public static final int CTR_SLOT_WRITES_ELIDED = 1;
public static final int CTR_SSBO_BINDS = 2;
@@ -398,4 +398,50 @@ public final class ContextState {
cachedScissor = SDL_Rect.calloc();
cachedBlendColor = SDL_FColor.calloc();
}
public void invalidateMirror() {
boundFboId = -1;
boundReadFboId = -1;
viewportDirty = true;
scissorDirty = true;
blendColorDirty = true;
lastBoundPipeline = 0;
lastAppliedRenderPassGen = 0;
lastAppliedStencilRef = Integer.MIN_VALUE;
lastBoundEboHandle = 0;
lastBoundEboIndexSize = -1;
lastBoundEboOffset = 0;
lastAppliedSamplerBindGen = -1;
lastAppliedSamplerProgram = 0;
lastAppliedSamplerCb = 0;
lastFragSamplerProgram = 0;
lastVertSamplerProgram = 0;
lastAppliedStorageTexBindGen = -1;
lastAppliedStorageTexProgram = 0;
lastAppliedStorageTexCb = 0;
lastFragStorageTexProgram = 0;
lastVertStorageTexProgram = 0;
lastAppliedStorageBufBindGen = -1;
lastAppliedStorageBufProgram = 0;
lastAppliedStorageBufCb = 0;
lastFragStorageBufProgram = 0;
lastVertStorageBufProgram = 0;
lastAppliedVboBindGen = -1;
lastAppliedVboBindProgram = 0;
lastAppliedVboBindCb = 0;
lastFlushedSamplerBindGen = -1;
lastFlushedProgram = -1;
lastFlushedPendingMutationGen = -1;
lastPushedCbVs = 0;
lastPushedCbFs = 0;
lastPushedProgramVs = -1;
lastPushedProgramFs = -1;
lastPushedUboGenVs = -1;
lastPushedUboGenFs = -1;
pipeline.markInputDirty();
pipeline.markOutputDirty();
pipeline.markShaderDirty();
currentVao.invalidateInputHash();
needsSeed = true;
}
}
@@ -582,6 +582,17 @@ public class BatchingFontRenderer {
endBatch();
}
public void resetAfterCrash() {
discardDeferredText();
batchDepth = 0;
deferredCmdWatermark = 0;
deferredVertexPos = 0;
deferredIdxPos = 0;
truncateBatchToWatermark();
arenaOwner = null;
flushLastTexture = null;
}
private void deferBatch() {
sealBatchSegment();
if (batchSegments.isEmpty()) {
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.debug.ChunkDebugMinimap;
import com.gtnewhorizons.angelica.debug.flyby.FlybyRoute;
import com.gtnewhorizons.angelica.debug.flyby.FlybyRunner;
import com.gtnewhorizons.angelica.debug.profiling.AsprofRecorder;
import com.gtnewhorizons.angelica.rendering.RenderRecovery;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasDebugScreenHandler;
import com.gtnewhorizons.angelica.rendering.celeritas.CeleritasWorldRenderer;
import net.minecraft.client.Minecraft;
@@ -23,7 +24,19 @@ import java.util.List;
public class AngelicaCommand extends CommandBase {
private static final List<String> SUBCOMMANDS = Arrays.asList("wireframe", "fog", "minimap", "flyby", "profile", "help");
private static final List<String> SUBCOMMANDS = buildSubcommands();
private static List<String> buildSubcommands() {
final List<String> subcommands = new ArrayList<>(Arrays.asList("wireframe", "fog", "minimap", "flyby", "profile", "help"));
if (isDeobfuscatedEnvironment()) {
subcommands.add("crashtest");
}
return subcommands;
}
private static boolean isDeobfuscatedEnvironment() {
return SystemProperties.isDeobf();
}
@Override
public String getCommandName() {
@@ -32,7 +45,7 @@ public class AngelicaCommand extends CommandBase {
@Override
public String getCommandUsage(ICommandSender sender) {
return "/angelica <wireframe|fog|minimap|flyby|profile|help>";
return "/angelica <wireframe|fog|minimap|flyby|profile" + (isDeobfuscatedEnvironment() ? "|crashtest" : "") + "|help>";
}
@Override
@@ -95,6 +108,13 @@ public class AngelicaCommand extends CommandBase {
case "minimap" -> handleMinimap(sender);
case "flyby" -> handleFlyby(sender, args);
case "profile" -> handleProfile(sender, args);
case "crashtest" -> {
if (isDeobfuscatedEnvironment()) {
handleCrashTest(sender);
} else {
sendHelp(sender);
}
}
default -> sendHelp(sender);
}
}
@@ -119,6 +139,11 @@ public class AngelicaCommand extends CommandBase {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Chunk debug minimap: " + state));
}
private void handleCrashTest(ICommandSender sender) {
RenderRecovery.armCrashTest();
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.AQUA + "[Angelica] " + EnumChatFormatting.WHITE + "Crash test armed: fires on the next tile entity render"));
}
private void handleFlyby(ICommandSender sender, String[] args) {
if (args.length >= 2 && "cancel".equalsIgnoreCase(args[1])) {
FlybyRunner.INSTANCE.cancel();
@@ -221,6 +246,9 @@ public class AngelicaCommand extends CommandBase {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica minimap" + EnumChatFormatting.WHITE + " - Toggle chunk debug overlay"));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica flyby <" + FlybyRoute.ids() + ">" + EnumChatFormatting.WHITE + " - Run a deterministic benchmark route"));
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica profile <start|stop|status>" + EnumChatFormatting.WHITE + " - Control async-profiler (JFR) recording"));
if (isDeobfuscatedEnvironment()) {
sender.addChatMessage(new ChatComponentText(EnumChatFormatting.GRAY + " /angelica crashtest" + EnumChatFormatting.WHITE + " - Arm a crash on the next tile entity render"));
}
}
@Override
@@ -1,8 +1,8 @@
package com.gtnewhorizons.angelica.compat.bettercrashes;
import com.gtnewhorizons.angelica.AngelicaMod;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.mixins.interfaces.IRenderGlobalExt;
import com.gtnewhorizons.angelica.rendering.RenderRecovery;
import cpw.mods.fml.common.Optional;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.Tessellator;
@@ -29,7 +29,6 @@ public class BetterCrashesCompat implements StateManager.IResettable {
Tessellator.instance.isDrawing = false;
AngelicaMod.LOGGER.info("Reloading SodiumRenderer");
((IRenderGlobalExt) Minecraft.getMinecraft().renderGlobal).angelica$reload();
AngelicaMod.LOGGER.info("Resetting GLStateManager");
GLStateManager.reset();
RenderRecovery.resetAfterCrash();
}
}
@@ -65,6 +65,12 @@ public class HUDCaching {
framebuffer = new SharedDepthFramebuffer(CustomFramebuffer.STENCIL_BUFFER);
}
public static void resetAfterCrash() {
renderingCacheOverride = false;
GLSMConfig.hudCacheOverride = false;
dirty = true;
}
public static void renderCachedHud(EntityRenderer renderer, GuiIngame ingame, float partialTicks, boolean hasScreen, int mouseX, int mouseY) {
ClientEvent.post(ClientEventType.PRE_RENDER_GUI, renderer, partialTicks);
ClientEvent.post(ClientEventType.PRE_RENDER_HUD, renderer, partialTicks);
@@ -99,10 +105,13 @@ public class HUDCaching {
}
renderingCacheOverride = true;
GLSMConfig.hudCacheOverride = true;
ingame.renderGameOverlay(partialTicks, hasScreen, mouseX, mouseY);
renderingCacheOverride = false;
GLSMConfig.hudCacheOverride = false;
mc.getFramebuffer().bindFramebuffer(false);
try {
ingame.renderGameOverlay(partialTicks, hasScreen, mouseX, mouseY);
} finally {
renderingCacheOverride = false;
GLSMConfig.hudCacheOverride = false;
mc.getFramebuffer().bindFramebuffer(false);
}
} else {
renderer.setupOverlayRendering();
}
@@ -0,0 +1,58 @@
package com.gtnewhorizons.angelica.rendering;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.hudcaching.HUDCaching;
import com.gtnewhorizons.angelica.mixins.interfaces.FontRendererAccessor;
import com.gtnewhorizons.angelica.rendering.tesr.TesrAttribution;
import com.gtnewhorizons.angelica.rendering.tesr.TesrBlendScope;
import com.gtnewhorizons.angelica.rendering.tesr.TesrLifecycle;
import com.prupe.mcpatcher.ctm.CTMUtils;
import net.coderbot.iris.Iris;
import net.coderbot.iris.pipeline.PipelineManager;
import net.coderbot.iris.uniforms.CapturedRenderingState;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.FontRenderer;
public final class RenderRecovery {
private RenderRecovery() {}
private static boolean crashTestArmed;
public static void resetAfterCrash() {
DisplayListManager.abortCompilation();
final PipelineManager pipelineManager = Iris.getPipelineManagerNullable();
if (pipelineManager != null) {
pipelineManager.destroyPipeline();
}
GLStateManager.reset();
GLDebug.resetGroupStack();
final Minecraft mc = Minecraft.getMinecraft();
resetFont(mc.fontRenderer);
resetFont(mc.standardGalacticFontRenderer);
HUDCaching.resetAfterCrash();
mc.getFramebuffer().bindFramebuffer(false);
TesrLifecycle.reset();
TesrBlendScope.reset();
TesrAttribution.currentRenderable = null;
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
CTMUtils.clearCurrentCompact();
}
private static void resetFont(FontRenderer fr) {
if (fr instanceof FontRendererAccessor a && a.angelica$getBatcher() != null) a.angelica$getBatcher().resetAfterCrash();
}
public static void armCrashTest() {
crashTestArmed = true;
}
public static void throwIfCrashTestArmed() {
if (crashTestArmed) {
crashTestArmed = false;
throw new IllegalStateException("angelica crashtest");
}
}
}
@@ -495,6 +495,7 @@ public final class ModelPartBatcher {
groups.clear();
shadowGroups.clear();
templates.clear();
bufferSource.discard();
bufferSource.freeBuffers();
active = false;
shaderPipeline = null;
@@ -118,6 +118,7 @@ public final class TesrBatchRenderer {
}
private void discardDeferred() {
activePass = -1;
deferredFlushPending = false;
pendingDeferredHook = null;
bufferSource.discard();
@@ -0,0 +1,30 @@
package com.gtnewhorizons.angelica.rendering.tesr;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import java.util.Arrays;
public final class TesrBlendScope {
private TesrBlendScope() {}
private static int[] depths = new int[4];
private static int depth;
public static void enter() {
if (depth == depths.length) {
depths = Arrays.copyOf(depths, depth * 2);
}
depths[depth++] = GLStateManager.pushState(StateSet.BLEND);
}
public static void exit() {
if (depth == 0) return;
GLStateManager.popStateTo(depths[--depth]);
}
public static void reset() {
depth = 0;
}
}
@@ -1,9 +1,9 @@
package com.gtnewhorizons.angelica.mixins.early.shaders;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.StateSet;
import com.gtnewhorizons.angelica.rendering.tesr.PassRebindGate;
import com.gtnewhorizons.angelica.rendering.tesr.TesrAttribution;
import com.gtnewhorizons.angelica.rendering.RenderRecovery;
import com.gtnewhorizons.angelica.rendering.tesr.TesrBlendScope;
import com.gtnewhorizons.angelica.rendering.tesr.TesrProviderDispatch;
import com.prupe.mcpatcher.ctm.CTMUtils;
import net.coderbot.iris.uniforms.CapturedRenderingState;
@@ -15,39 +15,29 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import java.util.Arrays;
@Mixin(TileEntityRendererDispatcher.class)
public class MixinTileEntityRendererDispatcher {
@Unique private static int[] iris$depths = new int[4];
@Unique private static int iris$n;
@Inject(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDF)V", at = @At("HEAD"))
private void iris$setBlockEntityId(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) {
if (iris$n == iris$depths.length) {
iris$depths = Arrays.copyOf(iris$depths, iris$n * 2);
}
iris$depths[iris$n++] = GLStateManager.pushState(StateSet.BLEND);
private void iris$beginBlockEntity(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) {
RenderRecovery.throwIfCrashTestArmed();
TesrBlendScope.enter();
CTMUtils.clearCurrentCompact();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(iris$resolveBlockEntityId(te));
TesrAttribution.currentRenderable = te != null ? te.getClass() : null;
// Rebind Iris's pass in case the previous TESR changed it
PassRebindGate.rebindIfDirty();
}
@Inject(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDF)V", at = @At("RETURN"))
private void iris$endBlockEntity(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) {
CTMUtils.clearCurrentCompact();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
TesrAttribution.currentRenderable = null;
TesrBlendScope.exit();
}
@Unique
private static int iris$resolveBlockEntityId(TileEntity te) {
return TesrProviderDispatch.resolveBlockEntityId(te);
}
@Inject(method = "renderTileEntityAt(Lnet/minecraft/tileentity/TileEntity;DDDF)V", at = @At("RETURN"))
private void iris$resetBlockEntityId(TileEntity te, double x, double y, double z, float partialTicks, CallbackInfo ci) {
CTMUtils.clearCurrentCompact();
CapturedRenderingState.INSTANCE.setCurrentBlockEntity(0);
TesrAttribution.currentRenderable = null;
if (iris$n == 0) return;
GLStateManager.popStateTo(iris$depths[--iris$n]);
}
}