client API: expose OpenGL renderer

This adds API to libmpv that lets host applications use the mpv opengl
renderer. This is a more flexible (and possibly more portable) option to
foreign window embedding (via --wid).

This assumes that methods like context sharing and multithreaded OpenGL
rendering are infeasible, and that a way is needed to integrate it with
an application that uses a single thread to render everything.

Add an example that does this with QtQuick/qml. The example is
relatively lazy, but still shows how relatively simple the integration
is. The FBO indirection could probably be avoided, but would require
more work (and would probably lead to worse QtQuick integration, because
it would have to ignore transformations like rotation).

Because this makes mpv directly use the host application's OpenGL
context, there is no platform specific code involved in mpv, except
for hw decoding interop.

main.qml is derived from some Qt example.

The following things are still missing:
- a way to do better video timing
- expose GL renderer options, allow changing them at runtime
- support for color equalizer controls
- support for screenshots
This commit is contained in:
wm4 2014-12-09 17:47:02 +01:00
parent d38bc531cc
commit fb855b8659
28 changed files with 993 additions and 25 deletions

View File

@ -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)

View File

@ -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();
}

View File

@ -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."
}
}

View File

@ -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);
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/mpvtest">
<file>main.qml</file>
</qresource>
</RCC>

View File

@ -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

View File

@ -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

199
libmpv/opengl_cb.h Normal file
View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -474,15 +474,15 @@ static const struct gl_functions gl_functions[] = {
// log: used to output messages
// Note: if you create a CONTEXT_FORWARD_COMPATIBLE_BIT_ARB with OpenGL 3.0,
// you must append "GL_ARB_compatibility" to ext2.
void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *),
const char *ext2, struct mp_log *log)
void mpgl_load_functions2(GL *gl, void *(*get_fn)(void *ctx, const char *n),
void *fn_ctx, const char *ext2, struct mp_log *log)
{
talloc_free_children(gl);
*gl = (GL) {
.extensions = talloc_strdup(gl, ext2 ? ext2 : ""),
};
gl->GetString = getProcAddress ? getProcAddress("glGetString") : NULL;
gl->GetString = get_fn(fn_ctx, "glGetString");
if (!gl->GetString) {
mp_err(log, "Can't load OpenGL functions.\n");
return;
@ -508,8 +508,8 @@ void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *),
bool has_legacy = false;
if (gl->version >= MPGL_VER(3, 0)) {
gl->GetStringi = getProcAddress("glGetStringi");
gl->GetIntegerv = getProcAddress("glGetIntegerv");
gl->GetStringi = get_fn(fn_ctx, "glGetStringi");
gl->GetIntegerv = get_fn(fn_ctx, "glGetIntegerv");
if (!(gl->GetStringi && gl->GetIntegerv))
return;
@ -571,7 +571,7 @@ void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *),
const struct gl_function *fn = &section->functions[i];
void *ptr = NULL;
for (int x = 0; fn->funcnames[x]; x++) {
ptr = getProcAddress((const GLubyte *)fn->funcnames[x]);
ptr = get_fn(fn_ctx, fn->funcnames[x]);
if (ptr)
break;
}
@ -620,6 +620,18 @@ void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *),
list_features(gl->mpgl_caps, log, MSGL_V, false);
}
static void *get_procaddr_wrapper(void *ctx, const char *name)
{
void *(*getProcAddress)(const GLubyte *) = ctx;
return getProcAddress ? getProcAddress((const GLubyte*)name) : NULL;
}
void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *),
const char *ext2, struct mp_log *log)
{
mpgl_load_functions2(gl, get_procaddr_wrapper, getProcAddress, ext2, log);
}
/**
* \brief return the number of bytes per pixel for the given format
* \param format OpenGL format

View File

@ -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()

View File

@ -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;
}

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -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)
{

View File

@ -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);

View File

@ -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,

View File

@ -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

View File

@ -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)),

370
video/out/vo_opengl_cb.c Normal file
View File

@ -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,
};

View File

@ -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)