summaryrefslogtreecommitdiffstats
path: root/video
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
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')
-rw-r--r--video/out/gl_common.c24
-rw-r--r--video/out/gl_common.h2
-rw-r--r--video/out/gl_hwdec.c15
-rw-r--r--video/out/gl_hwdec.h2
-rw-r--r--video/out/gl_hwdec_vaglx.c4
-rw-r--r--video/out/gl_hwdec_vdpau.c4
-rw-r--r--video/out/gl_video.c29
-rw-r--r--video/out/gl_video.h5
-rw-r--r--video/out/vo.c2
-rw-r--r--video/out/vo.h2
-rw-r--r--video/out/vo_opengl.c4
-rw-r--r--video/out/vo_opengl_cb.c370
12 files changed, 445 insertions, 18 deletions
diff --git a/video/out/gl_common.c b/video/out/gl_common.c
index 1f005934d4..dddb11a53e 100644
--- a/video/out/gl_common.c
+++ b/video/out/gl_common.c
@@ -474,15 +474,15 @@ static const struct gl_functions gl_functions[] = {
// log: used to output messages
// Note: if you create a CONTEXT_FORWARD_COMPATIBLE_BIT_ARB with OpenGL 3.0,
// you must append "GL_ARB_compatibility" to ext2.
-void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *),
- const char *ext2, struct mp_log *log)
+void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n),
+ void *fn_ctx, const char *ext2, struct mp_log *log)
{
talloc_free_children(gl);
*gl = (GL) {
.extensions = talloc_strdup(gl, ext2 ? ext2 : ""),
};
- gl->GetString = getProcAddress ? getProcAddress("glGetString") : NULL;
+ gl->GetString = get_fn(fn_ctx, "glGetString");
if (!gl->GetString) {
mp_err(log, "Can't load OpenGL functions.\n");
return;
@@ -508,8 +508,8 @@ void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *),
bool has_legacy = false;
if (gl->version >= MPGL_VER(3, 0)) {
- gl->GetStringi = getProcAddress("glGetStringi");
- gl->GetIntegerv = getProcAddress("glGetIntegerv");
+ gl->GetStringi = get_fn(fn_ctx, "glGetStringi");
+ gl->GetIntegerv = get_fn(fn_ctx, "glGetIntegerv");
if (!(gl->GetStringi && gl->GetIntegerv))
return;
@@ -571,7 +571,7 @@ void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *),
const struct gl_function *fn = &section->functions[i];
void *ptr = NULL;
for (int x = 0; fn->funcnames[x]; x++) {
- ptr = getProcAddress((const GLubyte *)fn->funcnames[x]);
+ ptr = get_fn(fn_ctx, fn->funcnames[x]);
if (ptr)
break;
}
@@ -620,6 +620,18 @@ void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *),
list_features(gl->mpgl_caps, log, MSGL_V, false);
}
+static void *get_procaddr_wrapper(void *ctx, const char *name)
+{
+ void *(*getProcAddress)(const GLubyte *) = ctx;
+ return getProcAddress ? getProcAddress((const GLubyte*)name) : NULL;
+}
+
+void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *),
+ const char *ext2, struct mp_log *log)
+{
+ mpgl_load_functions2(gl, get_procaddr_wrapper, getProcAddress, ext2, log);
+}
+
/**
* \brief return the number of bytes per pixel for the given format
* \param format OpenGL format
diff --git a/video/out/gl_common.h b/video/out/gl_common.h
index 38d952ae1b..951d2efefb 100644
--- a/video/out/gl_common.h
+++ b/video/out/gl_common.h
@@ -168,6 +168,8 @@ void mpgl_set_backend_wayland(MPGLContext *ctx);
void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *),
const char *ext2, struct mp_log *log);
+void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n),
+ void *fn_ctx, const char *ext2, struct mp_log *log);
// print a multi line string with line numbers (e.g. for shader sources)
// log, lev: module and log level, as in mp_msg()
diff --git a/video/out/gl_hwdec.c b/video/out/gl_hwdec.c
index 92f0ad095e..3bab1c1e9c 100644
--- a/video/out/gl_hwdec.c
+++ b/video/out/gl_hwdec.c
@@ -38,18 +38,19 @@ static const struct gl_hwdec_driver *const mpgl_hwdec_drivers[] = {
#if HAVE_VAAPI_GLX
&gl_hwdec_vaglx,
#endif
-#if HAVE_VDA_GL
- &gl_hwdec_vda,
-#endif
#if HAVE_VDPAU_GL_X11
&gl_hwdec_vdpau,
#endif
+#if HAVE_VDA_GL
+ &gl_hwdec_vda,
+#endif
NULL
};
static struct gl_hwdec *load_hwdec_driver(struct mp_log *log, GL *gl,
const struct gl_hwdec_driver *drv,
- struct mp_hwdec_info *info)
+ struct mp_hwdec_info *info,
+ bool is_auto)
{
struct gl_hwdec *hwdec = talloc(NULL, struct gl_hwdec);
*hwdec = (struct gl_hwdec) {
@@ -58,6 +59,7 @@ static struct gl_hwdec *load_hwdec_driver(struct mp_log *log, GL *gl,
.gl = gl,
.info = info,
.gl_texture_target = GL_TEXTURE_2D,
+ .reject_emulated = is_auto,
};
if (hwdec->driver->create(hwdec) < 0) {
talloc_free(hwdec);
@@ -71,10 +73,11 @@ struct gl_hwdec *gl_hwdec_load_api(struct mp_log *log, GL *gl,
const char *api_name,
struct mp_hwdec_info *info)
{
+ bool is_auto = api_name && strcmp(api_name, "auto") == 0;
for (int n = 0; mpgl_hwdec_drivers[n]; n++) {
const struct gl_hwdec_driver *drv = mpgl_hwdec_drivers[n];
- if (api_name && strcmp(drv->api_name, api_name) == 0) {
- struct gl_hwdec *r = load_hwdec_driver(log, gl, drv, info);
+ if (is_auto || (api_name && strcmp(drv->api_name, api_name) == 0)) {
+ struct gl_hwdec *r = load_hwdec_driver(log, gl, drv, info, is_auto);
if (r)
return r;
}
diff --git a/video/out/gl_hwdec.h b/video/out/gl_hwdec.h
index 5c70f7fd0d..ea10ac08f8 100644
--- a/video/out/gl_hwdec.h
+++ b/video/out/gl_hwdec.h
@@ -13,6 +13,8 @@ struct gl_hwdec {
struct mp_hwdec_info *info;
// For free use by hwdec driver
void *priv;
+ // For working around the vdpau vs. vaapi mess.
+ bool reject_emulated;
// hwdec backends must set this to an IMGFMT_ that has an equivalent
// internal representation in gl_video.c as the hardware texture.
// It's used to build the rendering chain, and also as screenshot format.
diff --git a/video/out/gl_hwdec_vaglx.c b/video/out/gl_hwdec_vaglx.c
index d93fa6253e..555e61aec3 100644
--- a/video/out/gl_hwdec_vaglx.c
+++ b/video/out/gl_hwdec_vaglx.c
@@ -80,6 +80,10 @@ static int create(struct gl_hwdec *hw)
vaTerminate(p->display);
return -1;
}
+ if (hw->reject_emulated && va_guess_if_emulated(p->ctx)) {
+ destroy(hw);
+ return -1;
+ }
hw->info->vaapi_ctx = p->ctx;
hw->converted_imgfmt = IMGFMT_RGB0;
return 0;
diff --git a/video/out/gl_hwdec_vdpau.c b/video/out/gl_hwdec_vdpau.c
index 7a68ddd83f..c59d97bc1b 100644
--- a/video/out/gl_hwdec_vdpau.c
+++ b/video/out/gl_hwdec_vdpau.c
@@ -111,6 +111,10 @@ static int create(struct gl_hwdec *hw)
return -1;
p->vdp_surface = VDP_INVALID_HANDLE;
p->mixer = mp_vdpau_mixer_create(p->ctx, hw->log);
+ if (hw->reject_emulated && mp_vdpau_guess_if_emulated(p->ctx)) {
+ destroy(hw);
+ return -1;
+ }
hw->info->vdpau_ctx = p->ctx;
hw->converted_imgfmt = IMGFMT_RGB0;
return 0;
diff --git a/video/out/gl_video.c b/video/out/gl_video.c
index eef5dfc467..9a762118c3 100644
--- a/video/out/gl_video.c
+++ b/video/out/gl_video.c
@@ -190,6 +190,7 @@ struct gl_video {
struct mp_rect dst_rect; // video rectangle on output window
struct mp_osd_res osd_rect; // OSD size/margins
int vp_x, vp_y, vp_w, vp_h; // GL viewport
+ bool vp_vflipped;
int frames_rendered;
@@ -574,7 +575,10 @@ static void update_uniforms(struct gl_video *p, GLuint program)
loc = gl->GetUniformLocation(program, "transform");
if (loc >= 0 && p->vp_w > 0 && p->vp_h > 0) {
float matrix[3][3];
- matrix_ortho2d(matrix, 0, p->vp_w, p->vp_h, 0);
+ int vvp[2] = {p->vp_h, 0};
+ if (p->vp_vflipped)
+ MPSWAP(int, vvp[0], vvp[1]);
+ matrix_ortho2d(matrix, 0, p->vp_w, vvp[0], vvp[1]);
gl->UniformMatrix3fv(loc, 1, GL_FALSE, &matrix[0][0]);
}
@@ -1786,7 +1790,7 @@ static void check_resize(struct gl_video *p)
void gl_video_resize(struct gl_video *p, struct mp_rect *window,
struct mp_rect *src, struct mp_rect *dst,
- struct mp_osd_res *osd)
+ struct mp_osd_res *osd, bool vflip)
{
p->src_rect = *src;
p->src_rect_rot = *src;
@@ -1803,6 +1807,7 @@ void gl_video_resize(struct gl_video *p, struct mp_rect *window,
p->vp_w = window->x1 - window->x0;
p->vp_h = window->y1 - window->y0;
+ p->vp_vflipped = vflip;
check_resize(p);
}
@@ -2188,7 +2193,7 @@ static int init_gl(struct gl_video *p)
gl->BindBuffer(GL_ARRAY_BUFFER, 0);
- gl->ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ gl_video_set_gl_state(p);
debug_check_gl(p, "after init_gl");
@@ -2214,6 +2219,24 @@ void gl_video_uninit(struct gl_video *p)
talloc_free(p);
}
+void gl_video_set_gl_state(struct gl_video *p)
+{
+ GL *gl = p->gl;
+
+ gl->ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ gl->ActiveTexture(GL_TEXTURE0);
+}
+
+void gl_video_unset_gl_state(struct gl_video *p)
+{
+ GL *gl = p->gl;
+
+ gl->PixelStorei(GL_PACK_ROW_LENGTH, 0);
+ gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
+ gl->PixelStorei(GL_PACK_ALIGNMENT, 4);
+ gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4);
+}
+
// dest = src.<w> (always using 4 components)
static void packed_fmt_swizzle(char w[5], const struct packed_fmt_entry *fmt)
{
diff --git a/video/out/gl_video.h b/video/out/gl_video.h
index bba24e364a..3d733c8a80 100644
--- a/video/out/gl_video.h
+++ b/video/out/gl_video.h
@@ -69,7 +69,7 @@ void gl_video_render_frame(struct gl_video *p, int fbo);
struct mp_image *gl_video_download_image(struct gl_video *p);
void gl_video_resize(struct gl_video *p, struct mp_rect *window,
struct mp_rect *src, struct mp_rect *dst,
- struct mp_osd_res *osd);
+ struct mp_osd_res *osd, bool vflip);
void gl_video_get_colorspace(struct gl_video *p, struct mp_image_params *params);
bool gl_video_set_equalizer(struct gl_video *p, const char *name, int val);
bool gl_video_get_equalizer(struct gl_video *p, const char *name, int *val);
@@ -77,6 +77,9 @@ bool gl_video_get_equalizer(struct gl_video *p, const char *name, int *val);
void gl_video_set_debug(struct gl_video *p, bool enable);
void gl_video_resize_redraw(struct gl_video *p, int w, int h);
+void gl_video_set_gl_state(struct gl_video *p);
+void gl_video_unset_gl_state(struct gl_video *p);
+
struct gl_hwdec;
void gl_video_set_hwdec(struct gl_video *p, struct gl_hwdec *hwdec);
diff --git a/video/out/vo.c b/video/out/vo.c
index 518841007f..710f2f28ce 100644
--- a/video/out/vo.c
+++ b/video/out/vo.c
@@ -55,6 +55,7 @@ extern const struct vo_driver video_out_xv;
extern const struct vo_driver video_out_opengl;
extern const struct vo_driver video_out_opengl_hq;
extern const struct vo_driver video_out_opengl_old;
+extern const struct vo_driver video_out_opengl_cb;
extern const struct vo_driver video_out_null;
extern const struct vo_driver video_out_image;
extern const struct vo_driver video_out_lavc;
@@ -103,6 +104,7 @@ const struct vo_driver *const video_out_drivers[] =
#endif
#if HAVE_GL
&video_out_opengl_hq,
+ &video_out_opengl_cb,
#endif
#if HAVE_WAYLAND
&video_out_wayland,
diff --git a/video/out/vo.h b/video/out/vo.h
index f54e2c1a55..8b47d04cfe 100644
--- a/video/out/vo.h
+++ b/video/out/vo.h
@@ -105,6 +105,8 @@ enum mp_voctrl {
VOCTRL_GET_RECENT_FLIP_TIME, // int64_t* (using mp_time_us())
VOCTRL_GET_PREF_DEINT, // int*
+
+ VOCTRL_SET_LIBMPV_OPENGL_CB_CONTEXT,// struct mpv_opengl_cb_context*
};
// VOCTRL_SET_EQUALIZER
diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c
index 2043a4cd3c..567f83be97 100644
--- a/video/out/vo_opengl.c
+++ b/video/out/vo_opengl.c
@@ -96,7 +96,7 @@ static void resize(struct gl_priv *p)
struct mp_osd_res osd;
vo_get_src_dst_rects(vo, &src, &dst, &osd);
- gl_video_resize(p->renderer, &wnd, &src, &dst, &osd);
+ gl_video_resize(p->renderer, &wnd, &src, &dst, &osd, false);
vo->want_redraw = true;
}
@@ -447,7 +447,7 @@ err_out:
}
#define OPT_BASE_STRUCT struct gl_priv
-const struct m_option options[] = {
+static const struct m_option options[] = {
OPT_FLAG("glfinish", use_glFinish, 0),
OPT_FLAG("waitvsync", waitvsync, 0),
OPT_INT("swapinterval", swap_interval, 0, OPTDEF_INT(1)),
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,
+};