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=`` 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=`` 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 ````.) + (See ````.) - 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 #include #include +#include #include #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); } -#include "libmpv/opengl_cb.h" +// 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 +#include +#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 similarity index 50% rename from video/out/vo_opengl_cb.c rename to 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); -static void forget_frames(struct mpv_opengl_cb_context *ctx, bool all) +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_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) -{ - 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); - - reset_gl_state(ctx->gl); - return 0; -} - -int mpv_opengl_cb_uninit_gl(struct mpv_opengl_cb_context *ctx) +void mpv_render_context_free(mpv_render_context *ctx) { if (!ctx) - return 0; + return; - // Bring down the decoder etc., which still might be using the hwdec - // context. Setting initialized=false guarantees it can't come back. + // 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); - 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) + // 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); - pthread_mutex_lock(&ctx->lock); - assert(!ctx->active); - pthread_mutex_unlock(&ctx->lock); + assert(!atomic_load(&ctx->in_use)); + assert(!ctx->vo); - 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; + forget_frames(ctx, true); + + ctx->renderer->fns->destroy(ctx->renderer); + talloc_free(ctx->renderer->priv); + talloc_free(ctx->renderer); + + pthread_cond_destroy(&ctx->wakeup); + pthread_mutex_destroy(&ctx->lock); + pthread_mutex_destroy(&ctx->control_lock); + + 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); + bool prev = false; + return atomic_compare_exchange_strong(&ctx->in_use, &prev, true); +} - 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; - } - - 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; + struct vo *vo = ctx->vo; - ctx->force_update |= ctx->reconfigured; - - 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->reset) { - gl_video_reset(ctx->renderer); - ctx->reset = false; + if (ctx->need_update_external) + ctx->renderer->fns->update_external(ctx->renderer, vo); + ctx->need_update_external = 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" ),