summaryrefslogtreecommitdiffstats
path: root/video/out/vo_opengl_cb.c
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2014-12-09 17:47:02 +0100
committerwm4 <wm4@nowhere>2014-12-09 17:59:04 +0100
commitfb855b86593ad2a9db71cce3aa652ace93af38b5 (patch)
treeffca8eb74c8cf58dc5e97e0fac2c519b958b7cf7 /video/out/vo_opengl_cb.c
parentd38bc531cc7ce9c90b74145e2be2e24cb48e501a (diff)
downloadmpv-fb855b86593ad2a9db71cce3aa652ace93af38b5.tar.bz2
mpv-fb855b86593ad2a9db71cce3aa652ace93af38b5.tar.xz
client API: expose OpenGL renderer
This adds API to libmpv that lets host applications use the mpv opengl renderer. This is a more flexible (and possibly more portable) option to foreign window embedding (via --wid). This assumes that methods like context sharing and multithreaded OpenGL rendering are infeasible, and that a way is needed to integrate it with an application that uses a single thread to render everything. Add an example that does this with QtQuick/qml. The example is relatively lazy, but still shows how relatively simple the integration is. The FBO indirection could probably be avoided, but would require more work (and would probably lead to worse QtQuick integration, because it would have to ignore transformations like rotation). Because this makes mpv directly use the host application's OpenGL context, there is no platform specific code involved in mpv, except for hw decoding interop. main.qml is derived from some Qt example. The following things are still missing: - a way to do better video timing - expose GL renderer options, allow changing them at runtime - support for color equalizer controls - support for screenshots
Diffstat (limited to 'video/out/vo_opengl_cb.c')
-rw-r--r--video/out/vo_opengl_cb.c370
1 files changed, 370 insertions, 0 deletions
diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c
new file mode 100644
index 0000000000..09649c3b4b
--- /dev/null
+++ b/video/out/vo_opengl_cb.c
@@ -0,0 +1,370 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <stdbool.h>
+#include <limits.h>
+#include <pthread.h>
+#include <assert.h>
+
+#include "config.h"
+
+#include "talloc.h"
+#include "common/common.h"
+#include "misc/bstr.h"
+#include "common/msg.h"
+#include "options/m_config.h"
+#include "options/options.h"
+#include "aspect.h"
+#include "vo.h"
+#include "video/vfcap.h"
+#include "video/mp_image.h"
+#include "sub/osd.h"
+
+#include "common/global.h"
+#include "player/client.h"
+
+#include "gl_common.h"
+#include "gl_video.h"
+#include "gl_hwdec.h"
+
+#include "video/decode/lavc.h" // HWDEC_* values
+
+#include "libmpv/opengl_cb.h"
+
+/*
+ * mpv_opengl_cb_context is created 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.
+ */
+
+struct vo_priv {
+ struct vo *vo;
+
+ struct mpv_opengl_cb_context *ctx;
+};
+
+struct mpv_opengl_cb_context {
+ struct mp_log *log;
+
+ pthread_mutex_t lock;
+
+ // --- Protected by lock
+ mpv_opengl_cb_update_fn update_cb;
+ void *update_cb_ctx;
+ struct mp_image *next_frame;
+ struct mp_image_params img_params;
+ struct mp_image_params *new_params;
+ struct mp_rect wnd;
+ bool flip;
+ bool force_update;
+ bool imgfmt_supported[IMGFMT_END - IMGFMT_START];
+ struct mp_vo_opts vo_opts;
+
+ // --- 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 gl_video *renderer;
+ struct gl_hwdec *hwdec;
+
+ // --- Immutable or semi-threadsafe.
+
+ struct osd_state *osd;
+ struct mp_hwdec_info hwdec_info;
+ const char *hwapi;
+
+ struct vo *active;
+};
+
+struct mpv_opengl_cb_context *mp_opengl_create(struct mpv_global *g,
+ struct osd_state *osd)
+{
+ mpv_opengl_cb_context *ctx = talloc_zero(NULL, mpv_opengl_cb_context);
+ ctx->log = mp_log_new(ctx, g->log, "opengl-cb");
+ pthread_mutex_init(&ctx->lock, NULL);
+
+ ctx->gl = talloc_zero(ctx, GL);
+
+ ctx->osd = osd;
+
+ switch (g->opts->hwdec_api) {
+ case HWDEC_AUTO: ctx->hwapi = "auto"; break;
+ case HWDEC_VDPAU: ctx->hwapi = "vdpau"; break;
+ case HWDEC_VDA: ctx->hwapi = "vda"; break;
+ case HWDEC_VAAPI: ctx->hwapi = "vaapi"; break;
+ default: ctx->hwapi = "";
+ }
+
+ return ctx;
+}
+
+// To be called from VO thread, with p->ctx->lock held.
+static void copy_vo_opts(struct vo *vo)
+{
+ struct vo_priv *p = vo->priv;
+
+ // We're being lazy: none of the options we need use dynamic data, so
+ // copy the struct with an assignment.
+ // Just remove all the dynamic data to avoid confusion.
+ struct mp_vo_opts opts = *vo->opts;
+ opts.video_driver_list = opts.vo_defs = NULL;
+ opts.winname = NULL;
+ opts.sws_opts = NULL;
+ p->ctx->vo_opts = opts;
+}
+
+void mpv_opengl_cb_set_update_callback(struct mpv_opengl_cb_context *ctx,
+ mpv_opengl_cb_update_fn callback,
+ void *callback_ctx)
+{
+ pthread_mutex_lock(&ctx->lock);
+ ctx->update_cb = callback;
+ ctx->update_cb_ctx = callback_ctx;
+ pthread_mutex_unlock(&ctx->lock);
+}
+
+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)
+{
+ if (ctx->renderer)
+ return MPV_ERROR_INVALID_PARAMETER;
+
+ mpgl_load_functions2(ctx->gl, get_proc_address, get_proc_address_ctx,
+ exts, ctx->log);
+ int caps = MPGL_CAP_GL21 | MPGL_CAP_TEX_RG;
+ if ((ctx->gl->mpgl_caps & caps) != caps) {
+ MP_FATAL(ctx, "Missing OpenGL features.\n");
+ return MPV_ERROR_UNSUPPORTED;
+ }
+ ctx->renderer = gl_video_init(ctx->gl, ctx->log, ctx->osd);
+ ctx->hwdec = gl_hwdec_load_api(ctx->log, ctx->gl, ctx->hwapi, &ctx->hwdec_info);
+ gl_video_set_hwdec(ctx->renderer, ctx->hwdec);
+
+ 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);
+ }
+ pthread_mutex_unlock(&ctx->lock);
+
+ gl_video_unset_gl_state(ctx->renderer);
+ return 0;
+}
+
+int mpv_opengl_cb_uninit_gl(struct mpv_opengl_cb_context *ctx)
+{
+ gl_video_uninit(ctx->renderer);
+ ctx->renderer = NULL;
+ gl_hwdec_uninit(ctx->hwdec);
+ ctx->hwdec = NULL;
+ talloc_free(ctx->gl);
+ ctx->gl = NULL;
+ return 0;
+}
+
+int mpv_opengl_cb_render(struct mpv_opengl_cb_context *ctx, int fbo, int vp[4])
+{
+ assert(ctx->renderer);
+
+ gl_video_set_gl_state(ctx->renderer);
+
+ pthread_mutex_lock(&ctx->lock);
+
+ struct vo *vo = ctx->active;
+
+ struct mp_image_params *new_params = ctx->new_params;
+ ctx->new_params = NULL;
+ if (new_params) {
+ ctx->img_params = *new_params;
+ ctx->force_update = true;
+ }
+
+ int h = vp[3];
+ bool flip = h < 0 && h > INT_MIN;
+ if (flip)
+ h = -h;
+ struct mp_rect wnd = {vp[0], vp[1], vp[0] + vp[2], vp[1] + h};
+ if (wnd.x0 != ctx->wnd.x0 || wnd.y0 != ctx->wnd.y0 ||
+ wnd.x1 != ctx->wnd.x1 || wnd.y1 != ctx->wnd.y1 ||
+ ctx->flip != flip)
+ ctx->force_update = true;
+
+ if (ctx->force_update && vo) {
+ ctx->force_update = false;
+ ctx->wnd = wnd;
+
+ struct mp_rect src, dst;
+ struct mp_osd_res osd;
+ mp_get_src_dst_rects(ctx->log, &ctx->vo_opts, vo->driver->caps,
+ &ctx->img_params, wnd.x1 - wnd.x0, wnd.y1 - wnd.y0,
+ 1.0, &src, &dst, &osd);
+
+ gl_video_resize(ctx->renderer, &wnd, &src, &dst, &osd, !ctx->flip);
+ }
+
+ if (new_params) {
+ gl_video_config(ctx->renderer, new_params);
+ talloc_free(new_params);
+ }
+
+ struct mp_image *mpi = ctx->next_frame;
+ ctx->next_frame = NULL;
+
+ pthread_mutex_unlock(&ctx->lock);
+
+ if (mpi)
+ gl_video_upload_image(ctx->renderer, mpi);
+
+ gl_video_render_frame(ctx->renderer, fbo);
+
+ gl_video_unset_gl_state(ctx->renderer);
+
+ return 0;
+}
+
+static void draw_image(struct vo *vo, mp_image_t *mpi)
+{
+ struct vo_priv *p = vo->priv;
+ if (p->ctx) {
+ pthread_mutex_lock(&p->ctx->lock);
+ mp_image_setrefp(&p->ctx->next_frame, mpi);
+ pthread_mutex_unlock(&p->ctx->lock);
+ }
+ talloc_free(mpi);
+}
+
+static void flip_page(struct vo *vo)
+{
+ struct vo_priv *p = vo->priv;
+ if (p->ctx) {
+ pthread_mutex_lock(&p->ctx->lock);
+ if (p->ctx->update_cb)
+ p->ctx->update_cb(p->ctx->update_cb_ctx);
+ pthread_mutex_unlock(&p->ctx->lock);
+ }
+}
+
+static int query_format(struct vo *vo, uint32_t format)
+{
+ struct vo_priv *p = vo->priv;
+
+ bool ok = false;
+ if (p->ctx) {
+ pthread_mutex_lock(&p->ctx->lock);
+ if (format >= IMGFMT_START && format < IMGFMT_END)
+ ok = p->ctx->imgfmt_supported[format - IMGFMT_START];
+ pthread_mutex_unlock(&p->ctx->lock);
+ }
+ return ok ? VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW : 0;
+}
+
+static int reconfig(struct vo *vo, struct mp_image_params *params, int flags)
+{
+ struct vo_priv *p = vo->priv;
+
+ if (p->ctx) {
+ pthread_mutex_lock(&p->ctx->lock);
+ mp_image_unrefp(&p->ctx->next_frame);
+ talloc_free(p->ctx->new_params);
+ p->ctx->new_params = talloc_memdup(NULL, params, sizeof(*params));
+ pthread_mutex_unlock(&p->ctx->lock);
+ } else {
+ return -1;
+ }
+
+ return 0;
+}
+
+static int control(struct vo *vo, uint32_t request, void *data)
+{
+ struct vo_priv *p = vo->priv;
+
+ switch (request) {
+ case VOCTRL_SET_LIBMPV_OPENGL_CB_CONTEXT: {
+ if (p->ctx)
+ return VO_FALSE;
+ struct mpv_opengl_cb_context *nctx = data;
+ if (nctx) {
+ pthread_mutex_lock(&nctx->lock);
+ if (nctx->active) {
+ MP_FATAL(vo, "There is already a VO using the OpenGL context.\n");
+ } else {
+ nctx->active = vo;
+ p->ctx = nctx;
+ assert(vo->osd == p->ctx->osd);
+ copy_vo_opts(vo);
+ }
+ pthread_mutex_unlock(&nctx->lock);
+ }
+ return VO_TRUE;
+ }
+ case VOCTRL_GET_PANSCAN:
+ return VO_TRUE;
+ case VOCTRL_SET_PANSCAN:
+ case VOCTRL_REDRAW_FRAME:
+ pthread_mutex_lock(&p->ctx->lock);
+ copy_vo_opts(vo);
+ p->ctx->force_update = true;
+ if (p->ctx->update_cb)
+ p->ctx->update_cb(p->ctx->update_cb_ctx);
+ 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;
+ }
+ }
+
+ return VO_NOTIMPL;
+}
+
+static void uninit(struct vo *vo)
+{
+ struct vo_priv *p = vo->priv;
+
+ if (p->ctx) {
+ pthread_mutex_lock(&p->ctx->lock);
+ mp_image_unrefp(&p->ctx->next_frame);
+ talloc_free(p->ctx->new_params);
+ p->ctx->new_params = NULL;
+ p->ctx->active = NULL;
+ pthread_mutex_unlock(&p->ctx->lock);
+ }
+}
+
+static int preinit(struct vo *vo)
+{
+ struct vo_priv *p = vo->priv;
+ p->vo = vo;
+ // Currently, there's no video timing in the API, and it's questionable
+ // how API users would make use of it too.
+ vo_set_flip_queue_offset(vo, 0);
+ return 0;
+}
+
+#define OPT_BASE_STRUCT struct gl_priv
+static const struct m_option options[] = {
+ {0},
+};
+
+const struct vo_driver video_out_opengl_cb = {
+ .description = "OpenGL Callbacks for libmpv",
+ .name = "opengl-cb",
+ .caps = VO_CAP_ROTATE90,
+ .preinit = preinit,
+ .query_format = query_format,
+ .reconfig = reconfig,
+ .control = control,
+ .draw_image = draw_image,
+ .flip_page = flip_page,
+ .uninit = uninit,
+ .priv_size = sizeof(struct vo_priv),
+ .options = options,
+};