summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--video/out/gpu/context.h9
-rw-r--r--video/out/opengl/context.c7
-rw-r--r--video/out/opengl/context.h3
-rw-r--r--video/out/opengl/context_glx.c101
-rw-r--r--video/out/vo.c9
-rw-r--r--video/out/vo.h5
-rw-r--r--video/out/vo_gpu.c8
7 files changed, 142 insertions, 0 deletions
diff --git a/video/out/gpu/context.h b/video/out/gpu/context.h
index a2fcb3711a..b6b6ffcf43 100644
--- a/video/out/gpu/context.h
+++ b/video/out/gpu/context.h
@@ -83,6 +83,15 @@ struct ra_swapchain_fns {
// Performs a buffer swap. This blocks for as long as necessary to meet
// params.swapchain_depth, or until the next vblank (for vsynced contexts)
void (*swap_buffers)(struct ra_swapchain *sw);
+
+ // Return the latency at which swap_buffers() is performed. This is in
+ // seconds and always >= 0. Essentially, it's the predicted time the last
+ // shown frame will take until it is actually displayed on the physical
+ // screen. (A reasonable implementation is returning the duration the
+ // last actually displayed frame took after its swap_buffers() was called.)
+ // Should return -1 on error (e.g. discontinuities).
+ // Can be NULL 0 or always return -1 if unsupported.
+ double (*get_latency)(struct ra_swapchain *sw);
};
// Create and destroy a ra_ctx. This also takes care of creating and destroying
diff --git a/video/out/opengl/context.c b/video/out/opengl/context.c
index 43b57aa4ed..44e64b7d9a 100644
--- a/video/out/opengl/context.c
+++ b/video/out/opengl/context.c
@@ -313,9 +313,16 @@ void ra_gl_ctx_swap_buffers(struct ra_swapchain *sw)
}
}
+static double ra_gl_ctx_get_latency(struct ra_swapchain *sw)
+{
+ struct priv *p = sw->priv;
+ return p->params.get_latency ? p->params.get_latency(sw->ctx) : -1;
+}
+
static const struct ra_swapchain_fns ra_gl_swapchain_fns = {
.color_depth = ra_gl_ctx_color_depth,
.start_frame = ra_gl_ctx_start_frame,
.submit_frame = ra_gl_ctx_submit_frame,
.swap_buffers = ra_gl_ctx_swap_buffers,
+ .get_latency = ra_gl_ctx_get_latency,
};
diff --git a/video/out/opengl/context.h b/video/out/opengl/context.h
index 5fccc70033..feaf8e1ab6 100644
--- a/video/out/opengl/context.h
+++ b/video/out/opengl/context.h
@@ -23,6 +23,9 @@ struct ra_gl_ctx_params {
// function or if you override it yourself.
void (*swap_buffers)(struct ra_ctx *ctx);
+ // See ra_swapchain_fns.get_latency.
+ double (*get_latency)(struct ra_ctx *ctx);
+
// Set to false if the implementation follows normal GL semantics, which is
// upside down. Set to true if it does *not*, i.e. if rendering is right
// side up
diff --git a/video/out/opengl/context_glx.c b/video/out/opengl/context_glx.c
index 462f2cf592..fe210c5f7d 100644
--- a/video/out/opengl/context_glx.c
+++ b/video/out/opengl/context_glx.c
@@ -41,11 +41,20 @@
#include "context.h"
#include "utils.h"
+// Must be >= max. assumed and supported display latency in frames.
+#define SYNC_SAMPLES 16
+
struct priv {
GL gl;
XVisualInfo *vinfo;
GLXContext context;
GLXFBConfig fbc;
+
+ Bool (*XGetSyncValues)(Display*, GLXDrawable, int64_t*, int64_t*, int64_t*);
+ uint64_t ust[SYNC_SAMPLES];
+ uint64_t last_sbc;
+ uint64_t last_msc;
+ double latency;
};
static void glx_uninit(struct ra_ctx *ctx)
@@ -161,6 +170,13 @@ static bool create_context_x11_gl3(struct ra_ctx *ctx, GL *gl, int gl_version,
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;
}
@@ -208,9 +224,91 @@ static void set_glx_attrib(int *attribs, int name, int value)
}
}
+static double update_latency_oml(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ assert(p->XGetSyncValues);
+
+ p->last_sbc += 1;
+
+ memmove(&p->ust[1], &p->ust[0], (SYNC_SAMPLES - 1) * sizeof(p->ust[0]));
+ p->ust[0] = 0;
+
+ int64_t ust, msc, sbc;
+ if (!p->XGetSyncValues(ctx->vo->x11->display, ctx->vo->x11->window,
+ &ust, &msc, &sbc))
+ return -1;
+
+ p->ust[0] = ust;
+
+ uint64_t last_msc = p->last_msc;
+ p->last_msc = msc;
+
+ // There was a driver-level discontinuity.
+ if (msc != last_msc + 1)
+ return -1;
+
+ // No frame displayed yet.
+ if (!ust || !sbc || !msc)
+ return -1;
+
+ // We really need to know the time since the vsync happened. There is no way
+ // to get the UST at the time which the frame was queued. So we have to make
+ // assumptions about the UST. 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 -1;
+ uint64_t now_monotonic = ts.tv_sec * 1000000LL + ts.tv_nsec / 1000;
+
+ // Actually we need two consecutive displays before we can accurately
+ // measure the latency (because we need to compute vsync_duration).
+ if (!p->ust[1])
+ return -1;
+
+ // Display frame duration.
+ int64_t vsync_duration = p->ust[0] - p->ust[1];
+
+ // Display latency in frames.
+ int64_t n_frames = p->last_sbc - sbc;
+
+ // Too high latency, or other nonsense.
+ if (n_frames < 0 || n_frames >= SYNC_SAMPLES)
+ return -1;
+
+ // Values were not recorded? (Temporary failures etc.)
+ if (!p->ust[n_frames])
+ return -1;
+
+ // Time since last frame display event.
+ int64_t latency_us = now_monotonic - p->ust[n_frames];
+
+ // The frame display event probably happened very recently (about within one
+ // vsync), but the corresponding video frame can be much older.
+ latency_us = (n_frames + 1) * vsync_duration - latency_us;
+
+ return latency_us / (1000.0 * 1000.0);
+}
+
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)
+ p->latency = update_latency_oml(ctx);
+}
+
+static double glx_get_latency(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+ return p->latency;
}
static bool glx_init(struct ra_ctx *ctx)
@@ -296,11 +394,14 @@ static bool glx_init(struct ra_ctx *ctx)
struct ra_gl_ctx_params params = {
.swap_buffers = glx_swap_buffers,
+ .get_latency = glx_get_latency,
};
if (!ra_gl_ctx_init(ctx, gl, params))
goto uninit;
+ p->latency = -1;
+
return true;
uninit:
diff --git a/video/out/vo.c b/video/out/vo.c
index a33d9fd15f..466759f595 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -141,6 +141,7 @@ struct vo_internal {
double estimated_vsync_jitter;
bool expecting_vsync;
int64_t num_successive_vsyncs;
+ double last_vo_latency;
int64_t flip_queue_offset; // queue flip events at most this much in advance
int64_t timing_offset; // same (but from options; not VO configured)
@@ -481,6 +482,10 @@ static void update_vsync_timing_after_swap(struct vo *vo)
int64_t now = mp_time_us();
int64_t prev_vsync = in->prev_vsync;
+ // If we can, use a "made up" expected display time.
+ if (in->last_vo_latency >= 0)
+ now += in->last_vo_latency * (1000.0 * 1000.0);
+
in->prev_vsync = now;
if (!in->expecting_vsync) {
@@ -910,11 +915,15 @@ bool vo_render_frame_external(struct vo *vo)
vo->driver->flip_page(vo);
+ double latency =
+ vo->driver->get_latency ? vo->driver->get_latency(vo) : -1;
+
MP_STATS(vo, "end video-flip");
pthread_mutex_lock(&in->lock);
in->dropped_frame = prev_drop_count < vo->in->drop_count;
in->rendering = false;
+ in->last_vo_latency = latency;
update_vsync_timing_after_swap(vo);
}
diff --git a/video/out/vo.h b/video/out/vo.h
index 3c00bb988e..848f71e472 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -374,6 +374,11 @@ struct vo_driver {
*/
void (*flip_page)(struct vo *vo);
+ /*
+ * See struct ra_swapchain. Optional.
+ */
+ double (*get_latency)(struct vo *vo);
+
/* These optional callbacks can be provided if the GUI framework used by
* the VO requires entering a message loop for receiving events and does
* not call vo_wakeup() from a separate thread when there are new events.
diff --git a/video/out/vo_gpu.c b/video/out/vo_gpu.c
index a80ba233c2..3535ae3e7e 100644
--- a/video/out/vo_gpu.c
+++ b/video/out/vo_gpu.c
@@ -98,6 +98,13 @@ static void flip_page(struct vo *vo)
sw->fns->swap_buffers(sw);
}
+static double get_latency(struct vo *vo)
+{
+ struct gpu_priv *p = vo->priv;
+ struct ra_swapchain *sw = p->ctx->swapchain;
+ return sw->fns->get_latency ? sw->fns->get_latency(sw) : -1;
+}
+
static int query_format(struct vo *vo, int format)
{
struct gpu_priv *p = vo->priv;
@@ -326,6 +333,7 @@ const struct vo_driver video_out_gpu = {
.get_image = get_image,
.draw_frame = draw_frame,
.flip_page = flip_page,
+ .get_latency = get_latency,
.wait_events = wait_events,
.wakeup = wakeup,
.uninit = uninit,