Stop resetting SDL GPU to OpenGL on unrelated crashes (#2266)

This commit is contained in:
Jason Mitchell authored and GitHub committed 2026-10-16 21:33:06 -07:00
1 parent f81ee4b03a
commit 8e1809622c
9 files changed
+125 -77

No files matched your search

@@ -245,7 +245,6 @@ public class GLContextState {
public final int[] clientAttribSavedPackBuffer = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final VAOManager.ClientArrays[] clientAttribSavedArrays = new VAOManager.ClientArrays[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public int clientAttribStackPointer = 0;
public float maxPointSize = Float.NaN;
public int restoreChangedMask;
public long restoreUnitChangedMask;
public final BlendState vanillaBlendBefore = new BlendState();
@@ -415,6 +415,7 @@ public class GLStateManager {
// This setting varies depending on driver, so it gets queried at runtime
public static int DEFAULT_DRAW_BUFFER = GL11.GL_BACK;
private static float maxPointSize;
private static volatile Thread CurrentThread = MainThread;
@Setter @Getter private static boolean runningSplash = true;
@@ -517,18 +518,6 @@ public class GLStateManager {
}
}
private static float maxPointSize(GLContextState glCtx) {
if (Float.isNaN(glCtx.maxPointSize)) {
final FloatBuffer range = glCtx.queryScratch;
range.clear();
RENDER_BACKEND.getFloat(GL11.GL_POINT_SIZE_RANGE, range);
final float smooth = range.get(1);
RENDER_BACKEND.getFloat(GL12.GL_ALIASED_POINT_SIZE_RANGE, range);
glCtx.maxPointSize = Math.max(smooth, range.get(1));
}
return glCtx.maxPointSize;
}
private static <T extends CowStateStack<?>> T mod(T s) {
s.beforeModify();
return s;
@@ -777,6 +766,13 @@ public class GLStateManager {
initCallback.run();
}
final FloatBuffer range = glCtx.queryScratch;
range.clear();
RENDER_BACKEND.getFloat(GL11.GL_POINT_SIZE_RANGE, range);
final float smooth = range.get(1);
RENDER_BACKEND.getFloat(GL12.GL_ALIASED_POINT_SIZE_RANGE, range);
maxPointSize = Math.max(smooth, range.get(1));
// Drain any pending GL errors from initialization. In core profile, some legacy queries may generate GL_INVALID_ENUM. The splash thread inherits the
// DrawableGL context and its error state, so stale errors here would cause SplashProgress.checkGLError() to fail.
int err;
@@ -1482,7 +1478,7 @@ public class GLStateManager {
case GL11.GL_LINE_WIDTH -> glCtx.lineState.getWidth();
case GL11.GL_POINT_SIZE -> glCtx.pointState.getSize();
case GL14.GL_POINT_SIZE_MIN -> glCtx.pointState.getMinSize();
case GL14.GL_POINT_SIZE_MAX -> Float.isNaN(glCtx.pointState.getMaxSize()) ? maxPointSize(glCtx) : glCtx.pointState.getMaxSize();
case GL14.GL_POINT_SIZE_MAX -> Float.isNaN(glCtx.pointState.getMaxSize()) ? maxPointSize : glCtx.pointState.getMaxSize();
case GL14.GL_POINT_FADE_THRESHOLD_SIZE -> glCtx.pointState.getFadeThreshold();
default -> {
final int desc = isCachingEnabled() ? LegacyQueries.desc(pname) : 0;
@@ -21,7 +21,11 @@ public final class BackendOptions {
}
public static String sdlGpuDriver() {
return SystemProperties.SDL_GPU_DRIVER_OVERRIDE != null ? SystemProperties.SDL_GPU_DRIVER_OVERRIDE : configDriver;
return sdlGpuDriver(configDriver);
}
public static String sdlGpuDriver(String driver) {
return SystemProperties.SDL_GPU_DRIVER_OVERRIDE != null ? SystemProperties.SDL_GPU_DRIVER_OVERRIDE : driver;
}
public static boolean configSdlGpu() {
@@ -1,55 +1,96 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.google.common.primitives.Ints;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
public final class BackendStartGuard {
private static final Logger LOGGER = LogManager.getLogger("Angelica/BackendStartGuard");
private static File pending;
static final int STRIKE_LIMIT = 3;
private static File marker;
private static File verified;
private static String key;
private static int strikes;
private static boolean pending;
private static boolean tripped;
private BackendStartGuard() {}
public static boolean begin(File marker, boolean sdlGpuConfigured, boolean flagForced) {
pending = null;
tripped = false;
if (!sdlGpuConfigured || flagForced) {
public static boolean begin(File dir, boolean sdlGpuConfigured, boolean flagForced, int requirementsRevision, String requestedDriver) {
marker = new File(dir, "sdlgpu-starting");
verified = new File(dir, "sdlgpu-verified");
key = requirementsRevision + "/" + requestedDriver;
pending = tripped = false;
if (flagForced) return false;
if (!sdlGpuConfigured) {
delete(marker);
return false;
}
if (marker.isFile()) {
if (key.equals(read(verified))) return false;
final String count = read(marker);
final Integer parsed = count == null ? null : Ints.tryParse(count.trim());
strikes = parsed == null ? 0 : parsed;
if (strikes >= STRIKE_LIMIT) {
LOGGER.warn("SDL GPU failed to bring up the device {} times in a row; resetting the render backend to OpenGL", strikes);
delete(marker);
tripped = true;
return true;
}
try {
final File dir = marker.getParentFile();
if (dir != null && !dir.isDirectory() && !dir.mkdirs()) throw new IOException("cannot create " + dir);
if (marker.createNewFile()) pending = marker;
} catch (IOException e) {
LOGGER.warn("Could not write {}: {}", marker, e.toString());
if (strikes == STRIKE_LIMIT - 1) {
LOGGER.warn("SDL GPU failed to bring up the device {} times in a row; one more and the render backend switches to OpenGL", strikes);
} else if (strikes > 0) {
LOGGER.info("The last start ended while bringing up the SDL GPU device");
}
pending = true;
return false;
}
public static void finish() {
if (pending == null) return;
delete(pending);
pending = null;
public static void arm() {
if (pending) write(marker, Integer.toString(strikes + 1));
}
public static void disarm() {
if (pending) delete(marker);
}
public static void latch() {
if (!pending) return;
pending = false;
write(verified, key);
LOGGER.info("SDL GPU booted; the startup guard stays off until the requirements revision or driver changes");
}
public static boolean tripped() {
return tripped;
}
private static void delete(File marker) {
if (marker.isFile() && !marker.delete()) {
LOGGER.warn("Could not delete {}", marker);
private static String read(File file) {
try {
return file.isFile() ? new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8) : null;
} catch (IOException e) {
LOGGER.warn("Could not read {}: {}", file, e.toString());
return null;
}
}
private static void write(File file, String content) {
try {
Files.createDirectories(file.toPath().getParent());
Files.write(file.toPath(), content.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
LOGGER.warn("Could not write {}: {}", file, e.toString());
}
}
private static void delete(File file) {
if (file.isFile() && !file.delete()) LOGGER.warn("Could not delete {}", file);
}
}
@@ -4,7 +4,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -12,54 +11,54 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class BackendStartGuardTest {
private static File marker(Path dir) {
return dir.resolve("angelica").resolve("sdlgpu-starting").toFile();
@TempDir
Path dir;
private boolean boot() {
return boot(1);
}
private boolean boot(int revision) {
return BackendStartGuard.begin(dir.toFile(), true, false, revision, "");
}
private File marker() {
return dir.resolve("sdlgpu-starting").toFile();
}
@Test
void firstStartCreatesTheMarkerAndFinishRemovesIt(@TempDir Path dir) {
final File marker = marker(dir);
assertFalse(BackendStartGuard.begin(marker, true, false));
assertFalse(BackendStartGuard.tripped());
assertTrue(marker.isFile());
BackendStartGuard.finish();
assertFalse(marker.exists());
void bootsThatReachTheBackendNeverCountAsStrikes() {
for (int i = 0; i < BackendStartGuard.STRIKE_LIMIT; i++) {
assertFalse(boot());
BackendStartGuard.arm();
BackendStartGuard.disarm();
}
assertFalse(boot());
}
@Test
void leftoverMarkerTripsOnceAndIsRemoved(@TempDir Path dir) {
final File marker = marker(dir);
assertFalse(BackendStartGuard.begin(marker, true, false));
assertTrue(BackendStartGuard.begin(marker, true, false));
void thirdDeathInTheDeviceWindowResets() {
for (int i = 0; i < BackendStartGuard.STRIKE_LIMIT; i++) {
assertFalse(boot());
BackendStartGuard.arm();
}
assertTrue(boot());
assertTrue(BackendStartGuard.tripped());
assertFalse(marker.exists());
BackendStartGuard.finish();
assertFalse(marker.exists());
assertFalse(BackendStartGuard.begin(marker, true, false));
assertFalse(BackendStartGuard.tripped());
BackendStartGuard.finish();
assertFalse(marker().exists());
}
@Test
void openGlConfigWritesNoMarkerAndClearsAStaleOne(@TempDir Path dir) throws IOException {
final File marker = marker(dir);
assertFalse(BackendStartGuard.begin(marker, false, false));
assertFalse(marker.exists());
assertTrue(marker.getParentFile().mkdirs());
assertTrue(marker.createNewFile());
assertFalse(BackendStartGuard.begin(marker, false, false));
assertFalse(BackendStartGuard.tripped());
assertFalse(marker.exists());
}
void latchStopsArmingUntilTheRequirementsRevisionChanges() {
assertFalse(boot());
BackendStartGuard.arm();
BackendStartGuard.disarm();
BackendStartGuard.latch();
assertFalse(boot());
BackendStartGuard.arm();
assertFalse(marker().exists());
@Test
void forcedFlagNeverTripsOrWrites(@TempDir Path dir) throws IOException {
final File marker = marker(dir);
assertFalse(BackendStartGuard.begin(marker, true, true));
assertFalse(marker.exists());
assertTrue(marker.getParentFile().mkdirs());
assertTrue(marker.createNewFile());
assertFalse(BackendStartGuard.begin(marker, true, true));
assertFalse(BackendStartGuard.tripped());
assertFalse(boot(2));
BackendStartGuard.arm();
assertTrue(marker().isFile());
}
}
@@ -4,6 +4,7 @@ import static org.lwjgl.sdl.SDLVideo.SDL_PROP_WINDOW_CREATE_METAL_BOOLEAN;
import com.gtnewhorizons.angelica.glsm.backend.BackendOptions;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendStartGuard;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.loading.DependencyVerifier;
import com.gtnewhorizons.angelica.glsm.loading.DependencyVerifier.Check;
@@ -55,6 +56,7 @@ public final class SDLGPUGate {
}
public static final String LWJGL3IFY_MIN_VERSION = "3.0.25";
public static final int REQUIREMENTS_REVISION = 1;
private static final List<Check> REQUIREMENTS = List.of(
new Check("/org/lwjgl/sdl/SDLGPU.class", "no lwjgl-sdl on the classpath"),
@@ -90,12 +92,16 @@ public final class SDLGPUGate {
boolean ok;
try {
Sys.initialize();
BackendStartGuard.arm();
ok = MainThreadExec.runOnMainThread(device()::createDevice);
} catch (Throwable t) {
LOG.error("SDL GPU device probe failed", t);
ok = false;
}
if (!ok) resetSdlLogging();
if (!ok) {
resetSdlLogging();
BackendStartGuard.disarm();
}
deviceReady = ok;
return ok;
}
@@ -182,6 +188,7 @@ public final class SDLGPUGate {
throw new IllegalStateException("Celeritas already selected the SDL GPU LWJGL service; falling back to OpenGL would leave it bound to a dead backend");
}
SDLGPULWJGLService.unbind();
BackendStartGuard.disarm();
disarmed = true;
engaged = false;
deviceReady = false;
@@ -72,8 +72,7 @@ public final class AngelicaClientTweaker implements IFMLLoadingPlugin, IEarlyMix
// Angelica Config
ConfigurationManager.registerConfig(AngelicaConfig.class);
TracyOptions.latch(AngelicaConfig.enableTracy, AngelicaConfig.tracyAllowRemote, AngelicaConfig.tracyFineZones, AngelicaConfig.tracyMaxSrcLocs);
if (BackendStartGuard.begin(new File(Launch.minecraftHome, "angelica" + File.separator + "sdlgpu-starting"), AngelicaConfig.sdlGpuConfigured(), SystemProperties.SDL_GPU_OVERRIDE != null)) {
LOGGER.warn("The last start with SDL GPU did not reach the main menu; resetting the render backend to OpenGL");
if (BackendStartGuard.begin(new File(Launch.minecraftHome, "angelica"), AngelicaConfig.sdlGpuConfigured(), SystemProperties.SDL_GPU_OVERRIDE != null, SDLGPUGate.REQUIREMENTS_REVISION, BackendOptions.sdlGpuDriver(AngelicaConfig.sdlGpuDriverName()))) {
AngelicaConfig.renderBackend = RenderBackendChoice.OPENGL;
ConfigurationManager.save(AngelicaConfig.class);
}
@@ -61,6 +61,7 @@ import com.gtnewhorizons.angelica.debug.TPSGraph;
import com.gtnewhorizons.angelica.dynamiclights.DynamicLights;
import com.gtnewhorizons.angelica.dynamiclights.config.EntityLightConfig;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendStartGuard;
import com.gtnewhorizons.angelica.glsm.backend.VSyncMode;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
@@ -383,7 +384,7 @@ public final class ClientProxy extends CommonProxy {
@SubscribeEvent(priority = EventPriority.LOWEST)
public void onGuiOpen(GuiOpenEvent event) {
BackendStartGuard.finish();
if (BackendManager.RENDER_BACKEND.isSDLGPU()) BackendStartGuard.latch();
if (!event.isCanceled()) {
if (event.gui instanceof GuiMainMenu && gameStartTime == -1) {
gameStartTime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
@@ -8,6 +8,7 @@ import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.compat.DriverCompatabilityCheck;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.BackendStartGuard;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
@@ -47,6 +48,7 @@ public class MixinInitGLStateManager {
.enableDSA(AngelicaConfig.enableDSA)
.postInitCallback(SelectionBoxRenderer::init)
.build());
BackendStartGuard.disarm();
GLSMConfig.extendedAttribsExpected = AngelicaConfig.enableIris;