aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-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" ),