diff options
-rw-r--r-- | video/out/opengl/context_glx.c | 101 | ||||
-rw-r--r-- | video/out/opengl/oml_sync.c | 112 | ||||
-rw-r--r-- | video/out/opengl/oml_sync.h | 28 | ||||
-rw-r--r-- | wscript_build.py | 1 |
4 files changed, 146 insertions, 96 deletions
diff --git a/video/out/opengl/context_glx.c b/video/out/opengl/context_glx.c index 1f3225a9ac..7dc8062c1c 100644 --- a/video/out/opengl/context_glx.c +++ b/video/out/opengl/context_glx.c @@ -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; - } - - 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; - } + ust = msc = sbc = -1; - // 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: diff --git a/video/out/opengl/oml_sync.c b/video/out/opengl/oml_sync.c new file mode 100644 index 0000000000..aaa97a7311 --- /dev/null +++ b/video/out/opengl/oml_sync.c @@ -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; +} diff --git a/video/out/opengl/oml_sync.h b/video/out/opengl/oml_sync.h new file mode 100644 index 0000000000..e27ccb943f --- /dev/null +++ b/video/out/opengl/oml_sync.h @@ -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); diff --git a/wscript_build.py b/wscript_build.py index 3e9cfadd34..fb662c0d59 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -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" ), |