While trying to make a compositing window manager for X11 using OpenGL as backend, I'm caught in a nasty situation where glXSwapBuffers() blocks until vblank rendering the compositor irresponsive to X events which causes windows being dragged around to lag behind the cursor by roughly one frame. I've tried multithreading but it didn't work well, so I decide the only proper solution is to make glXSwapBuffers() asynchronous. It would be desirable to send drawing commands to GPU and return immediately without waiting for the operations to actually finish, and AFAIK this is possible under modern Linux with DRI2. So what should I do?
Actually glXSwapBuffers
should return immediately. What's blocking however is the very next OpenGL command that introduces a so called synchronization point. Usually this is the next glClear
that follows the call of glXSwapBuffers
.
Note that it's actually desireable to somehow synchronize with the V-Blank, otherwise nasty tearing artifacts happen. But you're right, that in a naive implementation this introduces about one display refresh interval of latency.
The big problem here is, that double buffered windows redirected to an off-screen surface may still subjected to the active swap interval (i.e. V-Sync setting); and of course double buffering itself doesn't make a lot of sense in a composited setting.
So here's what you can do: Use a swap interval extension to set your compositor's swap interval to 0 (no V-Sync); depending on your system's settings this choice may actually not be honored (user configured all applications are forced to V-Sync). Unfortunately there are several swap interval extensions and what works with one display driver doesn't work with another. I suggest you look at the swap interval example programs of Mesa and the sources of glxgears of Mesa, which contain code that deals with pretty much every situation you may encounter.
It's also desireable to somehow turn off V-Sync in the clients too. I don't see better way than injecting a shared object into them, hooking glXSwapBuffers
, glXCreateContext
and the swap interval extensions to override them.
Finally you must use one of the available video synchronization GLX extensions to implement a timed buffer swap in your compositor (i.e. call an "unsynchronized" glXSwapBuffers
at just the right moment when the V-Blank happens). With a direct OpenGL context and a realtime scheduling policy applied to the compositor process you can do that.
Note that all these issues are shortcomings in the existing X11 protocol. But if you think Wayland would get rid of these issues, think again. While Wayland was originally intended to make "every frame perfect" and do away with the synchronization issues, in practice I encountered many of the problems, again. In this blog post the creator of Wayland talks about roundtrips and overhead, but he completely avoids the point of pipeline synchronization and buffer swap latency. The problems are inherent to the concept of stacked composition and buffer swap based V-Sync. To really solve the issue there must be some kind of screen associated V-Sync event that's independent from graphics operations and can be applied an arbitrary phase offset, so that applications can synchronize their rendering loops with display refresh. And there should be an additional "framebuffer commit" function that makes the whole composition chain consider the newly arrived frame. This would allow the compositor to sync applications to a few 100µs before the V-Blank happens, so that composition can happen in that margin between framebuffer commit and V-Blank.
As @datenwork says, I don't think it's glxSwapBuffers
that's blocking. But something is. I solved the issue inspired by this blog post.
Specifically, on my platform (Ubuntu 14 + Nvidia driver + Nvidia OpenGL implementation), the following code works:
PFNGLXSWAPINTERVALEXTPROC glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC)glXGetProcAddress((const GLubyte*)"glXSwapIntervalEXT"); // Set the glxSwapInterval to 0, ie. disable vsync! khronos.org/opengl/wiki/Swap_Interval
glXSwapIntervalEXT(x11_display, glx_window, 0); // glXSwapIntervalEXT(0);
where x11_display
is built as
Display* x11_display = XOpenDisplay(0);
and glx_window
is built as
GLXWindow glx_window = glXCreateWindow(x11_display, fb_config, window, 0);
and fb_config
is a suitable GLXFBConfig
, that I, specifically, get as follows:
int visual, n_fb_configs;
GLXFBConfig* fb_configs = glXGetFBConfigs(x11_display, screen_number, &n_fb_configs);
GLXFBConfig fb_config = fb_configs[2]; // Select 3rd FB config! Many others work!
glXGetFBConfigAttrib(x11_display, fb_config, GLX_VISUAL_ID, &visual); // Query visual?
printf("screen %x fb_configs %d fb_config %llx visual %x\n", screen_number, n_fb_configs, (ull)fb_config, visual);
Note that I don't get any screen tearing that wasn't alread there with (the largely useless, imo) vsync enabled.
Also, depending on your GPU driver and OpenGL implementation (Is it Nvidia's? Is it Mesa's?), there's other glxSwapInteral*(...)
functions that you may have to use. Eg. there's a glXSwapIntervalMESA(...)
and a glXSwapIntervalSGI(...)
.
Rant: As usual with X11 programming, there's very little documentation to hold your hand... Good luck! :)
Bonus. To quote the OpenGL documentation:
Your application's use of swap interval may be overridden by external, driver-specific configuration. For example, forcing Vsync Off in a driver's control panel will prevent Vsync, even if swap interval is set to 1 in your application.
© 2022 - 2024 — McMap. All rights reserved.