diff options
-rw-r--r-- | misc/dispatch.c | 56 | ||||
-rw-r--r-- | misc/dispatch.h | 4 | ||||
-rw-r--r-- | options/m_config.c | 91 | ||||
-rw-r--r-- | options/m_config.h | 31 | ||||
-rw-r--r-- | options/m_option.h | 2 | ||||
-rw-r--r-- | options/options.c | 16 | ||||
-rw-r--r-- | player/command.c | 8 | ||||
-rw-r--r-- | video/out/opengl/video.c | 1 | ||||
-rw-r--r-- | video/out/vo.c | 36 | ||||
-rw-r--r-- | video/out/vo.h | 1 |
10 files changed, 223 insertions, 23 deletions
diff --git a/misc/dispatch.c b/misc/dispatch.c index 086896ba79..e625f196dc 100644 --- a/misc/dispatch.c +++ b/misc/dispatch.c @@ -59,6 +59,7 @@ struct mp_dispatch_item { mp_dispatch_fn fn; void *fn_data; bool asynchronous; + bool mergeable; bool completed; struct mp_dispatch_item *next; }; @@ -113,12 +114,25 @@ static void mp_dispatch_append(struct mp_dispatch_queue *queue, struct mp_dispatch_item *item) { pthread_mutex_lock(&queue->lock); + if (item->mergeable) { + for (struct mp_dispatch_item *cur = queue->head; cur; cur = cur->next) { + if (cur->mergeable && cur->fn == item->fn && + cur->fn_data == item->fn_data) + { + talloc_free(item); + pthread_mutex_unlock(&queue->lock); + return; + } + } + } + if (queue->tail) { queue->tail->next = item; } else { queue->head = item; } queue->tail = item; + // Wake up the main thread; note that other threads might wait on this // condition for reasons, so broadcast the condition. pthread_cond_broadcast(&queue->cond); @@ -127,6 +141,7 @@ static void mp_dispatch_append(struct mp_dispatch_queue *queue, if (!queue->wakeup_fn) queue->interrupted = true; pthread_mutex_unlock(&queue->lock); + if (queue->wakeup_fn) queue->wakeup_fn(queue->wakeup_ctx); } @@ -165,6 +180,47 @@ void mp_dispatch_enqueue_autofree(struct mp_dispatch_queue *queue, mp_dispatch_append(queue, item); } +// Like mp_dispatch_enqueue(), but +void mp_dispatch_enqueue_notify(struct mp_dispatch_queue *queue, + mp_dispatch_fn fn, void *fn_data) +{ + struct mp_dispatch_item *item = talloc_ptrtype(NULL, item); + *item = (struct mp_dispatch_item){ + .fn = fn, + .fn_data = fn_data, + .mergeable = true, + .asynchronous = true, + }; + mp_dispatch_append(queue, item); +} + +// Remove already queued item. Only items enqueued with the following functions +// can be canceled: +// - mp_dispatch_enqueue() +// - mp_dispatch_enqueue_notify() +// Items which were enqueued, and which are currently executing, can not be +// canceled anymore. This function is mostly for being called from the same +// context as mp_dispatch_queue_process(), where the "currently executing" case +// can be excluded. +void mp_dispatch_cancel_fn(struct mp_dispatch_queue *queue, + mp_dispatch_fn fn, void *fn_data) +{ + pthread_mutex_lock(&queue->lock); + struct mp_dispatch_item **pcur = &queue->head; + queue->tail = NULL; + while (*pcur) { + struct mp_dispatch_item *cur = *pcur; + if (cur->fn == fn && cur->fn_data == fn_data) { + *pcur = cur->next; + talloc_free(cur); + } else { + queue->tail = cur; + pcur = &cur->next; + } + } + pthread_mutex_unlock(&queue->lock); +} + // Run fn(fn_data) on the target thread synchronously. This function enqueues // the callback and waits until the target thread is done doing this. // This is redundant to calling the function inside mp_dispatch_[un]lock(), diff --git a/misc/dispatch.h b/misc/dispatch.h index a762e47cd2..d850437934 100644 --- a/misc/dispatch.h +++ b/misc/dispatch.h @@ -12,6 +12,10 @@ void mp_dispatch_enqueue(struct mp_dispatch_queue *queue, mp_dispatch_fn fn, void *fn_data); void mp_dispatch_enqueue_autofree(struct mp_dispatch_queue *queue, mp_dispatch_fn fn, void *fn_data); +void mp_dispatch_enqueue_notify(struct mp_dispatch_queue *queue, + mp_dispatch_fn fn, void *fn_data); +void mp_dispatch_cancel_fn(struct mp_dispatch_queue *queue, + mp_dispatch_fn fn, void *fn_data); void mp_dispatch_run(struct mp_dispatch_queue *queue, mp_dispatch_fn fn, void *fn_data); void mp_dispatch_queue_process(struct mp_dispatch_queue *queue, double timeout); diff --git a/options/m_config.c b/options/m_config.c index 378bed25ad..24f4b83c45 100644 --- a/options/m_config.c +++ b/options/m_config.c @@ -39,6 +39,7 @@ #include "common/global.h" #include "common/msg.h" #include "common/msg_control.h" +#include "misc/dispatch.h" #include "misc/node.h" #include "osdep/atomic.h" @@ -57,6 +58,8 @@ struct m_config_shadow { pthread_mutex_t lock; struct m_config *root; char *data; + struct m_config_cache **listeners; + int num_listeners; }; // Represents a sub-struct (OPT_SUBSTRUCT()). @@ -156,8 +159,11 @@ static void config_destroy(void *p) m_option_free(co->opt, config->shadow->data + co->shadow_offset); } - if (config->shadow) + if (config->shadow) { + // must all have been unregistered + assert(config->shadow->num_listeners == 0); pthread_mutex_destroy(&config->shadow->lock); + } } struct m_config *m_config_new(void *talloc_ctx, struct mp_log *log, @@ -1190,6 +1196,16 @@ static bool is_group_included(struct m_config *config, int group, int parent) return false; } +static void cache_destroy(void *p) +{ + struct m_config_cache *cache = p; + + // (technically speaking, being able to call them both without anything + // breaking is a feature provided by these functions) + m_config_cache_set_wakeup_cb(cache, NULL, NULL); + m_config_cache_set_dispatch_change_cb(cache, NULL, NULL, NULL); +} + struct m_config_cache *m_config_cache_alloc(void *ta_parent, struct mpv_global *global, const struct m_sub_options *group) @@ -1198,6 +1214,7 @@ struct m_config_cache *m_config_cache_alloc(void *ta_parent, struct m_config *root = shadow->root; struct m_config_cache *cache = talloc_zero(ta_parent, struct m_config_cache); + talloc_set_destructor(cache, cache_destroy); cache->shadow = shadow; cache->shadow_config = m_config_new(cache, mp_null_log, root->size, root->defaults, root->options); @@ -1292,12 +1309,84 @@ void m_config_notify_change_co(struct m_config *config, group = g->parent_group; } + if (shadow) { + pthread_mutex_lock(&shadow->lock); + for (int n = 0; n < shadow->num_listeners; n++) { + struct m_config_cache *cache = shadow->listeners[n]; + if (cache->wakeup_cb) + cache->wakeup_cb(cache->wakeup_cb_ctx); + } + pthread_mutex_unlock(&shadow->lock); + } + if (config->option_change_callback) { config->option_change_callback(config->option_change_callback_ctx, co, changed); } } +void m_config_cache_set_wakeup_cb(struct m_config_cache *cache, + void (*cb)(void *ctx), void *cb_ctx) +{ + struct m_config_shadow *shadow = cache->shadow; + + pthread_mutex_lock(&shadow->lock); + if (cache->in_list) { + for (int n = 0; n < shadow->num_listeners; n++) { + if (shadow->listeners[n] == cache) + MP_TARRAY_REMOVE_AT(shadow->listeners, shadow->num_listeners, n); + } + if (!shadow->num_listeners) { + talloc_free(shadow->listeners); + shadow->listeners = NULL; + } + } + if (cb) { + MP_TARRAY_APPEND(NULL, shadow->listeners, shadow->num_listeners, cache); + cache->in_list = true; + cache->wakeup_cb = cb; + cache->wakeup_cb_ctx = cb_ctx; + } + pthread_mutex_unlock(&shadow->lock); +} + +static void dispatch_notify(void *p) +{ + struct m_config_cache *cache = p; + + assert(cache->wakeup_dispatch_queue); + mp_dispatch_enqueue_notify(cache->wakeup_dispatch_queue, + cache->wakeup_dispatch_cb, + cache->wakeup_dispatch_cb_ctx); +} + +void m_config_cache_set_dispatch_change_cb(struct m_config_cache *cache, + struct mp_dispatch_queue *dispatch, + void (*cb)(void *ctx), void *cb_ctx) +{ + // Remove the old one is tricky. Firts make sure no new notifications will + // come. + m_config_cache_set_wakeup_cb(cache, NULL, NULL); + // Remove any pending notifications (assume we're on the same thread as + // any potential mp_dispatch_queue_process() callers). + if (cache->wakeup_dispatch_queue) { + mp_dispatch_cancel_fn(cache->wakeup_dispatch_queue, + cache->wakeup_dispatch_cb, + cache->wakeup_dispatch_cb_ctx); + } + + cache->wakeup_dispatch_queue = NULL; + cache->wakeup_dispatch_cb = NULL; + cache->wakeup_dispatch_cb_ctx = NULL; + + if (cb) { + cache->wakeup_dispatch_queue = dispatch; + cache->wakeup_dispatch_cb = cb; + cache->wakeup_dispatch_cb_ctx = cb_ctx; + m_config_cache_set_wakeup_cb(cache, dispatch_notify, cache); + } +} + bool m_config_is_in_group(struct m_config *config, const struct m_sub_options *group, struct m_config_option *co) diff --git a/options/m_config.h b/options/m_config.h index 65145c093b..fc32ca5bf0 100644 --- a/options/m_config.h +++ b/options/m_config.h @@ -275,9 +275,20 @@ struct m_config_cache { struct m_config *shadow_config; long long ts; int group; + bool in_list; + // --- Implicitly synchronized by setting/unsetting wakeup_cb. + struct mp_dispatch_queue *wakeup_dispatch_queue; + void (*wakeup_dispatch_cb)(void *ctx); + void *wakeup_dispatch_cb_ctx; + // --- Protected by shadow->lock + void (*wakeup_cb)(void *ctx); + void *wakeup_cb_ctx; }; // Create a mirror copy from the global options. +// Keep in mind that a m_config_cache object is not thread-safe; it merely +// provides thread-safe access to the global options. All API functions for +// the same m_config_cache object must synchronized, unless otherwise noted. // ta_parent: parent for the returned allocation // global: option data source // group: the option group to return. This can be NULL for the global option @@ -287,6 +298,22 @@ struct m_config_cache *m_config_cache_alloc(void *ta_parent, struct mpv_global *global, const struct m_sub_options *group); +// If any of the options in the group possibly changes, call this callback. The +// callback must not actually access the cache or anything option related. +// Instead, it must wake up the thread that normally accesses the cache. +void m_config_cache_set_wakeup_cb(struct m_config_cache *cache, + void (*cb)(void *ctx), void *cb_ctx); + +// If any of the options in the group change, call this callback on the given +// dispatch queue. This is higher level than m_config_cache_set_wakeup_cb(), +// and you can do anything you want in the callback (assuming the dispatch +// queue is processed in the same thread that accesses m_config_cache API). +// To ensure clean shutdown, you must destroy the m_config_cache (or unset the +// callback) before the dispatch queue is destroyed. +void m_config_cache_set_dispatch_change_cb(struct m_config_cache *cache, + struct mp_dispatch_queue *dispatch, + void (*cb)(void *ctx), void *cb_ctx); + // Update the options in cache->opts to current global values. Return whether // there was an update notification at all (which may or may not indicate that // some options have changed). @@ -295,13 +322,13 @@ struct m_config_cache *m_config_cache_alloc(void *ta_parent, bool m_config_cache_update(struct m_config_cache *cache); // Like m_config_cache_alloc(), but return the struct (m_config_cache->opts) -// directly, with no way to update the config. +// directly, with no way to update the config. Basically this returns a copy +// with a snapshot of the current option values. // Warning: does currently not set the child as its own talloc root, which // means the only way to free the struct is by freeing ta_parent. void *mp_get_config_group(void *ta_parent, struct mpv_global *global, const struct m_sub_options *group); - // Read a single global option in a thread-safe way. For multiple options, // use m_config_cache. The option must exist and match the provided type (the // type is used as a sanity check only). Performs semi-expensive lookup. diff --git a/options/m_option.h b/options/m_option.h index 5feed14542..cfc5f6bb15 100644 --- a/options/m_option.h +++ b/options/m_option.h @@ -398,8 +398,6 @@ struct m_option { // certain groups of options. #define UPDATE_OPT_FIRST (1 << 7) #define UPDATE_TERM (1 << 7) // terminal options -#define UPDATE_RENDERER (1 << 8) // mainly vo_opengl options -#define UPDATE_VIDEOPOS (1 << 9) // video position (panscan etc.) #define UPDATE_OSD (1 << 10) // related to OSD rendering #define UPDATE_BUILTIN_SCRIPTS (1 << 11) // osc/ytdl #define UPDATE_IMGPAR (1 << 12) // video image params overrides diff --git a/options/options.c b/options/options.c index 2bd7bf869d..b35dd9b265 100644 --- a/options/options.c +++ b/options/options.c @@ -146,20 +146,20 @@ static const m_option_t mp_vo_opt_list[] = { OPT_FLAG("fullscreen", fullscreen, 0), OPT_ALIAS("fs", "fullscreen"), OPT_FLAG("native-keyrepeat", native_keyrepeat, 0), - OPT_FLOATRANGE("panscan", panscan, UPDATE_VIDEOPOS, 0.0, 1.0), - OPT_FLOATRANGE("video-zoom", zoom, UPDATE_VIDEOPOS, -20.0, 20.0), - OPT_FLOATRANGE("video-pan-x", pan_x, UPDATE_VIDEOPOS, -3.0, 3.0), - OPT_FLOATRANGE("video-pan-y", pan_y, UPDATE_VIDEOPOS, -3.0, 3.0), - OPT_FLOATRANGE("video-align-x", align_x, UPDATE_VIDEOPOS, -1.0, 1.0), - OPT_FLOATRANGE("video-align-y", align_y, UPDATE_VIDEOPOS, -1.0, 1.0), - OPT_CHOICE("video-unscaled", unscaled, UPDATE_VIDEOPOS, + OPT_FLOATRANGE("panscan", panscan, 0, 0.0, 1.0), + OPT_FLOATRANGE("video-zoom", zoom, 0, -20.0, 20.0), + OPT_FLOATRANGE("video-pan-x", pan_x, 0, -3.0, 3.0), + OPT_FLOATRANGE("video-pan-y", pan_y, 0, -3.0, 3.0), + OPT_FLOATRANGE("video-align-x", align_x, 0, -1.0, 1.0), + OPT_FLOATRANGE("video-align-y", align_y, 0, -1.0, 1.0), + OPT_CHOICE("video-unscaled", unscaled, 0, ({"no", 0}, {"yes", 1}, {"downscale-big", 2})), OPT_INT64("wid", WinID, 0), OPT_CHOICE_OR_INT("screen", screen_id, 0, 0, 32, ({"default", -1})), OPT_CHOICE_OR_INT("fs-screen", fsscreen_id, 0, 0, 32, ({"all", -2}, {"current", -1})), - OPT_FLAG("keepaspect", keepaspect, UPDATE_VIDEOPOS), + OPT_FLAG("keepaspect", keepaspect, 0), OPT_FLAG("keepaspect-window", keepaspect_window, 0), OPT_FLAG("hidpi-window-scale", hidpi_window_scale, 0), OPT_FLAG("native-fs", native_fs, 0), diff --git a/player/command.c b/player/command.c index 51b025cad4..80c0c970fa 100644 --- a/player/command.c +++ b/player/command.c @@ -5854,14 +5854,6 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags) if (flags & UPDATE_TERM) mp_update_logging(mpctx, false); - if (mpctx->video_out) { - if (flags & UPDATE_VIDEOPOS) - vo_control(mpctx->video_out, VOCTRL_SET_PANSCAN, NULL); - - if (flags & UPDATE_RENDERER) - vo_control(mpctx->video_out, VOCTRL_UPDATE_RENDER_OPTS, NULL); - } - if (flags & UPDATE_OSD) { osd_changed(mpctx->osd); for (int n = 0; n < NUM_PTRACKS; n++) { diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c index bb3730022a..bbf88e2747 100644 --- a/video/out/opengl/video.c +++ b/video/out/opengl/video.c @@ -414,7 +414,6 @@ const struct m_sub_options gl_video_conf = { }, .size = sizeof(struct gl_video_opts), .defaults = &gl_video_opts_def, - .change_flags = UPDATE_RENDERER, }; static void uninit_rendering(struct gl_video *p); diff --git a/video/out/vo.c b/video/out/vo.c index e52495e195..217baac632 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -167,6 +167,8 @@ struct vo_internal { int opt_framedrop; }; +extern const struct m_sub_options gl_video_conf; + static void forget_frames(struct vo *vo); static void *vo_thread(void *ptr); @@ -210,10 +212,33 @@ static void dispatch_wakeup_cb(void *ptr) vo_wakeup(vo); } +static void update_opts(void *p) +{ + struct vo *vo = p; + + if (m_config_cache_update(vo->opts_cache)) { + // "Legacy" update of video position related options. + if (vo->driver->control) + vo->driver->control(vo, VOCTRL_SET_PANSCAN, NULL); + } + + if (vo->gl_opts_cache && m_config_cache_update(vo->gl_opts_cache)) + { + // "Legacy" update of video GL renderer related options. + if (vo->driver->control) + vo->driver->control(vo, VOCTRL_UPDATE_RENDER_OPTS, NULL); + } +} + // Does not include thread- and VO uninit. static void dealloc_vo(struct vo *vo) { forget_frames(vo); // implicitly synchronized + + // These must be free'd before vo->in->dispatch. + talloc_free(vo->opts_cache); + talloc_free(vo->gl_opts_cache); + pthread_mutex_destroy(&vo->in->lock); pthread_cond_destroy(&vo->in->wakeup); talloc_free(vo); @@ -254,9 +279,18 @@ static struct vo *vo_create(bool probing, struct mpv_global *global, pthread_mutex_init(&vo->in->lock, NULL); pthread_cond_init(&vo->in->wakeup, NULL); - vo->opts_cache = m_config_cache_alloc(vo, global, &vo_sub_opts); + vo->opts_cache = m_config_cache_alloc(NULL, global, &vo_sub_opts); vo->opts = vo->opts_cache->opts; + 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 + mp_input_set_mouse_transform(vo->input_ctx, NULL, NULL); if (vo->driver->encode != !!vo->encode_lavc_ctx) goto error; diff --git a/video/out/vo.h b/video/out/vo.h index 82ec284219..c3145d5a5d 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -400,6 +400,7 @@ struct vo { struct m_config_cache *opts_cache; // cache for ->opts struct mp_vo_opts *opts; + struct m_config_cache *gl_opts_cache; bool want_redraw; // redraw as soon as possible |