From 74a25425646d64edeff508ec8e99622a41576905 Mon Sep 17 00:00:00 2001 From: "Ryan C. Gordon" Date: Thu, 23 Nov 2023 13:47:13 -0500 Subject: [PATCH] x11: Deal with difference in GLX_EXT_swap_control_tear behavior. Mesa and Nvidia handle it differently, and one or the other may fix their implementation in the future, so test which way it works at runtime. Reference Issue #8004. --- src/video/x11/SDL_x11opengl.c | 68 ++++++++++++++++++++++++++++++++--- src/video/x11/SDL_x11opengl.h | 10 ++++++ test/testgl.c | 21 ++++++----- 3 files changed, 86 insertions(+), 13 deletions(-) diff --git a/src/video/x11/SDL_x11opengl.c b/src/video/x11/SDL_x11opengl.c index 1a24358b2..aad96462d 100644 --- a/src/video/x11/SDL_x11opengl.c +++ b/src/video/x11/SDL_x11opengl.c @@ -237,6 +237,8 @@ int X11_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path) return SDL_SetError("GLX is not supported"); } + _this->gl_data->swap_interval_tear_behavior = SDL_SWAPINTERVALTEAR_UNTESTED; + /* Initialize extensions */ /* See lengthy comment about the inc/dec in ../windows/SDL_windowsopengl.c. */ @@ -902,7 +904,6 @@ int X11_GL_SetSwapInterval(SDL_VideoDevice *_this, int interval) X11_GL_GetSwapInterval(_this, ¤tInterval); _this->gl_data->glXSwapIntervalEXT(display, drawable, currentInterval); _this->gl_data->glXSwapIntervalEXT(display, drawable, interval); - status = 0; swapinterval = interval; } else if (_this->gl_data->glXSwapIntervalMESA) { @@ -925,6 +926,53 @@ int X11_GL_SetSwapInterval(SDL_VideoDevice *_this, int interval) return status; } +static SDL_GLSwapIntervalTearBehavior CheckSwapIntervalTearBehavior(SDL_VideoDevice *_this, Window drawable, unsigned int current_val, unsigned int current_allow_late) +{ + /* Mesa and Nvidia interpret GLX_EXT_swap_control_tear differently, as of this writing, so + figure out which behavior we have. + Technical details: https://github.com/libsdl-org/SDL/issues/8004#issuecomment-1819603282 */ + if (_this->gl_data->swap_interval_tear_behavior == SDL_SWAPINTERVALTEAR_UNTESTED) { + if (!_this->gl_data->HAS_GLX_EXT_swap_control_tear) { + _this->gl_data->swap_interval_tear_behavior = SDL_SWAPINTERVALTEAR_UNKNOWN; + } else { + Display *display = _this->driverdata->display; + unsigned int allow_late_swap_tearing = 22; + int original_val = (int) current_val; + + /* + * This is a workaround for a bug in NVIDIA drivers. Bug has been reported + * and will be fixed in a future release (probably 319.xx). + * + * There's a bug where glXSetSwapIntervalEXT ignores updates because + * it has the wrong value cached. To work around it, we just run a no-op + * update to the current value. + */ + _this->gl_data->glXSwapIntervalEXT(display, drawable, current_val); + + /* set it to no swap interval and see how it affects GLX_LATE_SWAPS_TEAR_EXT... */ + _this->gl_data->glXSwapIntervalEXT(display, drawable, 0); + _this->gl_data->glXQueryDrawable(display, drawable, GLX_LATE_SWAPS_TEAR_EXT, &allow_late_swap_tearing); + + if (allow_late_swap_tearing == 0) { /* GLX_LATE_SWAPS_TEAR_EXT says whether late swapping is currently in use */ + _this->gl_data->swap_interval_tear_behavior = SDL_SWAPINTERVALTEAR_NVIDIA; + if (current_allow_late) { + original_val = -original_val; + } + } else if (allow_late_swap_tearing == 1) { /* GLX_LATE_SWAPS_TEAR_EXT says whether the Drawable can use late swapping at all */ + _this->gl_data->swap_interval_tear_behavior = SDL_SWAPINTERVALTEAR_MESA; + } else { /* unexpected outcome! */ + _this->gl_data->swap_interval_tear_behavior = SDL_SWAPINTERVALTEAR_UNKNOWN; + } + + /* set us back to what it was originally... */ + _this->gl_data->glXSwapIntervalEXT(display, drawable, original_val); + } + } + + return _this->gl_data->swap_interval_tear_behavior; +} + + int X11_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval) { if (_this->gl_data->glXSwapIntervalEXT) { @@ -935,6 +983,7 @@ int X11_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval) unsigned int val = 0; if (_this->gl_data->HAS_GLX_EXT_swap_control_tear) { + allow_late_swap_tearing = 22; /* set this to nonsense. */ _this->gl_data->glXQueryDrawable(display, drawable, GLX_LATE_SWAPS_TEAR_EXT, &allow_late_swap_tearing); @@ -943,12 +992,21 @@ int X11_GL_GetSwapInterval(SDL_VideoDevice *_this, int *interval) _this->gl_data->glXQueryDrawable(display, drawable, GLX_SWAP_INTERVAL_EXT, &val); - if ((allow_late_swap_tearing) && (val > 0)) { - *interval = -((int)val); - return 0; + *interval = (int)val; + + switch (CheckSwapIntervalTearBehavior(_this, drawable, val, allow_late_swap_tearing)) { + case SDL_SWAPINTERVALTEAR_MESA: + *interval = (int)val; /* unsigned int cast to signed that generates negative value if necessary. */ + break; + + case SDL_SWAPINTERVALTEAR_NVIDIA: + default: + if ((allow_late_swap_tearing) && (val > 0)) { + *interval = -((int)val); + } + break; } - *interval = (int)val; return 0; } else if (_this->gl_data->glXGetSwapIntervalMESA) { int val = _this->gl_data->glXGetSwapIntervalMESA(); diff --git a/src/video/x11/SDL_x11opengl.h b/src/video/x11/SDL_x11opengl.h index f49f55668..70840231c 100644 --- a/src/video/x11/SDL_x11opengl.h +++ b/src/video/x11/SDL_x11opengl.h @@ -29,6 +29,14 @@ typedef void (*__GLXextFuncPtr)(void); +typedef enum SDL_GLSwapIntervalTearBehavior +{ + SDL_SWAPINTERVALTEAR_UNTESTED, + SDL_SWAPINTERVALTEAR_UNKNOWN, + SDL_SWAPINTERVALTEAR_MESA, + SDL_SWAPINTERVALTEAR_NVIDIA +} SDL_GLSwapIntervalTearBehavior; + struct SDL_GLDriverData { int errorBase, eventBase; @@ -50,6 +58,8 @@ struct SDL_GLDriverData int minor; } es_profile_max_supported_version; + SDL_GLSwapIntervalTearBehavior swap_interval_tear_behavior; + Bool (*glXQueryExtension)(Display *, int *, int *); __GLXextFuncPtr (*glXGetProcAddress)(const GLubyte *); XVisualInfo *(*glXChooseVisual)(Display *, int, int *); diff --git a/test/testgl.c b/test/testgl.c index 635782e9b..0df67e73f 100644 --- a/test/testgl.c +++ b/test/testgl.c @@ -199,6 +199,17 @@ static void Render(void) ctx.glRotatef(5.0, 1.0, 1.0, 1.0); } +static void LogSwapInterval(void) +{ + int interval = 0; + const int ret_interval = SDL_GL_GetSwapInterval(&interval); + if (ret_interval < 0) { + SDL_Log("Swap Interval : %d error: %s\n", interval, SDL_GetError()); + } else { + SDL_Log("Swap Interval : %d\n", interval); + } +} + int main(int argc, char *argv[]) { int fsaa, accel; @@ -211,8 +222,6 @@ int main(int argc, char *argv[]) int status; int dw, dh; int swap_interval = 0; - int interval = 0; - int ret_interval = 0; /* Initialize parameters */ fsaa = 0; @@ -304,12 +313,7 @@ int main(int argc, char *argv[]) SDL_Log("Screen BPP : %" SDL_PRIu32 "\n", SDL_BITSPERPIXEL(mode->format)); } - ret_interval = SDL_GL_GetSwapInterval(&interval); - if (ret_interval < 0) { - SDL_Log("Swap Interval : %d error: %s\n", interval, SDL_GetError()); - } else { - SDL_Log("Swap Interval : %d\n", interval); - } + LogSwapInterval(); SDL_GetWindowSize(state->windows[0], &dw, &dh); SDL_Log("Window Size : %d,%d\n", dw, dh); @@ -421,6 +425,7 @@ int main(int argc, char *argv[]) SDL_GL_MakeCurrent(state->windows[i], context); if (update_swap_interval) { SDL_GL_SetSwapInterval(swap_interval); + LogSwapInterval(); } SDL_GetWindowSizeInPixels(state->windows[i], &w, &h); ctx.glViewport(0, 0, w, h);