diff --git a/meson.build b/meson.build index 018546d22a..97e1c29131 100644 --- a/meson.build +++ b/meson.build @@ -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!', 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 #include +#include #include #include #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; 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 - -#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 -#include - -// 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 +#include "present_sync.h" #include "x11_common.h" #include @@ -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 #include #include +#include #include #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); diff --git a/wscript b/wscript index 63069a2248..283f9b3ac5 100644 --- a/wscript +++ b/wscript @@ -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', diff --git a/wscript_build.py b/wscript_build.py index d0cb4ef332..86c1f93866 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -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" ),