diff options
-rw-r--r-- | DOCS/client_api_examples/qml/mpvrenderer.cpp | 12 | ||||
-rw-r--r-- | libmpv/opengl_cb.h | 17 | ||||
-rw-r--r-- | player/client.c | 11 | ||||
-rw-r--r-- | player/client.h | 4 | ||||
-rw-r--r-- | video/out/vo_opengl_cb.c | 36 |
5 files changed, 59 insertions, 21 deletions
diff --git a/DOCS/client_api_examples/qml/mpvrenderer.cpp b/DOCS/client_api_examples/qml/mpvrenderer.cpp index 13c1f0a747..6d214dfba8 100644 --- a/DOCS/client_api_examples/qml/mpvrenderer.cpp +++ b/DOCS/client_api_examples/qml/mpvrenderer.cpp @@ -32,15 +32,9 @@ public: virtual ~MpvRenderer() { - // Before we can really destroy the OpenGL state, we must make sure - // that the video output is destroyed. This is important for some - // forms of hardware decoding, where the decoder shares some state - // with the video output and the OpenGL context. - // Deselecting the video track is the easiest way to achieve this in - // a synchronous way. If no file is playing, setting the property - // will fail and do nothing. - mpv::qt::set_property_variant(mpv, "vid", "no"); - + // Until this call is done, we need to make sure the player remains + // alive. This is done implicitly with the mpv::qt::Handle instance + // in this class. mpv_opengl_cb_uninit_gl(mpv_gl); } diff --git a/libmpv/opengl_cb.h b/libmpv/opengl_cb.h index 3f5010b26e..8f7e1df76d 100644 --- a/libmpv/opengl_cb.h +++ b/libmpv/opengl_cb.h @@ -96,6 +96,17 @@ extern "C" { * as used with mpv_opengl_cb_init_gl() * - never can be called from within the callbacks set with * mpv_set_wakeup_callback() or mpv_opengl_cb_set_update_callback() + * + * Context and handle lifecycle + * ---------------------------- + * + * Video initialization will fail if the OpenGL context was not initialized yet + * (with mpv_opengl_cb_init_gl()). Likewise, mpv_opengl_cb_uninit_gl() will + * disable video. + * + * When the mpv core is destroyed (e.g. via mpv_terminate_destroy()), the OpenGL + * context must have been uninitialized. If this doesn't happen, undefined + * behavior will result. */ /** @@ -181,10 +192,8 @@ int mpv_opengl_cb_render(mpv_opengl_cb_context *ctx, int fbo, int vp[4]); /** * Destroy the mpv OpenGL state. * - * This will trigger undefined behavior (i.e. crash hard) if the hardware - * decoder is still active, because the OpenGL hardware decoding interop state - * can't be destroyed synchronously. If no hardware decoding is active, the - * state can be destroyed at any time. + * If video is still active (e.g. a file playing), video will be disabled + * forcefully. * * Calling this multiple times is ok. * diff --git a/player/client.c b/player/client.c index e83f3568f8..7d75b22256 100644 --- a/player/client.c +++ b/player/client.c @@ -1613,6 +1613,15 @@ int64_t mpv_get_time_us(mpv_handle *ctx) return mp_time_us(); } +// Used by vo_opengl_cb to synchronously uninitialize video. +void kill_video(struct mp_client_api *client_api) +{ + struct MPContext *mpctx = client_api->mpctx; + mp_dispatch_lock(mpctx->dispatch); + mp_switch_track(mpctx, STREAM_VIDEO, NULL); + mp_dispatch_unlock(mpctx->dispatch); +} + #include "libmpv/opengl_cb.h" #if HAVE_GL @@ -1620,7 +1629,7 @@ static mpv_opengl_cb_context *opengl_cb_get_context(mpv_handle *ctx) { mpv_opengl_cb_context *cb = ctx->mpctx->gl_cb_ctx; if (!cb) { - cb = mp_opengl_create(ctx->mpctx->global, ctx->mpctx->osd); + cb = mp_opengl_create(ctx->mpctx->global, ctx->mpctx->osd, ctx->clients); ctx->mpctx->gl_cb_ctx = cb; } return cb; diff --git a/player/client.h b/player/client.h index aeb2e886e7..656e3601cb 100644 --- a/player/client.h +++ b/player/client.h @@ -42,6 +42,8 @@ struct mpv_opengl_cb_context; struct mpv_global; struct osd_state; struct mpv_opengl_cb_context *mp_opengl_create(struct mpv_global *g, - struct osd_state *osd); + struct osd_state *osd, + struct mp_client_api *client_api); +void kill_video(struct mp_client_api *client_api); #endif diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c index d2d4221e94..991a90f6cf 100644 --- a/video/out/vo_opengl_cb.c +++ b/video/out/vo_opengl_cb.c @@ -50,10 +50,12 @@ struct vo_priv { struct mpv_opengl_cb_context { struct mp_log *log; + struct mp_client_api *client_api; pthread_mutex_t lock; // --- Protected by lock + bool initialized; mpv_opengl_cb_update_fn update_cb; void *update_cb_ctx; struct mp_image *waiting_frame; @@ -72,11 +74,11 @@ struct mpv_opengl_cb_context { GL *gl; struct gl_video *renderer; struct gl_hwdec *hwdec; + struct mp_hwdec_info hwdec_info; // it's also semi-immutable after init // --- Immutable or semi-threadsafe. struct osd_state *osd; - struct mp_hwdec_info hwdec_info; const char *hwapi; struct vo *active; @@ -86,20 +88,26 @@ static void free_ctx(void *ptr) { mpv_opengl_cb_context *ctx = ptr; + // This can trigger if the client API user doesn't call + // mpv_opengl_cb_uninit_gl() properly. + assert(!ctx->initialized); + pthread_mutex_destroy(&ctx->lock); } struct mpv_opengl_cb_context *mp_opengl_create(struct mpv_global *g, - struct osd_state *osd) + struct osd_state *osd, + struct mp_client_api *client_api) { mpv_opengl_cb_context *ctx = talloc_zero(NULL, mpv_opengl_cb_context); talloc_set_destructor(ctx, free_ctx); - ctx->log = mp_log_new(ctx, g->log, "opengl-cb"); pthread_mutex_init(&ctx->lock, NULL); ctx->gl = talloc_zero(ctx, GL); + ctx->log = mp_log_new(ctx, g->log, "opengl-cb"); ctx->osd = osd; + ctx->client_api = client_api; switch (g->opts->hwdec_api) { case HWDEC_AUTO: ctx->hwapi = "auto"; break; @@ -149,6 +157,7 @@ int mpv_opengl_cb_init_gl(struct mpv_opengl_cb_context *ctx, const char *exts, ctx->renderer = gl_video_init(ctx->gl, ctx->log, ctx->osd); if (!ctx->renderer) return MPV_ERROR_UNSUPPORTED; + ctx->hwdec = gl_hwdec_load_api(ctx->log, ctx->gl, ctx->hwapi, &ctx->hwdec_info); gl_video_set_hwdec(ctx->renderer, ctx->hwdec); @@ -157,6 +166,7 @@ int mpv_opengl_cb_init_gl(struct mpv_opengl_cb_context *ctx, const char *exts, ctx->imgfmt_supported[n - IMGFMT_START] = gl_video_check_format(ctx->renderer, n); } + ctx->initialized = true; pthread_mutex_unlock(&ctx->lock); gl_video_unset_gl_state(ctx->renderer); @@ -165,6 +175,18 @@ int mpv_opengl_cb_init_gl(struct mpv_opengl_cb_context *ctx, const char *exts, int mpv_opengl_cb_uninit_gl(struct mpv_opengl_cb_context *ctx) { + // Bring down the decoder etc., which still might be using the hwdec + // context. Setting initialized=false guarantees it can't come back. + pthread_mutex_lock(&ctx->lock); + ctx->initialized = false; + pthread_mutex_unlock(&ctx->lock); + + kill_video(ctx->client_api); + + pthread_mutex_lock(&ctx->lock); + assert(!ctx->active); + pthread_mutex_unlock(&ctx->lock); + gl_video_uninit(ctx->renderer); ctx->renderer = NULL; gl_hwdec_uninit(ctx->hwdec); @@ -298,9 +320,6 @@ static int control(struct vo *vo, uint32_t request, void *data) pthread_mutex_unlock(&p->ctx->lock); return VO_TRUE; case VOCTRL_GET_HWDEC_INFO: { - // Warning: in theory, the API user could destroy the OpenGL context - // while the decoder uses the hwdec thing, and bad things would - // happen. Currently, the API user is told not to do this. struct mp_hwdec_info **arg = data; *arg = p->ctx ? &p->ctx->hwdec_info : NULL; return true; @@ -334,6 +353,11 @@ static int preinit(struct vo *vo) } pthread_mutex_lock(&p->ctx->lock); + if (!p->ctx->initialized) { + MP_FATAL(vo, "OpenGL context not initialized.\n"); + pthread_mutex_unlock(&p->ctx->lock); + return -1; + } p->ctx->active = vo; p->ctx->reconfigured = true; assert(vo->osd == p->ctx->osd); |