vo: move wayland presentation to separate files

Wayland had some specific code that it used for implementing the
presentation time protocol. It turns out that xorg's present extension
is extremely similar, so it would be silly to duplicate this whole mess
again. Factor this out to separate, independent code and introduce the
mp_present struct which is used for handling the ust/msc values and some
other associated values. Also, add in some helper functions so all the
dirty details live specifically in present_sync. The only
wayland-specific part is actually obtaining ust/msc values. Since only
wayland or xorg are expected to use this, add a conditional to the build
that only adds this file when either one of those are present.

You may observe that sbc is completely omitted. This field existed in
wayland, but was completely unused (presentation time doesn't return
this). Xorg's present extension also doesn't use this so just get rid of
it all together. The actual calculation is slightly altered so it is
correct for our purposes. We want to get the presentation event of the
last frame that was just occured (this function executes right after the
buffer swap). The adjustment is to just remove the vsync_duration
subtraction. Also, The overly-complicated queue approach is removed.
This has no actual use in practice (on wayland or xorg). Presentation
statistics are only ever used after the immediate preceding swap to
update vsync timings or thrown away.
This commit is contained in:
Dudemanguy 2022-06-10 11:49:28 -05:00
parent 44ecf83a1b
commit 7ce26dd324
10 changed files with 163 additions and 120 deletions

View File

@ -1099,6 +1099,10 @@ if xv.found()
sources += files('video/out/vo_xv.c')
endif
if wayland['use'] or x11['use']
sources += ('video/out/present_sync.c')
endif
# OpenGL feature checking
gl = {

View File

@ -20,6 +20,7 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include "video/out/present_sync.h"
#include "video/out/wayland_common.h"
#include "context.h"
#include "egl_helpers.h"
@ -101,17 +102,14 @@ static void wayland_egl_swap_buffers(struct ra_ctx *ctx)
vo_wayland_wait_frame(wl);
if (wl->presentation)
vo_wayland_sync_swap(wl);
present_sync_swap(wl->present);
}
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;
}
if (wl->presentation)
present_sync_get_info(wl->present, info);
}
static bool egl_create_context(struct ra_ctx *ctx)

85
video/out/present_sync.c Normal file
View File

@ -0,0 +1,85 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include <time.h>
#include "mpv_talloc.h"
#include "osdep/timer.h"
#include "present_sync.h"
/* General nonsense about this mechanism.
*
* This requires that that caller has access to two, related values:
* (ust, msc): clock time and incrementing counter of last vsync (this is
* increased continuously, even if we don't swap)
*
* Note that this concept originates from the GLX_OML_sync_control extension
* which includes another parameter: sbc (swap counter of frame that was
* last displayed). Both the xorg present extension and wayland's
* presentation-time protocol do not include sbc values so they are omitted
* from this mechanism. mpv does not need to keep track of sbc calls and can
* have reliable presentation without it.
*/
void present_sync_get_info(struct mp_present *present, struct vo_vsync_info *info)
{
info->vsync_duration = present->vsync_duration;
info->skipped_vsyncs = present->last_skipped_vsyncs;
info->last_queue_display_time = present->last_queue_display_time;
}
void present_sync_swap(struct mp_present *present)
{
int64_t ust = present->current_ust;
int64_t msc = present->current_msc;
// Avoid attempting to use any presentation statistics if the ust is 0 or has
// not actually updated (i.e. the last_ust is equal to current_ust).
if (!ust || ust == present->last_ust) {
present->last_skipped_vsyncs = -1;
present->vsync_duration = -1;
present->last_queue_display_time = -1;
return;
}
present->last_skipped_vsyncs = 0;
int64_t ust_passed = ust ? ust - present->last_ust: 0;
present->last_ust = ust;
int64_t msc_passed = msc ? msc - present->last_msc: 0;
present->last_msc = msc;
if (msc_passed && ust_passed)
present->vsync_duration = ust_passed / msc_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 - ust);
present->last_queue_display_time = ust_mp_time;
}
void present_update_sync_values(struct mp_present *present, int64_t ust,
int64_t msc)
{
present->current_ust = ust;
present->current_msc = msc;
}

48
video/out/present_sync.h Normal file
View File

@ -0,0 +1,48 @@
/*
* This file is part of mpv video player.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MP_PRESENT_SYNC_H
#define MP_PRESENT_SYNC_H
#include <stdbool.h>
#include <stdint.h>
#include "vo.h"
/* Generic helpers for obtaining presentation feedback from
* backend APIs. This requires ust/msc values. */
struct mp_present {
int64_t current_ust;
int64_t current_msc;
int64_t last_ust;
int64_t last_msc;
int64_t vsync_duration;
int64_t last_skipped_vsyncs;
int64_t last_queue_display_time;
};
// Used during the get_vsync call to deliver the presentation statistics to the VO.
void present_sync_get_info(struct mp_present *present, struct vo_vsync_info *info);
// Called after every buffer swap to update presentation statistics.
void present_sync_swap(struct mp_present *present);
// Called anytime the backend delivers new ust/msc values.
void present_update_sync_values(struct mp_present *present, int64_t ust,
int64_t msc);
#endif /* MP_PRESENT_SYNC_H */

View File

@ -21,9 +21,11 @@
#include <va/va_wayland.h>
#include <va/va_drmcommon.h>
#include "present_sync.h"
#include "sub/osd.h"
#include "video/vaapi.h"
#include "wayland_common.h"
#include "generated/wayland/linux-dmabuf-unstable-v1.h"
#include "generated/wayland/viewporter.h"
@ -383,7 +385,7 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame)
if (!wl->opts->disable_vsync)
vo_wayland_wait_frame(wl);
if (wl->presentation)
vo_wayland_sync_swap(wl);
present_sync_swap(wl->present);
}
static void flip_page(struct vo *vo)
{
@ -393,11 +395,8 @@ static void flip_page(struct vo *vo)
static void get_vsync(struct vo *vo, struct vo_vsync_info *info)
{
struct vo_wayland_state *wl = 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;
}
if (wl->presentation)
present_sync_get_info(wl->present, info);
}
const struct vo_driver video_out_vaapi_wayland = {

View File

@ -24,6 +24,7 @@
#include <libswscale/swscale.h>
#include "osdep/endian.h"
#include "present_sync.h"
#include "sub/osd.h"
#include "video/fmt-conversion.h"
#include "video/mp_image.h"
@ -262,17 +263,14 @@ static void flip_page(struct vo *vo)
vo_wayland_wait_frame(wl);
if (wl->presentation)
vo_wayland_sync_swap(wl);
present_sync_swap(wl->present);
}
static void get_vsync(struct vo *vo, struct vo_vsync_info *info)
{
struct vo_wayland_state *wl = 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;
}
if (wl->presentation)
present_sync_get_info(wl->present, info);
}
static void uninit(struct vo *vo)

View File

@ -16,6 +16,7 @@
*/
#include "video/out/gpu/context.h"
#include "video/out/present_sync.h"
#include "video/out/wayland_common.h"
#include "common.h"
@ -39,17 +40,14 @@ static void wayland_vk_swap_buffers(struct ra_ctx *ctx)
vo_wayland_wait_frame(wl);
if (wl->presentation)
vo_wayland_sync_swap(wl);
present_sync_swap(wl->present);
}
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;
}
if (wl->presentation)
present_sync_get_info(wl->present, info);
}
static void wayland_vk_uninit(struct ra_ctx *ctx)

View File

@ -30,6 +30,7 @@
#include "options/m_config.h"
#include "osdep/io.h"
#include "osdep/timer.h"
#include "present_sync.h"
#include "wayland_common.h"
#include "win_state.h"
@ -143,28 +144,18 @@ struct vo_wayland_output {
struct wl_list link;
};
struct vo_wayland_sync {
int64_t ust;
int64_t msc;
int64_t sbc;
bool filled;
};
static int check_for_resize(struct vo_wayland_state *wl, wl_fixed_t x_w, wl_fixed_t y_w,
int edge_pixels, enum xdg_toplevel_resize_edge *edge);
static int get_mods(struct vo_wayland_state *wl);
static int last_available_sync(struct vo_wayland_state *wl);
static int lookupkey(int key);
static int set_cursor_visibility(struct vo_wayland_state *wl, bool on);
static int spawn_cursor(struct vo_wayland_state *wl);
static void greatest_common_divisor(struct vo_wayland_state *wl, int a, int b);
static void queue_new_sync(struct vo_wayland_state *wl);
static void remove_output(struct vo_wayland_output *out);
static void request_decoration_mode(struct vo_wayland_state *wl, uint32_t mode);
static void set_geometry(struct vo_wayland_state *wl);
static void set_surface_scaling(struct vo_wayland_state *wl);
static void sync_shift(struct vo_wayland_state *wl);
static void window_move(struct vo_wayland_state *wl, uint32_t serial);
/* Wayland listener boilerplate */
@ -980,7 +971,6 @@ static void feedback_presented(void *data, struct wp_presentation_feedback *fbac
uint32_t flags)
{
struct vo_wayland_state *wl = data;
sync_shift(wl);
if (fback)
wp_presentation_feedback_destroy(fback);
@ -996,15 +986,10 @@ static void feedback_presented(void *data, struct wp_presentation_feedback *fbac
// - 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].ust = sec * 1000000LL + (uint64_t) tv_nsec / 1000;
wl->sync[index].msc = (uint64_t) seq_lo + ((uint64_t) seq_hi << 32);
wl->sync[index].filled = true;
int64_t ust = sec * 1000000LL + (uint64_t) tv_nsec / 1000;
int64_t msc = (uint64_t) seq_lo + ((uint64_t) seq_hi << 32);
present_update_sync_values(wl->present, ust, msc);
}
static void feedback_discarded(void *data, struct wp_presentation_feedback *fback)
@ -1378,15 +1363,6 @@ static struct vo_wayland_output *find_output(struct vo_wayland_state *wl)
return fallback_output;
}
static 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;
}
static int lookupkey(int key)
{
const char *passthrough_keys = " -+*/<>`~!@#$%^&()_{}:;\"\',.?\\|=[]";
@ -1403,13 +1379,6 @@ static int lookupkey(int key)
return mpkey;
}
static 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);
sync_shift(wl);
}
static void request_decoration_mode(struct vo_wayland_state *wl, uint32_t mode)
{
wl->requested_decoration = mode;
@ -1544,15 +1513,6 @@ static int spawn_cursor(struct vo_wayland_state *wl)
return 0;
}
static void 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;
}
static void toggle_fullscreen(struct vo_wayland_state *wl)
{
if (!wl->xdg_toplevel)
@ -1813,6 +1773,7 @@ int vo_wayland_init(struct vo *vo)
.display = wl_display_connect(NULL),
.vo = vo,
.log = mp_log_new(wl, vo->log, "wayland"),
.refresh_interval = 0,
.scaling = 1,
.wakeup_pipe = {-1, -1},
.dnd_fd = -1,
@ -1874,13 +1835,7 @@ int vo_wayland_init(struct vo *vo)
}
if (wl->presentation) {
wl->last_ust = 0;
wl->last_msc = 0;
wl->refresh_interval = 0;
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;
wl->present = talloc_zero(wl, struct mp_present);
} else {
MP_VERBOSE(wl, "Compositor doesn't support the %s protocol!\n",
wp_presentation_interface.name);
@ -1979,43 +1934,6 @@ bool vo_wayland_supported_format(struct vo *vo, uint32_t drm_format)
return false;
}
void vo_wayland_sync_swap(struct vo_wayland_state *wl)
{
int index = wl->sync_size - 1;
// If these are the same, presentation feedback has not been received.
// This can happen if a frame takes too long and misses vblank.
// Additionally, a compositor may return an ust value of 0. In either case,
// Don't attempt to use these statistics and wait until the next presentation
// event arrives.
if (!wl->sync[index].ust || wl->sync[index].ust == wl->last_ust) {
wl->last_skipped_vsyncs = -1;
wl->vsync_duration = -1;
wl->last_queue_display_time = -1;
return;
}
wl->last_skipped_vsyncs = 0;
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;
if (msc_passed && ust_passed)
wl->vsync_duration = ust_passed / msc_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_queue_display_time = ust_mp_time + wl->vsync_duration;
}
void vo_wayland_uninit(struct vo *vo)
{
struct vo_wayland_state *wl = vo->wl;
@ -2149,7 +2067,7 @@ void vo_wayland_wait_frame(struct vo_wayland_state *wl)
* 4. make up crap if vblank_time is still <= 0 (better than nothing) */
if (wl->presentation)
vblank_time = wl->vsync_duration;
vblank_time = wl->present->vsync_duration;
if (vblank_time <= 0 && wl->refresh_interval > 0)
vblank_time = wl->refresh_interval;

View File

@ -87,14 +87,8 @@ struct vo_wayland_state {
/* presentation-time */
struct wp_presentation *presentation;
struct wp_presentation_feedback *feedback;
struct vo_wayland_sync *sync;
int sync_size;
int64_t last_ust;
int64_t last_msc;
int64_t last_skipped_vsyncs;
int64_t last_queue_display_time;
struct mp_present *present;
int64_t refresh_interval;
int64_t vsync_duration;
/* xdg-decoration */
struct zxdg_decoration_manager_v1 *xdg_decoration_manager;

View File

@ -503,6 +503,7 @@ def build(ctx):
( "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" ),
( "video/out/vo.c" ),
( "video/out/vo_caca.c", "caca" ),
( "video/out/vo_direct3d.c", "direct3d" ),