x11: support xorg present extension

This builds off of present_sync which was introduced in a previous
commit to support xorg's present extension in all of the X11 backends
(sans vdpau) in mpv. It turns out there is an Xpresent library that
integrates the xorg present extention with Xlib (which barely anyone
seems to use), so this can be added without too much trouble. The
workflow is to first setup the event by telling Xorg we would like to
receive PresentCompleteNotify (there are others in the extension but
this is the only one we really care about). After that, just call
XPresentNotifyMSC after every buffer swap with a target_msc of 0. Xorg
then returns the last presentation through its usual event loop and we
go ahead and use that information to update mpv's values for vsync
timing purposes. One theoretical weakness of this approach is that the
present event is put on the same queue as the rest of the XEvents. It
would be nicer for it be placed somewhere else so we could just wait
on that queue without having to deal with other possible events in
there. In theory, xcb could do that with special events, but it doesn't
really matter in practice.

Unsurprisingly, this doesn't work on NVIDIA. Well NVIDIA does actually
receive presentation events, but for whatever the calculations used make
timings worse which defeats the purpose. This works perfectly fine on
Mesa however. Utilizing the previous commit that detects Xrandr
providers, we can enable this mechanism for users that have both Mesa
and not NVIDIA (to avoid messing up anyone that has a switchable
graphics system or such). Patches welcome if anyone figures out how to
fix this on NVIDIA.

Unlike the EGL/GLX sync extensions, the present extension works with any
graphics API (good for vulkan since its timing extension has been in
development hell). NVIDIA also happens to have zero support for the
EGL/GLX sync extensions, so we can just remove it with no loss. Only
Xorg ever used it and other backends already have their own present
methods. vo_vdpau VO is a special case that has its own fancying timing
code in its flip_page. This presumably works well, and I have no way of
testing it so just leave it as it is.
This commit is contained in:
Dudemanguy 2022-06-10 11:49:38 -05:00
parent ceade34930
commit 3d459832a8
13 changed files with 99 additions and 196 deletions

View File

@ -1071,6 +1071,7 @@ x11 = {
dependency('xscrnsaver', version: '>= 1.0.0', required: x11_opt),
dependency('xext', version: '>= 1.0.0', required: x11_opt),
dependency('xinerama', version: '>= 1.0.0', required: x11_opt),
dependency('xpresent', version: '>= 1.0.0', required: x11_opt),
dependency('xrandr', version: '>= 1.2.0', required: x11_opt)],
'use': true,
}
@ -1255,10 +1256,6 @@ if egl_x11.allowed()
sources += files('video/out/opengl/context_x11egl.c')
endif
if egl_x11.allowed() or gl_x11.allowed()
sources += files('video/out/opengl/oml_sync.c')
endif
plain_gl = get_option('plain-gl').require(
get_option('libmpv') and gl['opt'],
error_message: 'libmpv was not enabled!',

View File

@ -38,9 +38,9 @@
#endif
#include "osdep/timer.h"
#include "video/out/present_sync.h"
#include "video/out/x11_common.h"
#include "context.h"
#include "oml_sync.h"
#include "utils.h"
struct priv {
@ -48,9 +48,6 @@ struct priv {
XVisualInfo *vinfo;
GLXContext context;
GLXFBConfig fbc;
Bool (*XGetSyncValues)(Display*, GLXDrawable, int64_t*, int64_t*, int64_t*);
struct oml_sync sync;
};
static void glx_uninit(struct ra_ctx *ctx)
@ -154,14 +151,6 @@ static bool create_context_x11(struct ra_ctx *ctx, GL *gl, bool es)
p->context = context;
mpgl_load_functions(gl, (void *)glXGetProcAddressARB, glxstr, vo->log);
if (gl_check_extension(glxstr, "GLX_OML_sync_control")) {
p->XGetSyncValues =
(void *)glXGetProcAddressARB((const GLubyte *)"glXGetSyncValuesOML");
}
if (p->XGetSyncValues)
MP_VERBOSE(vo, "Using GLX_OML_sync_control.\n");
return true;
}
@ -209,20 +198,6 @@ static void set_glx_attrib(int *attribs, int name, int value)
}
}
static void update_vsync_oml(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
assert(p->XGetSyncValues);
int64_t ust, msc, sbc;
if (!p->XGetSyncValues(ctx->vo->x11->display, ctx->vo->x11->window,
&ust, &msc, &sbc))
ust = msc = sbc = -1;
oml_sync_swap(&p->sync, ust, msc, sbc);
}
static bool glx_check_visible(struct ra_ctx *ctx)
{
return vo_x11_check_visible(ctx->vo);
@ -230,18 +205,15 @@ static bool glx_check_visible(struct ra_ctx *ctx)
static void glx_swap_buffers(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
glXSwapBuffers(ctx->vo->x11->display, ctx->vo->x11->window);
if (p->XGetSyncValues)
update_vsync_oml(ctx);
vo_x11_present(ctx->vo);
present_sync_swap(ctx->vo->x11->present);
}
static void glx_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
{
struct priv *p = ctx->priv;
oml_sync_get_info(&p->sync, info);
struct vo_x11_state *x11 = ctx->vo->x11;
present_sync_get_info(x11->present, info);
}
static bool glx_init(struct ra_ctx *ctx)

View File

@ -18,14 +18,15 @@
#include <assert.h>
#include <X11/Xlib.h>
#include <X11/extensions/Xpresent.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include "common/common.h"
#include "video/out/present_sync.h"
#include "video/out/x11_common.h"
#include "context.h"
#include "egl_helpers.h"
#include "oml_sync.h"
#include "utils.h"
#define EGL_PLATFORM_X11_EXT 0x31D5
@ -35,10 +36,6 @@ struct priv {
EGLDisplay egl_display;
EGLContext egl_context;
EGLSurface egl_surface;
EGLBoolean (*GetSyncValues)(EGLDisplay, EGLSurface,
int64_t*, int64_t*, int64_t*);
struct oml_sync sync;
};
static void mpegl_uninit(struct ra_ctx *ctx)
@ -83,20 +80,16 @@ static bool mpegl_check_visible(struct ra_ctx *ctx)
static void mpegl_swap_buffers(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
eglSwapBuffers(p->egl_display, p->egl_surface);
int64_t ust, msc, sbc;
if (!p->GetSyncValues || !p->GetSyncValues(p->egl_display, p->egl_surface,
&ust, &msc, &sbc))
ust = msc = sbc = -1;
oml_sync_swap(&p->sync, ust, msc, sbc);
vo_x11_present(ctx->vo);
present_sync_swap(ctx->vo->x11->present);
}
static void mpegl_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
{
struct priv *p = ctx->priv;
oml_sync_get_info(&p->sync, info);
struct vo_x11_state *x11 = ctx->vo->x11;
present_sync_get_info(x11->present, info);
}
static bool mpegl_init(struct ra_ctx *ctx)
@ -182,10 +175,6 @@ static bool mpegl_init(struct ra_ctx *ctx)
if (!ra_gl_ctx_init(ctx, &p->gl, params))
goto uninit;
const char *exts = eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS);
if (gl_check_extension(exts, "EGL_CHROMIUM_sync_control"))
p->GetSyncValues = (void *)eglGetProcAddress("eglGetSyncValuesCHROMIUM");
ra_add_native_resource(ctx->ra, "x11", vo->x11->display);
return true;

View File

@ -1,112 +0,0 @@
#include <time.h>
#include "osdep/timer.h"
#include "oml_sync.h"
#include "video/out/vo.h"
// General nonsense about the associated extension.
//
// This extension returns two unrelated values:
// (ust, msc): clock time and incrementing counter of last vsync (this is
// reported continuously, even if we don't swap)
// sbc: swap counter of frame that was last displayed (every swap
// increments the user_sbc, and the reported sbc is the sbc
// of the frame that was just displayed)
// Invariants:
// - ust and msc change in lockstep (no value can change without the other)
// - msc is incremented; if you query it in a loop, and your thread isn't
// frozen or starved by the scheduler, it will usually either not change, or
// be incremented by 1 (while the ust will be incremented by vsync
// duration)
// - sbc is never higher than the user_sbc
// - (ust, msc) are equal to or higher by vsync increments than the display
// time of the frame referenced by the sbc
// Note that (ust, msc) and sbc are not locked to each other. The following
// can easily happen if vsync skips occur:
// - you draw a frame, in the meantime hardware swaps sbc_1
// - another display vsync happens during drawing
// - you call swap()
// - query (ust, msc) and sbc
// - sbc contains sbc_1, but (ust, msc) contains the vsync after it
// As a consequence, it's hard to detect the latency or vsync skips.
static void oml_sync_reset(struct oml_sync *oml)
{
oml->vsync_duration = -1;
oml->last_skipped_vsyncs = -1;
oml->last_queue_display_time = -1;
}
void oml_sync_swap(struct oml_sync *oml, int64_t ust, int64_t msc, int64_t sbc)
{
if (!oml->state_ok)
oml_sync_reset(oml);
oml->last_skipped_vsyncs = 0;
oml->user_sbc += 1;
if (sbc < 0)
return;
oml->state_ok = true;
int64_t ust_passed = oml->last_ust ? ust - oml->last_ust : 0;
oml->last_ust = ust;
int64_t msc_passed = oml->last_msc ? msc - oml->last_msc : 0;
oml->last_msc = msc;
int64_t sbc_passed = sbc - oml->last_sbc;
oml->last_sbc = sbc;
// Display frame duration. This makes assumptions about UST (see below).
if (msc_passed && ust_passed)
oml->vsync_duration = ust_passed / msc_passed;
// Only if a new frame was displayed (sbc increased) we have sort-of a
// chance that the current (ust, msc) is for the sbc. But this is racy,
// because skipped frames drops could have increased the msc right after the
// display event and before we queried the values. This code hopes for the
// best and ignores this.
if (sbc_passed) {
// The GLX extension spec doesn't define what the UST is (not even its
// unit). Simply assume UST is a simple CLOCK_MONOTONIC usec value. This
// is what Mesa does, and what the Google EGL extension seems to imply
// (they mention CLOCK_MONOTONIC, but not the unit).
// The swap buffer call happened "some" but very small time ago, so we
// can get away with querying the current time. There is also the
// implicit assumption that mpv's timer and the UST use the same clock
// (which it does on POSIX).
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts))
return;
uint64_t now_monotonic = ts.tv_sec * 1000000LL + ts.tv_nsec / 1000;
uint64_t ust_mp_time = mp_time_us() - (now_monotonic - ust);
// Assume this is exactly when the actual display event for this sbc
// happened. This is subject to the race mentioned above.
oml->last_sbc_mp_time = ust_mp_time;
}
// At least one frame needs to be actually displayed before
// oml->last_sbc_mp_time is set.
if (!sbc)
return;
// Extrapolate from the last sbc time (when a frame was actually displayed),
// and by adding the number of frames that were queued since to it.
// For every unit the sbc is smaller than user_sbc, the actual display
// is one frame ahead (assumes oml_sync_swap() is called for every
// vsync).
oml->last_queue_display_time =
oml->last_sbc_mp_time + (oml->user_sbc - sbc) * oml->vsync_duration;
}
void oml_sync_get_info(struct oml_sync *oml, struct vo_vsync_info *info)
{
if (!oml->state_ok)
oml_sync_reset(oml);
info->vsync_duration = oml->vsync_duration;
info->skipped_vsyncs = oml->last_skipped_vsyncs;
info->last_queue_display_time = oml->last_queue_display_time;
}

View File

@ -1,28 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
// Must be initialized to {0} by user.
struct oml_sync {
bool state_ok;
int64_t last_ust;
int64_t last_msc;
int64_t last_sbc;
int64_t last_sbc_mp_time;
int64_t user_sbc;
int64_t vsync_duration;
int64_t last_skipped_vsyncs;
int64_t last_queue_display_time;
};
struct vo_vsync_info;
// This must be called on every SwapBuffer call. Pass the ust/msc/sbc values
// returned by a successful GetSyncValues call. Pass -1 for all these 3 values
// if GetSyncValues returned failure (but note that you need to set them to -1
// manually).
void oml_sync_swap(struct oml_sync *oml, int64_t ust, int64_t msc, int64_t sbc);
// Can be called any time; returns state determined by last oml_sync_swap() call.
void oml_sync_get_info(struct oml_sync *oml, struct vo_vsync_info *info);

View File

@ -36,6 +36,7 @@
#include "sub/draw_bmp.h"
#include "sub/img_convert.h"
#include "sub/osd.h"
#include "present_sync.h"
#include "x11_common.h"
#include "video/mp_image.h"
@ -553,6 +554,14 @@ static void flip_page(struct vo *vo)
p->visible_surface = p->output_surface;
render_to_screen(p, p->output_surfaces[p->output_surface]);
p->output_surface = (p->output_surface + 1) % MAX_OUTPUT_SURFACES;
vo_x11_present(vo);
present_sync_swap(vo->x11->present);
}
static void get_vsync(struct vo *vo, struct vo_vsync_info *info)
{
struct vo_x11_state *x11 = vo->x11;
present_sync_get_info(x11->present, info);
}
static void draw_image(struct vo *vo, struct mp_image *mpi)
@ -851,6 +860,7 @@ const struct vo_driver video_out_vaapi = {
.control = control,
.draw_image = draw_image,
.flip_page = flip_page,
.get_vsync = get_vsync,
.wakeup = vo_x11_wakeup,
.wait_events = vo_x11_wait_events,
.uninit = uninit,

View File

@ -34,6 +34,7 @@
#include <errno.h>
#include "present_sync.h"
#include "x11_common.h"
#include <sys/ipc.h>
@ -307,6 +308,14 @@ static void flip_page(struct vo *vo)
struct priv *p = vo->priv;
Display_Image(p, p->myximage[p->current_buf]);
p->current_buf = (p->current_buf + 1) % 2;
vo_x11_present(vo);
present_sync_swap(vo->x11->present);
}
static void get_vsync(struct vo *vo, struct vo_vsync_info *info)
{
struct vo_x11_state *x11 = vo->x11;
present_sync_get_info(x11->present, info);
}
// Note: REDRAW_FRAME can call this with NULL.
@ -435,6 +444,7 @@ const struct vo_driver video_out_x11 = {
.control = control,
.draw_image = draw_image,
.flip_page = flip_page,
.get_vsync = get_vsync,
.wakeup = vo_x11_wakeup,
.wait_events = vo_x11_wait_events,
.uninit = uninit,

View File

@ -45,6 +45,7 @@
#include "common/msg.h"
#include "vo.h"
#include "video/mp_image.h"
#include "present_sync.h"
#include "x11_common.h"
#include "sub/osd.h"
#include "sub/draw_bmp.h"
@ -689,6 +690,15 @@ static void flip_page(struct vo *vo)
if (!ctx->Shmem_Flag)
XSync(vo->x11->display, False);
vo_x11_present(vo);
present_sync_swap(vo->x11->present);
}
static void get_vsync(struct vo *vo, struct vo_vsync_info *info)
{
struct vo_x11_state *x11 = vo->x11;
present_sync_get_info(x11->present, info);
}
// Note: REDRAW_FRAME can call this with NULL.
@ -889,6 +899,7 @@ const struct vo_driver video_out_xv = {
.control = control,
.draw_image = draw_image,
.flip_page = flip_page,
.get_vsync = get_vsync,
.wakeup = vo_x11_wakeup,
.wait_events = vo_x11_wait_events,
.uninit = uninit,

View File

@ -16,6 +16,7 @@
*/
#include "video/out/gpu/context.h"
#include "video/out/present_sync.h"
#include "video/out/x11_common.h"
#include "common.h"
@ -31,6 +32,18 @@ static bool xlib_check_visible(struct ra_ctx *ctx)
return vo_x11_check_visible(ctx->vo);
}
static void xlib_vk_swap_buffers(struct ra_ctx *ctx)
{
vo_x11_present(ctx->vo);
present_sync_swap(ctx->vo->x11->present);
}
static void xlib_vk_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
{
struct vo_x11_state *x11 = ctx->vo->x11;
present_sync_get_info(x11->present, info);
}
static void xlib_uninit(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
@ -63,6 +76,8 @@ static bool xlib_init(struct ra_ctx *ctx)
struct ra_vk_ctx_params params = {
.check_visible = xlib_check_visible,
.swap_buffers = xlib_vk_swap_buffers,
.get_vsync = xlib_vk_get_vsync,
};
VkInstance inst = vk->vkinst->instance;

View File

@ -36,6 +36,7 @@
#include <X11/extensions/scrnsaver.h>
#include <X11/extensions/dpms.h>
#include <X11/extensions/Xinerama.h>
#include <X11/extensions/Xpresent.h>
#include <X11/extensions/Xrandr.h>
#include "config.h"
@ -48,6 +49,7 @@
#include "input/event.h"
#include "video/image_loader.h"
#include "video/mp_image.h"
#include "present_sync.h"
#include "x11_common.h"
#include "mpv_talloc.h"
@ -593,6 +595,7 @@ int vo_x11_init(struct vo *vo)
x11_error_output = x11->log;
XSetErrorHandler(x11_errorhandler);
x11->present = talloc_zero(x11, struct mp_present);
dispName = XDisplayName(NULL);
@ -1277,6 +1280,21 @@ void vo_x11_check_events(struct vo *vo)
x11->pending_vo_events |= VO_EVENT_ICC_PROFILE_CHANGED;
}
break;
case GenericEvent: {
XGenericEventCookie *cookie = (XGenericEventCookie *)&Event.xcookie;
if (cookie->extension == x11->present_code && x11->have_present &&
x11->has_mesa && !x11->has_nvidia)
{
XGetEventData(x11->display, cookie);
if (cookie->evtype == PresentCompleteNotify) {
XPresentCompleteNotifyEvent *present_event;
present_event = (XPresentCompleteNotifyEvent *)cookie->data;
present_update_sync_values(x11->present, present_event->ust,
present_event->msc);
}
}
break;
}
default:
if (Event.type == x11->ShmCompletionEvent) {
if (x11->ShmCompletionWaitCount > 0)
@ -1504,6 +1522,14 @@ static void vo_x11_create_window(struct vo *vo, XVisualInfo *vis,
Atom protos[1] = {XA(x11, WM_DELETE_WINDOW)};
XSetWMProtocols(x11->display, x11->window, protos, 1);
if (!XPresentQueryExtension(x11->display, &x11->present_code, NULL, NULL)) {
MP_VERBOSE(x11, "The XPresent extension is not supported.\n");
x11->have_present = false;
} else {
x11->have_present = true;
XPresentSelectInput(x11->display, x11->window, PresentCompleteNotifyMask);
}
x11->mouse_cursor_set = false;
x11->mouse_cursor_visible = true;
vo_update_cursor(vo);
@ -2045,6 +2071,13 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg)
return VO_NOTIMPL;
}
void vo_x11_present(struct vo *vo)
{
struct vo_x11_state *x11 = vo->x11;
XPresentNotifyMSC(x11->display, x11->window,
0, 0, 1, 0);
}
void vo_x11_wakeup(struct vo *vo)
{
struct vo_x11_state *x11 = vo->x11;

View File

@ -75,6 +75,10 @@ struct vo_x11_state {
bool dpms_touched;
double screensaver_time_last;
struct mp_present *present;
bool have_present;
int present_code;
XIM xim;
XIC xic;
bool no_autorepeat;
@ -147,6 +151,8 @@ bool vo_x11_create_vo_window(struct vo *vo, XVisualInfo *vis,
void vo_x11_config_vo_window(struct vo *vo);
bool vo_x11_check_visible(struct vo *vo);
int vo_x11_control(struct vo *vo, int *events, int request, void *arg);
void vo_x11_present(struct vo *vo);
void vo_x11_sync_swap(struct vo *vo);
void vo_x11_wakeup(struct vo *vo);
void vo_x11_wait_events(struct vo *vo, int64_t until_time_us);

View File

@ -534,6 +534,7 @@ video_output_features = [
'xscrnsaver', '>= 1.0.0',
'xext', '>= 1.0.0',
'xinerama', '>= 1.0.0',
'xpresent', '>= 1.0.0',
'xrandr', '>= 1.2.0'),
} , {
'name': '--xv',

View File

@ -500,7 +500,6 @@ def build(ctx):
( "video/out/opengl/hwdec_rpi.c", "rpi-mmal" ),
( "video/out/opengl/hwdec_vdpau.c", "vdpau-gl-x11" ),
( "video/out/opengl/libmpv_gl.c", "gl" ),
( "video/out/opengl/oml_sync.c", "egl-x11 || gl-x11" ),
( "video/out/opengl/ra_gl.c", "gl" ),
( "video/out/opengl/utils.c", "gl" ),
( "video/out/present_sync.c", "wayland || x11" ),