summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorwm4 <wm4@nowhere>2018-02-20 13:30:18 +0100
committerKevin Mitchell <kevmitch@gmail.com>2018-02-28 00:55:06 -0800
commitb03712143012f08591de59d09675fdd3a0eaeed8 (patch)
tree34bdfe31802573c1452dcbe023610acad3f1b5a9
parente76fda8594a7fa19888260c86d95396c5e799cad (diff)
downloadmpv-b03712143012f08591de59d09675fdd3a0eaeed8.tar.bz2
mpv-b03712143012f08591de59d09675fdd3a0eaeed8.tar.xz
client API: deprecate opengl-cb API and introduce a replacement API
The purpose of the new API is to make it useable with other APIs than OpenGL, especially D3D11 and vulkan. In theory it's now possible to support other vo_gpu backends, as well as backends that don't use the vo_gpu code at all. This also aims to get rid of the dumb mpv_get_sub_api() function. The life cycle of the new mpv_render_context is a bit different from mpv_opengl_cb_context, and you explicitly create/destroy the new context, instead of calling init/uninit on an object returned by mpv_get_sub_api(). In other to make the render API generic, it's annoyingly EGL style, and requires you to pass in API-specific objects to generic functions. This is to avoid explicit objects like the internal ra API has, because that sounds more complicated and annoying for an API that's supposed to never change. The opengl_cb API will continue to exist for a bit longer, but internally there are already a few tradeoffs, like reduced thread-safety. Mostly untested. Seems to work fine with mpc-qt.
-rw-r--r--DOCS/client-api-changes.rst13
-rw-r--r--DOCS/interface-changes.rst2
-rw-r--r--DOCS/man/options.rst3
-rw-r--r--DOCS/man/vo.rst9
-rw-r--r--libmpv/client.h13
-rw-r--r--libmpv/mpv.def6
-rw-r--r--libmpv/opengl_cb.h11
-rw-r--r--libmpv/render.h309
-rw-r--r--libmpv/render_gl.h155
-rw-r--r--player/client.c164
-rw-r--r--player/client.h19
-rw-r--r--player/core.h2
-rw-r--r--player/main.c3
-rw-r--r--player/playloop.c1
-rw-r--r--player/video.c1
-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
-rw-r--r--wscript_build.py4
23 files changed, 1267 insertions, 288 deletions
diff --git a/DOCS/client-api-changes.rst b/DOCS/client-api-changes.rst
index df2b42aa70..845953580a 100644
--- a/DOCS/client-api-changes.rst
+++ b/DOCS/client-api-changes.rst
@@ -33,6 +33,19 @@ API changes
::
--- mpv 0.29.0 ---
+ 1.28 - deprecate the render opengl_cb API, and replace it with render.h
+ and render_gl.h. The goal is allowing support for APIs other than
+ OpenGL. The old API is emulated with the new API.
+ Likewise, the "opengl-cb" VO is renamed to "libmpv".
+ mpv_get_sub_api() is deprecated along the opengl_cb API.
+ The new API is relatively similar, but not the same. The rough
+ equivalents are:
+ mpv_opengl_cb_init_gl => mpv_render_context_create
+ mpv_opengl_cb_set_update_callback => mpv_render_context_set_update_callback
+ mpv_opengl_cb_draw => mpv_render_context_render
+ mpv_opengl_cb_report_flip => mpv_render_context_report_swap
+ mpv_opengl_cb_uninit_gl => mpv_render_context_free
+ The VO opengl-cb is also renamed to "libmpv".
1.27 - make opengl-cb the default VO. This causes a subtle behavior change
if the API user called mpv_opengl_cb_init_gl(), but does not set
the "vo" option. Before, it would still have used another VO (like
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst
index e18fec4c1d..5830b413e0 100644
--- a/DOCS/interface-changes.rst
+++ b/DOCS/interface-changes.rst
@@ -72,6 +72,8 @@ Interface changes
will probably stall.
- deprecate the OpenGL cocoa backend, option choice --gpu-context=cocoa
when used with --gpu-api=opengl (use --vo=opengl-cb)
+ - rename --vo=opengl-cb to --vo=libmpv (goes in hand with the opengl-cb
+ API deprecation, see client-api-changes.rst)
- make --deinterlace=yes always deinterlace, instead of trying to check
certain unreliable video metadata. Also flip the defaults of all builtin
HW deinterlace filters to always deinterlace.
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 8246dd0f49..2716302e72 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -5221,6 +5221,9 @@ The following video options are currently all specific to ``--vo=gpu`` and
NOTE: On Windows, the default profile must be an ICC profile. WCS profiles
are not supported.
+ Applications using libmpv with the render API need to provide the ICC
+ profile via ``MPV_RENDER_PARAM_ICC_PROFILE``.
+
``--icc-cache-dir=<dirname>``
Store and load the 3D LUTs created from the ICC profile in this directory.
This can be used to speed up loading, since LittleCMS 2 can take a while to
diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst
index 8e8b9803be..672f3c68f6 100644
--- a/DOCS/man/vo.rst
+++ b/DOCS/man/vo.rst
@@ -428,13 +428,14 @@ Available video output drivers are:
``--vo-image-outdir=<dirname>``
Specify the directory to save the image files to (default: ``./``).
-``opengl-cb``
- For use with libmpv direct OpenGL embedding. As a special case, on OS X it
+``libmpv``
+ For use with libmpv direct embedding. As a special case, on OS X it
is used like a normal VO within mpv (cocoa-cb). Otherwise useless in any
other contexts.
- (See ``<mpv/opengl_cb.h>``.)
+ (See ``<mpv/render.h>``.)
- This also supports many of the options the ``gpu`` VO has.
+ This also supports many of the options the ``gpu`` VO has, depending on the
+ backend.
``rpi`` (Raspberry Pi)
Native video output on the Raspberry Pi using the MMAL API.
diff --git a/libmpv/client.h b/libmpv/client.h
index 424bf0613d..07237c9d4d 100644
--- a/libmpv/client.h
+++ b/libmpv/client.h
@@ -210,7 +210,7 @@ extern "C" {
* relational operators (<, >, <=, >=).
*/
#define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL)
-#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 27)
+#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 28)
/**
* The API user is allowed to "#define MPV_ENABLE_DEPRECATED 0" before
@@ -1691,6 +1691,11 @@ int mpv_get_wakeup_pipe(mpv_handle *ctx);
*/
void mpv_wait_async_requests(mpv_handle *ctx);
+#if MPV_ENABLE_DEPRECATED
+
+/**
+ * @deprecated use render.h
+ */
typedef enum mpv_sub_api {
/**
* For using mpv's OpenGL renderer on an external OpenGL context.
@@ -1698,6 +1703,8 @@ typedef enum mpv_sub_api {
* This context can be used with mpv_opengl_cb_* functions.
* Will return NULL if unavailable (if OpenGL support was not compiled in).
* See opengl_cb.h for details.
+ *
+ * @deprecated use render.h
*/
MPV_SUB_API_OPENGL_CB = 1
} mpv_sub_api;
@@ -1705,9 +1712,13 @@ typedef enum mpv_sub_api {
/**
* This is used for additional APIs that are not strictly part of the core API.
* See the individual mpv_sub_api member values.
+ *
+ * @deprecated use render.h
*/
void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api);
+#endif
+
#ifdef __cplusplus
}
#endif
diff --git a/libmpv/mpv.def b/libmpv/mpv.def
index 0384c7db9d..5299f69c9a 100644
--- a/libmpv/mpv.def
+++ b/libmpv/mpv.def
@@ -28,6 +28,12 @@ mpv_opengl_cb_report_flip
mpv_opengl_cb_render
mpv_opengl_cb_set_update_callback
mpv_opengl_cb_uninit_gl
+mpv_render_context_create
+mpv_render_context_free
+mpv_render_context_render
+mpv_render_context_report_swap
+mpv_render_context_set_parameter
+mpv_render_context_set_update_callback
mpv_request_event
mpv_request_log_messages
mpv_resume
diff --git a/libmpv/opengl_cb.h b/libmpv/opengl_cb.h
index 384372b617..ce88a8570a 100644
--- a/libmpv/opengl_cb.h
+++ b/libmpv/opengl_cb.h
@@ -18,6 +18,10 @@
#include "client.h"
+#if !MPV_ENABLE_DEPRECATED
+#error "This header and all API provided by it is deprecated. Use render.h instead."
+#else
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -27,6 +31,9 @@ extern "C" {
* Overview
* --------
*
+ * Warning: this API is deprecated. A very similar API is provided by render.h
+ * and render_gl.h. The deprecated API is emulated with the new API.
+ *
* This API can be used to make mpv render into a foreign OpenGL context. It
* can be used to handle video display.
*
@@ -328,7 +335,6 @@ int mpv_opengl_cb_init_gl(mpv_opengl_cb_context *ctx, const char *exts,
*/
int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int w, int h);
-#if MPV_ENABLE_DEPRECATED
/**
* Deprecated. Use mpv_opengl_cb_draw(). This function is equivalent to:
*
@@ -341,7 +347,6 @@ int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int w, int h);
* was never marked as stable).
*/
int mpv_opengl_cb_render(mpv_opengl_cb_context *ctx, int fbo, int vp[4]);
-#endif
/**
* Tell the renderer that a frame was flipped at the given time. This is
@@ -375,4 +380,6 @@ int mpv_opengl_cb_uninit_gl(mpv_opengl_cb_context *ctx);
}
#endif
+#endif /* else #if MPV_ENABLE_DEPRECATED */
+
#endif
diff --git a/libmpv/render.h b/libmpv/render.h
new file mode 100644
index 0000000000..64a317d73c
--- /dev/null
+++ b/libmpv/render.h
@@ -0,0 +1,309 @@
+/* Copyright (C) 2018 the mpv developers
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MPV_CLIENT_API_RENDER_H_
+#define MPV_CLIENT_API_RENDER_H_
+
+#include "client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Overview
+ * --------
+ *
+ * This API can be used to make mpv render using supported graphic APIs (such
+ * as OpenGL). It can be used to handle video display.
+ *
+ * The renderer needs to be created with mpv_render_context_create() before
+ * you start playback (or otherwise cause a VO to be created). Then (with most
+ * backends) mpv_render_context_render() can be used to explicitly render the
+ * current video frame. Use mpv_render_context_set_update_callback() to get
+ * notified when there is a new frame to draw.
+ *
+ * Preferably rendering should be done in a separate thread. If you call
+ * normal libmpv API functions on the renderer thread, deadlocks can result
+ * (these are made non-fatal with timeouts, but user experience will obviously
+ * suffer).
+ *
+ * You can output and embed video without this API by setting the mpv "wid"
+ * option to a native window handle (see "Embedding the video window" section
+ * in the client.h header). In general, using the render API is recommended,
+ * because window embedding can cause various issues, especially with GUI
+ * toolkits and certain platforms.
+ *
+ * Supported backends
+ * ------------------
+ *
+ * OpenGL: via MPV_RENDER_API_TYPE_OPENGL, see render_gl.h header.
+ *
+ * Threading
+ * ---------
+ *
+ * The mpv_render_* functions can be called from any thread, under the
+ * following conditions:
+ * - only one of the mpv_render_* functions can be called at the same time
+ * (unless they belong to different mpv cores created by mpv_create())
+ * - never can be called from within the callbacks set with
+ * mpv_set_wakeup_callback() or mpv_render_context_set_update_callback()
+ * - if the OpenGL backend is used, for all functions the OpenGL context
+ * must be "current" in the calling thread, and it must be the same OpenGL
+ * context as the mpv_render_context was created with. Otherwise, undefined
+ * behavior will occur.
+ *
+ * Context and handle lifecycle
+ * ----------------------------
+ *
+ * Video initialization will fail if the render context was not initialized yet
+ * (with mpv_render_context_create()), or it will revert to a VO that creates
+ * its own window.
+ *
+ * Calling mpv_render_context_free() while a VO is using the render context is
+ * active will disable video.
+ *
+ * You must free the context with mpv_render_context_free() before the mpv core
+ * is destroyed. If this doesn't happen, undefined behavior will result.
+ */
+
+/**
+ * Opaque context, returned by mpv_render_context_create().
+ */
+typedef struct mpv_render_context mpv_render_context;
+
+/**
+ * Parameters for mpv_render_param (which is used in a few places such as
+ * mpv_render_context_create().
+ *
+ * Also see mpv_render_param for conventions and how to use it.
+ */
+typedef enum mpv_render_param_type {
+ /**
+ * Not a valid value, but also used to terminate a params array. Its value
+ * is always guaranteed to be 0 (even if the ABI changes in the future).
+ */
+ MPV_RENDER_PARAM_INVALID = 0,
+ /**
+ * The render API to use. Valid for mpv_render_context_create().
+ *
+ * Type: char*
+ *
+ * Defined APIs:
+ *
+ * MPV_RENDER_API_TYPE_OPENGL:
+ * OpenGL desktop 2.1 or later (preferably core profile compatible to
+ * OpenGL 3.2), or OpenGLES 2.0 or later.
+ * Providing MPV_RENDER_PARAM_OPENGL_INIT_PARAMS is required.
+ * It is expected that an OpenGL context is valid and "current" when
+ * calling mpv_render_* functions (unless specified otherwise). It
+ * must be the same context for the same mpv_render_context.
+ */
+ MPV_RENDER_PARAM_API_TYPE = 1,
+ /**
+ * Required parameters for initializing the OpenGL renderer. Valid for
+ * mpv_render_context_create().
+ * Type: mpv_opengl_init_params*
+ */
+ MPV_RENDER_PARAM_OPENGL_INIT_PARAMS = 2,
+ /**
+ * Describes a GL render target. Valid for mpv_render_context_render().
+ * Type: mpv_opengl_fbo*
+ */
+ MPV_RENDER_PARAM_OPENGL_FBO = 3,
+ /**
+ * Control flipped rendering. Valid for mpv_render_context_render().
+ * Type: int*
+ * If the value is set to 0, render normally. Otherwise, render it flipped,
+ * which is needed e.g. when rendering to an OpenGL default framebuffer
+ * (which has a flipped coordinate system).
+ */
+ MPV_RENDER_PARAM_FLIP_Y = 4,
+ /**
+ * Control surface depth. Valid for mpv_render_context_render().
+ * Type: int*
+ * This implies the depth of the surface passed to the render function in
+ * bits per channel. If omitted or set to 0, the renderer will assume 8.
+ * Typically used to control dithering.
+ */
+ MPV_RENDER_PARAM_DEPTH = 5,
+ /**
+ * ICC profile blob. Valid for mpv_render_context_set_parameter().
+ * Type: mpv_byte_array*
+ * Set an ICC profile for use with the "icc-profile-auto" option. (If the
+ * option is not enabled, the ICC data will not be used.)
+ */
+ MPV_RENDER_PARAM_ICC_PROFILE = 6,
+ /**
+ * Ambient light in lux. Valid for mpv_render_context_set_parameter().
+ * Type: int*
+ * This can be used for automatic gamma correction.
+ */
+ MPV_RENDER_PARAM_AMBIENT_LIGHT = 7,
+} mpv_render_param_type;
+
+/**
+ * Predefined values for MPV_RENDER_PARAM_API_TYPE.
+ */
+#define MPV_RENDER_API_TYPE_OPENGL "opengl"
+
+/**
+ * Used to pass arbitrary parameters to some mpv_render_* functions. The
+ * meaning of the data parameter is determined by the type, and each
+ * MPV_RENDER_PARAM_* documents what type the value must point to.
+ *
+ * Each value documents the required data type as the pointer you cast to
+ * void* and set on mpv_render_param.data. For example, if MPV_RENDER_PARAM_FOO
+ * documents the type as Something* , then the code should look like this:
+ *
+ * Something foo = {...};
+ * mpv_render_param param;
+ * param.type = MPV_RENDER_PARAM_FOO;
+ * param.data = & foo;
+ *
+ * Normally, the data field points to exactly 1 object. If the type is char*,
+ * it points to a 0-terminated string.
+ *
+ * In all cases (unless documented otherwise) the pointers need to remain
+ * valid during the call only. Unless otherwise documented, the API functions
+ * will not write to the params array or any data pointed to it.
+ *
+ * As a convention, parameter arrays are always terminated by type==0. There
+ * is no specific order of the parameters required. The order of fields is
+ * guaranteed (even after ABI changes).
+ */
+typedef struct mpv_render_param {
+ enum mpv_render_param_type type;
+ void *data;
+} mpv_render_param;
+
+/**
+ * Initialize the renderer state. Depending on the backend used, this will
+ * access the underlying GPU API and initialize its own objects.
+ *
+ * You must free the context with mpv_render_context_free(). Not doing so before
+ * the mpv core is destroyed may result in memory leaks or crashes.
+ *
+ * Currently, only at most 1 context can exists per mpv core (it represents the
+ * main video output).
+ *
+ * @param res set to the context (on success) or NULL (on failure). The value
+ * is never read and always overwritten.
+ * @param mpv handle used to get the core (the mpv_render_context won't depend
+ * on this specific handle, only the core referenced by it)
+ * @param params an array of parameters, terminated by type==0. It's left
+ * unspecified what happens with unknown parameters. At least
+ * MPV_RENDER_PARAM_API_TYPE is required, and most backends will
+ * require another backend-specific parameter.
+ * @return error code, including but not limited to:
+ * MPV_ERROR_UNSUPPORTED: the OpenGL version is not supported
+ * (or required extensions are missing)
+ * MPV_ERROR_NOT_IMPLEMENTED: an unknown API type was provided, or
+ * support for the requested API was not
+ * built in the used libmpv binary.
+ * MPV_ERROR_INVALID_PARAMETER: at least one of the provided parameters was
+ * not valid.
+ */
+int mpv_render_context_create(mpv_render_context **res, mpv_handle *mpv,
+ mpv_render_param *params);
+
+/**
+ * Attempt to change a single parameter. Not all backends and parameter types
+ * support all kinds of changes.
+ *
+ * @param ctx a valid render context
+ * @param param the parameter type and data that should be set
+ * @return error code. If a parameter could actually be changed, this returns
+ * success, otherwise an error code depending on the parameter type
+ * and situation.
+ */
+int mpv_render_context_set_parameter(mpv_render_context *ctx,
+ mpv_render_param param);
+
+typedef void (*mpv_render_update_fn)(void *cb_ctx);
+
+/**
+ * Set the callback that notifies you when a new video frame is available, or
+ * if the video display configuration somehow changed and requires a redraw.
+ * Similar to mpv_set_wakeup_callback(), you must not call any mpv API from
+ * the callback, and all the other listed restrictions apply (such as not
+ * exiting the callback by throwing exceptions).
+ *
+ * This can be called from any thread, except from an update callback. In case
+ * of the OpenGL backend, no OpenGL state or API is accessed.
+ *
+ * @param callback callback(callback_ctx) is called if the frame should be
+ * redrawn
+ * @param callback_ctx opaque argument to the callback
+ */
+void mpv_render_context_set_update_callback(mpv_render_context *ctx,
+ mpv_render_update_fn callback,
+ void *callback_ctx);
+
+/**
+ * Render video.
+ *
+ * Typically renders the video to a target surface provided via mpv_render_param
+ * (the details depend on the backend in use). Options like "panscan" are
+ * applied to determine which part of the video should be visible and how the
+ * video should be scaled. You can change these options at runtime by using the
+ * mpv property API.
+ *
+ * The renderer will reconfigure itself every time the target surface
+ * configuration (such as size) is changed.
+ *
+ * This function implicitly pulls a video frame from the internal queue and
+ * renders it. If no new frame is available, the previous frame is redrawn.
+ * The update callback set with mpv_render_context_set_update_callback()
+ * notifies you when a new frame was added. The details potentially depend on
+ * the backends and the provided parameters.
+ *
+ * @param ctx a valid render context
+ * @param params an array of parameters, terminated by type==0. Which parameters
+ * are required depends on the backend. It's left unspecified what
+ * happens with unknown parameters.
+ * @return error code
+ */
+int mpv_render_context_render(mpv_render_context *ctx, mpv_render_param *params);
+
+/**
+ * Tell the renderer that a frame was flipped at the given time. This is
+ * optional, but can help the player to achieve better timing.
+ *
+ * Note that calling this at least once informs libmpv that you will use this
+ * function. If you use it inconsistently, expect bad video playback.
+ *
+ * If this is called while no video is initialized, it is ignored.
+ *
+ * @param ctx a valid render context
+ */
+void mpv_render_context_report_swap(mpv_render_context *ctx);
+
+/**
+ * Destroy the mpv renderer state.
+ *
+ * If video is still active (e.g. a file playing), video will be disabled
+ * forcefully.
+ *
+ * @param ctx a valid render context. After this function returns, this is not
+ * a valid pointer anymore. NULL is also allowed and does nothing.
+ */
+void mpv_render_context_free(mpv_render_context *ctx);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/libmpv/render_gl.h b/libmpv/render_gl.h
new file mode 100644
index 0000000000..01c09e80df
--- /dev/null
+++ b/libmpv/render_gl.h
@@ -0,0 +1,155 @@
+/* Copyright (C) 2018 the mpv developers
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MPV_CLIENT_API_RENDER_GL_H_
+#define MPV_CLIENT_API_RENDER_GL_H_
+
+#include "render.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * OpenGL backend
+ * --------------
+ *
+ * This header contains definitions for using OpenGL with the render.h API.
+ *
+ * OpenGL interop
+ * --------------
+ *
+ * The OpenGL backend has some special rules, because OpenGL itself uses
+ * implicit per-thread contexts, which causes additional API problems.
+ *
+ * This assumes the OpenGL context lives on a certain thread controlled by the
+ * API user. All mpv_render_* APIs have to be assumed to implicitly use the
+ * OpenGL context if you pass a mpv_render_context using the OpenGL backend,
+ * unless specified otherwise.
+ *
+ * The OpenGL context is indirectly accessed through the OpenGL function
+ * pointers returned by the get_proc_address callback in mpv_opengl_init_params.
+ * Generally, mpv will not load the system OpenGL library when using this API.
+ *
+ * OpenGL state
+ * ------------
+ *
+ * OpenGL has a large amount of implicit state. All the mpv functions mentioned
+ * above expect that the OpenGL state is reasonably set to OpenGL standard
+ * defaults. Likewise, mpv will attempt to leave the OpenGL context with
+ * standard defaults. The following state is excluded from this:
+ *
+ * - the glViewport state
+ * - the glScissor state (but GL_SCISSOR_TEST is in its default value)
+ * - glBlendFuncSeparate() state (but GL_BLEND is in its default value)
+ * - glClearColor() state
+ * - mpv may overwrite the callback set with glDebugMessageCallback()
+ * - mpv always disables GL_DITHER at init
+ *
+ * Messing with the state could be avoided by creating shared OpenGL contexts,
+ * but this is avoided for the sake of compatibility and interoperability.
+ *
+ * On OpenGL 2.1, mpv will strictly call functions like glGenTextures() to
+ * create OpenGL objects. You will have to do the same. This ensures that
+ * objects created by mpv and the API users don't clash. Also, legacy state
+ * must be either in its defaults, or not interfere with core state.
+ *
+ * API use
+ * -------
+ *
+ * The mpv_render_* API is used. That API supports multiple backends, and this
+ * section documents specifics for the OpenGL backend.
+ *
+ * Use mpv_render_context_create() with MPV_RENDER_PARAM_API_TYPE set to
+ * MPV_RENDER_API_TYPE_OPENGL, and MPV_RENDER_PARAM_OPENGL_INIT_PARAMS provided.
+ *
+ * Call mpv_render_context_render() with MPV_RENDER_PARAM_OPENGL_FBO to render
+ * the video frame to an FBO.
+ *
+ * Hardware decoding
+ * -----------------
+ *
+ * Hardware decoding via this API is fully supported, but requires some
+ * additional setup. (At least if direct hardware decoding modes are wanted,
+ * instead of copying back surface data from GPU to CPU RAM.)
+ *
+ * There may be certain requirements on the OpenGL implementation:
+ *
+ * - Windows: ANGLE is required (although in theory GL/DX interop could be used)
+ * - Intel/Linux: EGL is required, and also a glMPGetNativeDisplay() callback
+ * must be provided (see sections below)
+ * - nVidia/Linux: Both GLX and EGL should work (GLX is required if vdpau is
+ * used, e.g. due to old drivers.)
+ * - OSX: CGL is required (CGLGetCurrentContext() returning non-NULL)
+ * - iOS: EAGL is required (EAGLContext.currentContext returning non-nil)
+ *
+ * Once these things are setup, hardware decoding can be enabled/disabled at
+ * any time by setting the "hwdec" property.
+ */
+
+/**
+ * For initializing the mpv OpenGL state via MPV_RENDER_PARAM_OPENGL_INIT_PARAMS.
+ */
+typedef struct mpv_opengl_init_params {
+ /**
+ * This retrieves OpenGL function pointers, and will use them in subsequent
+ * operation.
+ * Usually, GL context APIs do this for you (e.g. with glXGetProcAddressARB
+ * or wglGetProcAddress), but some APIs do not always return pointers for
+ * all standard functions (even if present); in this case you have to
+ * compensate by looking up these functions yourself and returning them
+ * from this callback.
+ */
+ void *(*get_proc_address)(void *ctx, const char *name);
+ /**
+ * Value passed as ctx parameter to get_proc_address().
+ */
+ void *get_proc_address_ctx;
+ /**
+ * This should not be used. The main purpose is signaling support for
+ * "GL_MP_MPGetNativeDisplay", which is needed for compatibility with the
+ * opengl_cb API only. Thus it's deprecated and will be removed or ignored
+ * when the opengl_cb API is removed.
+ */
+ const char *extra_exts;
+} mpv_opengl_init_params;
+
+/**
+ * For MPV_RENDER_PARAM_OPENGL_FBO.
+ */
+typedef struct mpv_opengl_fbo {
+ /**
+ * Framebuffer object name. This must be either a valid FBO generated by
+ * glGenFramebuffers() that is complete and color-renderable, or 0. If the
+ * value is 0, this refers to the OpenGL default framebuffer.
+ */
+ int fbo;
+ /**
+ * Valid dimensions. This must refer to the size of the framebuffer. This
+ * must always be set.
+ */
+ int w, h;
+ /**
+ * Underlying texture internal format (e.g. GL_RGBA8), or 0 if unknown. If
+ * this is the default framebuffer, this can be an equivalent.
+ */
+ int internal_format;
+} mpv_opengl_fbo;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/player/client.c b/player/client.c
index 1df7e659d8..45af55bee9 100644
--- a/player/client.c
+++ b/player/client.c
@@ -20,6 +20,7 @@
#include <fcntl.h>
#include <errno.h>
#include <locale.h>
+#include <math.h>
#include <assert.h>
#include "common/common.h"
@@ -46,8 +47,6 @@
#include "core.h"
#include "client.h"
-#include "config.h"
-
/*
* Locking hierarchy:
*
@@ -74,6 +73,9 @@ struct mp_client_api {
struct mp_custom_protocol *custom_protocols;
int num_custom_protocols;
+
+ struct mpv_render_context *render_context;
+ struct mpv_opengl_cb_context *gl_cb_ctx;
};
struct observe_property {
@@ -156,6 +158,16 @@ void mp_clients_destroy(struct MPContext *mpctx)
if (!mpctx->clients)
return;
assert(mpctx->clients->num_clients == 0);
+
+ TA_FREEP(&mpctx->clients->gl_cb_ctx);
+
+ // The API user is supposed to call mpv_render_context_free(). It's simply
+ // not allowed not to do this.
+ if (mpctx->clients->render_context) {
+ MP_FATAL(mpctx, "Broken API use: mpv_render_context_free() not called.\n");
+ abort();
+ }
+
pthread_mutex_destroy(&mpctx->clients->lock);
talloc_free(mpctx->clients);
mpctx->clients = NULL;
@@ -279,6 +291,11 @@ struct mp_log *mp_client_get_log(struct mpv_handle *ctx)
return ctx->log;
}
+struct mpv_global *mp_client_get_global(struct mpv_handle *ctx)
+{
+ return ctx->mpctx->global;
+}
+
struct MPContext *mp_client_get_core(struct mpv_handle *ctx)
{
return ctx->mpctx;
@@ -1672,7 +1689,9 @@ int64_t mpv_get_time_us(mpv_handle *ctx)
return mp_time_us();
}
-// Used by vo_opengl_cb to synchronously uninitialize video.
+#include "video/out/libmpv.h"
+
+// Used by vo_libmpv to synchronously uninitialize video.
void kill_video(struct mp_client_api *client_api)
{
struct MPContext *mpctx = client_api->mpctx;
@@ -1686,58 +1705,163 @@ void kill_video(struct mp_client_api *client_api)
mp_dispatch_unlock(mpctx->dispatch);
}
+// Used by vo_libmpv to set the current render context.
+bool mp_set_main_render_context(struct mp_client_api *client_api,
+ struct mpv_render_context *ctx, bool active)
+{
+ assert(ctx);
+
+ pthread_mutex_lock(&client_api->lock);
+ bool is_set = !!client_api->render_context;
+ bool is_same = client_api->render_context == ctx;
+ // Can set if it doesn't remove another existing ctx.
+ bool res = is_same || !is_set;
+ if (res)
+ client_api->render_context = active ? ctx : NULL;
+ pthread_mutex_unlock(&client_api->lock);
+ return res;
+}
+
+// Used by vo_libmpv. Relies on guarantees by mp_render_context_acquire().
+struct mpv_render_context *
+mp_client_api_acquire_render_context(struct mp_client_api *ca)
+{
+ struct mpv_render_context *res = NULL;
+ pthread_mutex_lock(&ca->lock);
+ if (ca->render_context && mp_render_context_acquire(ca->render_context))
+ res = ca->render_context;
+ pthread_mutex_unlock(&ca->lock);
+ return res;
+}
+
+// Emulation of old opengl_cb API.
+
#include "libmpv/opengl_cb.h"
+#include "libmpv/render_gl.h"
+
+struct mpv_opengl_cb_context {
+ struct mp_client_api *client_api;
+ mpv_opengl_cb_update_fn callback;
+ void *callback_ctx;
+};
-#if HAVE_GL
static mpv_opengl_cb_context *opengl_cb_get_context(mpv_handle *ctx)
{
- mpv_opengl_cb_context *cb = ctx->mpctx->gl_cb_ctx;
+ pthread_mutex_lock(&ctx->clients->lock);
+ mpv_opengl_cb_context *cb = ctx->clients->gl_cb_ctx;
if (!cb) {
- cb = mp_opengl_create(ctx->mpctx->global, ctx->clients);
- ctx->mpctx->gl_cb_ctx = cb;
+ cb = talloc_zero(NULL, struct mpv_opengl_cb_context);
+ cb->client_api = ctx->clients;
+ cb->client_api->gl_cb_ctx = cb;
}
+ pthread_mutex_unlock(&ctx->clients->lock);
return cb;
}
-#else
-static mpv_opengl_cb_context *opengl_cb_get_context(mpv_handle *ctx)
-{
- return NULL;
-}
+
void mpv_opengl_cb_set_update_callback(mpv_opengl_cb_context *ctx,
mpv_opengl_cb_update_fn callback,
void *callback_ctx)
{
+ // This was probably supposed to be thread-safe, but we don't care. It's
+ // compatibility code, and if you have problems, use the new API.
+ if (ctx->client_api->render_context) {
+ mpv_render_context_set_update_callback(ctx->client_api->render_context,
+ callback, callback_ctx);
+ }
+ // Nasty thing: could set this even while not initialized, so we need to
+ // preserve it.
+ ctx->callback = callback;
+ ctx->callback_ctx = callback_ctx;
}
+
int mpv_opengl_cb_init_gl(mpv_opengl_cb_context *ctx, const char *exts,
mpv_opengl_cb_get_proc_address_fn get_proc_address,
void *get_proc_address_ctx)
{
- return MPV_ERROR_NOT_IMPLEMENTED;
+ if (ctx->client_api->render_context)
+ return MPV_ERROR_INVALID_PARAMETER;
+
+ // mpv_render_context_create() only calls mp_client_get_global() on it.
+ mpv_handle dummy = {.mpctx = ctx->client_api->mpctx};
+
+ mpv_render_param params[] = {
+ {MPV_RENDER_PARAM_API_TYPE, MPV_RENDER_API_TYPE_OPENGL},
+ {MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &(mpv_opengl_init_params){
+ .get_proc_address = get_proc_address,
+ .get_proc_address_ctx = get_proc_address_ctx,
+ .extra_exts = exts,
+ }},
+ {0}
+ };
+ int err = mpv_render_context_create(&ctx->client_api->render_context,
+ &dummy, params);
+ if (err >= 0) {
+ mpv_render_context_set_update_callback(ctx->client_api->render_context,
+ ctx->callback, ctx->callback_ctx);
+ }
+ return err;
}
+
int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int w, int h)
{
- return MPV_ERROR_NOT_IMPLEMENTED;
+ if (!ctx->client_api->render_context)
+ return MPV_ERROR_INVALID_PARAMETER;
+
+ mpv_render_param params[] = {
+ {MPV_RENDER_PARAM_OPENGL_FBO, &(mpv_opengl_fbo){
+ .fbo = fbo,
+ .w = w,
+ .h = abs(h),
+ }},
+ {MPV_RENDER_PARAM_FLIP_Y, &(int){h < 0}},
+ {0}
+ };
+ return mpv_render_context_render(ctx->client_api->render_context, params);
}
+
int mpv_opengl_cb_report_flip(mpv_opengl_cb_context *ctx, int64_t time)
{
- return MPV_ERROR_NOT_IMPLEMENTED;
+ if (!ctx->client_api->render_context)
+ return MPV_ERROR_INVALID_PARAMETER;
+
+ mpv_render_context_report_swap(ctx->client_api->render_context);
+ return 0;
}
+
int mpv_opengl_cb_uninit_gl(mpv_opengl_cb_context *ctx)
{
- return MPV_ERROR_NOT_IMPLEMENTED;
+ if (ctx->client_api->render_context)
+ mpv_render_context_free(ctx->client_api->render_context);
+ ctx->client_api->render_context = NULL;
+ return 0;
}
+
void mp_client_set_control_callback(struct mpv_opengl_cb_context *ctx,
mpv_opengl_cb_control_fn callback,
void *callback_ctx)
{
+ if (ctx->client_api->render_context) {
+ mp_render_context_set_control_callback(ctx->client_api->render_context,
+ callback, callback_ctx);
+ }
}
+
void mp_client_set_icc_profile(struct mpv_opengl_cb_context *ctx, bstr icc_data)
{
+ if (!ctx->client_api->render_context)
+ return;
+ mpv_render_param param = {MPV_RENDER_PARAM_ICC_PROFILE,
+ &(mpv_byte_array){icc_data.start, icc_data.len}};
+ mpv_render_context_set_parameter(ctx->client_api->render_context, param);
}
+
void mp_client_set_ambient_lux(struct mpv_opengl_cb_context *ctx, int lux)
{
+ if (!ctx->client_api->render_context)
+ return;
+ mpv_render_param param = {MPV_RENDER_PARAM_AMBIENT_LIGHT, &(int){lux}};
+ mpv_render_context_set_parameter(ctx->client_api->render_context, param);
}
-#endif
int mpv_opengl_cb_render(mpv_opengl_cb_context *ctx, int fbo, int vp[4])
{
@@ -1749,16 +1873,12 @@ void *mp_get_sub_api2(mpv_handle *ctx, mpv_sub_api sub_api, bool lock)
if (!ctx->mpctx->initialized)
return NULL;
void *res = NULL;
- if (lock)
- lock_core(ctx);
switch (sub_api) {
case MPV_SUB_API_OPENGL_CB:
res = opengl_cb_get_context(ctx);
break;
default:;
}
- if (lock)
- unlock_core(ctx);
return res;
}
@@ -1767,6 +1887,8 @@ void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api)
return mp_get_sub_api2(ctx, sub_api, true);
}
+// stream_cb
+
struct mp_custom_protocol {
char *protocol;
void *user_data;
diff --git a/player/client.h b/player/client.h
index 042934cde3..58e84418a2 100644
--- a/player/client.h
+++ b/player/client.h
@@ -12,6 +12,7 @@ struct MPContext;
struct mpv_handle;
struct mp_client_api;
struct mp_log;
+struct mpv_global;
// Includes space for \0
#define MAX_CLIENT_NAME 64
@@ -33,6 +34,7 @@ void mp_client_property_change(struct MPContext *mpctx, const char *name);
struct mpv_handle *mp_new_client(struct mp_client_api *clients, const char *name);
struct mp_log *mp_client_get_log(struct mpv_handle *ctx);
+struct mpv_global *mp_client_get_global(struct mpv_handle *ctx);
struct MPContext *mp_client_get_core(struct mpv_handle *ctx);
struct MPContext *mp_client_api_get_core(struct mp_client_api *api);
void *mp_get_sub_api2(mpv_handle *ctx, mpv_sub_api sub_api, bool lock);
@@ -40,19 +42,22 @@ void *mp_get_sub_api2(mpv_handle *ctx, mpv_sub_api sub_api, bool lock);
// m_option.c
void *node_get_alloc(struct mpv_node *node);
-// vo_opengl_cb.c
-struct mpv_opengl_cb_context;
-struct mpv_global;
+// for vo_libmpv.c
struct osd_state;
-struct mpv_opengl_cb_context *mp_opengl_create(struct mpv_global *g,
- struct mp_client_api *client_api);
+struct mpv_render_context;
+bool mp_set_main_render_context(struct mp_client_api *client_api,
+ struct mpv_render_context *ctx, bool active);
+struct mpv_render_context *
+mp_client_api_acquire_render_context(struct mp_client_api *ca);
void kill_video(struct mp_client_api *client_api);
bool mp_streamcb_lookup(struct mpv_global *g, const char *protocol,
void **out_user_data, mpv_stream_cb_open_ro_fn *out_fn);
-typedef int (*mpv_opengl_cb_control_fn)(void *cb_ctx, int *events, uint32_t request, void *data);
-
+// Legacy.
+typedef int (*mpv_opengl_cb_control_fn)(void *cb_ctx, int *events,
+ uint32_t request, void *data);
+struct mpv_opengl_cb_context;
void mp_client_set_control_callback(struct mpv_opengl_cb_context *ctx,
mpv_opengl_cb_control_fn callback,
void *callback_ctx);
diff --git a/player/core.h b/player/core.h
index 27696e8f06..bfc5f662a4 100644
--- a/player/core.h
+++ b/player/core.h
@@ -433,8 +433,6 @@ typedef struct MPContext {
struct mp_ipc_ctx *ipc_ctx;
- struct mpv_opengl_cb_context *gl_cb_ctx;
-
pthread_mutex_t lock;
// --- The following fields are protected by lock
diff --git a/player/main.c b/player/main.c
index 711b413735..243eae605f 100644
--- a/player/main.c
+++ b/player/main.c
@@ -183,9 +183,6 @@ void mp_destroy(struct MPContext *mpctx)
mp_clients_destroy(mpctx);
- talloc_free(mpctx->gl_cb_ctx);
- mpctx->gl_cb_ctx = NULL;
-
osd_free(mpctx->osd);
#if HAVE_COCOA
diff --git a/player/playloop.c b/player/playloop.c
index e77200f2d7..852fc7c199 100644
--- a/player/playloop.c
+++ b/player/playloop.c
@@ -891,7 +891,6 @@ int handle_force_window(struct MPContext *mpctx, bool force)
.input_ctx = mpctx->input,
.osd = mpctx->osd,
.encode_lavc_ctx = mpctx->encode_lavc_ctx,
- .opengl_cb_context = mpctx->gl_cb_ctx,
.wakeup_cb = mp_wakeup_core_cb,
.wakeup_ctx = mpctx,
};
diff --git a/player/video.c b/player/video.c
index e0919a7c5e..e03ada4863 100644
--- a/player/video.c
+++ b/player/video.c
@@ -222,7 +222,6 @@ void reinit_video_chain_src(struct MPContext *mpctx, struct track *track)
.input_ctx = mpctx->input,
.osd = mpctx->osd,
.encode_lavc_ctx = mpctx->encode_lavc_ctx,
- .opengl_cb_context = mpctx->gl_cb_ctx,
.wakeup_cb = mp_wakeup_core_cb,
.wakeup_ctx = mpctx,
};
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 mpv_render_context_report_swap(mpv_render_context *ctx)
{
MP_STATS(ctx, "glcb-reportflip");
@@ -375,12 +320,24 @@ int mpv_opengl_cb_report_flip(mpv_opengl_cb_context *ctx, int64_t time)
ctx->flip_count += 1;
pthread_cond_signal(&ctx->wakeup);
pthread_mutex_unlock(&ctx->lock);
+}
- return 0;
+int mpv_render_context_set_parameter(mpv_render_context *ctx,
+ mpv_render_param param)
+{
+ int err = ctx->renderer->fns->set_parameter(ctx->renderer, param);
+ if (err >= 0) {
+ // Might need to redraw.
+ pthread_mutex_lock(&ctx->lock);
+ if (ctx->vo)
+ update(ctx);
+ pthread_mutex_unlock(&ctx->lock);
+ }
+ return err;
}
// Called locked.
-static void update(struct mpv_opengl_cb_context *ctx)
+static void update(struct mpv_render_context *ctx)
{
if (ctx->update_cb)
ctx->update_cb(ctx->update_cb_ctx);
@@ -410,13 +367,14 @@ static void flip_page(struct vo *vo)
while (p->ctx->next_frame) {
if (pthread_cond_timedwait(&p->ctx->wakeup, &p->ctx->lock, &ts)) {
if (p->ctx->next_frame) {
- MP_VERBOSE(vo, "mpv_opengl_cb_draw() not being called or stuck.\n");
+ MP_VERBOSE(vo, "mpv_render_context_render() not being called "
+ "or stuck.\n");
goto done;
}
}
}
- // Unblock mpv_opengl_cb_draw().
+ // Unblock mpv_render_context_render().
p->ctx->present_count += 1;
pthread_cond_signal(&p->ctx->wakeup);
@@ -425,12 +383,12 @@ static void flip_page(struct vo *vo)
// Wait until frame was presented
while (p->ctx->expected_flip_count > p->ctx->flip_count) {
- // mpv_opengl_cb_report_flip() is declared as optional API.
+ // mpv_render_report_swap() is declared as optional API.
// Assume the user calls it consistently _if_ it's called at all.
if (!p->ctx->flip_count)
break;
if (pthread_cond_timedwait(&p->ctx->wakeup, &p->ctx->lock, &ts)) {
- MP_VERBOSE(vo, "mpv_opengl_cb_report_flip() not being called.\n");
+ MP_VERBOSE(vo, "mpv_render_report_swap() not being called.\n");
goto done;
}
}
@@ -462,22 +420,6 @@ static int query_format(struct vo *vo, int format)
return ok;
}
-// Can only be called from the GL thread
-void mp_client_set_icc_profile(struct mpv_opengl_cb_context *ctx, bstr icc_data)
-{
- gl_video_set_icc_profile(ctx->renderer, icc_data);
- pthread_mutex_lock(&ctx->lock);
- ctx->icc_was_set = true;
- if (ctx->active)
- update(ctx);
- pthread_mutex_unlock(&ctx->lock);
-}
-
-void mp_client_set_ambient_lux(struct mpv_opengl_cb_context *ctx, int lux)
-{
- gl_video_set_ambient_lux(ctx->renderer, lux);
-}
-
static int control(struct vo *vo, uint32_t request, void *data)
{
struct vo_priv *p = vo->priv;
@@ -486,7 +428,8 @@ static int control(struct vo *vo, uint32_t request, void *data)
case VOCTRL_RESET:
pthread_mutex_lock(&p->ctx->lock);
forget_frames(p->ctx, false);
- p->ctx->reset = true;
+ p->ctx->need_reset = true;
+ update(p->ctx);
pthread_mutex_unlock(&p->ctx->lock);
return VO_TRUE;
case VOCTRL_PAUSE:
@@ -497,13 +440,13 @@ static int control(struct vo *vo, uint32_t request, void *data)
return VO_TRUE;
case VOCTRL_SET_PANSCAN:
pthread_mutex_lock(&p->ctx->lock);
- p->ctx->force_update = true;
+ p->ctx->need_resize = true;
update(p->ctx);
pthread_mutex_unlock(&p->ctx->lock);
return VO_TRUE;
case VOCTRL_UPDATE_RENDER_OPTS:
pthread_mutex_lock(&p->ctx->lock);
- p->ctx->update_new_opts = true;
+ p->ctx->need_update_external = true;
update(p->ctx);
pthread_mutex_unlock(&p->ctx->lock);
return VO_TRUE;
@@ -528,7 +471,8 @@ static int reconfig(struct vo *vo, struct mp_image_params *params)
pthread_mutex_lock(&p->ctx->lock);
forget_frames(p->ctx, true);
p->ctx->img_params = *params;
- p->ctx->reconfigured = true;
+ p->ctx->need_reconfig = true;
+ p->ctx->need_resize = true;
pthread_mutex_unlock(&p->ctx->lock);
control(vo, VOCTRL_RECONFIG, NULL);
@@ -543,31 +487,34 @@ static void uninit(struct vo *vo)
pthread_mutex_lock(&p->ctx->lock);
forget_frames(p->ctx, true);
p->ctx->img_params = (struct mp_image_params){0};
- p->ctx->reconfigured = true;
- p->ctx->active = NULL;
+ p->ctx->need_reconfig = true;
+ p->ctx->need_resize = true;
+ p->ctx->need_update_external = true;
+ p->ctx->need_reset = true;
+ p->ctx->vo = NULL;
update(p->ctx);
pthread_mutex_unlock(&p->ctx->lock);
+
+ bool state = atomic_exchange(&p->ctx->in_use, false);
+ assert(state); // obviously must have been set
}
static int preinit(struct vo *vo)
{
struct vo_priv *p = vo->priv;
- p->ctx = vo->extra.opengl_cb_context;
+
+ p->ctx = mp_client_api_acquire_render_context(vo->global->client_api);
+
if (!p->ctx) {
if (!vo->probing)
- MP_FATAL(vo, "No context set.\n");
+ MP_FATAL(vo, "No render context set.\n");
return -1;
}
pthread_mutex_lock(&p->ctx->lock);
- if (!p->ctx->initialized) {
- MP_FATAL(vo, "OpenGL context not initialized.\n");
- pthread_mutex_unlock(&p->ctx->lock);
- return -1;
- }
- p->ctx->active = vo;
- p->ctx->reconfigured = true;
- p->ctx->update_new_opts = true;
+ p->ctx->vo = vo;
+ p->ctx->need_resize = true;
+ p->ctx->need_update_external = true;
pthread_mutex_unlock(&p->ctx->lock);
vo->hwdec_devs = p->ctx->hwdec_devs;
@@ -576,9 +523,9 @@ static int preinit(struct vo *vo)
return 0;
}
-const struct vo_driver video_out_opengl_cb = {
- .description = "OpenGL Callbacks for libmpv",
- .name = "opengl-cb",
+const struct vo_driver video_out_libmpv = {
+ .description = "render API for libmpv",
+ .name = "libmpv",
.caps = VO_CAP_ROTATE90,
.preinit = preinit,
.query_format = query_format,
diff --git a/wscript_build.py b/wscript_build.py
index 7814958fe2..ff99bfc186 100644
--- a/wscript_build.py
+++ b/wscript_build.py
@@ -436,6 +436,7 @@ def build(ctx):
( "video/out/gpu/d3d11_helpers.c", "d3d11 || egl-angle-win32" ),
( "video/out/gpu/hwdec.c" ),
( "video/out/gpu/lcms.c" ),
+ ( "video/out/gpu/libmpv_gpu.c" ),
( "video/out/gpu/osd.c" ),
( "video/out/gpu/ra.c" ),
( "video/out/gpu/spirv.c" ),
@@ -449,6 +450,7 @@ def build(ctx):
( "video/out/opengl/formats.c", "gl" ),
( "video/out/opengl/utils.c", "gl" ),
( "video/out/opengl/ra_gl.c", "gl" ),
+ ( "video/out/opengl/libmpv_gl.c", "gl" ),
( "video/out/opengl/context.c", "gl" ),
( "video/out/opengl/context_angle.c", "egl-angle-win32" ),
( "video/out/opengl/context_cocoa.c", "gl-cocoa" ),
@@ -483,9 +485,9 @@ def build(ctx):
( "video/out/vo_image.c" ),
( "video/out/vo_lavc.c", "encoding" ),
( "video/out/vo_rpi.c", "rpi" ),
+ ( "video/out/vo_libmpv.c" ),
( "video/out/vo_null.c" ),
( "video/out/vo_gpu.c" ),
- ( "video/out/vo_opengl_cb.c", "gl" ),
( "video/out/vo_sdl.c", "sdl2" ),
( "video/out/vo_tct.c" ),
( "video/out/vo_vaapi.c", "vaapi-x11 && gpl" ),