wayland: shuffle around the render loop again

Take two. f4e89dd went wrong by moving vo_wayland_wait_frame before
start_frame was called. Whether or not this matters depends on the
compositor, but some weird things can happen. Basically, it's a
scheduling issue. vo_wayland_wait_frame queues all events and sends them
to the server to process (with no blocking if presentation time is
available). If mpv changes state while rendering (and this function is
called before every frame is drawn), then that event also gets
dispatched and sent to the compositor. This, in some cases, can cause
some funny behavior because the next frame gets attached to the surface
while the old buffer is getting released. It's safer to call this
function after the swap already happens and well before mpv calls its
next draw. There's no weird scheduling of events, and the compositor log
is more normal.

The second part of this is to fix some stuttering issues. This is mostly
just conjecture, but probably what was happening was this thing called
"composition". The easiest way to see this is to play a video on the
default audio sync mode (probably easiest to see on a typical 23.976
video). Have that in a window and float it over firefox (floating
windows are bloat on a tiling wm anyway). Then in firefox, do some short
bursts of smooth scrolling (likely uses egl). Some stutter in video
rendering could be observed, particularly in panning shots.

Compositors are supposed to prevent tearing so what likely was happening
was that the compositor was simply holding the buffer a wee bit longer
to make sure it happened in sync with the smooth scrolling. Because the
mpv code waits precisely on presentation time, the loop would timeout on
occasion instead of receiving the frame callback. This would then lead
to a skipped frame when rendering and thus causing stuttering.

The fix is simple: just only count consecutive timeouts as not receiving
frame callback. If a compositor holds the mpv buffer slightly longer to
avoid tearing, then we will definitely receive frame callback on the
next round of the render loop. This logic also appears to be sound for
plasma (funfact: Plasma always returns frame callback even when the
window is hidden. Not sure what's up with that, but luckily it doesn't
matter to us.), so get rid of the goofy 1/vblank_time thing and just
keep it a simple > 1 check.
This commit is contained in:
Dudemanguy 2021-05-23 14:36:19 -05:00
parent 193814497b
commit c26d83348b
5 changed files with 36 additions and 36 deletions

View File

@ -63,12 +63,9 @@ static bool wayland_egl_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_
{
struct ra_ctx *ctx = sw->ctx;
struct vo_wayland_state *wl = ctx->vo->wl;
bool render = true;
if (!wl->opts->disable_vsync)
render = vo_wayland_wait_frame(wl);
bool render = wl->render || wl->opts->disable_vsync;
wl->frame_wait = true;
return render ? ra_gl_ctx_start_frame(sw, out_fbo) : false;
}
@ -80,6 +77,9 @@ static void wayland_egl_swap_buffers(struct ra_swapchain *sw)
eglSwapBuffers(p->egl_display, p->egl_surface);
if (!wl->opts->disable_vsync)
vo_wayland_wait_frame(wl);
if (wl->presentation)
wayland_sync_swap(wl);
}

View File

@ -218,13 +218,10 @@ static void draw_image(struct vo *vo, struct mp_image *src)
struct priv *p = vo->priv;
struct vo_wayland_state *wl = vo->wl;
struct buffer *buf;
bool callback = true;
if (!wl->opts->disable_vsync)
callback = vo_wayland_wait_frame(wl);
bool render = wl->render || wl->opts->disable_vsync;
wl->frame_wait = true;
if (!callback)
if (!render)
return;
buf = p->free_buffers;
@ -277,6 +274,9 @@ static void flip_page(struct vo *vo)
mp_rect_h(wl->geometry));
wl_surface_commit(wl->surface);
if (!wl->opts->disable_vsync)
vo_wayland_wait_frame(wl);
if (wl->presentation)
wayland_sync_swap(wl);
}

View File

@ -32,18 +32,19 @@ struct priv {
static bool wayland_vk_start_frame(struct ra_ctx *ctx)
{
struct vo_wayland_state *wl = ctx->vo->wl;
bool render = true;
if (!wl->opts->disable_vsync)
render = vo_wayland_wait_frame(wl);
bool render = wl->render || wl->opts->disable_vsync;
wl->frame_wait = true;
return render;
}
static void wayland_vk_swap_buffers(struct ra_ctx *ctx)
{
struct vo_wayland_state *wl = ctx->vo->wl;
if (!wl->opts->disable_vsync)
vo_wayland_wait_frame(wl);
if (wl->presentation)
wayland_sync_swap(wl);
}

View File

@ -1865,7 +1865,7 @@ void vo_wayland_wakeup(struct vo *vo)
(void)write(wl->wakeup_pipe[1], &(char){0}, 1);
}
bool vo_wayland_wait_frame(struct vo_wayland_state *wl)
void vo_wayland_wait_frame(struct vo_wayland_state *wl)
{
int64_t vblank_time = 0;
struct pollfd fds[1] = {
@ -1917,27 +1917,25 @@ bool vo_wayland_wait_frame(struct vo_wayland_state *wl)
}
/* If the compositor does not have presentatiom time, we cannot be sure
* that this wait is accurate. Do some crap with wl_display_roundtrip
* and randomly assume that if timeouts > refresh rate, the window is
* hidden. This is neccesary otherwise we may mistakeningly skip rendering.*/
if (!wl->presentation) {
if (wl_display_get_error(wl->display) == 0)
wl_display_roundtrip(wl->display);
if (wl->frame_wait) {
if (wl->timeout_count > ((1 / (double)vblank_time) * 1e6)) {
return false;
} else {
wl->timeout_count += 1;
return true;
}
} else {
wl->timeout_count = 0;
return true;
}
* that this wait is accurate. Do a hacky block with wl_display_roundtrip. */
if (!wl->presentation && !wl_display_get_error(wl->display))
wl_display_roundtrip(wl->display);
if (wl->frame_wait) {
// Only consider consecutive missed callbacks.
if (wl->timeout_count > 1) {
wl->render = false;
return;
} else {
wl->timeout_count += 1;
wl->render = true;
return;
}
}
return !wl->frame_wait;
wl->timeout_count = 0;
wl->render = true;
return;
}
void vo_wayland_wait_events(struct vo *vo, int64_t until_time_us)

View File

@ -74,6 +74,7 @@ struct vo_wayland_state {
int reduced_width;
int reduced_height;
bool frame_wait;
bool render;
bool state_change;
bool toplevel_configured;
bool activated;
@ -152,7 +153,7 @@ int last_available_sync(struct vo_wayland_state *wl);
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);
bool vo_wayland_wait_frame(struct vo_wayland_state *wl);
void vo_wayland_wait_frame(struct vo_wayland_state *wl);
void vo_wayland_set_opaque_region(struct vo_wayland_state *wl, int alpha);
void wayland_sync_swap(struct vo_wayland_state *wl);
void vo_wayland_sync_shift(struct vo_wayland_state *wl);