From 629cfec6305e4b7324563344436d5f13daa31ea2 Mon Sep 17 00:00:00 2001 From: David Gheghea <71325082+DavidutzDev@users.noreply.github.com> Date: Sun, 18 Oct 2026 14:54:03 +0200 Subject: [PATCH] fix(nvidia): keep threaded optimizations off for driver 580 With __GL_THREADED_OPTIMIZATIONS=1, NVIDIA 580 (the last branch for Pascal GPUs) with egl-x11 1.0.6 rejects the X11 EGLDisplay with EGL_BAD_DISPLAY on every thread that has a context current. A native EGL program reproduces it outside Darling. In the Riot client the main thread loses EGL after its first window frame, so CGLCreateContext fails with error 10004 and the game window stays black. Default the launcher to 0; setting the variable restores 1. CGLDestroyWindow also returns early for the NULL window CGLGetWindow gives when eglCreateWindowSurface fails, instead of passing EGL_NO_SURFACE on. Co-Authored-By: Claude Opus 5.5 --- docs/TROUBLESHOOTING.md | 21 +++++++++++++++++++++ patches/darling.patch | 27 +++++++++++++++------------ tools/run-riot-client.sh | 5 ++++- 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 7d8b3f4..3075637 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -348,6 +348,27 @@ with sway/Xwayland using CPU readback. The initial polling change improved their reported rate from about 20 to 27 FPS. Those observations predate the narrowed integration; they do not establish hybrid-GPU or general NVIDIA support. +### NVIDIA 580 (Pascal and older): black game window + +Driver 580 is the last branch for Pascal (GTX 10-series) and older GPUs. With +`__GL_THREADED_OPTIMIZATIONS=1`, this driver with egl-x11 1.0.6 rejects the X11 +`EGLDisplay` with `EGL_BAD_DISPLAY` on every thread that has a context current. +The display stays valid on other threads, and `eglReleaseThread()` makes it work +again on the failing one. A small native EGL program outside Darling shows the +same: `eglQueryString(display, EGL_VENDOR)` after `eglMakeCurrent` returns +`NVIDIA` without the variable and fails with `EGL_BAD_DISPLAY` with it. + +In the Riot client the main thread loses EGL right after its first window frame; +`CGLCreateContext` then fails with CGL error 10004 and, if another display is +opened, glvnd hands it to Mesa's software renderer, which cannot present the +game's NVIDIA Vulkan frames. The launcher therefore leaves threaded optimizations +off (`__GL_THREADED_OPTIMIZATIONS=0`). Setting the variable to `1` before launch +restores the old behaviour. + +Tested on a GTX 1060 6GB with driver 580.178.04 on NixOS, Hyprland and Xwayland: +the client and the tutorial match render on the NVIDIA GPU. Newer drivers were not +retested with the option off. + ### Host-side checks These are host settings, not game settings. Run on AC power, and use a performance power profile (`powerprofilesctl set performance`, or `asusctl profile -P Performance` on ASUS laptops). On the tested desktop the game is limited mostly by its own main thread on the CPU, not by the GPU (see [status](STATUS.md#game-fps-2026-10-1314)), so laptop CPU power limits matter as much as the GPU choice. diff --git a/patches/darling.patch b/patches/darling.patch index 66477f9..49e767f 100644 --- a/patches/darling.patch +++ b/patches/darling.patch @@ -6257,7 +6257,7 @@ index deca16b87..d75d5e22d 100644 set_property(TARGET OpenGL APPEND_STRING PROPERTY LINK_FLAGS diff --git a/src/frameworks/OpenGL/OpenGL.c b/src/frameworks/OpenGL/OpenGL.c -index ffd7fe054..1ff212d34 100644 +index ffd7fe054..5b5712ea2 100644 --- a/src/frameworks/OpenGL/OpenGL.c +++ b/src/frameworks/OpenGL/OpenGL.c @@ -2,11 +2,22 @@ @@ -6527,11 +6527,14 @@ index ffd7fe054..1ff212d34 100644 if (surface == EGL_NO_SURFACE) { return NULL; -@@ -171,14 +309,18 @@ CGLWindowRef CGLGetWindow(void *native_window) { +@@ -171,14 +309,21 @@ CGLWindowRef CGLGetWindow(void *native_window) { } CGL_EXPORT void CGLDestroyWindow(CGLWindowRef window) { - eglDestroySurface(display, (EGLSurface) window); ++ /* CGLGetWindow returns NULL when eglCreateWindowSurface fails. */ ++ if (window == NULL) ++ return; + p_eglDestroySurface(display, (EGLSurface) window); } @@ -6548,7 +6551,7 @@ index ffd7fe054..1ff212d34 100644 } static pthread_key_t current_context_key; -@@ -198,18 +340,42 @@ CGLContextObj CGLGetCurrentContext(void) { +@@ -198,18 +343,42 @@ CGLContextObj CGLGetCurrentContext(void) { return pthread_getspecific(key); } @@ -6596,7 +6599,7 @@ index ffd7fe054..1ff212d34 100644 } CGLError CGLSetFullScreen(CGLContextObj ctx) { -@@ -223,11 +389,13 @@ CGLError CGLChoosePixelFormat( +@@ -223,11 +392,13 @@ CGLError CGLChoosePixelFormat( CGLPixelFormatObj *result, GLint *number_of_screens ) { @@ -6611,7 +6614,7 @@ index ffd7fe054..1ff212d34 100644 for (int i = 0; i < count; i++) { format->attributes[i] = attrs[i]; } -@@ -251,6 +419,10 @@ CGLError CGLDescribePixelFormat( +@@ -251,6 +422,10 @@ CGLError CGLDescribePixelFormat( CGLPixelFormatAttribute attr, GLint *value ) { @@ -6622,7 +6625,7 @@ index ffd7fe054..1ff212d34 100644 for (int i = 0; format->attributes[i] != 0; i++) { int has_arg = attribute_has_argument(format->attributes[i]); -@@ -304,24 +476,67 @@ GLuint CGLGetPixelFormatRetainCount(CGLPixelFormatObj pixelFormat) { +@@ -304,24 +479,67 @@ GLuint CGLGetPixelFormatRetainCount(CGLPixelFormatObj pixelFormat) { } CGLError CGLCreateContext(CGLPixelFormatObj pixelFormat, CGLContextObj share, CGLContextObj *resultp) { @@ -6691,7 +6694,7 @@ index ffd7fe054..1ff212d34 100644 *resultp = context; -@@ -333,7 +548,7 @@ CGLContextObj CGLRetainContext(CGLContextObj context) { +@@ -333,7 +551,7 @@ CGLContextObj CGLRetainContext(CGLContextObj context) { return NULL; } @@ -6700,7 +6703,7 @@ index ffd7fe054..1ff212d34 100644 return context; } -@@ -342,11 +557,17 @@ void CGLReleaseContext(CGLContextObj context) { +@@ -342,11 +560,17 @@ void CGLReleaseContext(CGLContextObj context) { return; } @@ -6721,7 +6724,7 @@ index ffd7fe054..1ff212d34 100644 if (CGLGetCurrentContext() == context) { CGLSetCurrentContext(NULL); -@@ -354,7 +575,11 @@ void CGLReleaseContext(CGLContextObj context) { +@@ -354,7 +578,11 @@ void CGLReleaseContext(CGLContextObj context) { pthread_mutex_destroy(&(context->lock)); @@ -6734,7 +6737,7 @@ index ffd7fe054..1ff212d34 100644 free(context); } -@@ -383,9 +608,330 @@ CGLError CGLUnlockContext(CGLContextObj context) { +@@ -383,9 +611,330 @@ CGLError CGLUnlockContext(CGLContextObj context) { return kCGLNoError; } @@ -7067,7 +7070,7 @@ index ffd7fe054..1ff212d34 100644 } CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, const GLint *value) { -@@ -394,8 +940,9 @@ CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, c +@@ -394,8 +943,9 @@ CGLError CGLSetParameter(CGLContextObj context, CGLContextParameter parameter, c if (parameter == kCGLCPSwapInterval) { @@ -7078,7 +7081,7 @@ index ffd7fe054..1ff212d34 100644 if (success) context->swap_interval = v; return success ? kCGLNoError : kCGLBadValue; -@@ -417,14 +964,19 @@ CGLError CGLGetParameter(CGLContextObj context, CGLContextParameter parameter, G +@@ -417,14 +967,19 @@ CGLError CGLGetParameter(CGLContextObj context, CGLContextParameter parameter, G return kCGLNoError; } diff --git a/tools/run-riot-client.sh b/tools/run-riot-client.sh index 75b6ec7..94ea1be 100755 --- a/tools/run-riot-client.sh +++ b/tools/run-riot-client.sh @@ -92,7 +92,10 @@ if [ -n "${RCLOL_REFRESH_HZ-}" ]; then export RCLOL_REFRESH_HZ; fi export MESA_NO_ERROR="${MESA_NO_ERROR:-1}" # NVIDIA proprietary driver: limit pre-rendered frames. export __GL_MaxFramesAllowed="${__GL_MaxFramesAllowed:-1}" -export __GL_THREADED_OPTIMIZATIONS="${__GL_THREADED_OPTIMIZATIONS:-1}" +# Threaded optimizations stay off: with them, NVIDIA 580 (the last branch for +# Pascal GPUs) with egl-x11 1.0.6 rejects the X11 EGLDisplay with EGL_BAD_DISPLAY +# on every thread that has a context current, and the game window stays black. +export __GL_THREADED_OPTIMIZATIONS="${__GL_THREADED_OPTIMIZATIONS:-0}" export __GL_YIELD="${__GL_YIELD:-NOTHING}" # Reduce timer slack for tighter scheduling in the game process tree. if [ -w /proc/self/timerslack_ns ]; then