diff options
Diffstat (limited to 'video')
-rw-r--r-- | video/out/gpu/libmpv_gpu.c | 182 | ||||
-rw-r--r-- | video/out/gpu/libmpv_gpu.h | 38 | ||||
-rw-r--r-- | video/out/libmpv.h | 72 | ||||
-rw-r--r-- | video/out/opengl/libmpv_gl.c | 113 | ||||
-rw-r--r-- | video/out/vo.c | 9 | ||||
-rw-r--r-- | video/out/vo.h | 1 | ||||
-rw-r--r-- | video/out/vo_libmpv.c (renamed from video/out/vo_opengl_cb.c) | 425 |
7 files changed, 595 insertions, 245 deletions
diff --git a/video/out/gpu/libmpv_gpu.c b/video/out/gpu/libmpv_gpu.c new file mode 100644 index 0000000000..bd597c302a --- /dev/null +++ b/video/out/gpu/libmpv_gpu.c @@ -0,0 +1,182 @@ +#include "config.h" +#include "hwdec.h" +#include "libmpv_gpu.h" +#include "video.h" +#include "video/out/libmpv.h" + +static const struct libmpv_gpu_context_fns *context_backends[] = { +#if HAVE_GL + &libmpv_gpu_context_gl, +#endif + NULL +}; + +struct priv { + struct libmpv_gpu_context *context; + + struct gl_video *renderer; +}; + +static int init(struct render_backend *ctx, mpv_render_param *params) +{ + ctx->priv = talloc_zero(NULL, struct priv); + struct priv *p = ctx->priv; + + char *api = get_mpv_render_param(params, MPV_RENDER_PARAM_API_TYPE, NULL); + if (!api) + return MPV_ERROR_INVALID_PARAMETER; + + for (int n = 0; context_backends[n]; n++) { + const struct libmpv_gpu_context_fns *backend = context_backends[n]; + if (strcmp(backend->api_name, api) == 0) { + p->context = talloc_zero(NULL, struct libmpv_gpu_context); + *p->context = (struct libmpv_gpu_context){ + .global = ctx->global, + .log = ctx->log, + .fns = backend, + }; + break; + } + } + + if (!p->context) + return MPV_ERROR_INVALID_PARAMETER; + + int err = p->context->fns->init(p->context, params); + if (err < 0) + return err; + + p->renderer = gl_video_init(p->context->ra, ctx->log, ctx->global); + + ctx->hwdec_devs = hwdec_devices_create(); + gl_video_load_hwdecs(p->renderer, ctx->hwdec_devs, true); + ctx->driver_caps = VO_CAP_ROTATE90; + return 0; +} + +static bool check_format(struct render_backend *ctx, int imgfmt) +{ + struct priv *p = ctx->priv; + + return gl_video_check_format(p->renderer, imgfmt); +} + +static int set_parameter(struct render_backend *ctx, mpv_render_param param) +{ + struct priv *p = ctx->priv; + + switch (param.type) { + case MPV_RENDER_PARAM_ICC_PROFILE: { + mpv_byte_array *data = param.data; + gl_video_set_icc_profile(p->renderer, (bstr){data->data, data->size}); + return 0; + } + case MPV_RENDER_PARAM_AMBIENT_LIGHT: { + int lux = *(int *)param.data; + gl_video_set_ambient_lux(p->renderer, lux); + return 0; + } + default: + return MPV_ERROR_NOT_IMPLEMENTED; + } +} + +static void reconfig(struct render_backend *ctx, struct mp_image_params *params) +{ + struct priv *p = ctx->priv; + + gl_video_config(p->renderer, params); +} + +static void reset(struct render_backend *ctx) +{ + struct priv *p = ctx->priv; + + gl_video_reset(p->renderer); +} + +static void update_external(struct render_backend *ctx, struct vo *vo) +{ + struct priv *p = ctx->priv; + + gl_video_set_osd_source(p->renderer, vo ? vo->osd : NULL); + if (vo) + gl_video_configure_queue(p->renderer, vo); +} + +static void resize(struct render_backend *ctx, struct mp_rect *src, + struct mp_rect *dst, struct mp_osd_res *osd) +{ + struct priv *p = ctx->priv; + + gl_video_resize(p->renderer, src, dst, osd); +} + +static int get_target_size(struct render_backend *ctx, mpv_render_param *params, + int *out_w, int *out_h) +{ + struct priv *p = ctx->priv; + + // Mapping the surface is cheap, better than adding new backend entrypoints. + struct ra_tex *tex; + int err = p->context->fns->wrap_fbo(p->context, params, &tex); + if (err < 0) + return err; + *out_w = tex->params.w; + *out_h = tex->params.h; + return 0; +} + +static int render(struct render_backend *ctx, mpv_render_param *params, + struct vo_frame *frame) +{ + struct priv *p = ctx->priv; + + // Mapping the surface is cheap, better than adding new backend entrypoints. + struct ra_tex *tex; + int err = p->context->fns->wrap_fbo(p->context, params, &tex); + if (err < 0) + return err; + + int depth = *(int *)get_mpv_render_param(params, MPV_RENDER_PARAM_DEPTH, + &(int){0}); + gl_video_set_fb_depth(p->renderer, depth); + + bool flip = *(int *)get_mpv_render_param(params, MPV_RENDER_PARAM_FLIP_Y, + &(int){0}); + + struct ra_fbo target = {.tex = tex, .flip = flip}; + gl_video_render_frame(p->renderer, frame, target, RENDER_FRAME_DEF); + p->context->fns->done_frame(p->context, frame->display_synced); + + return 0; +} + +static void destroy(struct render_backend *ctx) +{ + struct priv *p = ctx->priv; + + if (p->renderer) + gl_video_uninit(p->renderer); + + hwdec_devices_destroy(ctx->hwdec_devs); + + if (p->context) { + p->context->fns->destroy(p->context); + talloc_free(p->context->priv); + talloc_free(p->context); + } +} + +const struct render_backend_fns render_backend_gpu = { + .init = init, + .check_format = check_format, + .set_parameter = set_parameter, + .reconfig = reconfig, + .reset = reset, + .update_external = update_external, + .resize = resize, + .get_target_size = get_target_size, + .render = render, + .destroy = destroy, +}; diff --git a/video/out/gpu/libmpv_gpu.h b/video/out/gpu/libmpv_gpu.h new file mode 100644 index 0000000000..5b41116f14 --- /dev/null +++ b/video/out/gpu/libmpv_gpu.h @@ -0,0 +1,38 @@ +#pragma once + +#include "video/out/libmpv.h" + +struct libmpv_gpu_context { + struct mpv_global *global; + struct mp_log *log; + const struct libmpv_gpu_context_fns *fns; + + struct ra *ra; + void *priv; +}; + +// Manage backend specific interaction between libmpv and ra backend, that can't +// be managed by ra itself (initialization and passing FBOs). +struct libmpv_gpu_context_fns { + // The libmpv API type name, see MPV_RENDER_PARAM_API_TYPE. + const char *api_name; + // Pretty much works like render_backend_fns.init, except that the + // API type is already checked by the caller. + // Successful init must set ctx->ra. + int (*init)(struct libmpv_gpu_context *ctx, mpv_render_param *params); + // Wrap the surface passed to mpv_render_context_render() (via the params + // array) into a ra_tex and return it. Returns a libmpv error code, and sets + // *out to a temporary object on success. The returned object is valid until + // another wrap_fbo() or done_frame() is called. + // This does not need to care about generic attributes, like flipping. + int (*wrap_fbo)(struct libmpv_gpu_context *ctx, mpv_render_param *params, + struct ra_tex **out); + // Signal that the ra_tex object obtained with wrap_fbo is no longer used. + // For certain backends, this might also be used to signal the end of + // rendering (like OpenGL doing weird crap). + void (*done_frame)(struct libmpv_gpu_context *ctx, bool ds); + // Free all data in ctx->priv. + void (*destroy)(struct libmpv_gpu_context *ctx); +}; + +extern const struct libmpv_gpu_context_fns libmpv_gpu_context_gl; diff --git a/video/out/libmpv.h b/video/out/libmpv.h new file mode 100644 index 0000000000..8b99587984 --- /dev/null +++ b/video/out/libmpv.h @@ -0,0 +1,72 @@ +#pragma once + +#include <stdint.h> +#include <stdbool.h> +#include "libmpv/render.h" +#include "vo.h" + +// Helper for finding a parameter value. It returns the direct pointer to the +// value, and if not present, just returns the def argument. In particular, if +// def is not NULL, this never returns NULL (unless a param value is defined +// as accepting NULL, or the libmpv API user is triggering UB). +void *get_mpv_render_param(mpv_render_param *params, mpv_render_param_type type, + void *def); + +typedef int (*mp_render_cb_control_fn)(void *cb_ctx, int *events, + uint32_t request, void *data); +void mp_render_context_set_control_callback(mpv_render_context *ctx, + mp_render_cb_control_fn callback, + void *callback_ctx); +bool mp_render_context_acquire(mpv_render_context *ctx); + +struct render_backend { + struct mpv_global *global; + struct mp_log *log; + const struct render_backend_fns *fns; + + // Set on init, immutable afterwards. + int driver_caps; + struct mp_hwdec_devices *hwdec_devs; + + void *priv; +}; + +// Generic backend for rendering via libmpv. This corresponds to vo/vo_driver, +// except for rendering via the mpv_render_*() API. (As a consequence it's as +// generic as the VO API.) Like with VOs, one backend can support multiple +// underlying GPU APIs. +struct render_backend_fns { + // Returns libmpv error code. In particular, this function has to check for + // MPV_RENDER_PARAM_API_TYPE, and silently return MPV_ERROR_NOT_IMPLEMENTED + // if the API is not included in this backend. + // If this fails, ->destroy() will be called. + int (*init)(struct render_backend *ctx, mpv_render_param *params); + // Check if the passed IMGFMT_ is supported. + bool (*check_format)(struct render_backend *ctx, int imgfmt); + // Implementation of mpv_render_context_set_parameter(). Optional. + int (*set_parameter)(struct render_backend *ctx, mpv_render_param param); + // Like vo_driver.reconfig(). + void (*reconfig)(struct render_backend *ctx, struct mp_image_params *params); + // Like VOCTRL_RESET. + void (*reset)(struct render_backend *ctx); + // This has two purposes: 1. set queue attributes on VO, 2. update the + // renderer's OSD pointer. Keep in mind that as soon as the caller releases + // the renderer lock, the VO pointer can become invalid. The OSD pointer + // will technically remain valid (even though it's a vo field), until it's + // unset with this function. + // Will be called if vo changes, or if renderer options change. + void (*update_external)(struct render_backend *ctx, struct vo *vo); + // Update screen area. + void (*resize)(struct render_backend *ctx, struct mp_rect *src, + struct mp_rect *dst, struct mp_osd_res *osd); + // Get target surface size from mpv_render_context_render() arguments. + int (*get_target_size)(struct render_backend *ctx, mpv_render_param *params, + int *out_w, int *out_h); + // Implementation of mpv_render_context_render(). + int (*render)(struct render_backend *ctx, mpv_render_param *params, + struct vo_frame *frame); + // Free all data in ctx->priv. + void (*destroy)(struct render_backend *ctx); +}; + +extern const struct render_backend_fns render_backend_gpu; diff --git a/video/out/opengl/libmpv_gl.c b/video/out/opengl/libmpv_gl.c new file mode 100644 index 0000000000..fc22c67053 --- /dev/null +++ b/video/out/opengl/libmpv_gl.c @@ -0,0 +1,113 @@ +#include "common.h" +#include "context.h" +#include "ra_gl.h" +#include "options/m_config.h" +#include "libmpv/render_gl.h" +#include "video/out/gpu/libmpv_gpu.h" +#include "video/out/gpu/ra.h" + +struct priv { + GL *gl; + struct ra_ctx *ra_ctx; +}; + +static int init(struct libmpv_gpu_context *ctx, mpv_render_param *params) +{ + ctx->priv = talloc_zero(NULL, struct priv); + struct priv *p = ctx->priv; + + mpv_opengl_init_params *init_params = + get_mpv_render_param(params, MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, NULL); + if (!init_params) + return MPV_ERROR_INVALID_PARAMETER; + + p->gl = talloc_zero(p, GL); + + mpgl_load_functions2(p->gl, init_params->get_proc_address, + init_params->get_proc_address_ctx, + init_params->extra_exts, ctx->log); + if (!p->gl->version && !p->gl->es) { + MP_FATAL(ctx, "OpenGL not initialized.\n"); + return MPV_ERROR_UNSUPPORTED; + } + + // initialize a blank ra_ctx to reuse ra_gl_ctx + p->ra_ctx = talloc_zero(p, struct ra_ctx); + p->ra_ctx->log = ctx->log; + p->ra_ctx->global = ctx->global; + p->ra_ctx->opts = (struct ra_ctx_opts) { + .probing = false, + .allow_sw = true, + }; + + static const struct ra_swapchain_fns empty_swapchain_fns = {0}; + struct ra_gl_ctx_params gl_params = { + // vo_opengl_cb is essentially like a gigantic external swapchain where + // the user is in charge of presentation / swapping etc. But we don't + // actually need to provide any of these functions, since we can just + // not call them to begin with - so just set it to an empty object to + // signal to ra_gl_p that we don't care about its latency emulation + // functionality + .external_swapchain = &empty_swapchain_fns + }; + + p->gl->SwapInterval = NULL; // we shouldn't randomly change this, so lock it + if (!ra_gl_ctx_init(p->ra_ctx, p->gl, gl_params)) + return MPV_ERROR_UNSUPPORTED; + + int debug; + mp_read_option_raw(ctx->global, "gpu-debug", &m_option_type_flag, &debug); + p->ra_ctx->opts.debug = debug; + p->gl->debug_context = debug; + ra_gl_set_debug(p->ra_ctx->ra, debug); + + ctx->ra = p->ra_ctx->ra; + return 0; +} + +static int wrap_fbo(struct libmpv_gpu_context *ctx, mpv_render_param *params, + struct ra_tex **out) +{ + struct priv *p = ctx->priv; + + mpv_opengl_fbo *fbo = + get_mpv_render_param(params, MPV_RENDER_PARAM_OPENGL_FBO, NULL); + if (!fbo) + return MPV_ERROR_INVALID_PARAMETER; + + if (fbo->fbo && !(p->gl->mpgl_caps & MPGL_CAP_FB)) { + MP_FATAL(ctx, "Rendering to FBO requested, but no FBO extension found!\n"); + return MPV_ERROR_UNSUPPORTED; + } + + struct ra_swapchain *sw = p->ra_ctx->swapchain; + struct ra_fbo target; + ra_gl_ctx_resize(sw, fbo->w, fbo->h, fbo->fbo); + ra_gl_ctx_start_frame(sw, &target); + *out = target.tex; + return 0; +} + +static void done_frame(struct libmpv_gpu_context *ctx, bool ds) +{ + struct priv *p = ctx->priv; + + struct ra_swapchain *sw = p->ra_ctx->swapchain; + struct vo_frame dummy = {.display_synced = ds}; + ra_gl_ctx_submit_frame(sw, &dummy); +} + +static void destroy(struct libmpv_gpu_context *ctx) +{ + struct priv *p = ctx->priv; + + ra_gl_ctx_uninit(p->ra_ctx); +} + +const struct libmpv_gpu_context_fns libmpv_gpu_context_gl = { + .api_name = MPV_RENDER_API_TYPE_OPENGL, + .init = init, + .wrap_fbo = wrap_fbo, + .done_frame = done_frame, + .destroy = destroy, +}; diff --git a/video/out/vo.c b/video/out/vo.c index 6586ebc63e..af4f0fb163 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -52,7 +52,7 @@ extern const struct vo_driver video_out_x11; extern const struct vo_driver video_out_vdpau; extern const struct vo_driver video_out_xv; extern const struct vo_driver video_out_gpu; -extern const struct vo_driver video_out_opengl_cb; +extern const struct vo_driver video_out_libmpv; extern const struct vo_driver video_out_null; extern const struct vo_driver video_out_image; extern const struct vo_driver video_out_lavc; @@ -66,9 +66,7 @@ extern const struct vo_driver video_out_tct; const struct vo_driver *const video_out_drivers[] = { -#if HAVE_GL - &video_out_opengl_cb, -#endif + &video_out_libmpv, #if HAVE_ANDROID &video_out_mediacodec_embed, #endif @@ -185,7 +183,7 @@ static bool get_desc(struct m_obj_desc *dst, int index) .options = vo->options, .options_prefix = vo->options_prefix, .global_opts = vo->global_opts, - .hidden = vo->encode || !strcmp(vo->name, "opengl-cb"), + .hidden = vo->encode, .p = vo, }; return true; @@ -199,6 +197,7 @@ const struct m_obj_list vo_obj_list = { {"gl", "gpu"}, {"direct3d_shaders", "direct3d"}, {"opengl", "gpu"}, + {"opengl-cb", "libmpv"}, {0} }, .allow_unknown_entries = true, diff --git a/video/out/vo.h b/video/out/vo.h index 90b1d9a61d..4e21221c74 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -205,7 +205,6 @@ struct vo_extra { struct input_ctx *input_ctx; struct osd_state *osd; struct encode_lavc_context *encode_lavc_ctx; - struct mpv_opengl_cb_context *opengl_cb_context; void (*wakeup_cb)(void *ctx); void *wakeup_ctx; }; diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_libmpv.c index 40dcb035ca..61f73aee07 100644 --- a/video/out/vo_opengl_cb.c +++ b/video/out/vo_libmpv.c @@ -19,54 +19,51 @@ #include "vo.h" #include "video/mp_image.h" #include "sub/osd.h" +#include "osdep/atomic.h" #include "osdep/timer.h" #include "common/global.h" #include "player/client.h" -#include "gpu/video.h" -#include "gpu/hwdec.h" -#include "opengl/common.h" -#include "opengl/context.h" -#include "opengl/ra_gl.h" - -#include "libmpv/opengl_cb.h" +#include "libmpv.h" /* - * mpv_opengl_cb_context is created by the host application - the host application + * mpv_render_context is managed by the host application - the host application * can access it any time, even if the VO is destroyed (or not created yet). - * The OpenGL object allows initializing the renderer etc. The VO object is only - * here to transfer the video frames somehow. * - * Locking hierarchy: - * - the libmpv user can mix openglcb and normal API; thus openglcb API + * - the libmpv user can mix render API and normal API; thus render API * functions can wait on the core, but not the reverse * - the core does blocking calls into the VO thread, thus the VO functions * can't wait on the user calling the API functions * - to make video timing work like it should, the VO thread waits on the - * openglcb API user anyway, and the (unlikely) deadlock is avoided with + * render API user anyway, and the (unlikely) deadlock is avoided with * a timeout + * + * So: mpv core > VO > mpv_render_context > mp_client_api.lock (locking) + * And: render thread > VO (wait for present) + * VO > render thread (wait for present done, via timeout) */ struct vo_priv { - struct mpv_opengl_cb_context *ctx; + struct mpv_render_context *ctx; }; -struct mpv_opengl_cb_context { +struct mpv_render_context { struct mp_log *log; struct mpv_global *global; struct mp_client_api *client_api; + atomic_bool in_use; + pthread_mutex_t control_lock; - mpv_opengl_cb_control_fn control_cb; + mp_render_cb_control_fn control_cb; void *control_cb_ctx; pthread_mutex_t lock; pthread_cond_t wakeup; // --- Protected by lock - bool initialized; - mpv_opengl_cb_update_fn update_cb; + mpv_render_update_fn update_cb; void *update_cb_ctx; struct vo_frame *next_frame; // next frame to draw int64_t present_count; // incremented when next frame can be shown @@ -75,32 +72,43 @@ struct mpv_opengl_cb_context { int64_t flip_count; struct vo_frame *cur_frame; struct mp_image_params img_params; - bool reconfigured, reset; int vp_w, vp_h; bool flip; - bool force_update; bool imgfmt_supported[IMGFMT_END - IMGFMT_START]; - bool update_new_opts; - struct vo *active; - bool icc_was_set; + bool need_reconfig; + bool need_resize; + bool need_reset; + bool need_update_external; + struct vo *vo; - // --- This is only mutable while initialized=false, during which nothing - // except the OpenGL context manager is allowed to access it. + // --- Mostly immutable after init. struct mp_hwdec_devices *hwdec_devs; - // --- All of these can only be accessed from the thread where the host - // application's OpenGL context is current - i.e. only while the - // host application is calling certain mpv_opengl_cb_* APIs. - GL *gl; - struct ra_ctx *ra_ctx; - struct gl_video *renderer; + // --- All of these can only be accessed from mpv_render_*() API, for + // which the user makes sure they're called synchronized. + struct render_backend *renderer; struct m_config_cache *vo_opts_cache; struct mp_vo_opts *vo_opts; }; -static void update(struct mpv_opengl_cb_context *ctx); +static void update(struct mpv_render_context *ctx); + +const struct render_backend_fns *render_backends[] = { + &render_backend_gpu, + NULL +}; + +void *get_mpv_render_param(mpv_render_param *params, mpv_render_param_type type, + void *def) +{ + for (int n = 0; params && params[n].type; n++) { + if (params[n].type == type) + return params[n].data; + } + return def; +} -static void forget_frames(struct mpv_opengl_cb_context *ctx, bool all) +static void forget_frames(struct mpv_render_context *ctx, bool all) { pthread_cond_broadcast(&ctx->wakeup); if (all) { @@ -109,41 +117,64 @@ static void forget_frames(struct mpv_opengl_cb_context *ctx, bool all) } } -static void free_ctx(void *ptr) +int mpv_render_context_create(mpv_render_context **res, mpv_handle *mpv, + mpv_render_param *params) { - 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_cond_destroy(&ctx->wakeup); - pthread_mutex_destroy(&ctx->lock); - pthread_mutex_destroy(&ctx->control_lock); -} - -struct mpv_opengl_cb_context *mp_opengl_create(struct mpv_global *g, - struct mp_client_api *client_api) -{ - mpv_opengl_cb_context *ctx = talloc_zero(NULL, mpv_opengl_cb_context); - talloc_set_destructor(ctx, free_ctx); + mpv_render_context *ctx = talloc_zero(NULL, mpv_render_context); pthread_mutex_init(&ctx->control_lock, NULL); pthread_mutex_init(&ctx->lock, NULL); pthread_cond_init(&ctx->wakeup, NULL); - ctx->global = g; - ctx->log = mp_log_new(ctx, g->log, "opengl-cb"); - ctx->client_api = client_api; + ctx->global = mp_client_get_global(mpv); + ctx->client_api = ctx->global->client_api; + ctx->log = mp_log_new(ctx, ctx->global->log, "libmpv_render"); ctx->vo_opts_cache = m_config_cache_alloc(ctx, ctx->global, &vo_sub_opts); ctx->vo_opts = ctx->vo_opts_cache->opts; - return ctx; + int err = MPV_ERROR_NOT_IMPLEMENTED; + for (int n = 0; render_backends[n]; n++) { + ctx->renderer = talloc_zero(NULL, struct render_backend); + *ctx->renderer = (struct render_backend){ + .global = ctx->global, + .log = ctx->log, + .fns = render_backends[n], + }; + err = ctx->renderer->fns->init(ctx->renderer, params); + if (err >= 0) + break; + ctx->renderer->fns->destroy(ctx->renderer); + talloc_free(ctx->renderer->priv); + TA_FREEP(&ctx->renderer); + if (err != MPV_ERROR_NOT_IMPLEMENTED) + break; + } + + if (err < 0) { + mpv_render_context_free(ctx); + return err; + } + + ctx->hwdec_devs = ctx->renderer->hwdec_devs; + + for (int n = IMGFMT_START; n < IMGFMT_END; n++) { + ctx->imgfmt_supported[n - IMGFMT_START] = + ctx->renderer->fns->check_format(ctx->renderer, n); + } + + if (!mp_set_main_render_context(ctx->client_api, ctx, true)) { + MP_ERR(ctx, "There is already a mpv_render_context set.\n"); + mpv_render_context_free(ctx); + return MPV_ERROR_GENERIC; + } + + *res = ctx; + return 0; } -void mpv_opengl_cb_set_update_callback(struct mpv_opengl_cb_context *ctx, - mpv_opengl_cb_update_fn callback, - void *callback_ctx) +void mpv_render_context_set_update_callback(mpv_render_context *ctx, + mpv_render_update_fn callback, + void *callback_ctx) { pthread_mutex_lock(&ctx->lock); ctx->update_cb = callback; @@ -151,10 +182,9 @@ void mpv_opengl_cb_set_update_callback(struct mpv_opengl_cb_context *ctx, pthread_mutex_unlock(&ctx->lock); } - -void mp_client_set_control_callback(struct mpv_opengl_cb_context *ctx, - mpv_opengl_cb_control_fn callback, - void *callback_ctx) +void mp_render_context_set_control_callback(mpv_render_context *ctx, + mp_render_cb_control_fn callback, + void *callback_ctx) { pthread_mutex_lock(&ctx->control_lock); ctx->control_cb = callback; @@ -162,129 +192,60 @@ void mp_client_set_control_callback(struct mpv_opengl_cb_context *ctx, pthread_mutex_unlock(&ctx->control_lock); } -// Reset some GL attributes the user might clobber. For mid-term compatibility -// only - we expect both user code and our code to do this correctly. -static void reset_gl_state(GL *gl) -{ - gl->ActiveTexture(GL_TEXTURE0); - if (gl->mpgl_caps & MPGL_CAP_ROW_LENGTH) - gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0); - gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4); -} - -int mpv_opengl_cb_init_gl(struct mpv_opengl_cb_context *ctx, const char *exts, - mpv_opengl_cb_get_proc_address_fn get_proc_address, - void *get_proc_address_ctx) +void mpv_render_context_free(mpv_render_context *ctx) { - if (ctx->renderer) - return MPV_ERROR_INVALID_PARAMETER; - - talloc_free(ctx->gl); - ctx->gl = talloc_zero(ctx, GL); - - mpgl_load_functions2(ctx->gl, get_proc_address, get_proc_address_ctx, - exts, ctx->log); - if (!ctx->gl->version && !ctx->gl->es) { - MP_FATAL(ctx, "OpenGL not initialized.\n"); - return MPV_ERROR_UNSUPPORTED; - } - - // initialize a blank ra_ctx to reuse ra_gl_ctx - ctx->ra_ctx = talloc_zero(ctx, struct ra_ctx); - ctx->ra_ctx->log = ctx->log; - ctx->ra_ctx->global = ctx->global; - ctx->ra_ctx->opts = (struct ra_ctx_opts) { - .probing = false, - .allow_sw = true, - }; - - static const struct ra_swapchain_fns empty_swapchain_fns = {0}; - struct ra_gl_ctx_params gl_params = { - // vo_opengl_cb is essentially like a gigantic external swapchain where - // the user is in charge of presentation / swapping etc. But we don't - // actually need to provide any of these functions, since we can just - // not call them to begin with - so just set it to an empty object to - // signal to ra_gl_ctx that we don't care about its latency emulation - // functionality - .external_swapchain = &empty_swapchain_fns - }; - - ctx->gl->SwapInterval = NULL; // we shouldn't randomly change this, so lock it - if (!ra_gl_ctx_init(ctx->ra_ctx, ctx->gl, gl_params)) - return MPV_ERROR_UNSUPPORTED; - - ctx->renderer = gl_video_init(ctx->ra_ctx->ra, ctx->log, ctx->global); - - ctx->hwdec_devs = hwdec_devices_create(); - gl_video_load_hwdecs(ctx->renderer, ctx->hwdec_devs, true); - - pthread_mutex_lock(&ctx->lock); - for (int n = IMGFMT_START; n < IMGFMT_END; n++) { - ctx->imgfmt_supported[n - IMGFMT_START] = - gl_video_check_format(ctx->renderer, n); - } - ctx->initialized = true; - pthread_mutex_unlock(&ctx->lock); + if (!ctx) + return; - reset_gl_state(ctx->gl); - return 0; -} + // From here on, ctx becomes invisible and cannot be newly acquired. Only + // a VO could still hold a reference. + mp_set_main_render_context(ctx->client_api, ctx, false); -int mpv_opengl_cb_uninit_gl(struct mpv_opengl_cb_context *ctx) -{ - if (!ctx) - return 0; + // If it's still in use, a VO using it must be active. Destroy the VO, and + // also bring down the decoder etc., which still might be using the hwdec + // context. The above removal guarantees it can't come back (so ctx->vo + // can't change to non-NULL). + if (atomic_load(&ctx->in_use)) + kill_video(ctx->client_api); - // Bring down the decoder etc., which still might be using the hwdec - // context. Setting initialized=false guarantees it can't come back. + assert(!atomic_load(&ctx->in_use)); + assert(!ctx->vo); - pthread_mutex_lock(&ctx->lock); forget_frames(ctx, true); - ctx->initialized = false; - bool was_active = ctx->active ? true : false; - pthread_mutex_unlock(&ctx->lock); - if (was_active) - kill_video(ctx->client_api); + ctx->renderer->fns->destroy(ctx->renderer); + talloc_free(ctx->renderer->priv); + talloc_free(ctx->renderer); - pthread_mutex_lock(&ctx->lock); - assert(!ctx->active); - pthread_mutex_unlock(&ctx->lock); + pthread_cond_destroy(&ctx->wakeup); + pthread_mutex_destroy(&ctx->lock); + pthread_mutex_destroy(&ctx->control_lock); - gl_video_uninit(ctx->renderer); - ctx->renderer = NULL; - hwdec_devices_destroy(ctx->hwdec_devs); - ctx->hwdec_devs = NULL; - ra_gl_ctx_uninit(ctx->ra_ctx); - talloc_free(ctx->ra_ctx); - talloc_free(ctx->gl); - ctx->ra_ctx = NULL; - ctx->gl = NULL; - return 0; + talloc_free(ctx); } -int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h) +// Try to mark the context as "in exclusive use" (e.g. by a VO). +// Note: the function must not acquire any locks, because it's called with an +// external leaf lock held. +bool mp_render_context_acquire(mpv_render_context *ctx) { - assert(ctx->renderer); - - if (fbo && !(ctx->gl->mpgl_caps & MPGL_CAP_FB)) { - MP_FATAL(ctx, "Rendering to FBO requested, but no FBO extension found!\n"); - return MPV_ERROR_UNSUPPORTED; - } + bool prev = false; + return atomic_compare_exchange_strong(&ctx->in_use, &prev, true); +} - reset_gl_state(ctx->gl); +int mpv_render_context_render(mpv_render_context *ctx, mpv_render_param *params) +{ + int vp_w, vp_h; + int err = ctx->renderer->fns->get_target_size(ctx->renderer, params, + &vp_w, &vp_h); + if (err < 0) + return err; pthread_mutex_lock(&ctx->lock); - struct vo *vo = ctx->active; - - ctx->force_update |= ctx->reconfigured; + struct vo *vo = ctx->vo; - if (ctx->vp_w != vp_w || ctx->vp_h != vp_h) - ctx->force_update = true; - - if (ctx->force_update && vo) { - ctx->force_update = false; + if (vo && (ctx->vp_w != vp_w || ctx->vp_h != vp_h || ctx->need_resize)) { ctx->vp_w = vp_w; ctx->vp_h = vp_h; @@ -296,33 +257,24 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h) &ctx->img_params, vp_w, abs(vp_h), 1.0, &src, &dst, &osd); - gl_video_resize(ctx->renderer, &src, &dst, &osd); + ctx->renderer->fns->resize(ctx->renderer, &src, &dst, &osd); } + ctx->need_resize = false; - if (ctx->reconfigured) { - gl_video_set_osd_source(ctx->renderer, vo ? vo->osd : NULL); - gl_video_config(ctx->renderer, &ctx->img_params); - } - if (ctx->update_new_opts) { - if (vo) - gl_video_configure_queue(ctx->renderer, vo); - int debug; - mp_read_option_raw(ctx->global, "gpu-debug", &m_option_type_flag, - &debug); - ctx->gl->debug_context = debug; - ra_gl_set_debug(ctx->ra_ctx->ra, debug); - if (!ctx->icc_was_set && gl_video_icc_auto_enabled(ctx->renderer)) - MP_ERR(ctx, "icc-profile-auto is not available with opengl-cb\n"); - } - ctx->reconfigured = false; - ctx->update_new_opts = false; + if (ctx->need_reconfig) + ctx->renderer->fns->reconfig(ctx->renderer, &ctx->img_params); + ctx->need_reconfig = false; + + if (ctx->need_update_external) + ctx->renderer->fns->update_external(ctx->renderer, vo); + ctx->need_update_external = false; - if (ctx->reset) { - gl_video_reset(ctx->renderer); - ctx->reset = false; + if (ctx->need_reset) { + ctx->renderer->fns->reset(ctx->renderer); if (ctx->cur_frame) ctx->cur_frame->still = true; } + ctx->need_reset = false; struct vo_frame *frame = ctx->next_frame; int64_t wait_present_count = ctx->present_count; @@ -346,15 +298,8 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int |