How to Wait for Vsync in Xlib App

How to query Vsync phase in Linux

When you search for FBIO_WAITFORVSYNC in the Linux kernel sources, you can see, that it is implemented only for a few graphics cards, but not for all.

So, if you happen to have one of the many other cards, you get "Inappropriate ioctl for device", which just means not implemented for this graphics card driver.

Maybe How to wait for VSYNC in Xlib app? gives you some hint into the right direction.

GLX Vsync event

It looks like you can use the libdrm API to see vsync events. See this blog entry, and in particular this example code. A comment from the code explains how it works:

/* (...)
* The DRM_MODE_PAGE_FLIP_EVENT flag tells drmModePageFlip() to send us a
* page-flip event on the DRM-fd when the page-flip happened. The last argument
* is a data-pointer that is returned with this event.
* (...)
*/

You need to set up a page-flip event handler to be notified when a vsync occurs, which will be called by the drmHandleEvent method (from libdrm) which you can call when there is activity on the drm file descriptor.

However, mapping all this into an X client might prove difficult or impossible. It may be that you can open the drm device yourself and just listen for vsync events (without attempting to set the mode etc), but this might also prove impossible. Pertinent code:

drmEventContext ev;
memset(&ev, 0, sizeof(ev));
ev.version = DRM_EVENT_CONTEXT_VERSION;
ev.page_flip_handler = modeset_page_flip_event;

// When file descriptor input is available:
drmHandleEvent(fd, &ev);
// If above works, "modeset_page_flip_event" will be called on vertical refresh.

The problem is that a page flip event only seems to be generated if you have actually issued a page flip (buffer swap) request. Presumably it would be the X server that issued such requests, but it doesn't even necessarily flag that it wants to be notified when the vsync actually occurs (i.e. uses the DRM_MODE_PAGE_FLIP_EVENT flag).

There's also the difficulty of opening the correct dri device (/dev/dri/card0 or /dev/dri/card1 or ...?)

Given the difficulty/unreliability/general unworkability of all the above, the easiest solution is probably to:

  1. Use a separate thread to wait for vsync using standard GL calls. According to this page on the OpenGL wiki you should use glXSwapIntervalEXT(1) to enable vsync, then glXSwapBuffers and glFinish to ensure that the vertical retrace actually occurs.
  2. Then, notify the main thread that vertical retrace occurred. You can do this by sending data to a pipe or, on Linux, use an eventfd.

Update:

You may be able to use the GLX_INTEL_swap_event extension:

Accepted by the parameter of glXSelectEvent and returned in the parameter of glXGetSelectedEvent:

GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK 0x04000000

Returned in the <event_type> field of a "swap complete" event:

GLX_EXCHANGE_COMPLETE_INTEL 0x8180

GLX_COPY_COMPLETE_INTEL 0x8181

GLX_FLIP_COMPLETE_INTEL 0x8182

...

A client can ask to receive swap complete GLX events on a window.
When an event is received, the caller can determine what kind of swap occurred by checking the event_type field.

This means that, if the extension is supported, you can receive swap notifications (which correspond to vertical retrace if vsync is enabled) via regular X events, which you'll see on the X connection file descriptor.

glXSwapBuffers() blocks until vblank

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.

X11: How to delay repainting until all events are processed?

Try something like the following (not actually tested):

while (TRUE) {
if (XPending(display) || !pendingRedraws) {
// if an event is pending, fetch it and process it
// otherwise, we have neither events nor pending redraws, so we can
// safely block on the event queue
XNextEvent (display, &ev);
if (isExposeEvent(&ev)) {
pendingRedraws = TRUE;
}
else {
processEvent(&ev);
}
}
else {
// we must have a pending redraw
redraw();
pendingRedraws = FALSE;
}
}

It could be beneficial to wait for 10 ms or so before doing the redraw. Unfortunately the raw Xlib has no interface for timers. You need a higher-level toolkit for that (all toolkits including Xt have some kind of timer interface), or work directly with the underlying socket of the X11 connection.



Related Topics



Leave a reply



Submit