summaryrefslogtreecommitdiffstats
path: root/video
diff options
context:
space:
mode:
Diffstat (limited to 'video')
-rw-r--r--video/decode/vd_lavc.c62
-rw-r--r--video/out/gpu/context.h3
-rw-r--r--video/out/gpu/video.c4
-rw-r--r--video/out/opengl/context.c9
-rw-r--r--video/out/opengl/context.h3
-rw-r--r--video/out/opengl/context_glx.c129
-rw-r--r--video/out/vo.c45
-rw-r--r--video/out/vo.h38
-rw-r--r--video/out/vo_gpu.c9
-rw-r--r--video/out/x11_common.c49
-rw-r--r--video/out/x11_common.h4
11 files changed, 297 insertions, 58 deletions
diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c
index cce3b4510b..97b3fa8b7d 100644
--- a/video/decode/vd_lavc.c
+++ b/video/decode/vd_lavc.c
@@ -28,9 +28,12 @@
#include <libavutil/intreadwrite.h>
#include <libavutil/pixdesc.h>
+#include "config.h"
+
#include "mpv_talloc.h"
#include "common/global.h"
#include "common/msg.h"
+#include "options/m_config.h"
#include "options/options.h"
#include "misc/bstr.h"
#include "common/av_common.h"
@@ -59,6 +62,8 @@ static void uninit_avctx(struct mp_filter *vd);
static int get_buffer2_direct(AVCodecContext *avctx, AVFrame *pic, int flags);
static enum AVPixelFormat get_format_hwdec(struct AVCodecContext *avctx,
const enum AVPixelFormat *pix_fmt);
+static int hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
+ struct bstr name, struct bstr param);
#define HWDEC_DELAY_QUEUE_COUNT 2
@@ -84,6 +89,9 @@ struct vd_lavc_params {
int software_fallback;
char **avopts;
int dr;
+ char *hwdec_api;
+ char *hwdec_codecs;
+ int hwdec_image_format;
};
static const struct m_opt_choice_alternatives discard_names[] = {
@@ -101,20 +109,24 @@ static const struct m_opt_choice_alternatives discard_names[] = {
const struct m_sub_options vd_lavc_conf = {
.opts = (const m_option_t[]){
- OPT_FLAG("fast", fast, 0),
- OPT_FLAG("show-all", show_all, 0),
- OPT_DISCARD("skiploopfilter", skip_loop_filter, 0),
- OPT_DISCARD("skipidct", skip_idct, 0),
- OPT_DISCARD("skipframe", skip_frame, 0),
- OPT_DISCARD("framedrop", framedrop, 0),
- OPT_INT("threads", threads, M_OPT_MIN, .min = 0),
- OPT_FLAG("bitexact", bitexact, 0),
- OPT_FLAG("assume-old-x264", old_x264, 0),
- OPT_FLAG("check-hw-profile", check_hw_profile, 0),
- OPT_CHOICE_OR_INT("software-fallback", software_fallback, 0, 1, INT_MAX,
- ({"no", INT_MAX}, {"yes", 1})),
- OPT_KEYVALUELIST("o", avopts, 0),
- OPT_FLAG("dr", dr, 0),
+ OPT_FLAG("vd-lavc-fast", fast, 0),
+ OPT_FLAG("vd-lavc-show-all", show_all, 0),
+ OPT_DISCARD("vd-lavc-skiploopfilter", skip_loop_filter, 0),
+ OPT_DISCARD("vd-lavc-skipidct", skip_idct, 0),
+ OPT_DISCARD("vd-lavc-skipframe", skip_frame, 0),
+ OPT_DISCARD("vd-lavc-framedrop", framedrop, 0),
+ OPT_INT("vd-lavc-threads", threads, M_OPT_MIN, .min = 0),
+ OPT_FLAG("vd-lavc-bitexact", bitexact, 0),
+ OPT_FLAG("vd-lavc-assume-old-x264", old_x264, 0),
+ OPT_FLAG("vd-lavc-check-hw-profile", check_hw_profile, 0),
+ OPT_CHOICE_OR_INT("vd-lavc-software-fallback", software_fallback,
+ 0, 1, INT_MAX, ({"no", INT_MAX}, {"yes", 1})),
+ OPT_KEYVALUELIST("vd-lavc-o", avopts, 0),
+ OPT_FLAG("vd-lavc-dr", dr, 0),
+ OPT_STRING_VALIDATE("hwdec", hwdec_api, M_OPT_OPTIONAL_PARAM,
+ hwdec_validate_opt),
+ OPT_STRING("hwdec-codecs", hwdec_codecs, 0),
+ OPT_IMAGEFORMAT("hwdec-image-format", hwdec_image_format, 0, .min = -1),
{0}
},
.size = sizeof(struct vd_lavc_params),
@@ -127,6 +139,8 @@ const struct m_sub_options vd_lavc_conf = {
.skip_frame = AVDISCARD_DEFAULT,
.framedrop = AVDISCARD_NONREF,
.dr = 1,
+ .hwdec_api = HAVE_RPI ? "mmal" : "no",
+ .hwdec_codecs = "h264,vc1,hevc,vp9",
},
};
@@ -147,7 +161,8 @@ struct hwdec_info {
typedef struct lavc_ctx {
struct mp_log *log;
- struct MPOpts *opts;
+ struct m_config_cache *opts_cache;
+ struct vd_lavc_params *opts;
struct mp_codec_params *codec;
AVCodecContext *avctx;
AVFrame *pic;
@@ -409,6 +424,8 @@ static void select_and_set_hwdec(struct mp_filter *vd)
vd_ffmpeg_ctx *ctx = vd->priv;
const char *codec = ctx->codec->codec;
+ m_config_cache_update(ctx->opts_cache);
+
bstr opt = bstr0(ctx->opts->hwdec_api);
bool hwdec_requested = !bstr_equals0(opt, "no");
@@ -493,8 +510,8 @@ static void select_and_set_hwdec(struct mp_filter *vd)
}
}
-int hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
- struct bstr name, struct bstr param)
+static int hwdec_validate_opt(struct mp_log *log, const m_option_t *opt,
+ struct bstr name, struct bstr param)
{
if (bstr_equals0(param, "help")) {
struct hwdec_info *hwdecs = NULL;
@@ -543,9 +560,11 @@ static void reinit(struct mp_filter *vd)
static void init_avctx(struct mp_filter *vd)
{
vd_ffmpeg_ctx *ctx = vd->priv;
- struct vd_lavc_params *lavc_param = ctx->opts->vd_lavc_params;
+ struct vd_lavc_params *lavc_param = ctx->opts;
struct mp_codec_params *c = ctx->codec;
+ m_config_cache_update(ctx->opts_cache);
+
assert(!ctx->avctx);
const AVCodec *lavc_codec = NULL;
@@ -911,7 +930,7 @@ static bool prepare_decoding(struct mp_filter *vd)
{
vd_ffmpeg_ctx *ctx = vd->priv;
AVCodecContext *avctx = ctx->avctx;
- struct vd_lavc_params *opts = ctx->opts->vd_lavc_params;
+ struct vd_lavc_params *opts = ctx->opts;
if (!avctx || ctx->hwdec_failed)
return false;
@@ -937,7 +956,7 @@ static bool prepare_decoding(struct mp_filter *vd)
static void handle_err(struct mp_filter *vd)
{
vd_ffmpeg_ctx *ctx = vd->priv;
- struct vd_lavc_params *opts = ctx->opts->vd_lavc_params;
+ struct vd_lavc_params *opts = ctx->opts;
MP_WARN(vd, "Error while decoding frame!\n");
@@ -1194,7 +1213,8 @@ static struct mp_decoder *create(struct mp_filter *parent,
vd_ffmpeg_ctx *ctx = vd->priv;
ctx->log = vd->log;
- ctx->opts = vd->global->opts;
+ ctx->opts_cache = m_config_cache_alloc(ctx, vd->global, &vd_lavc_conf);
+ ctx->opts = ctx->opts_cache->opts;
ctx->codec = codec;
ctx->decoder = talloc_strdup(ctx, decoder);
ctx->hwdec_swpool = mp_image_pool_new(ctx);
diff --git a/video/out/gpu/context.h b/video/out/gpu/context.h
index a2fcb3711a..ef48e6053f 100644
--- a/video/out/gpu/context.h
+++ b/video/out/gpu/context.h
@@ -83,6 +83,9 @@ 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);
+
+ // See vo. Usually called after swap_buffers().
+ void (*get_vsync)(struct ra_swapchain *sw, struct vo_vsync_info *info);
};
// Create and destroy a ra_ctx. This also takes care of creating and destroying
diff --git a/video/out/gpu/video.c b/video/out/gpu/video.c
index 416ba928d1..6004a0ab60 100644
--- a/video/out/gpu/video.c
+++ b/video/out/gpu/video.c
@@ -3889,7 +3889,9 @@ static void reinit_from_options(struct gl_video *p)
gl_video_setup_hooks(p);
reinit_osd(p);
- if (p->opts.interpolation && !p->global->opts->video_sync && !p->dsi_warned) {
+ int vs;
+ mp_read_option_raw(p->global, "video-sync", &m_option_type_choice, &vs);
+ if (p->opts.interpolation && !vs && !p->dsi_warned) {
MP_WARN(p, "Interpolation now requires enabling display-sync mode.\n"
"E.g.: --video-sync=display-resample\n");
p->dsi_warned = true;
diff --git a/video/out/opengl/context.c b/video/out/opengl/context.c
index 43b57aa4ed..9b3dd6a827 100644
--- a/video/out/opengl/context.c
+++ b/video/out/opengl/context.c
@@ -313,9 +313,18 @@ void ra_gl_ctx_swap_buffers(struct ra_swapchain *sw)
}
}
+static void ra_gl_ctx_get_vsync(struct ra_swapchain *sw,
+ struct vo_vsync_info *info)
+{
+ struct priv *p = sw->priv;
+ if (p->params.get_vsync)
+ p->params.get_vsync(sw->ctx, info);
+}
+
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_vsync = ra_gl_ctx_get_vsync,
};
diff --git a/video/out/opengl/context.h b/video/out/opengl/context.h
index 5fccc70033..2dd2517ef5 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_vsync.
+ void (*get_vsync)(struct ra_ctx *ctx, struct vo_vsync_info *info);
+
// 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..1f3225a9ac 100644
--- a/video/out/opengl/context_glx.c
+++ b/video/out/opengl/context_glx.c
@@ -37,6 +37,7 @@
#define GLX_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004
#endif
+#include "osdep/timer.h"
#include "video/out/x11_common.h"
#include "context.h"
#include "utils.h"
@@ -46,6 +47,16 @@ struct priv {
XVisualInfo *vinfo;
GLXContext context;
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;
};
static void glx_uninit(struct ra_ctx *ctx)
@@ -161,6 +172,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 +226,115 @@ static void set_glx_attrib(int *attribs, int name, int value)
}
}
+static void update_vsync_oml(struct ra_ctx *ctx)
+{
+ struct priv *p = ctx->priv;
+
+ 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;
+ }
+
+ // 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;
+}
+
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)
+ update_vsync_oml(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;
}
static bool glx_init(struct ra_ctx *ctx)
@@ -296,11 +420,16 @@ static bool glx_init(struct ra_ctx *ctx)
struct ra_gl_ctx_params params = {
.swap_buffers = glx_swap_buffers,
+ .get_vsync = glx_get_vsync,
};
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/vo.c b/video/out/vo.c
index 9ecfd76a1f..0d4ed140ae 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -300,11 +300,9 @@ static struct vo *vo_create(bool probing, struct mpv_global *global,
m_config_cache_set_dispatch_change_cb(vo->opts_cache, vo->in->dispatch,
update_opts, vo);
-#if HAVE_GL
vo->gl_opts_cache = m_config_cache_alloc(NULL, global, &gl_video_conf);
m_config_cache_set_dispatch_change_cb(vo->gl_opts_cache, vo->in->dispatch,
update_opts, vo);
-#endif
vo->eq_opts_cache = m_config_cache_alloc(NULL, global, &mp_csp_equalizer_conf);
m_config_cache_set_dispatch_change_cb(vo->eq_opts_cache, vo->in->dispatch,
@@ -332,7 +330,9 @@ error:
struct vo *init_best_video_out(struct mpv_global *global, struct vo_extra *ex)
{
- struct m_obj_settings *vo_list = global->opts->vo->video_driver_list;
+ struct mp_vo_opts *opts = mp_get_config_group(NULL, global, &vo_sub_opts);
+ struct m_obj_settings *vo_list = opts->video_driver_list;
+ struct vo *vo = NULL;
// first try the preferred drivers, with their optional subdevice param:
if (vo_list && vo_list[0].name) {
for (int n = 0; vo_list[n].name; n++) {
@@ -340,11 +340,11 @@ struct vo *init_best_video_out(struct mpv_global *global, struct vo_extra *ex)
if (strlen(vo_list[n].name) == 0)
goto autoprobe;
bool p = !!vo_list[n + 1].name;
- struct vo *vo = vo_create(p, global, ex, vo_list[n].name);
+ vo = vo_create(p, global, ex, vo_list[n].name);
if (vo)
- return vo;
+ goto done;
}
- return NULL;
+ goto done;
}
autoprobe:
// now try the rest...
@@ -352,11 +352,13 @@ autoprobe:
const struct vo_driver *driver = video_out_drivers[i];
if (driver == &video_out_null)
break;
- struct vo *vo = vo_create(true, global, ex, (char *)driver->name);
+ vo = vo_create(true, global, ex, (char *)driver->name);
if (vo)
- return vo;
+ goto done;
}
- return NULL;
+done:
+ talloc_free(opts);
+ return vo;
}
static void terminate_vo(void *p)
@@ -472,14 +474,14 @@ 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_vsync_info *vsync)
{
struct vo_internal *in = vo->in;
- int64_t now = mp_time_us();
+ int64_t vsync_time = vsync->last_queue_display_time;
int64_t prev_vsync = in->prev_vsync;
-
- in->prev_vsync = now;
+ in->prev_vsync = vsync_time;
if (!in->expecting_vsync) {
reset_vsync_timings(vo);
@@ -493,13 +495,13 @@ static void update_vsync_timing_after_swap(struct vo *vo)
if (in->num_vsync_samples >= MAX_VSYNC_SAMPLES)
in->num_vsync_samples -= 1;
MP_TARRAY_INSERT_AT(in, in->vsync_samples, in->num_vsync_samples, 0,
- now - prev_vsync);
+ vsync_time - prev_vsync);
in->drop_point = MPMIN(in->drop_point + 1, in->num_vsync_samples);
in->num_total_vsync_samples += 1;
if (in->base_vsync) {
in->base_vsync += in->vsync_interval;
} else {
- in->base_vsync = now;
+ in->base_vsync = vsync_time;
}
double avg = 0;
@@ -908,13 +910,24 @@ bool vo_render_frame_external(struct vo *vo)
vo->driver->flip_page(vo);
+ struct vo_vsync_info vsync = {
+ .last_queue_display_time = -1,
+ .skipped_vsyncs = -1,
+ };
+ if (vo->driver->get_vsync)
+ vo->driver->get_vsync(vo, &vsync);
+
+ // Make up some crap if presentation feedback is missing.
+ if (vsync.last_queue_display_time < 0)
+ vsync.last_queue_display_time = mp_time_us();
+
MP_STATS(vo, "end video-flip");
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, &vsync);
}
if (vo->driver->caps & VO_CAP_NORETAIN) {
diff --git a/video/out/vo.h b/video/out/vo.h
index 3c00bb988e..3514b6df5c 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -263,6 +263,35 @@ struct vo_frame {
uint64_t frame_id;
};
+// Presentation feedback. See get_vsync() for how backends should fill this
+// struct.
+struct vo_vsync_info {
+ // mp_time_us() timestamp at which the last queued frame will likely be
+ // displayed (this is in the future, unless the frame is instantly output).
+ // -1 if unset or unsupported.
+ // This implies the latency of the output.
+ int64_t last_queue_display_time;
+
+ // Time between 2 vsync events in microseconds. The difference should be the
+ // from 2 times sampled from the same reference point (it should not be the
+ // difference between e.g. the end of scanout and the start of the next one;
+ // it must be continuous).
+ // -1 if unsupported.
+ // 0 if supported, but no value available yet. It is assumed that the value
+ // becomes available after enough swap_buffers() calls were done.
+ // >0 values are taken for granted. Very bad things will happen if it's
+ // inaccurate.
+ int64_t vsync_duration;
+
+ // Number of skipped physical vsyncs at some point in time. Typically, this
+ // value is some time in the past by an offset that equals to the latency.
+ // This value is reset and newly sampled at every swap_buffers() call.
+ // This can be used to detect delayed frames iff you try to call
+ // swap_buffers() for every physical vsync.
+ // -1 if unset or unsupported.
+ int64_t skipped_vsyncs;
+};
+
struct vo_driver {
// Encoding functionality, which can be invoked via --o only.
bool encode;
@@ -374,6 +403,15 @@ struct vo_driver {
*/
void (*flip_page)(struct vo *vo);
+ /*
+ * Return presentation feedback. The implementation should not touch fields
+ * it doesn't support; the info fields are preinitialized to neutral values.
+ * Usually called once after flip_page(), but can be called any time.
+ * The values returned by this are always relative to the last flip_page()
+ * call.
+ */
+ void (*get_vsync)(struct vo *vo, struct vo_vsync_info *info);
+
/* 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..0162e420e8 100644
--- a/video/out/vo_gpu.c
+++ b/video/out/vo_gpu.c
@@ -98,6 +98,14 @@ static void flip_page(struct vo *vo)
sw->fns->swap_buffers(sw);
}
+static void get_vsync(struct vo *vo, struct vo_vsync_info *info)
+{
+ struct gpu_priv *p = vo->priv;
+ struct ra_swapchain *sw = p->ctx->swapchain;
+ if (sw->fns->get_vsync)
+ sw->fns->get_vsync(sw, info);
+}
+
static int query_format(struct vo *vo, int format)
{
struct gpu_priv *p = vo->priv;
@@ -326,6 +334,7 @@ const struct vo_driver video_out_gpu = {
.get_image = get_image,
.draw_frame = draw_frame,
.flip_page = flip_page,
+ .get_vsync = get_vsync,
.wait_events = wait_events,
.wakeup = wakeup,
.uninit = uninit,
diff --git a/video/out/x11_common.c b/video/out/x11_common.c
index 77ab6bbb45..f01d7e2610 100644
--- a/video/out/x11_common.c
+++ b/video/out/x11_common.c
@@ -248,7 +248,7 @@ static void x11_set_ewmh_state(struct vo_x11_state *x11, char *state, bool set)
x11_send_ewmh_msg(x11, "_NET_WM_STATE", params);
}
-static void vo_set_cursor_hidden(struct vo *vo, bool cursor_hidden)
+static void vo_update_cursor(struct vo *vo)
{
Cursor no_ptr;
Pixmap bm_no;
@@ -258,16 +258,17 @@ static void vo_set_cursor_hidden(struct vo *vo, bool cursor_hidden)
struct vo_x11_state *x11 = vo->x11;
Display *disp = x11->display;
Window win = x11->window;
+ bool should_hide = x11->has_focus && !x11->mouse_cursor_visible;
- if (cursor_hidden == x11->mouse_cursor_hidden)
+ if (should_hide == x11->mouse_cursor_set)
return;
- x11->mouse_cursor_hidden = cursor_hidden;
+ x11->mouse_cursor_set = should_hide;
if (x11->parent == x11->rootwin || !win)
return; // do not hide if playing on the root window
- if (x11->mouse_cursor_hidden) {
+ if (x11->mouse_cursor_set) {
colormap = DefaultColormap(disp, DefaultScreen(disp));
if (!XAllocNamedColor(disp, colormap, "black", &black, &dummy))
return; // color alloc failed, give up
@@ -753,9 +754,6 @@ void vo_x11_uninit(struct vo *vo)
set_screensaver(x11, true);
- if (x11->window != None)
- vo_set_cursor_hidden(vo, false);
-
if (x11->window != None && x11->window != x11->rootwin) {
XUnmapWindow(x11->display, x11->window);
XDestroyWindow(x11->display, x11->window);
@@ -1040,6 +1038,17 @@ static void vo_x11_check_net_wm_state_fullscreen_change(struct vo *vo)
}
}
+// Releasing all keys on key-up or defocus is simpler and ensures no keys can
+// get "stuck".
+static void release_all_keys(struct vo *vo)
+{
+ struct vo_x11_state *x11 = vo->x11;
+
+ if (x11->no_autorepeat)
+ mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL);
+ x11->win_drag_button1_down = false;
+}
+
void vo_x11_check_events(struct vo *vo)
{
struct vo_x11_state *x11 = vo->x11;
@@ -1091,16 +1100,18 @@ void vo_x11_check_events(struct vo *vo)
}
break;
}
- // Releasing all keys in these situations is simpler and ensures no
- // keys can be get "stuck".
+ case FocusIn:
+ x11->has_focus = true;
+ vo_update_cursor(vo);
+ break;
case FocusOut:
+ release_all_keys(vo);
+ x11->has_focus = false;
+ vo_update_cursor(vo);
+ break;
case KeyRelease:
- {
- if (x11->no_autorepeat)
- mp_input_put_key(x11->input_ctx, MP_INPUT_RELEASE_ALL);
- x11->win_drag_button1_down = false;
+ release_all_keys(vo);
break;
- }
case MotionNotify:
if (x11->win_drag_button1_down && !x11->fs &&
!mp_input_test_dragging(x11->input_ctx, Event.xmotion.x,
@@ -1413,10 +1424,9 @@ static void vo_x11_create_window(struct vo *vo, XVisualInfo *vis,
Atom protos[1] = {XA(x11, WM_DELETE_WINDOW)};
XSetWMProtocols(x11->display, x11->window, protos, 1);
- if (x11->mouse_cursor_hidden) {
- x11->mouse_cursor_hidden = false;
- vo_set_cursor_hidden(vo, true);
- }
+ x11->mouse_cursor_set = false;
+ vo_update_cursor(vo);
+
if (x11->xim) {
x11->xic = XCreateIC(x11->xim,
XNInputStyle, XIMPreeditNone | XIMStatusNone,
@@ -1858,7 +1868,8 @@ int vo_x11_control(struct vo *vo, int *events, int request, void *arg)
return VO_TRUE;
}
case VOCTRL_SET_CURSOR_VISIBILITY:
- vo_set_cursor_hidden(vo, !(*(bool *)arg));
+ x11->mouse_cursor_visible = *(bool *)arg;
+ vo_update_cursor(vo);
return VO_TRUE;
case VOCTRL_KILL_SCREENSAVER:
set_screensaver(x11, false);
diff --git a/video/out/x11_common.h b/video/out/x11_common.h
index 1c0096329c..608534052c 100644
--- a/video/out/x11_common.h
+++ b/video/out/x11_common.h
@@ -88,7 +88,9 @@ struct vo_x11_state {
bool pseudo_mapped; // not necessarily mapped, but known window size
int fs; // whether we assume the window is in fullscreen mode
- bool mouse_cursor_hidden;
+ bool mouse_cursor_visible;
+ bool mouse_cursor_set;
+ bool has_focus;
long orig_layer;
// Current actual window position (updated on window move/resize events).