diff options
-rw-r--r-- | DOCS/client-api-changes.rst | 4 | ||||
-rw-r--r-- | DOCS/client_api_examples/qml/main.cpp | 19 | ||||
-rw-r--r-- | DOCS/client_api_examples/qml/main.qml | 39 | ||||
-rw-r--r-- | DOCS/client_api_examples/qml/mpvrenderer.cpp | 125 | ||||
-rw-r--r-- | DOCS/client_api_examples/qml/mpvrenderer.h | 33 | ||||
-rw-r--r-- | DOCS/client_api_examples/qml/mpvtest.pro | 11 | ||||
-rw-r--r-- | DOCS/client_api_examples/qml/mpvtest.qrc | 5 | ||||
-rw-r--r-- | libmpv/client.h | 40 | ||||
-rw-r--r-- | libmpv/mpv.def | 7 | ||||
-rw-r--r-- | libmpv/opengl_cb.h | 199 | ||||
-rw-r--r-- | old-makefile | 1 | ||||
-rw-r--r-- | player/client.c | 57 | ||||
-rw-r--r-- | player/client.h | 7 | ||||
-rw-r--r-- | player/core.h | 2 | ||||
-rw-r--r-- | player/video.c | 3 | ||||
-rw-r--r-- | video/out/gl_common.c | 24 | ||||
-rw-r--r-- | video/out/gl_common.h | 2 | ||||
-rw-r--r-- | video/out/gl_hwdec.c | 15 | ||||
-rw-r--r-- | video/out/gl_hwdec.h | 2 | ||||
-rw-r--r-- | video/out/gl_hwdec_vaglx.c | 4 | ||||
-rw-r--r-- | video/out/gl_hwdec_vdpau.c | 4 | ||||
-rw-r--r-- | video/out/gl_video.c | 29 | ||||
-rw-r--r-- | video/out/gl_video.h | 5 | ||||
-rw-r--r-- | video/out/vo.c | 2 | ||||
-rw-r--r-- | video/out/vo.h | 2 | ||||
-rw-r--r-- | video/out/vo_opengl.c | 4 | ||||
-rw-r--r-- | video/out/vo_opengl_cb.c | 370 | ||||
-rw-r--r-- | wscript_build.py | 3 |
28 files changed, 993 insertions, 25 deletions
diff --git a/DOCS/client-api-changes.rst b/DOCS/client-api-changes.rst index 918d986ee6..69458d0d49 100644 --- a/DOCS/client-api-changes.rst +++ b/DOCS/client-api-changes.rst @@ -25,6 +25,10 @@ API changes :: + 1.11 - add OpenGL rendering interop API - allows an application to combine + its own and mpv's OpenGL rendering + Warning: this API is not stable yet - anything in opengl_cb.h might + be changed in completely incompatible ways in minor API bumps --- mpv 0.7.0 is released --- 1.10 - deprecate/disable everything directly related to script_dispatch (most likely affects nobody) diff --git a/DOCS/client_api_examples/qml/main.cpp b/DOCS/client_api_examples/qml/main.cpp new file mode 100644 index 0000000000..cc86302be6 --- /dev/null +++ b/DOCS/client_api_examples/qml/main.cpp @@ -0,0 +1,19 @@ +#include <QGuiApplication> + +#include <QtQuick/QQuickView> + +#include "mpvrenderer.h" + +int main(int argc, char **argv) +{ + QGuiApplication app(argc, argv); + + qmlRegisterType<MpvObject>("mpvtest", 1, 0, "MpvObject"); + + QQuickView view; + view.setResizeMode(QQuickView::SizeRootObjectToView); + view.setSource(QUrl("qrc:///mpvtest/main.qml")); + view.show(); + + return app.exec(); +} diff --git a/DOCS/client_api_examples/qml/main.qml b/DOCS/client_api_examples/qml/main.qml new file mode 100644 index 0000000000..d921e2a86c --- /dev/null +++ b/DOCS/client_api_examples/qml/main.qml @@ -0,0 +1,39 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0 + +import mpvtest 1.0 + +Item { + width: 1280 + height: 720 + + MpvObject { + id: renderer + anchors.fill: parent + + MouseArea { + anchors.fill: parent + onClicked: renderer.loadfile("../../../test.mkv") + } + } + + Rectangle { + id: labelFrame + anchors.margins: -50 + radius: 5 + color: "white" + border.color: "black" + opacity: 0.8 + anchors.fill: label + } + + Text { + id: label + anchors.bottom: renderer.bottom + anchors.left: renderer.left + anchors.right: renderer.right + anchors.margins: 100 + wrapMode: Text.WordWrap + text: "QtQuick and mpv are both rendering stuff." + } +} diff --git a/DOCS/client_api_examples/qml/mpvrenderer.cpp b/DOCS/client_api_examples/qml/mpvrenderer.cpp new file mode 100644 index 0000000000..af62fd2762 --- /dev/null +++ b/DOCS/client_api_examples/qml/mpvrenderer.cpp @@ -0,0 +1,125 @@ +#include "mpvrenderer.h" + +#include <QObject> +#include <QtGlobal> +#include <QOpenGLContext> + +#include <QtGui/QOpenGLFramebufferObject> + +#include <QtQuick/QQuickWindow> +#include <qsgsimpletexturenode.h> + +class MpvRenderer : public QQuickFramebufferObject::Renderer +{ + static void *get_proc_address(void *ctx, const char *name) { + (void)ctx; + QOpenGLContext *glctx = QOpenGLContext::currentContext(); + if (!glctx) + return NULL; + return (void *)glctx->getProcAddress(QByteArray(name)); + } + + mpv_opengl_cb_context *mpv_gl; + QQuickWindow *window; +public: + MpvRenderer(mpv_opengl_cb_context *a_mpv_gl) + : mpv_gl(a_mpv_gl), window(NULL) + { + int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL); + if (r < 0) + throw "could not initialize OpenGL"; + } + + virtual ~MpvRenderer() + { + mpv_opengl_cb_uninit_gl(mpv_gl); + } + + void render() + { + assert(window); // must have been set by synchronize() + + QOpenGLFramebufferObject *fbo = framebufferObject(); + int vp[4] = {0, 0, fbo->width(), fbo->height()}; + window->resetOpenGLState(); + mpv_opengl_cb_render(mpv_gl, fbo->handle(), vp); + window->resetOpenGLState(); + } + + QOpenGLFramebufferObject *createFramebufferObject(const QSize &size) + { + QOpenGLFramebufferObjectFormat format; + format.setSamples(4); + return new QOpenGLFramebufferObject(size, format); + } + +protected: + virtual void synchronize(QQuickFramebufferObject *item) + { + window = item->window(); + } +}; + +MpvObject::MpvObject(QQuickItem * parent) + : QQuickFramebufferObject(parent) +{ + mpv = mpv_create(); + if (!mpv) + throw "could not create mpv context"; + + mpv_set_option_string(mpv, "terminal", "yes"); + mpv_set_option_string(mpv, "msg-level", "all=v"); + + if (mpv_initialize(mpv) < 0) { + mpv_terminate_destroy(mpv); + throw "could not initialize mpv context"; + } + + // Make use of the MPV_SUB_API_OPENGL_CB API. + mpv::qt::set_option_variant(mpv, "vo", "opengl-cb"); + + // Request hw decoding, just for testing. + mpv::qt::set_option_variant(mpv, "hwdec", "auto"); + + mpv_gl = (mpv_opengl_cb_context *)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB); + if (!mpv_gl) { + mpv_terminate_destroy(mpv); + throw "OpenGL not compiled in"; + } + + mpv_opengl_cb_set_update_callback(mpv_gl, on_update, (void *)this); + + connect(this, &MpvObject::onUpdate, this, &MpvObject::doUpdate, Qt::QueuedConnection); +} + +MpvObject::~MpvObject() +{ + mpv_terminate_destroy(mpv); +} + +void MpvObject::on_update(void *ctx) +{ + MpvObject *self = (MpvObject *)ctx; + emit self->onUpdate(); +} + +// connected to onUpdate(); signal makes sure it runs on the GUI thread +void MpvObject::doUpdate() +{ + update(); +} + +void MpvObject::loadfile(const QString& filename) +{ + QVariantList cmd; + cmd.append("loadfile"); + cmd.append(filename); + mpv::qt::command_variant(mpv, cmd); +} + +QQuickFramebufferObject::Renderer *MpvObject::createRenderer() const +{ + window()->setPersistentOpenGLContext(true); + window()->setPersistentSceneGraph(true); + return new MpvRenderer(mpv_gl); +} diff --git a/DOCS/client_api_examples/qml/mpvrenderer.h b/DOCS/client_api_examples/qml/mpvrenderer.h new file mode 100644 index 0000000000..d0bedf720b --- /dev/null +++ b/DOCS/client_api_examples/qml/mpvrenderer.h @@ -0,0 +1,33 @@ +#ifndef MPVRENDERER_H_ +#define MPVRENDERER_H_ + +#include <assert.h> + +#include <QtQuick/QQuickFramebufferObject> + +#include "libmpv/client.h" +#include "libmpv/opengl_cb.h" +#include "libmpv/qthelper.hpp" + +class MpvObject : public QQuickFramebufferObject +{ + Q_OBJECT + + mpv_handle *mpv; + mpv_opengl_cb_context *mpv_gl; + +public: + MpvObject(QQuickItem * parent = 0); + virtual ~MpvObject(); + Renderer *createRenderer() const; +public slots: + void loadfile(const QString& filename); +signals: + void onUpdate(); +private slots: + void doUpdate(); +private: + static void on_update(void *ctx); +}; + +#endif diff --git a/DOCS/client_api_examples/qml/mpvtest.pro b/DOCS/client_api_examples/qml/mpvtest.pro new file mode 100644 index 0000000000..6681fa1884 --- /dev/null +++ b/DOCS/client_api_examples/qml/mpvtest.pro @@ -0,0 +1,11 @@ +QT += qml quick + +HEADERS += mpvrenderer.h +SOURCES += mpvrenderer.cpp main.cpp + +CONFIG += link_pkgconfig debug +PKGCONFIG = mpv + +RESOURCES += mpvtest.qrc + +OTHER_FILES += main.qml diff --git a/DOCS/client_api_examples/qml/mpvtest.qrc b/DOCS/client_api_examples/qml/mpvtest.qrc new file mode 100644 index 0000000000..bb672657e5 --- /dev/null +++ b/DOCS/client_api_examples/qml/mpvtest.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/mpvtest"> + <file>main.qml</file> + </qresource> +</RCC> diff --git a/libmpv/client.h b/libmpv/client.h index 11b27b26df..9ed2cabd27 100644 --- a/libmpv/client.h +++ b/libmpv/client.h @@ -123,14 +123,15 @@ extern "C" { * -------------------------- * * Currently you have to get the raw window handle, and set it as "wid" option. - * This works on X11 and win32 only. In addition, it works with a few VOs only, - * and VOs which do not support this will just create a freestanding window. + * This works on X11, win32, and OSX only. In addition, it works with a few VOs + * only, and VOs which do not support this will just create a freestanding + * window. * * Both on X11 and win32, the player will fill the window referenced by the * "wid" option fully and letterbox the video (i.e. add black bars if the * aspect ratio of the window and the video mismatch). * - * On OSX, embedding is not yet possible, because Cocoa makes this non-trivial. + * Also see client API examples and the mpv manpage. * * Compatibility * ------------- @@ -164,7 +165,7 @@ extern "C" { * relational operators (<, >, <=, >=). */ #define MPV_MAKE_VERSION(major, minor) (((major) << 16) | (minor) | 0UL) -#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 10) +#define MPV_CLIENT_API_VERSION MPV_MAKE_VERSION(1, 11) /** * Return the MPV_CLIENT_API_VERSION the mpv source has been compiled with. @@ -269,7 +270,16 @@ typedef enum mpv_error { * When trying to load the file, the file format could not be determined, * or the file was too broken to open it. */ - MPV_ERROR_UNKNOWN_FORMAT = -17 + MPV_ERROR_UNKNOWN_FORMAT = -17, + /** + * Generic error for signaling that certain system requirements are not + * fulfilled. + */ + MPV_ERROR_UNSUPPORTED = -18, + /** + * The API function which was called is a stub only. + */ + MPV_ERROR_NOT_IMPLEMENTED = -19 } mpv_error; /** @@ -437,6 +447,9 @@ void mpv_resume(mpv_handle *ctx); * with playback time. For example, playback could go faster or slower due to * playback speed, or due to playback being paused. Use the "time-pos" property * instead to get the playback status. + * + * Unlike other libmpv APIs, this can be called at absolutely any time (even + * within wakeup callbacks), as long as the context is valid. */ int64_t mpv_get_time_us(mpv_handle *ctx); @@ -1426,6 +1439,23 @@ void mpv_set_wakeup_callback(mpv_handle *ctx, void (*cb)(void *d), void *d); */ int mpv_get_wakeup_pipe(mpv_handle *ctx); +typedef enum mpv_sub_api { + /** + * For using mpv's OpenGL renderer on an external OpenGL context. + * mpv_get_sub_api(MPV_SUB_API_OPENGL_CB) returns mpv_opengl_cb_context*. + * 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. + */ + MPV_SUB_API_OPENGL_CB = 1 +} 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. + */ +void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api); + #ifdef __cplusplus } #endif diff --git a/libmpv/mpv.def b/libmpv/mpv.def index e8dc65db58..83ad9b315b 100644 --- a/libmpv/mpv.def +++ b/libmpv/mpv.def @@ -15,11 +15,16 @@ mpv_get_property mpv_get_property_async mpv_get_property_osd_string mpv_get_property_string +mpv_get_sub_api mpv_get_time_us mpv_get_wakeup_pipe mpv_initialize mpv_load_config_file mpv_observe_property +mpv_opengl_cb_init_gl +mpv_opengl_cb_render +mpv_opengl_cb_set_update_callback +mpv_opengl_cb_uninit_gl mpv_request_event mpv_request_log_messages mpv_resume @@ -33,4 +38,4 @@ mpv_suspend mpv_terminate_destroy mpv_unobserve_property mpv_wait_event -mpv_wakeup +mpv_wakeup
\ No newline at end of file diff --git a/libmpv/opengl_cb.h b/libmpv/opengl_cb.h new file mode 100644 index 0000000000..3f5010b26e --- /dev/null +++ b/libmpv/opengl_cb.h @@ -0,0 +1,199 @@ +/* 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. + */ + +/* + * Note: the client API is licensed under ISC (see above) to ease + * interoperability with other licenses. But keep in mind that the + * mpv core is still mostly GPLv2+. It's up to lawyers to decide + * whether applications using this API are affected by the GPL. + * One argument against this is that proprietary applications + * using mplayer in slave mode is apparently tolerated, and this + * API is basically equivalent to slave mode. + */ + +#ifndef MPV_CLIENT_API_OPENGL_CB_H_ +#define MPV_CLIENT_API_OPENGL_CB_H_ + +#include "client.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Warning: this API is not stable yet. + * + * Overview + * -------- + * + * This API can be used to make mpv render into a foreign OpenGL context. It + * can be used to handle video display. Be aware that using this API is not + * required: you can embed the mpv window 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 "wid" option is recommended over + * the OpenGL API, because it's simpler and more flexible on the mpv side. + * + * The renderer needs to be explicitly initialized with mpv_opengl_cb_init_gl(), + * and then video can be drawn with mpv_opengl_cb_render(). The user thread can + * be notified by new frames with mpv_opengl_cb_set_update_callback(). + * + * OpenGL interop + * -------------- + * + * This assumes the OpenGL context lives on a certain thread controlled by the + * API user. The following functions require access to the OpenGL context: + * mpv_opengl_cb_init_gl + * mpv_opengl_cb_render + * mpv_opengl_cb_uninit_gl + * + * The OpenGL context is indirectly accessed through the OpenGL function + * pointers returned by the get_proc_address callback in mpv_opengl_cb_init_gl. + * Generally, mpv will not load the system OpenGL library when using this API. + * + * Only "desktop" OpenGL version 2.1 or later is supported. With OpenGL 2.1, + * the GL_ARB_texture_rg is required. The renderer was written against + * OpenGL 3.x core profile, with additional support for OpenGL 2.1. + * + * Note that some hardware decoding interop API (as set with the "hwdec" option) + * may actually access + * + * 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 current viewport (can have/is set to an arbitrary value) + * + * 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. + * + * Threading + * --------- + * + * The mpv_opengl_cb_* functions can be called from any thread, under the + * following conditions: + * - only one of the mpv_opengl_cb_* functions can be called at the same time + * (unless they belong to different mpv_handles) + * - for functions which need an OpenGL context (see above) the OpenGL context + * must be "current" in the current thread, and it must be the same context + * as used with mpv_opengl_cb_init_gl() + * - never can be called from within the callbacks set with + * mpv_set_wakeup_callback() or mpv_opengl_cb_set_update_callback() + */ + +/** + * Opaque context, returned by mpv_get_sub_api(MPV_SUB_API_OPENGL_CB). + * + * A context is bound to the mpv_handle it was retrieved from. The context + * will always be the same (for the same mpv_handle), and is valid until the + * mpv_handle it belongs to is released. + */ +typedef struct mpv_opengl_cb_context mpv_opengl_cb_context; + +typedef void (*mpv_opengl_cb_update_fn)(void *cb_ctx); +typedef void *(*mpv_opengl_cb_get_proc_address_fn)(void *fn_ctx, const char *name); + +/** + * 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. + * + * @param callback callback(callback_ctx) is called if the frame should be + * redrawn + * @param callback_ctx opaque argument to the callback + */ +void mpv_opengl_cb_set_update_callback(mpv_opengl_cb_context *ctx, + mpv_opengl_cb_update_fn callback, + void *callback_ctx); + +/** + * Initialize the mpv OpenGL state. This retrieves OpenGL function pointers via + * get_proc_address, and creates OpenGL objects needed by mpv internally. It + * will also call APIs needed for rendering hardware decoded video in OpenGL, + * according to the mpv "hwdec" option. + * + * You must free the associated state at some point by calling the + * mpv_opengl_cb_uninit_gl() function. Not doing so may result in memory leaks + * or worse. + * + * @param exts optional _additional_ extension string, can be NULL + * @param get_proc_address callback used to retrieve function pointers to OpenGL + * functions. This is used for both standard functions + * and extension functions. (The extension string is + * checked whether extensions are really available.) + * The callback will be called from this function only + * (it is not stored and never used later). + * @param get_proc_address_ctx arbitrary opaque user context passed to the + * get_proc_address callback + * @return error code (same as normal mpv_* API), including but not limited to: + * MPV_ERROR_UNSUPPORTED: the OpenGL version is not supported + * (or required extensions are missing) + * MPV_ERROR_INVALID_PARAMETER: the OpenGL state was already initialized + */ +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); + +/** + * Render video. Requires that the OpenGL state is initialized. + * + * The video will use the provided viewport rectangle as window size. 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 output rectangle/size + * is changed. (If you want to do animations, it might be better to do the + * animation on a FBO instead.) + * + * @param fbo The framebuffer object to render on. Because the renderer might + * manage multiple FBOs internally for the purpose of video + * postprocessing, it will always bind and unbind FBOs itself. If + * you want mpv to render on the main framebuffer, pass 0. + * @param vp Viewport to render on. The renderer will essentially call: + * glViewport(vp[0], vp[1], vp[2], vp[3]); + * before rendering. The height (vp[3]) can be negative to flip the + * image - the renderer will flip it before setting the viewport + * (typically you want to flip the image if you are rendering + * directly to the main framebuffer). + * @return error code + */ +int mpv_opengl_cb_render(mpv_opengl_cb_context *ctx, int fbo, int vp[4]); + +/** + * Destroy the mpv OpenGL state. + * + * This will trigger undefined behavior (i.e. crash hard) if the hardware + * decoder is still active, because the OpenGL hardware decoding interop state + * can't be destroyed synchronously. If no hardware decoding is active, the + * state can be destroyed at any time. + * + * Calling this multiple times is ok. + * + * @return error code + */ +int mpv_opengl_cb_uninit_gl(mpv_opengl_cb_context *ctx); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/old-makefile b/old-makefile index 69db63983e..a9432ed2c2 100644 --- a/old-makefile +++ b/old-makefile @@ -63,6 +63,7 @@ SOURCES-$(GL) += video/out/gl_common.c video/out/gl_osd.c \ video/out/vo_opengl.c video/out/gl_lcms.c \ video/out/gl_video.c video/out/dither.c \ video/out/gl_hwdec.c \ + video/out/vo_opengl_cb.c \ video/out/vo_opengl_old.c \ video/out/pnm_loader.c diff --git a/player/client.c b/player/client.c index d86217106e..a46a37b10a 100644 --- a/player/client.c +++ b/player/client.c @@ -1511,6 +1511,8 @@ static const char *const err_table[] = { [-MPV_ERROR_VO_INIT_FAILED] = "audio output initialization failed", [-MPV_ERROR_NOTHING_TO_PLAY] = "the file has no audio or video data", [-MPV_ERROR_UNKNOWN_FORMAT] = "unrecognized file format", + [-MPV_ERROR_UNSUPPORTED] = "not supported", + [-MPV_ERROR_NOT_IMPLEMENTED] = "operation not implemented", }; const char *mpv_error_string(int error) @@ -1567,3 +1569,58 @@ int64_t mpv_get_time_us(mpv_handle *ctx) { return mp_time_us(); } + +#include "libmpv/opengl_cb.h" + +#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; + if (!cb) { + cb = mp_opengl_create(ctx->mpctx->global, ctx->mpctx->osd); + ctx->mpctx->gl_cb_ctx = cb; + } + 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) +{ + return MPV_ERROR_NOT_IMPLEMENTED; +} +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; +} +int mpv_opengl_cb_render(mpv_opengl_cb_context *ctx, int fbo, int vp[4]) +{ + return MPV_ERROR_NOT_IMPLEMENTED; +} +int mpv_opengl_cb_uninit_gl(mpv_opengl_cb_context *ctx) +{ + return MPV_ERROR_NOT_IMPLEMENTED; +} +#endif + +void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api) +{ + if (!ctx->mpctx->initialized) + return NULL; + void *res = NULL; + lock_core(ctx); + switch (sub_api) { + case MPV_SUB_API_OPENGL_CB: + res = opengl_cb_get_context(ctx); + break; + default:; + } + unlock_core(ctx); + return res; +} diff --git a/player/client.h b/player/client.h index a275bb9728..4e116b3bca 100644 --- a/player/client.h +++ b/player/client.h @@ -35,4 +35,11 @@ struct MPContext *mp_client_get_core(struct mpv_handle *ctx); // m_option.c void *node_get_alloc(struct mpv_node *node); +// vo_opengl_cb.c +struct mpv_opengl_cb_context; +struct mpv_global; +struct osd_state; +struct mpv_opengl_cb_context *mp_opengl_create(struct mpv_global *g, + struct osd_state *osd); + #endif diff --git a/player/core.h b/player/core.h index 31a2b1eca4..344e55df19 100644 --- a/player/core.h +++ b/player/core.h @@ -339,6 +339,8 @@ typedef struct MPContext { struct mp_nav_state *nav_state; struct mp_ipc_ctx *ipc_ctx; + + struct mpv_opengl_cb_context *gl_cb_ctx; } MPContext; // audio.c diff --git a/player/video.c b/player/video.c index f0829a2c8d..a14b647872 100644 --- a/player/video.c +++ b/player/video.c @@ -275,6 +275,9 @@ int reinit_video_chain(struct MPContext *mpctx) goto err_out; } mpctx->mouse_cursor_visible = true; + + vo_control(mpctx->video_out, VOCTRL_SET_LIBMPV_OPENGL_CB_CONTEXT, + mpctx->gl_cb_ctx); } update_window_title(mpctx, true); diff --git a/video/out/gl_common.c b/video/out/gl_common.c index 1f005934d4..dddb11a53e 100644 --- a/video/out/gl_common.c +++ b/video/out/gl_common.c @@ -474,15 +474,15 @@ static const struct gl_functions gl_functions[] = { // log: used to output messages // Note: if you create a CONTEXT_FORWARD_COMPATIBLE_BIT_ARB with OpenGL 3.0, // you must append "GL_ARB_compatibility" to ext2. -void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *), - const char *ext2, struct mp_log *log) +void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n), + void *fn_ctx, const char *ext2, struct mp_log *log) { talloc_free_children(gl); *gl = (GL) { .extensions = talloc_strdup(gl, ext2 ? ext2 : ""), }; - gl->GetString = getProcAddress ? getProcAddress("glGetString") : NULL; + gl->GetString = get_fn(fn_ctx, "glGetString"); if (!gl->GetString) { mp_err(log, "Can't load OpenGL functions.\n"); return; @@ -508,8 +508,8 @@ void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *), bool has_legacy = false; if (gl->version >= MPGL_VER(3, 0)) { - gl->GetStringi = getProcAddress("glGetStringi"); - gl->GetIntegerv = getProcAddress("glGetIntegerv"); + gl->GetStringi = get_fn(fn_ctx, "glGetStringi"); + gl->GetIntegerv = get_fn(fn_ctx, "glGetIntegerv"); if (!(gl->GetStringi && gl->GetIntegerv)) return; @@ -571,7 +571,7 @@ void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *), const struct gl_function *fn = §ion->functions[i]; void *ptr = NULL; for (int x = 0; fn->funcnames[x]; x++) { - ptr = getProcAddress((const GLubyte *)fn->funcnames[x]); + ptr = get_fn(fn_ctx, fn->funcnames[x]); if (ptr) break; } @@ -620,6 +620,18 @@ void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *), list_features(gl->mpgl_caps, log, MSGL_V, false); } +static void *get_procaddr_wrapper(void *ctx, const char *name) +{ + void *(*getProcAddress)(const GLubyte *) = ctx; + return getProcAddress ? getProcAddress((const GLubyte*)name) : NULL; +} + +void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *), + const char *ext2, struct mp_log *log) +{ + mpgl_load_functions2(gl, get_procaddr_wrapper, getProcAddress, ext2, log); +} + /** * \brief return the number of bytes per pixel for the given format * \param format OpenGL format diff --git a/video/out/gl_common.h b/video/out/gl_common.h index 38d952ae1b..951d2efefb 100644 --- a/video/out/gl_common.h +++ b/video/out/gl_common.h @@ -168,6 +168,8 @@ void mpgl_set_backend_wayland(MPGLContext *ctx); void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *), const char *ext2, struct mp_log *log); +void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n), + void *fn_ctx, const char *ext2, struct mp_log *log); // print a multi line string with line numbers (e.g. for shader sources) // log, lev: module and log level, as in mp_msg() diff --git a/video/out/gl_hwdec.c b/video/out/gl_hwdec.c index 92f0ad095e..3bab1c1e9c 100644 --- a/video/out/gl_hwdec.c +++ b/video/out/gl_hwdec.c @@ -38,18 +38,19 @@ static const struct gl_hwdec_driver *const mpgl_hwdec_drivers[] = { #if HAVE_VAAPI_GLX &gl_hwdec_vaglx, #endif -#if HAVE_VDA_GL - &gl_hwdec_vda, -#endif #if HAVE_VDPAU_GL_X11 &gl_hwdec_vdpau, #endif +#if HAVE_VDA_GL + &gl_hwdec_vda, +#endif NULL }; static struct gl_hwdec *load_hwdec_driver(struct mp_log *log, GL *gl, const struct gl_hwdec_driver *drv, - struct mp_hwdec_info *info) + struct mp_hwdec_info *info, + bool is_auto) { struct gl_hwdec *hwdec = talloc(NULL, struct gl_hwdec); *hwdec = (struct gl_hwdec) { @@ -58,6 +59,7 @@ static struct gl_hwdec *load_hwdec_driver(struct mp_log *log, GL *gl, .gl = gl, .info = info, .gl_texture_target = GL_TEXTURE_2D, + .reject_emulated = is_auto, }; if (hwdec->driver->create(hwdec) < 0) { talloc_free(hwdec); @@ -71,10 +73,11 @@ struct gl_hwdec *gl_hwdec_load_api(struct mp_log *log, GL *gl, const char *api_name, struct mp_hwdec_info *info) { + bool is_auto = api_name && strcmp(api_name, "auto") == 0; for (int n = 0; mpgl_hwdec_drivers[n]; n++) { const struct gl_hwdec_driver *drv = mpgl_hwdec_drivers[n]; - if (api_name && strcmp(drv->api_name, api_name) == 0) { - struct gl_hwdec *r = load_hwdec_driver(log, gl, drv, info); + if (is_auto || (api_name && strcmp(drv->api_name, api_name) == 0)) { + struct gl_hwdec *r = load_hwdec_driver(log, gl, drv, info, is_auto); if (r) return r; } diff --git a/video/out/gl_hwdec.h b/video/out/gl_hwdec.h index 5c70f7fd0d..ea10ac08f8 100644 --- a/video/out/gl_hwdec.h +++ b/video/out/gl_hwdec.h @@ -13,6 +13,8 @@ struct gl_hwdec { struct mp_hwdec_info *info; // For free use by hwdec driver void *priv; + // For working around the vdpau vs. vaapi mess. + bool reject_emulated; // hwdec backends must set this to an IMGFMT_ that has an equivalent // internal representation in gl_video.c as the hardware texture. // It's used to build the rendering chain, and also as screenshot format. diff --git a/video/out/gl_hwdec_vaglx.c b/video/out/gl_hwdec_vaglx.c index d93fa6253e..555e61aec3 100644 --- a/video/out/gl_hwdec_vaglx.c +++ b/video/out/gl_hwdec_vaglx.c @@ -80,6 +80,10 @@ static int create(struct gl_hwdec *hw) vaTerminate(p->display); return -1; } + if (hw->reject_emulated && va_guess_if_emulated(p->ctx)) { + destroy(hw); + return -1; + } hw->info->vaapi_ctx = p->ctx; hw->converted_imgfmt = IMGFMT_RGB0; return 0; diff --git a/video/out/gl_hwdec_vdpau.c b/video/out/gl_hwdec_vdpau.c index 7a68ddd83f..c59d97bc1b 100644 --- a/video/out/gl_hwdec_vdpau.c +++ b/video/out/gl_hwdec_vdpau.c @@ -111,6 +111,10 @@ static int create(struct gl_hwdec *hw) return -1; p->vdp_surface = VDP_INVALID_HANDLE; p->mixer = mp_vdpau_mixer_create(p->ctx, hw->log); + if (hw->reject_emulated && mp_vdpau_guess_if_emulated(p->ctx)) { + destroy(hw); + return -1; + } hw->info->vdpau_ctx = p->ctx; hw->converted_imgfmt = IMGFMT_RGB0; return 0; diff --git a/video/out/gl_video.c b/video/out/gl_video.c index eef5dfc467..9a762118c3 100644 --- a/video/out/gl_video.c +++ b/video/out/gl_video.c @@ -190,6 +190,7 @@ struct gl_video { struct mp_rect dst_rect; // video rectangle on output window struct mp_osd_res osd_rect; // OSD size/margins int vp_x, vp_y, vp_w, vp_h; // GL viewport + bool vp_vflipped; int frames_rendered; @@ -574,7 +575,10 @@ static void update_uniforms(struct gl_video *p, GLuint program) loc = gl->GetUniformLocation(program, "transform"); if (loc >= 0 && p->vp_w > 0 && p->vp_h > 0) { float matrix[3][3]; - matrix_ortho2d(matrix, 0, p->vp_w, p->vp_h, 0); + int vvp[2] = {p->vp_h, 0}; + if (p->vp_vflipped) + MPSWAP(int, vvp[0], vvp[1]); + matrix_ortho2d(matrix, 0, p->vp_w, vvp[0], vvp[1]); gl->UniformMatrix3fv(loc, 1, GL_FALSE, &matrix[0][0]); } @@ -1786,7 +1790,7 @@ static void check_resize(struct gl_video *p) void gl_video_resize(struct gl_video *p, struct mp_rect *window, struct mp_rect *src, struct mp_rect *dst, - struct mp_osd_res *osd) + struct mp_osd_res *osd, bool vflip) { p->src_rect = *src; p->src_rect_rot = *src; @@ -1803,6 +1807,7 @@ void gl_video_resize(struct gl_video *p, struct mp_rect *window, p->vp_w = window->x1 - window->x0; p->vp_h = window->y1 - window->y0; + p->vp_vflipped = vflip; check_resize(p); } @@ -2188,7 +2193,7 @@ static int init_gl(struct gl_video *p) gl->BindBuffer(GL_ARRAY_BUFFER, 0); - gl->ClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl_video_set_gl_state(p); debug_check_gl(p, "after init_gl"); @@ -2214,6 +2219,24 @@ void gl_video_uninit(struct gl_video *p) talloc_free(p); } +void gl_video_set_gl_state(struct gl_video *p) +{ + GL *gl = p->gl; + + gl->ClearColor(0.0f, 0.0f, 0.0f, 1.0f); + gl->ActiveTexture(GL_TEXTURE0); +} + +void gl_video_unset_gl_state(struct gl_video *p) +{ + GL *gl = p->gl; + + gl->PixelStorei(GL_PACK_ROW_LENGTH, 0); + gl->PixelStorei(GL_UNPACK_ROW_LENGTH, 0); + gl->PixelStorei(GL_PACK_ALIGNMENT, 4); + gl->PixelStorei(GL_UNPACK_ALIGNMENT, 4); +} + // dest = src.<w> (always using 4 components) static void packed_fmt_swizzle(char w[5], const struct packed_fmt_entry *fmt) { diff --git a/video/out/gl_video.h b/video/out/gl_video.h index bba24e364a..3d733c8a80 100644 --- a/video/out/gl_video.h +++ b/video/out/gl_video.h @@ -69,7 +69,7 @@ void gl_video_render_frame(struct gl_video *p, int fbo); struct mp_image *gl_video_download_image(struct gl_video *p); void gl_video_resize(struct gl_video *p, struct mp_rect *window, struct mp_rect *src, struct mp_rect *dst, - struct mp_osd_res *osd); + struct mp_osd_res *osd, bool vflip); void gl_video_get_colorspace(struct gl_video *p, struct mp_image_params *params); bool gl_video_set_equalizer(struct gl_video *p, const char *name, int val); bool gl_video_get_equalizer(struct gl_video *p, const char *name, int *val); @@ -77,6 +77,9 @@ bool gl_video_get_equalizer(struct gl_video *p, const char *name, int *val); void gl_video_set_debug(struct gl_video *p, bool enable); void gl_video_resize_redraw(struct gl_video *p, int w, int h); +void gl_video_set_gl_state(struct gl_video *p); +void gl_video_unset_gl_state(struct gl_video *p); + struct gl_hwdec; void gl_video_set_hwdec(struct gl_video *p, struct gl_hwdec *hwdec); diff --git a/video/out/vo.c b/video/out/vo.c index 518841007f..710f2f28ce 100644 --- a/video/out/vo.c +++ b/video/out/vo.c @@ -55,6 +55,7 @@ extern const struct vo_driver video_out_xv; extern const struct vo_driver video_out_opengl; extern const struct vo_driver video_out_opengl_hq; extern const struct vo_driver video_out_opengl_old; +extern const struct vo_driver video_out_opengl_cb; extern const struct vo_driver video_out_null; extern const struct vo_driver video_out_image; extern const struct vo_driver video_out_lavc; @@ -103,6 +104,7 @@ const struct vo_driver *const video_out_drivers[] = #endif #if HAVE_GL &video_out_opengl_hq, + &video_out_opengl_cb, #endif #if HAVE_WAYLAND &video_out_wayland, diff --git a/video/out/vo.h b/video/out/vo.h index f54e2c1a55..8b47d04cfe 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -105,6 +105,8 @@ enum mp_voctrl { VOCTRL_GET_RECENT_FLIP_TIME, // int64_t* (using mp_time_us()) VOCTRL_GET_PREF_DEINT, // int* + + VOCTRL_SET_LIBMPV_OPENGL_CB_CONTEXT,// struct mpv_opengl_cb_context* }; // VOCTRL_SET_EQUALIZER diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c index 2043a4cd3c..567f83be97 100644 --- a/video/out/vo_opengl.c +++ b/video/out/vo_opengl.c @@ -96,7 +96,7 @@ static void resize(struct gl_priv *p) struct mp_osd_res osd; vo_get_src_dst_rects(vo, &src, &dst, &osd); - gl_video_resize(p->renderer, &wnd, &src, &dst, &osd); + gl_video_resize(p->renderer, &wnd, &src, &dst, &osd, false); vo->want_redraw = true; } @@ -447,7 +447,7 @@ err_out: } #define OPT_BASE_STRUCT struct gl_priv -const struct m_option options[] = { +static const struct m_option options[] = { OPT_FLAG("glfinish", use_glFinish, 0), OPT_FLAG("waitvsync", waitvsync, 0), OPT_INT("swapinterval", swap_interval, 0, OPTDEF_INT(1)), diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c new file mode 100644 index 0000000000..09649c3b4b --- /dev/null +++ b/video/out/vo_opengl_cb.c @@ -0,0 +1,370 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <stdbool.h> +#include <limits.h> +#include <pthread.h> +#include <assert.h> + +#include "config.h" + +#include "talloc.h" +#include "common/common.h" +#include "misc/bstr.h" +#include "common/msg.h" +#include "options/m_config.h" +#include "options/options.h" +#include "aspect.h" +#include "vo.h" +#include "video/vfcap.h" +#include "video/mp_image.h" +#include "sub/osd.h" + +#include "common/global.h" +#include "player/client.h" + +#include "gl_common.h" +#include "gl_video.h" +#include "gl_hwdec.h" + +#include "video/decode/lavc.h" // HWDEC_* values + +#include "libmpv/opengl_cb.h" + +/* + * mpv_opengl_cb_context is created by the host application - the host application + * can access it any time, even if the VO is destroyed (or not created yet). + * The OpenGL object allows initializing the renderer etc. The VO object is only + * here to transfer the video frames somehow. + */ + +struct vo_priv { + struct vo *vo; + + struct mpv_opengl_cb_context *ctx; +}; + +struct mpv_opengl_cb_context { + struct mp_log *log; + + pthread_mutex_t lock; + + // --- Protected by lock + mpv_opengl_cb_update_fn update_cb; + void *update_cb_ctx; + struct mp_image *next_frame; + struct mp_image_params img_params; + struct mp_image_params *new_params; + struct mp_rect wnd; + bool flip; + bool force_update; + bool imgfmt_supported[IMGFMT_END - IMGFMT_START]; + struct mp_vo_opts vo_opts; + + // --- All of these can only be accessed from the thread where the host + // application's OpenGL context is current - i.e. only while the + // host application is calling certain mpv_opengl_cb_* APIs. + GL *gl; + struct gl_video *renderer; + struct gl_hwdec *hwdec; + + // --- Immutable or semi-threadsafe. + + struct osd_state *osd; + struct mp_hwdec_info hwdec_info; + const char *hwapi; + + struct vo *active; +}; + +struct mpv_opengl_cb_context *mp_opengl_create(struct mpv_global *g, + struct osd_state *osd) +{ + mpv_opengl_cb_context *ctx = talloc_zero(NULL, mpv_opengl_cb_context); + ctx->log = mp_log_new(ctx, g->log, "opengl-cb"); + pthread_mutex_init(&ctx->lock, NULL); + + ctx->gl = talloc_zero(ctx, GL); + + ctx->osd = osd; + + switch (g->opts->hwdec_api) { + case HWDEC_AUTO: ctx->hwapi = "auto"; break; + case HWDEC_VDPAU: ctx->hwapi = "vdpau"; break; + case HWDEC_VDA: ctx->hwapi = "vda"; break; + case HWDEC_VAAPI: ctx->hwapi = "vaapi"; break; + default: ctx->hwapi = ""; + } + + return ctx; +} + +// To be called from VO thread, with p->ctx->lock held. +static void copy_vo_opts(struct vo *vo) +{ + struct vo_priv *p = vo->priv; + + // We're being lazy: none of the options we need use dynamic data, so + // copy the struct with an assignment. + // Just remove all the dynamic data to avoid confusion. + struct mp_vo_opts opts = *vo->opts; + opts.video_driver_list = opts.vo_defs = NULL; + opts.winname = NULL; + opts.sws_opts = NULL; + p->ctx->vo_opts = opts; +} + +void mpv_opengl_cb_set_update_callback(struct mpv_opengl_cb_context *ctx, + mpv_opengl_cb_update_fn callback, + void *callback_ctx) +{ + pthread_mutex_lock(&ctx->lock); + ctx->update_cb = callback; + ctx->update_cb_ctx = callback_ctx; + pthread_mutex_unlock(&ctx->lock); +} + +int mpv_opengl_cb_init_gl(struct mpv_opengl_cb_context *ctx, const char *exts, + mpv_opengl_cb_get_proc_address_fn get_proc_address, + void *get_proc_address_ctx) +{ + if (ctx->renderer) + return MPV_ERROR_INVALID_PARAMETER; + + mpgl_load_functions2(ctx->gl, get_proc_address, get_proc_address_ctx, + exts, ctx->log); + int caps = MPGL_CAP_GL21 | MPGL_CAP_TEX_RG; + if ((ctx->gl->mpgl_caps & caps) != caps) { + MP_FATAL(ctx, "Missing OpenGL features.\n"); + return MPV_ERROR_UNSUPPORTED; + } + ctx->renderer = gl_video_init(ctx->gl, ctx->log, ctx->osd); + ctx->hwdec = gl_hwdec_load_api(ctx->log, ctx->gl, ctx->hwapi, &ctx->hwdec_info); + gl_video_set_hwdec(ctx->renderer, ctx->hwdec); + + pthread_mutex_lock(&ctx->lock); + for (int n = IMGFMT_START; n < IMGFMT_END; n++) { + ctx->imgfmt_supported[n - IMGFMT_START] = + gl_video_check_format(ctx->renderer, n); + } + pthread_mutex_unlock(&ctx->lock); + + gl_video_unset_gl_state(ctx->renderer); + return 0; +} + +int mpv_opengl_cb_uninit_gl(struct mpv_opengl_cb_context *ctx) +{ + gl_video_uninit(ctx->renderer); + ctx->renderer = NULL; + gl_hwdec_uninit(ctx->hwdec); + ctx->hwdec = NULL; + talloc_free(ctx->gl); + ctx->gl = NULL; + return 0; +} + +int mpv_opengl_cb_render(struct mpv_opengl_cb_context *ctx, int fbo, int vp[4]) +{ + assert(ctx->renderer); + + gl_video_set_gl_state(ctx->renderer); + + pthread_mutex_lock(&ctx->lock); + + struct vo *vo = ctx->active; + + struct mp_image_params *new_params = ctx->new_params; + ctx->new_params = NULL; + if (new_params) { + ctx->img_params = *new_params; + ctx->force_update = true; + } + + int h = vp[3]; + bool flip = h < 0 && h > INT_MIN; + if (flip) + h = -h; + struct mp_rect wnd = {vp[0], vp[1], vp[0] + vp[2], vp[1] + h}; + if (wnd.x0 != ctx->wnd.x0 || wnd.y0 != ctx->wnd.y0 || + wnd.x1 != ctx->wnd.x1 || wnd.y1 != ctx->wnd.y1 || + ctx->flip != flip) + ctx->force_update = true; + + if (ctx->force_update && vo) { + ctx->force_update = false; + ctx->wnd = wnd; + + struct mp_rect src, dst; + struct mp_osd_res osd; + mp_get_src_dst_rects(ctx->log, &ctx->vo_opts, vo->driver->caps, + &ctx->img_params, wnd.x1 - wnd.x0, wnd.y1 - wnd.y0, + 1.0, &src, &dst, &osd); + + gl_video_resize(ctx->renderer, &wnd, &src, &dst, &osd, !ctx->flip); + } + + if (new_params) { + gl_video_config(ctx->renderer, new_params); + talloc_free(new_params); + } + + struct mp_image *mpi = ctx->next_frame; + ctx->next_frame = NULL; + + pthread_mutex_unlock(&ctx->lock); + + if (mpi) + gl_video_upload_image(ctx->renderer, mpi); + + gl_video_render_frame(ctx->renderer, fbo); + + gl_video_unset_gl_state(ctx->renderer); + + return 0; +} + +static void draw_image(struct vo *vo, mp_image_t *mpi) +{ + struct vo_priv *p = vo->priv; + if (p->ctx) { + pthread_mutex_lock(&p->ctx->lock); + mp_image_setrefp(&p->ctx->next_frame, mpi); + pthread_mutex_unlock(&p->ctx->lock); + } + talloc_free(mpi); +} + +static void flip_page(struct vo *vo) +{ + struct vo_priv *p = vo->priv; + if (p->ctx) { + pthread_mutex_lock(&p->ctx->lock); + if (p->ctx->update_cb) + p->ctx->update_cb(p->ctx->update_cb_ctx); + pthread_mutex_unlock(&p->ctx->lock); + } +} + +static int query_format(struct vo *vo, uint32_t format) +{ + struct vo_priv *p = vo->priv; + + bool ok = false; + if (p->ctx) { + pthread_mutex_lock(&p->ctx->lock); + if (format >= IMGFMT_START && format < IMGFMT_END) + ok = p->ctx->imgfmt_supported[format - IMGFMT_START]; + pthread_mutex_unlock(&p->ctx->lock); + } + return ok ? VFCAP_CSP_SUPPORTED | VFCAP_CSP_SUPPORTED_BY_HW : 0; +} + +static int reconfig(struct vo *vo, struct mp_image_params *params, int flags) +{ + struct vo_priv *p = vo->priv; + + if (p->ctx) { + pthread_mutex_lock(&p->ctx->lock); + mp_image_unrefp(&p->ctx->next_frame); + talloc_free(p->ctx->new_params); + p->ctx->new_params = talloc_memdup(NULL, params, sizeof(*params)); + pthread_mutex_unlock(&p->ctx->lock); + } else { + return -1; + } + + return 0; +} + +static int control(struct vo *vo, uint32_t request, void *data) +{ + struct vo_priv *p = vo->priv; + + switch (request) { + case VOCTRL_SET_LIBMPV_OPENGL_CB_CONTEXT: { + if (p->ctx) + return VO_FALSE; + struct mpv_opengl_cb_context *nctx = data; + if (nctx) { + pthread_mutex_lock(&nctx->lock); + if (nctx->active) { + MP_FATAL(vo, "There is already a VO using the OpenGL context.\n"); + } else { + nctx->active = vo; + p->ctx = nctx; + assert(vo->osd == p->ctx->osd); + copy_vo_opts(vo); + } + pthread_mutex_unlock(&nctx->lock); + } + return VO_TRUE; + } + case VOCTRL_GET_PANSCAN: + return VO_TRUE; + case VOCTRL_SET_PANSCAN: + case VOCTRL_REDRAW_FRAME: + pthread_mutex_lock(&p->ctx->lock); + copy_vo_opts(vo); + p->ctx->force_update = true; + if (p->ctx->update_cb) + p->ctx->update_cb(p->ctx->update_cb_ctx); + pthread_mutex_unlock(&p->ctx->lock); + return VO_TRUE; + case VOCTRL_GET_HWDEC_INFO: { + // Warning: in theory, the API user could destroy the OpenGL context + // while the decoder uses the hwdec thing, and bad things would + // happen. Currently, the API user is told not to do this. + struct mp_hwdec_info **arg = data; + *arg = p->ctx ? &p->ctx->hwdec_info : NULL; + return true; + } + } + + return VO_NOTIMPL; +} + +static void uninit(struct vo *vo) +{ + struct vo_priv *p = vo->priv; + + if (p->ctx) { + pthread_mutex_lock(&p->ctx->lock); + mp_image_unrefp(&p->ctx->next_frame); + talloc_free(p->ctx->new_params); + p->ctx->new_params = NULL; + p->ctx->active = NULL; + pthread_mutex_unlock(&p->ctx->lock); + } +} + +static int preinit(struct vo *vo) +{ + struct vo_priv *p = vo->priv; + p->vo = vo; + // Currently, there's no video timing in the API, and it's questionable + // how API users would make use of it too. + vo_set_flip_queue_offset(vo, 0); + return 0; +} + +#define OPT_BASE_STRUCT struct gl_priv +static const struct m_option options[] = { + {0}, +}; + +const struct vo_driver video_out_opengl_cb = { + .description = "OpenGL Callbacks for libmpv", + .name = "opengl-cb", + .caps = VO_CAP_ROTATE90, + .preinit = preinit, + .query_format = query_format, + .reconfig = reconfig, + .control = control, + .draw_image = draw_image, + .flip_page = flip_page, + .uninit = uninit, + .priv_size = sizeof(struct vo_priv), + .options = options, +}; diff --git a/wscript_build.py b/wscript_build.py index c53a1acbb2..e1626a1d5a 100644 --- a/wscript_build.py +++ b/wscript_build.py @@ -354,6 +354,7 @@ def build(ctx): ( "video/out/vo_lavc.c", "encoding" ), ( "video/out/vo_null.c" ), ( "video/out/vo_opengl.c", "gl" ), + ( "video/out/vo_opengl_cb.c", "gl" ), ( "video/out/vo_opengl_old.c", "gl" ), ( "video/out/vo_sdl.c", "sdl2" ), ( "video/out/vo_vaapi.c", "vaapi" ), @@ -494,7 +495,7 @@ def build(ctx): PRIV_LIBS = get_deps(), ) - headers = ["client.h", "qthelper.hpp"] + headers = ["client.h", "qthelper.hpp", "opengl_cb.h"] for f in headers: ctx.install_as(ctx.env.INCDIR + '/mpv/' + f, 'libmpv/' + f) |