summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2017-03-21 15:51:35 +0100
committerwm4 <wm4@nowhere>2017-03-23 14:10:04 +0100
commita398b4c09c16783a4f2aa50382c0bf2fea6fc042 (patch)
tree7174c1342eb865f3a8a7b660831b43a5f9642a95
parenta993a871ee7d3a8c462add81153bd10bb3600cf8 (diff)
downloadmpv-a398b4c09c16783a4f2aa50382c0bf2fea6fc042.tar.bz2
mpv-a398b4c09c16783a4f2aa50382c0bf2fea6fc042.tar.xz
frame statistics wip
vo.c doesn't use them yet, only calls it and prints some stuff derived from it. Also, those calculations are untested and probably wrong.
-rw-r--r--video/out/opengl/context.c7
-rw-r--r--video/out/opengl/context.h8
-rw-r--r--video/out/vo.c49
-rw-r--r--video/out/vo.h93
-rw-r--r--video/out/vo_opengl.c7
5 files changed, 161 insertions, 3 deletions
diff --git a/video/out/opengl/context.c b/video/out/opengl/context.c
index 6fdc123fb6..de11570ad8 100644
--- a/video/out/opengl/context.c
+++ b/video/out/opengl/context.c
@@ -251,6 +251,13 @@ void mpgl_swap_buffers(struct MPGLContext *ctx)
ctx->driver->swap_buffers(ctx);
}
+void mpgl_get_frame_statistics(struct MPGLContext *ctx,
+ struct vo_frame_statistics *st)
+{
+ if (ctx->driver->get_frame_statistics)
+ ctx->driver->get_frame_statistics(ctx, st);
+}
+
void mpgl_uninit(MPGLContext *ctx)
{
set_current_context(NULL);
diff --git a/video/out/opengl/context.h b/video/out/opengl/context.h
index 229c5ef54f..f392f446de 100644
--- a/video/out/opengl/context.h
+++ b/video/out/opengl/context.h
@@ -64,9 +64,13 @@ struct mpgl_driver {
// Optional.
void (*start_frame)(struct MPGLContext *ctx);
- // Present the frame.
+ // Present/finish the frame. See vo_driver.flip_page for remarks.
void (*swap_buffers)(struct MPGLContext *ctx);
+ // See vo_driver.get_frame_statistics for remarks.
+ void (*get_frame_statistics)(struct MPGLContext *ctx,
+ struct vo_frame_statistics *st);
+
// This behaves exactly like vo_driver.control().
int (*control)(struct MPGLContext *ctx, int *events, int request, void *arg);
@@ -106,6 +110,8 @@ int mpgl_reconfig_window(struct MPGLContext *ctx);
int mpgl_control(struct MPGLContext *ctx, int *events, int request, void *arg);
void mpgl_start_frame(struct MPGLContext *ctx);
void mpgl_swap_buffers(struct MPGLContext *ctx);
+void mpgl_get_frame_statistics(struct MPGLContext *ctx,
+ struct vo_frame_statistics *st);
int mpgl_find_backend(const char *name);
diff --git a/video/out/vo.c b/video/out/vo.c
index 1b71212f25..59a64f667b 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -165,6 +165,8 @@ struct vo_internal {
static void forget_frames(struct vo *vo);
static void *vo_thread(void *ptr);
+#define MP_VO_FRAME_STATISTICS_INIT { .present_count = -1, }
+
static bool get_desc(struct m_obj_desc *dst, int index)
{
if (index >= MP_ARRAY_SIZE(video_out_drivers) - 1)
@@ -414,7 +416,8 @@ static void vsync_skip_detection(struct vo *vo)
}
// Always called locked.
-static void update_vsync_timing_after_swap(struct vo *vo)
+static void update_vsync_timing_after_swap(struct vo *vo,
+ struct vo_frame_statistics *st)
{
struct vo_internal *in = vo->in;
@@ -456,6 +459,44 @@ static void update_vsync_timing_after_swap(struct vo *vo)
MP_STATS(vo, "value %f jitter", in->estimated_vsync_jitter);
MP_STATS(vo, "value %f vsync-diff", in->vsync_samples[0] / 1e6);
+
+ if (st->present_count >= 0) {
+ int64_t present_time = 0;
+ if (st->predicted_present_time_us > 0) {
+ present_time = st->predicted_present_time_us;
+ } else if (st->predicted_present_count > 0 && st->hw_present_count > 0) {
+ // obviously both hw_present_* fields should be set if any is set
+ assert(st->hw_present_vsync_count);
+ // hw_last_vsync_count < hw_present_vsync_count not allowed
+ assert(st->hw_last_vsync_count >= st->hw_present_vsync_count);
+ // both hw_last_vsync_* fields should be set if any is set
+ assert(st->hw_last_vsync_time_us);
+ // obviously the predicted count must be higher than whatever what
+ // was shown already (or == if the frame was shown)
+ assert(st->predicted_present_count >= st->hw_present_count);
+
+ // this is optional here, but for now simplifies the mess below
+ assert(st->hw_last_vsync_time_us > 0);
+
+ // if hw_last_vsync_count is ahead of hw_present_vsync_count,
+ // get the time for hw_present_vsync_count
+ int64_t hw_present_time_us = st->hw_last_vsync_time_us -
+ (st->hw_last_vsync_count - st->hw_present_vsync_count)
+ * st->hw_last_vsync_time_us;
+
+ present_time = hw_present_time_us -
+ (st->predicted_present_count - st->hw_present_count)
+ * st->hw_last_vsync_time_us;
+ }
+
+ if (present_time) {
+ MP_WARN(vo, "present at: %lld\n", (long long)present_time);
+ MP_STATS(vo, "event-timed %lld present-time", (long long)present_time);
+ } else {
+ MP_WARN(vo, "present time unknown\n");
+ MP_STATS(vo, "signal present-unknown");
+ }
+ }
}
// to be called from VO thread only
@@ -829,11 +870,15 @@ static bool render_frame(struct vo *vo)
MP_STATS(vo, "end video-flip");
+ struct vo_frame_statistics st = MP_VO_FRAME_STATISTICS_INIT;
+ if (vo->driver->get_frame_statistics)
+ vo->driver->get_frame_statistics(vo, &st);
+
pthread_mutex_lock(&in->lock);
in->dropped_frame = prev_drop_count < vo->in->drop_count;
in->rendering = false;
- update_vsync_timing_after_swap(vo);
+ update_vsync_timing_after_swap(vo, &st);
}
if (in->dropped_frame) {
diff --git a/video/out/vo.h b/video/out/vo.h
index 724e03ca41..796a7dcc83 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -227,6 +227,74 @@ struct vo_frame {
uint64_t frame_id;
};
+// Note: all fields are usually initialized to 0 or -1 by the caller, so that
+// the callee does not need to touch unsupported fields.
+// Where possible, all values should be consistent and acquired atomically.
+// For async present, present_count must be valid, and one of the following
+// groups of fields must have all fields set:
+// - present_count, predicted_present_time_us
+// - present_count, predicted_present_count, hw_present_*, hw_last_vsync_*
+struct vo_frame_statistics {
+ // Increased on every vo_driver.flip_page call.
+ // You can reset this (and all other fields) to 0 to signal discontinuities.
+ // (OML_sync_control: SBC returned by glXGetSyncValuesOML().)
+ int64_t present_count;
+
+ // present_count + predicted number of frames it will take to display the
+ // frame.
+ // (OML_sync_control: glXSwapBuffersMscOML() return value, SBC + latency.)
+ int64_t predicted_present_count;
+
+ // Absolute time in mp_time_us() time at which the frame will most likely
+ // be shown. (This is like predicted_present_count, but can include the
+ // clock time offset to the vsync event.)
+ int64_t predicted_present_time_us;
+
+ // The present_count of the last frame that was actually displayed. This
+ // must be set to 0 if no frame was displayed yet.
+ // (OML_sync_control: SBC returned by glXGetSyncValuesOML().)
+ int64_t hw_present_count;
+
+ // The hardware vsync counter at the time the frame corresponding to the
+ // hw_present_count field was presented. (The hardware vsync counter is
+ // incremented on each display refresh, even if no frame is presented. This
+ // field contains the counter value when the frame was presented.)
+ // (OML_sync_control: MSC returned by glXGetSyncValuesOML().)
+ int64_t hw_present_vsync_count;
+
+ // Last known hardware vsync count. This could be either the last frame that
+ // was presented by the VO, or the last hardware vsync that happened (even
+ // if no frame was presented, or the last frame was presented at a hw vsync
+ // before this). In particular, hw_last_vsync_count > hw_present_vsync_count
+ // is possible (but < is not allowed).
+ // (OML_sync_control: always the same value as hw_present_vsync_count,
+ // because it always refers to the last presented frame.)
+ int64_t hw_last_vsync_count;
+
+ // Absolute time in mp_time_us() time at which hw_last_vsync_count was
+ // incremented most recently. Graphics APIs will use something different
+ // than mp_time_us(), so you have to rebase the values to mp_time_us().
+ // (OML_sync_control: UST returned by glXGetSyncValuesOML().)
+ int64_t hw_last_vsync_time_us;
+
+ // The length of a display frame in microseconds. this doesn't not need to
+ // be set if VOCTRL_GET_DISPLAY_FPS returns a better value.
+ // (OML_sync_control: roughly what glXGetMscRateOML() returns.)
+ int64_t nominal_vsync_duration_us;
+};
+
+/*
+//simpler proposal
+struct vo_frame_statistics_simple {
+ // mp_time() time at which to display vsync
+ int64_t predicted_display_time_us;
+ // timing error for the most recently presented frame
+ // (we only expect display too late)
+ int64_t delayed_time_us;
+ int64_t nominal_vsync_duration_us;
+};
+*/
+
struct vo_driver {
// Encoding functionality, which can be invoked via --o only.
bool encode;
@@ -282,9 +350,34 @@ struct vo_driver {
/*
* Blit/Flip buffer to the screen. Must be called after each frame!
+ *
+ * Normally, this will block until the next vsync event. VOs can also
+ * support async presentation, in which case they must also implement
+ * the get_frame_statistics and return enough data to allow interpolating
+ * the final display time of the frame.
+ *
+ * If async presentation is used, the player will try to present as many
+ * frames as possible without waiting. The function should block
*/
void (*flip_page)(struct vo *vo);
+ /*
+ * Optional callback for retrieving frame statistics. Not setting this
+ * callback is equivalent to an empty callback (i.e. not setting any fields
+ * in the st parameter).
+ *
+ * This is required for async presentation, and also for correctly accounting
+ * for display latency (which is important for A/V sync).
+ *
+ * There is the danger of race conditions: the frame could be presented
+ * between flip_page and get_frame_statistics, or after the
+ * get_frame_statistics call. The backend must be aware of this situation,
+ * and provide enough information for this to be resolved. In practice,
+ * presentation lags behind by a specific delay that does normally not
+ * change radically.
+ */
+ void (*get_frame_statistics)(struct vo *vo, struct vo_frame_statistics *st);
+
/* 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_opengl.c b/video/out/vo_opengl.c
index 9b3f944e21..f70bd18bef 100644
--- a/video/out/vo_opengl.c
+++ b/video/out/vo_opengl.c
@@ -174,6 +174,12 @@ static void flip_page(struct vo *vo)
}
}
+static void get_frame_statistics(struct vo *vo, struct vo_frame_statistics *st)
+{
+ struct gl_priv *p = vo->priv;
+ mpgl_get_frame_statistics(p->glctx, st);
+}
+
static int query_format(struct vo *vo, int format)
{
struct gl_priv *p = vo->priv;
@@ -429,6 +435,7 @@ const struct vo_driver video_out_opengl = {
.control = control,
.draw_frame = draw_frame,
.flip_page = flip_page,
+ .get_frame_statistics = get_frame_statistics,
.wait_events = wait_events,
.wakeup = wakeup,
.uninit = uninit,