vo_gpu: glx: move OML sync code to an independent file

So the next commit can make EGL use it. EGL has a quite similar
function, that practically works the same. Although it's relatively
trivial, it's still tricky, and probably shouldn't end up as duplicated
code.

There are no functional changes, except initialization, and how failure
of the glXGetSyncValues call is handled. Also, some comments mention the
EGL extension.

Note that there's no intention for this code to handle anything else
than the very specific OML sync extension (and its EGL equivalent). This
is just too weirdly specific to the weird idiosyncrasies of the
extension, and it makes no sense to extend it to handle anything else.
(Such as Wayland or DXGI presentation feedback.)
This commit is contained in:
wm4 2019-06-30 00:28:46 +02:00 committed by James Ross-Gowan
parent 4b25ec3a9d
commit 8d7960f6ef
4 changed files with 146 additions and 96 deletions

View File

@ -40,6 +40,7 @@
#include "osdep/timer.h"
#include "video/out/x11_common.h"
#include "context.h"
#include "oml_sync.h"
#include "utils.h"
struct priv {
@ -49,14 +50,7 @@ struct priv {
GLXFBConfig fbc;
Bool (*XGetSyncValues)(Display*, GLXDrawable, int64_t*, int64_t*, int64_t*);
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 oml_sync sync;
};
static void glx_uninit(struct ra_ctx *ctx)
@ -232,91 +226,12 @@ static void update_vsync_oml(struct ra_ctx *ctx)
assert(p->XGetSyncValues);
p->last_skipped_vsyncs = 0;
p->user_sbc += 1;
// 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 with 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 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.
int64_t ust, msc, sbc;
if (!p->XGetSyncValues(ctx->vo->x11->display, ctx->vo->x11->window,
&ust, &msc, &sbc))
{
// Assume the extension is effectively unsupported.
p->vsync_duration = -1;
p->last_skipped_vsyncs = -1;
p->last_queue_display_time = -1;
return;
}
ust = msc = sbc = -1;
int64_t ust_passed = p->last_ust ? ust - p->last_ust : 0;
p->last_ust = ust;
int64_t msc_passed = p->last_msc ? msc - p->last_msc : 0;
p->last_msc = msc;
int64_t sbc_passed = sbc - p->last_sbc;
p->last_sbc = sbc;
// Display frame duration. This makes assumptions about UST (see below).
if (msc_passed && ust_passed)
p->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 extension spec doesn't define what the UST is (not even its unit).
// Simply assume UST is a simple CLOCK_MONOTONIC usec value. 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.
p->last_sbc_mp_time = ust_mp_time;
}
// At least one frame needs to be actually displayed before
// p->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 glx_swap_buffers() is called for every
// vsync).
p->last_queue_display_time =
p->last_sbc_mp_time + (p->user_sbc - sbc) * p->vsync_duration;
oml_sync_swap(&p->sync, ust, msc, sbc);
}
static void glx_swap_buffers(struct ra_ctx *ctx)
@ -332,9 +247,7 @@ static void glx_swap_buffers(struct ra_ctx *ctx)
static void glx_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info)
{
struct priv *p = ctx->priv;
info->vsync_duration = p->vsync_duration;
info->skipped_vsyncs = p->last_skipped_vsyncs;
info->last_queue_display_time = p->last_queue_display_time;
oml_sync_get_info(&p->sync, info);
}
static bool glx_init(struct ra_ctx *ctx)
@ -426,10 +339,6 @@ static bool glx_init(struct ra_ctx *ctx)
if (!ra_gl_ctx_init(ctx, gl, params))
goto uninit;
p->vsync_duration = -1;
p->last_skipped_vsyncs = -1;
p->last_queue_display_time = -1;
return true;
uninit:

112
video/out/opengl/oml_sync.c Normal file
View File

@ -0,0 +1,112 @@
#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 with 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 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;
}

View File

@ -0,0 +1,28 @@
#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);

View File

@ -480,6 +480,7 @@ def build(ctx):
( "video/out/opengl/hwdec_rpi.c", "rpi" ),
( "video/out/opengl/hwdec_vdpau.c", "vdpau-gl-x11" ),
( "video/out/opengl/libmpv_gl.c", "gl" ),
( "video/out/opengl/oml_sync.c", "gl-x11" ),
( "video/out/opengl/ra_gl.c", "gl" ),
( "video/out/opengl/utils.c", "gl" ),
( "video/out/vo.c" ),