summaryrefslogtreecommitdiffstats
path: root/video/out
diff options
context:
space:
mode:
authorDudemanguy <random342@airmail.cc>2022-06-10 11:49:38 -0500
committerDudemanguy <random342@airmail.cc>2022-06-19 18:13:55 +0000
commit3d459832a88a9bd2835b339cf6ca98f84aad0115 (patch)
treeaa1700e19dfd03239d08d2e4ac22d9a00565606e /video/out
parentceade3493045898a30f0da1bbb639552447ffcdf (diff)
downloadmpv-3d459832a88a9bd2835b339cf6ca98f84aad0115.tar.bz2
mpv-3d459832a88a9bd2835b339cf6ca98f84aad0115.tar.xz
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.
Diffstat (limited to 'video/out')
-rw-r--r--video/out/opengl/context_glx.c38
-rw-r--r--video/out/opengl/context_x11egl.c25
-rw-r--r--video/out/opengl/oml_sync.c112
-rw-r--r--video/out/opengl/oml_sync.h28
-rw-r--r--video/out/vo_vaapi.c10
-rw-r--r--video/out/vo_x11.c10
-rw-r--r--video/out/vo_xv.c11
-rw-r--r--video/out/vulkan/context_xlib.c15
-rw-r--r--video/out/x11_common.c33
-rw-r--r--video/out/x11_common.h6
10 files changed, 97 insertions, 191 deletions
diff --git a/video/out/opengl/context_glx.c b/video/out/opengl/context_glx.c
index 6ca9f19d3e..affe7c0f27 100644
--- a/video/out/opengl/context_glx.c
+++ b/video/out/opengl/context_glx.c
@@ -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)
diff --git a/video/out/opengl/context_x11egl.c b/video/out/opengl/context_x11egl.c
index 4e0b277da7..549498b435 100644
--- a/video/out/opengl/context_x11egl.c
+++ b/video/out/opengl/context_x11egl.c
@@ -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);
+ eglSwapBuffers(p->egl_display, p->egl_surface);
+ 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;
diff --git a/video/out/opengl/oml_sync.c b/video/out/opengl/oml_sync.c
deleted file mode 100644
index 6efd4265d7..0000000000
--- a/video/out/opengl/oml_sync.c
+++ /dev/null
@@ -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;
-}
diff --git a/video/out/opengl/oml_sync.h b/video/out/opengl/oml_sync.h
deleted file mode 100644
index e27ccb943f..0000000000
--- a/video/out/opengl/oml_sync.h
+++ /dev/null
@@ -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);
diff --git a/video/out/vo_vaapi.c b/video/out/vo_vaapi.c
index fc26135b7a..2766640e6c 100644
--- a/video/out/vo_vaapi.c
+++ b/video/out/vo_vaapi.c
@@ -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,
diff --git a/video/out/vo_x11.c b/video/out/vo_x11.c
index 6b7a797412..461b05f22a 100644
--- a/video/out/vo_x11.c
+++ b/video/out/vo_x11.c
@@ -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,
diff --git a/video/out/vo_xv.c b/video/out/vo_xv.c
index d93673493f..8fe98839ca 100644
--- a/video/out/vo_xv.c
+++ b/video/out/vo_xv.c
@@ -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,
diff --git a/video/out/vulkan/context_xlib.c b/video/out/vulkan/context_xlib.c
index 2d498723df..3392145e1a 100644
--- a/video/out/vulkan/context_xlib.c
+++ b/video/out/vulkan/context_xlib.c
@@ -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;
diff --git a/video/out/x11_common.c b/video/out/x11_common.c
index 802af60760..57e1fe6236 100644
--- a/video/out/x11_common.c
+++ b/video/out/x11_common.c
@@ -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;
diff --git a/video/out/x11_common.h b/video/out/x11_common.h
index e9abf51585..e08beca7b0 100644
--- a/video/out/x11_common.h
+++ b/video/out/x11_common.h
@@ -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);