summaryrefslogtreecommitdiffstats
path: root/video/out
diff options
context:
space:
mode:
Diffstat (limited to 'video/out')
-rw-r--r--video/out/gpu/libmpv_gpu.c182
-rw-r--r--video/out/gpu/libmpv_gpu.h38
-rw-r--r--video/out/libmpv.h72
-rw-r--r--video/out/opengl/libmpv_gl.c113
-rw-r--r--video/out/vo.c9
-rw-r--r--video/out/vo.h1
-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 fbo, int vp_w, int vp_h)
pthread_mutex_unlock(&ctx->lock);
MP_STATS(ctx, "glcb-render");
- struct ra_swapchain *sw = ctx->ra_ctx->swapchain;
- struct ra_fbo target;
- ra_gl_ctx_resize(sw, vp_w, abs(vp_h), fbo);
- ra_gl_ctx_start_frame(sw, &target);
- target.flip = vp_h < 0;
- gl_video_render_frame(ctx->renderer, frame, target, RENDER_FRAME_DEF);
- ra_gl_ctx_submit_frame(sw, frame);
- reset_gl_state(ctx->gl);
+ err = ctx->renderer->fns->render(ctx->renderer, params, frame);
if (frame != &dummy)
talloc_free(frame);
@@ -364,10 +309,10 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int vp_w, int vp_h)
pthread_cond_wait(&ctx->wakeup, &ctx->lock);
pthread_mutex_unlock(&ctx->lock);
- return 0;
+ return err;
}
-int mpv_opengl_cb_report_flip(mpv_opengl_cb_context *ctx, int64_t time)
+void