From bedca07a021863d264e7c4c471cc30102899500f Mon Sep 17 00:00:00 2001 From: dudemanguy Date: Thu, 10 Oct 2019 14:14:40 -0500 Subject: [PATCH] wayland: add presentation time Use ust/msc/refresh values from wayland's presentation time in mpv's ra_swapchain_fns.get_vsync for the wayland contexts. --- video/out/opengl/context_wayland.c | 80 +++++++++++++++++++++- video/out/vulkan/context.h | 3 + video/out/vulkan/context_wayland.c | 81 +++++++++++++++++++++- video/out/wayland_common.c | 106 ++++++++++++++++++++++++++++- video/out/wayland_common.h | 28 +++++++- wscript_build.py | 7 ++ 6 files changed, 298 insertions(+), 7 deletions(-) diff --git a/video/out/opengl/context_wayland.c b/video/out/opengl/context_wayland.c index 6511e34499..5e9150eb0d 100644 --- a/video/out/opengl/context_wayland.c +++ b/video/out/opengl/context_wayland.c @@ -25,6 +25,9 @@ #include "egl_helpers.h" #include "utils.h" +// Generated from presentation-time.xml +#include "video/out/wayland/presentation-time.h" + struct priv { GL gl; EGLDisplay egl_display; @@ -45,13 +48,62 @@ static void frame_callback(void *data, struct wl_callback *callback, uint32_t ti wl->frame_callback = wl_surface_frame(wl->surface); wl_callback_add_listener(wl->frame_callback, &frame_listener, wl); - wl->callback_wait = false; + wl->frame_wait = false; } static const struct wl_callback_listener frame_listener = { frame_callback, }; +static const struct wp_presentation_feedback_listener feedback_listener; + +static void feedback_sync_output(void *data, struct wp_presentation_feedback *fback, + struct wl_output *output) +{ +} + +static void feedback_presented(void *data, struct wp_presentation_feedback *fback, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, + uint32_t tv_nsec, uint32_t refresh_nsec, + uint32_t seq_hi, uint32_t seq_lo, + uint32_t flags) +{ + struct vo_wayland_state *wl = data; + wp_presentation_feedback_destroy(fback); + vo_wayland_sync_shift(wl); + + // Very similar to oml_sync_control, in this case we assume that every + // time the compositor receives feedback, a buffer swap has been already + // been performed. + // + // Notes: + // - tv_sec_lo + tv_sec_hi is the equivalent of oml's ust + // - seq_lo + seq_hi is the equivalent of oml's msc + // - these values are updated everytime the compositor receives feedback. + + int index = last_available_sync(wl); + if (index < 0) { + queue_new_sync(wl); + index = 0; + } + int64_t sec = (uint64_t) tv_sec_lo + ((uint64_t) tv_sec_hi << 32); + wl->sync[index].sbc = wl->user_sbc; + wl->sync[index].ust = sec * 1000000LL + (uint64_t) tv_nsec / 1000; + wl->sync[index].msc = (uint64_t) seq_lo + ((uint64_t) seq_hi << 32); + wl->sync[index].refresh_usec = (uint64_t)refresh_nsec/1000; + wl->sync[index].filled = true; +} + +static void feedback_discarded(void *data, struct wp_presentation_feedback *fback) +{ + wp_presentation_feedback_destroy(fback); +} + +static const struct wp_presentation_feedback_listener feedback_listener = { + feedback_sync_output, + feedback_presented, + feedback_discarded, +}; static void resize(struct ra_ctx *ctx) { @@ -77,9 +129,32 @@ static void wayland_egl_swap_buffers(struct ra_ctx *ctx) struct priv *p = ctx->priv; struct vo_wayland_state *wl = ctx->vo->wl; + if (wl->presentation) { + wl->feedback = wp_presentation_feedback(wl->presentation, wl->surface); + wp_presentation_feedback_add_listener(wl->feedback, &feedback_listener, wl); + wl->user_sbc += 1; + int index = last_available_sync(wl); + if (index < 0) + queue_new_sync(wl); + } + eglSwapBuffers(p->egl_display, p->egl_surface); vo_wayland_wait_frame(wl); - wl->callback_wait = true; + + if (wl->presentation) + wayland_sync_swap(wl); + + wl->frame_wait = true; +} + +static void wayland_egl_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info) +{ + struct vo_wayland_state *wl = ctx->vo->wl; + if (wl->presentation) { + info->vsync_duration = wl->vsync_duration; + info->skipped_vsyncs = wl->last_skipped_vsyncs; + info->last_queue_display_time = wl->last_queue_display_time; + } } static bool egl_create_context(struct ra_ctx *ctx) @@ -103,6 +178,7 @@ static bool egl_create_context(struct ra_ctx *ctx) struct ra_gl_ctx_params params = { .swap_buffers = wayland_egl_swap_buffers, + .get_vsync = wayland_egl_get_vsync, }; if (!ra_gl_ctx_init(ctx, &p->gl, params)) diff --git a/video/out/vulkan/context.h b/video/out/vulkan/context.h index 90d7f8b8b2..0b420e2daa 100644 --- a/video/out/vulkan/context.h +++ b/video/out/vulkan/context.h @@ -4,6 +4,9 @@ #include "common.h" struct ra_vk_ctx_params { + // See ra_swapchain_fns.get_vsync. + void (*get_vsync)(struct ra_ctx *ctx, struct vo_vsync_info *info); + // In case something special needs to be done on the buffer swap. void (*swap_buffers)(struct ra_ctx *ctx); }; diff --git a/video/out/vulkan/context_wayland.c b/video/out/vulkan/context_wayland.c index 783509e4a1..d3da628137 100644 --- a/video/out/vulkan/context_wayland.c +++ b/video/out/vulkan/context_wayland.c @@ -22,6 +22,9 @@ #include "context.h" #include "utils.h" +// Generated from presentation-time.xml +#include "video/out/wayland/presentation-time.h" + struct priv { struct mpvk_ctx vk; }; @@ -37,19 +40,92 @@ static void frame_callback(void *data, struct wl_callback *callback, uint32_t ti wl->frame_callback = wl_surface_frame(wl->surface); wl_callback_add_listener(wl->frame_callback, &frame_listener, wl); - wl->callback_wait = false; + wl->frame_wait = false; } static const struct wl_callback_listener frame_listener = { frame_callback, }; +static const struct wp_presentation_feedback_listener feedback_listener; + +static void feedback_sync_output(void *data, struct wp_presentation_feedback *fback, + struct wl_output *output) +{ +} + +static void feedback_presented(void *data, struct wp_presentation_feedback *fback, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, + uint32_t tv_nsec, uint32_t refresh_nsec, + uint32_t seq_hi, uint32_t seq_lo, + uint32_t flags) +{ + struct vo_wayland_state *wl = data; + wp_presentation_feedback_destroy(fback); + vo_wayland_sync_shift(wl); + + // Very similar to oml_sync_control, in this case we assume that every + // time the compositor receives feedback, a buffer swap has been already + // been performed. + // + // Notes: + // - tv_sec_lo + tv_sec_hi is the equivalent of oml's ust + // - seq_lo + seq_hi is the equivalent of oml's msc + // - these values are updated everytime the compositor receives feedback. + + int index = last_available_sync(wl); + if (index < 0) { + queue_new_sync(wl); + index = 0; + } + int64_t sec = (uint64_t) tv_sec_lo + ((uint64_t) tv_sec_hi << 32); + wl->sync[index].sbc = wl->user_sbc; + wl->sync[index].ust = sec * 1000000LL + (uint64_t) tv_nsec / 1000; + wl->sync[index].msc = (uint64_t) seq_lo + ((uint64_t) seq_hi << 32); + wl->sync[index].refresh_usec = (uint64_t)refresh_nsec/1000; + wl->sync[index].filled = true; +} + +static void feedback_discarded(void *data, struct wp_presentation_feedback *fback) +{ + wp_presentation_feedback_destroy(fback); +} + +static const struct wp_presentation_feedback_listener feedback_listener = { + feedback_sync_output, + feedback_presented, + feedback_discarded, +}; + static void wayland_vk_swap_buffers(struct ra_ctx *ctx) { struct vo_wayland_state *wl = ctx->vo->wl; + if (wl->presentation) { + wl->feedback = wp_presentation_feedback(wl->presentation, wl->surface); + wp_presentation_feedback_add_listener(wl->feedback, &feedback_listener, wl); + wl->user_sbc += 1; + int index = last_available_sync(wl); + if (index < 0) + queue_new_sync(wl); + } + vo_wayland_wait_frame(wl); - wl->callback_wait = true; + + if (wl->presentation) + wayland_sync_swap(wl); + + wl->frame_wait = true; +} + +static void wayland_vk_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info) +{ + struct vo_wayland_state *wl = ctx->vo->wl; + if (wl->presentation) { + info->vsync_duration = wl->vsync_duration; + info->skipped_vsyncs = wl->last_skipped_vsyncs; + info->last_queue_display_time = wl->last_queue_display_time; + } } static void wayland_vk_uninit(struct ra_ctx *ctx) @@ -81,6 +157,7 @@ static bool wayland_vk_init(struct ra_ctx *ctx) struct ra_vk_ctx_params params = { .swap_buffers = wayland_vk_swap_buffers, + .get_vsync = wayland_vk_get_vsync, }; VkInstance inst = vk->vkinst->instance; diff --git a/video/out/wayland_common.c b/video/out/wayland_common.c index fa409a5541..f66753018b 100644 --- a/video/out/wayland_common.c +++ b/video/out/wayland_common.c @@ -20,6 +20,7 @@ #include #include #include +#include #include "common/msg.h" #include "input/input.h" #include "input/keycodes.h" @@ -37,6 +38,9 @@ // Generated from xdg-decoration-unstable-v1.xml #include "video/out/wayland/xdg-decoration-v1.h" +// Generated from presentation-time.xml +#include "video/out/wayland/presentation-time.h" + static void xdg_wm_base_ping(void *data, struct xdg_wm_base *wm_base, uint32_t serial) { xdg_wm_base_pong(wm_base, serial); @@ -796,6 +800,19 @@ static const struct wl_surface_listener surface_listener = { surface_handle_leave, }; +static void pres_set_clockid(void *data, struct wp_presentation *pres, + uint32_t clockid) +{ + struct vo_wayland_state *wl = data; + + wl->presentation = pres; + clockid = CLOCK_MONOTONIC; +} + +static const struct wp_presentation_listener pres_listener = { + pres_set_clockid, +}; + static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id, const char *interface, uint32_t ver) { @@ -844,6 +861,11 @@ static void registry_handle_add(void *data, struct wl_registry *reg, uint32_t id wl->xdg_decoration_manager = wl_registry_bind(reg, id, &zxdg_decoration_manager_v1_interface, 1); } + if (!strcmp(interface, wp_presentation_interface.name) && found++) { + wl->presentation = wl_registry_bind(reg, id, &wp_presentation_interface, 1); + wp_presentation_add_listener(wl->presentation, &pres_listener, wl); + } + if (!strcmp(interface, zwp_idle_inhibit_manager_v1_interface.name) && found++) { wl->idle_inhibit_manager = wl_registry_bind(reg, id, &zwp_idle_inhibit_manager_v1_interface, 1); } @@ -1056,6 +1078,16 @@ int vo_wayland_init(struct vo *vo) wl_data_device_manager_interface.name); } + if (wl->presentation) { + wl->sync = talloc_zero_array(wl, struct vo_wayland_sync, 1); + struct vo_wayland_sync sync = {0, 0, 0, 0}; + wl->sync[0] = sync; + wl->sync_size += 1; + } else { + MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n", + wp_presentation_interface.name); + } + if (wl->xdg_decoration_manager) { wl->xdg_toplevel_decoration = zxdg_decoration_manager_v1_get_toplevel_decoration(wl->xdg_decoration_manager, wl->xdg_toplevel); set_border_decorations(wl, vo->opts->border); @@ -1142,6 +1174,9 @@ void vo_wayland_uninit(struct vo *vo) if (wl->frame_callback) wl_callback_destroy(wl->frame_callback); + if (wl->presentation) + wp_presentation_destroy(wl->presentation); + if (wl->pointer) wl_pointer_destroy(wl->pointer); @@ -1403,6 +1438,73 @@ int vo_wayland_control(struct vo *vo, int *events, int request, void *arg) return VO_NOTIMPL; } +void vo_wayland_sync_shift(struct vo_wayland_state *wl) +{ + for (int i = wl->sync_size - 1; i > 0; --i) { + wl->sync[i] = wl->sync[i-1]; + } + struct vo_wayland_sync sync = {0, 0, 0, 0}; + wl->sync[0] = sync; +} + +int last_available_sync(struct vo_wayland_state *wl) +{ + for (int i = wl->sync_size - 1; i > -1; --i) { + if (!wl->sync[i].filled) + return i; + } + return -1; +} + +void queue_new_sync(struct vo_wayland_state *wl) +{ + wl->sync_size += 1; + wl->sync = talloc_realloc(wl, wl->sync, struct vo_wayland_sync, wl->sync_size); + vo_wayland_sync_shift(wl); + wl->sync[0].sbc = wl->user_sbc; +} + +void wayland_sync_swap(struct vo_wayland_state *wl) +{ + int index = wl->sync_size - 1; + + wl->last_skipped_vsyncs = 0; + + // If these are the same (can happen if a frame takes too long), update + // the ust/msc/sbc based on when the next frame is expected to arrive. + if (wl->sync[index].ust == wl->last_ust && wl->last_ust) { + wl->sync[index].ust += wl->sync[index].refresh_usec; + wl->sync[index].msc += 1; + wl->sync[index].sbc += 1; + } + + int64_t ust_passed = wl->sync[index].ust ? wl->sync[index].ust - wl->last_ust: 0; + wl->last_ust = wl->sync[index].ust; + int64_t msc_passed = wl->sync[index].msc ? wl->sync[index].msc - wl->last_msc: 0; + wl->last_msc = wl->sync[index].msc; + int64_t sbc_passed = wl->sync[index].sbc ? wl->sync[index].sbc - wl->last_sbc: 0; + wl->last_sbc = wl->sync[index].sbc; + + if (msc_passed && ust_passed) + wl->vsync_duration = ust_passed / msc_passed; + + if (sbc_passed) { + 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 - wl->sync[index].ust); + wl->last_sbc_mp_time = ust_mp_time; + } + + if (!wl->sync[index].sbc) + return; + + wl->last_queue_display_time = wl->last_sbc_mp_time + sbc_passed*wl->vsync_duration; +} + void vo_wayland_wakeup(struct vo *vo) { struct vo_wayland_state *wl = vo->wl; @@ -1416,9 +1518,9 @@ void vo_wayland_wait_frame(struct vo_wayland_state *wl) }; double vblank_time = 1e6 / wl->current_output->refresh_rate; - int64_t finish_time = mp_time_us() + vblank_time; + int64_t finish_time = mp_time_us() + vblank_time + 1000; - while (wl->callback_wait && finish_time > mp_time_us()) { + while (wl->frame_wait && finish_time > mp_time_us()) { while (wl_display_prepare_read(wl->display) != 0) wl_display_dispatch_pending(wl->display); diff --git a/video/out/wayland_common.h b/video/out/wayland_common.h index 1415c6935d..606d9ed218 100644 --- a/video/out/wayland_common.h +++ b/video/out/wayland_common.h @@ -25,6 +25,14 @@ #include "vo.h" #include "input/event.h" +struct vo_wayland_sync { + int64_t ust; + int64_t msc; + int64_t sbc; + int64_t refresh_usec; + bool filled; +}; + struct vo_wayland_output { struct vo_wayland_state *wl; uint32_t id; @@ -56,7 +64,7 @@ struct vo_wayland_state { bool fullscreen; bool maximized; bool configured; - bool callback_wait; + bool frame_wait; int wakeup_pipe[2]; int pending_vo_events; int mouse_x; @@ -74,11 +82,25 @@ struct vo_wayland_state { struct xdg_wm_base *wm_base; struct xdg_toplevel *xdg_toplevel; struct xdg_surface *xdg_surface; + struct wp_presentation *presentation; + struct wp_presentation_feedback *feedback; struct zxdg_decoration_manager_v1 *xdg_decoration_manager; struct zxdg_toplevel_decoration_v1 *xdg_toplevel_decoration; struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager; struct zwp_idle_inhibitor_v1 *idle_inhibitor; + /* Presentation Feedback */ + struct vo_wayland_sync *sync; + int sync_size; + int64_t user_sbc; + int64_t last_ust; + int64_t last_msc; + int64_t last_sbc; + int64_t last_sbc_mp_time; + int64_t vsync_duration; + int64_t last_skipped_vsyncs; + int64_t last_queue_display_time; + /* Input */ struct wl_seat *seat; struct wl_pointer *pointer; @@ -109,10 +131,14 @@ struct vo_wayland_state { int vo_wayland_init(struct vo *vo); int vo_wayland_reconfig(struct vo *vo); int vo_wayland_control(struct vo *vo, int *events, int request, void *arg); +int last_available_sync(struct vo_wayland_state *wl); void vo_wayland_check_events(struct vo *vo); void vo_wayland_uninit(struct vo *vo); void vo_wayland_wakeup(struct vo *vo); void vo_wayland_wait_events(struct vo *vo, int64_t until_time_us); void vo_wayland_wait_frame(struct vo_wayland_state *wl); +void wayland_sync_swap(struct vo_wayland_state *wl); +void vo_wayland_sync_shift(struct vo_wayland_state *wl); +void queue_new_sync(struct vo_wayland_state *wl); #endif /* MPLAYER_WAYLAND_COMMON_H */ diff --git a/wscript_build.py b/wscript_build.py index 63495689fb..9f47153413 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -129,6 +129,12 @@ def build(ctx): ctx.wayland_protocol_header(proto_dir = ctx.env.WL_PROTO_DIR, protocol = "unstable/idle-inhibit/idle-inhibit-unstable-v1", target = "video/out/wayland/idle-inhibit-v1.h") + ctx.wayland_protocol_code(proto_dir = ctx.env.WL_PROTO_DIR, + protocol = "stable/presentation-time/presentation-time", + target = "video/out/wayland/presentation-time.c") + ctx.wayland_protocol_header(proto_dir = ctx.env.WL_PROTO_DIR, + protocol = "stable/presentation-time/presentation-time", + target = "video/out/wayland/presentation-time.h") ctx.wayland_protocol_code(proto_dir = ctx.env.WL_PROTO_DIR, protocol = "unstable/xdg-decoration/xdg-decoration-unstable-v1", target = "video/out/wayland/xdg-decoration-v1.c") @@ -499,6 +505,7 @@ def build(ctx): ( "video/out/vulkan/utils.c", "vulkan" ), ( "video/out/w32_common.c", "win32-desktop" ), ( "video/out/wayland/idle-inhibit-v1.c", "wayland" ), + ( "video/out/wayland/presentation-time.c", "wayland" ), ( "video/out/wayland/xdg-decoration-v1.c", "wayland" ), ( "video/out/wayland/xdg-shell.c", "wayland" ), ( "video/out/wayland_common.c", "wayland" ),