mirror of
https://github.com/mpv-player/mpv
synced 2025-01-02 21:12:23 +00:00
52dd38a48a
Hardware decoding things often need access to additional handles from the windowing system, such as the X11 or Wayland display when using vaapi. The opengl-cb had nothing dedicated for this, and used the weird GL_MP_MPGetNativeDisplay GL extension (which was mpv specific and not officially registered with OpenGL). This was awkward, and a pain due to having to emulate GL context behavior (like needing a TLS variable to store context for the pseudo GL extension function). In addition (and not inherently due to this), we could pass only one resource from mpv builtin context backends to hwdecs. It was also all GL specific. Replace this with a newer mechanism. It works for all RA backends, not just GL. the API user can explicitly pass the objects at init time via mpv_render_context_create(). Multiple resources are naturally possible. The API uses MPV_RENDER_PARAM_* defines, but internally we use strings. This is done for 2 reasons: 1. trying to leave libmpv and internal mechanisms decoupled, 2. not having to add public API for some of the internal resource types (especially D3D/GL interop stuff). To remain sane, drop support for obscure half-working opengl-cb things, like the DRM interop (was missing necessary things), the RPI window thing (nobody used it), and obscure D3D interop things (not needed with ANGLE, others were undocumented). In order not to break ABI and the C API, we don't remove the associated structs from opengl_cb.h. The parts which are still needed (in particular DRM interop) needs to be ported to the render API.
331 lines
8.9 KiB
C
331 lines
8.9 KiB
C
/*
|
|
* This file is part of mpv.
|
|
*
|
|
* mpv is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* mpv is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stddef.h>
|
|
#include <assert.h>
|
|
|
|
#include <bcm_host.h>
|
|
|
|
#include <EGL/egl.h>
|
|
#include <EGL/eglext.h>
|
|
|
|
#include "common/common.h"
|
|
#include "osdep/atomic.h"
|
|
#include "video/out/win_state.h"
|
|
#include "context.h"
|
|
#include "egl_helpers.h"
|
|
|
|
struct priv {
|
|
struct GL gl;
|
|
DISPMANX_DISPLAY_HANDLE_T display;
|
|
DISPMANX_ELEMENT_HANDLE_T window;
|
|
DISPMANX_UPDATE_HANDLE_T update;
|
|
EGLDisplay egl_display;
|
|
EGLConfig egl_config;
|
|
EGLContext egl_context;
|
|
EGLSurface egl_surface;
|
|
// yep, the API keeps a pointer to it
|
|
EGL_DISPMANX_WINDOW_T egl_window;
|
|
int x, y, w, h;
|
|
double display_fps;
|
|
atomic_int reload_display;
|
|
int win_params[4];
|
|
};
|
|
|
|
static void tv_callback(void *callback_data, uint32_t reason, uint32_t param1,
|
|
uint32_t param2)
|
|
{
|
|
struct ra_ctx *ctx = callback_data;
|
|
struct priv *p = ctx->priv;
|
|
atomic_store(&p->reload_display, true);
|
|
vo_wakeup(ctx->vo);
|
|
}
|
|
|
|
static void destroy_dispmanx(struct ra_ctx *ctx)
|
|
{
|
|
struct priv *p = ctx->priv;
|
|
|
|
if (p->egl_surface) {
|
|
eglMakeCurrent(p->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
|
|
EGL_NO_CONTEXT);
|
|
eglDestroySurface(p->egl_display, p->egl_surface);
|
|
p->egl_surface = EGL_NO_SURFACE;
|
|
}
|
|
|
|
if (p->window)
|
|
vc_dispmanx_element_remove(p->update, p->window);
|
|
p->window = 0;
|
|
if (p->display)
|
|
vc_dispmanx_display_close(p->display);
|
|
p->display = 0;
|
|
if (p->update)
|
|
vc_dispmanx_update_submit_sync(p->update);
|
|
p->update = 0;
|
|
}
|
|
|
|
static void rpi_uninit(struct ra_ctx *ctx)
|
|
{
|
|
struct priv *p = ctx->priv;
|
|
ra_gl_ctx_uninit(ctx);
|
|
|
|
vc_tv_unregister_callback_full(tv_callback, ctx);
|
|
|
|
destroy_dispmanx(ctx);
|
|
|
|
if (p->egl_context)
|
|
eglDestroyContext(p->egl_display, p->egl_context);
|
|
p->egl_context = EGL_NO_CONTEXT;
|
|
eglReleaseThread();
|
|
p->egl_display = EGL_NO_DISPLAY;
|
|
}
|
|
|
|
static bool recreate_dispmanx(struct ra_ctx *ctx)
|
|
{
|
|
struct priv *p = ctx->priv;
|
|
int display_nr = 0;
|
|
int layer = 0;
|
|
|
|
MP_VERBOSE(ctx, "Recreating DISPMANX state...\n");
|
|
|
|
destroy_dispmanx(ctx);
|
|
|
|
p->display = vc_dispmanx_display_open(display_nr);
|
|
p->update = vc_dispmanx_update_start(0);
|
|
if (!p->display || !p->update) {
|
|
MP_FATAL(ctx, "Could not get DISPMANX objects.\n");
|
|
goto fail;
|
|
}
|
|
|
|
uint32_t dispw, disph;
|
|
if (graphics_get_display_size(0, &dispw, &disph) < 0) {
|
|
MP_FATAL(ctx, "Could not get display size.\n");
|
|
goto fail;
|
|
}
|
|
p->w = dispw;
|
|
p->h = disph;
|
|
|
|
if (ctx->vo->opts->fullscreen) {
|
|
p->x = p->y = 0;
|
|
} else {
|
|
struct vo_win_geometry geo;
|
|
struct mp_rect screenrc = {0, 0, p->w, p->h};
|
|
|
|
vo_calc_window_geometry(ctx->vo, &screenrc, &geo);
|
|
|
|
mp_rect_intersection(&geo.win, &screenrc);
|
|
|
|
p->x = geo.win.x0;
|
|
p->y = geo.win.y0;
|
|
p->w = geo.win.x1 - geo.win.x0;
|
|
p->h = geo.win.y1 - geo.win.y0;
|
|
}
|
|
|
|
// dispmanx is like a neanderthal version of Wayland - you can add an
|
|
// overlay any place on the screen.
|
|
VC_RECT_T dst = {.x = p->x, .y = p->y, .width = p->w, .height = p->h};
|
|
VC_RECT_T src = {.width = p->w << 16, .height = p->h << 16};
|
|
VC_DISPMANX_ALPHA_T alpha = {
|
|
.flags = DISPMANX_FLAGS_ALPHA_FROM_SOURCE,
|
|
.opacity = 0xFF,
|
|
};
|
|
p->window = vc_dispmanx_element_add(p->update, p->display, layer, &dst, 0,
|
|
&src, DISPMANX_PROTECTION_NONE, &alpha,
|
|
0, 0);
|
|
if (!p->window) {
|
|
MP_FATAL(ctx, "Could not add DISPMANX element.\n");
|
|
goto fail;
|
|
}
|
|
|
|
vc_dispmanx_update_submit_sync(p->update);
|
|
p->update = vc_dispmanx_update_start(0);
|
|
|
|
p->egl_window = (EGL_DISPMANX_WINDOW_T){
|
|
.element = p->window,
|
|
.width = p->w,
|
|
.height = p->h,
|
|
};
|
|
p->egl_surface = eglCreateWindowSurface(p->egl_display, p->egl_config,
|
|
&p->egl_window, NULL);
|
|
|
|
if (p->egl_surface == EGL_NO_SURFACE) {
|
|
MP_FATAL(ctx, "Could not create EGL surface!\n");
|
|
goto fail;
|
|
}
|
|
|
|
if (!eglMakeCurrent(p->egl_display, p->egl_surface, p->egl_surface,
|
|
p->egl_context))
|
|
{
|
|
MP_FATAL(ctx, "Failed to set context!\n");
|
|
goto fail;
|
|
}
|
|
|
|
p->display_fps = 0;
|
|
TV_GET_STATE_RESP_T tvstate;
|
|
TV_DISPLAY_STATE_T tvstate_disp;
|
|
if (!vc_tv_get_state(&tvstate) && !vc_tv_get_display_state(&tvstate_disp)) {
|
|
if (tvstate_disp.state & (VC_HDMI_HDMI | VC_HDMI_DVI)) {
|
|
p->display_fps = tvstate_disp.display.hdmi.frame_rate;
|
|
|
|
HDMI_PROPERTY_PARAM_T param = {
|
|
.property = HDMI_PROPERTY_PIXEL_CLOCK_TYPE,
|
|
};
|
|
if (!vc_tv_hdmi_get_property(¶m) &&
|
|
param.param1 == HDMI_PIXEL_CLOCK_TYPE_NTSC)
|
|
p->display_fps = p->display_fps / 1.001;
|
|
} else {
|
|
p->display_fps = tvstate_disp.display.sdtv.frame_rate;
|
|
}
|
|
}
|
|
|
|
p->win_params[0] = display_nr;
|
|
p->win_params[1] = layer;
|
|
p->win_params[2] = p->x;
|
|
p->win_params[3] = p->y;
|
|
|
|
ctx->vo->dwidth = p->w;
|
|
ctx->vo->dheight = p->h;
|
|
if (ctx->swapchain)
|
|
ra_gl_ctx_resize(ctx->swapchain, p->w, p->h, 0);
|
|
|
|
ctx->vo->want_redraw = true;
|
|
|
|
vo_event(ctx->vo, VO_EVENT_WIN_STATE);
|
|
return true;
|
|
|
|
fail:
|
|
destroy_dispmanx(ctx);
|
|
return false;
|
|
}
|
|
|
|
static void rpi_swap_buffers(struct ra_ctx *ctx)
|
|
{
|
|
struct priv *p = ctx->priv;
|
|
eglSwapBuffers(p->egl_display, p->egl_surface);
|
|
}
|
|
|
|
static bool rpi_init(struct ra_ctx *ctx)
|
|
{
|
|
struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
|
|
|
|
bcm_host_init();
|
|
|
|
vc_tv_register_callback(tv_callback, ctx);
|
|
|
|
p->egl_display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
|
if (!eglInitialize(p->egl_display, NULL, NULL)) {
|
|
MP_FATAL(ctx, "EGL failed to initialize.\n");
|
|
goto fail;
|
|
}
|
|
|
|
if (!mpegl_create_context(ctx, p->egl_display, &p->egl_context, &p->egl_config))
|
|
goto fail;
|
|
|
|
if (recreate_dispmanx(ctx) < 0)
|
|
goto fail;
|
|
|
|
mpegl_load_functions(&p->gl, ctx->log);
|
|
|
|
struct ra_gl_ctx_params params = {
|
|
.swap_buffers = rpi_swap_buffers,
|
|
};
|
|
|
|
if (!ra_gl_ctx_init(ctx, &p->gl, params))
|
|
goto fail;
|
|
|
|
ra_add_native_resource(ctx->ra, "MPV_RPI_WINDOW", p->win_params);
|
|
|
|
ra_gl_ctx_resize(ctx->swapchain, ctx->vo->dwidth, ctx->vo->dheight, 0);
|
|
return true;
|
|
|
|
fail:
|
|
rpi_uninit(ctx);
|
|
return false;
|
|
}
|
|
|
|
static bool rpi_reconfig(struct ra_ctx *ctx)
|
|
{
|
|
return recreate_dispmanx(ctx);
|
|
}
|
|
|
|
static struct mp_image *take_screenshot(struct ra_ctx *ctx)
|
|
{
|
|
struct priv *p = ctx->priv;
|
|
|
|
if (!p->display)
|
|
return NULL;
|
|
|
|
struct mp_image *img = mp_image_alloc(IMGFMT_BGR0, p->w, p->h);
|
|
if (!img)
|
|
return NULL;
|
|
|
|
DISPMANX_RESOURCE_HANDLE_T resource =
|
|
vc_dispmanx_resource_create(VC_IMAGE_ARGB8888,
|
|
img->w | ((img->w * 4) << 16), img->h,
|
|
&(int32_t){0});
|
|
if (!resource)
|
|
goto fail;
|
|
|
|
if (vc_dispmanx_snapshot(p->display, resource, 0))
|
|
goto fail;
|
|
|
|
VC_RECT_T rc = {.width = img->w, .height = img->h};
|
|
if (vc_dispmanx_resource_read_data(resource, &rc, img->planes[0], img->stride[0]))
|
|
goto fail;
|
|
|
|
vc_dispmanx_resource_delete(resource);
|
|
return img;
|
|
|
|
fail:
|
|
vc_dispmanx_resource_delete(resource);
|
|
talloc_free(img);
|
|
return NULL;
|
|
}
|
|
|
|
static int rpi_control(struct ra_ctx *ctx, int *events, int request, void *arg)
|
|
{
|
|
struct priv *p = ctx->priv;
|
|
|
|
switch (request) {
|
|
case VOCTRL_SCREENSHOT_WIN:
|
|
*(struct mp_image **)arg = take_screenshot(ctx);
|
|
return VO_TRUE;
|
|
case VOCTRL_FULLSCREEN:
|
|
recreate_dispmanx(ctx);
|
|
return VO_TRUE;
|
|
case VOCTRL_CHECK_EVENTS:
|
|
if (atomic_fetch_and(&p->reload_display, 0)) {
|
|
MP_WARN(ctx, "Recovering from display mode switch...\n");
|
|
recreate_dispmanx(ctx);
|
|
}
|
|
return VO_TRUE;
|
|
case VOCTRL_GET_DISPLAY_FPS:
|
|
*(double *)arg = p->display_fps;
|
|
return VO_TRUE;
|
|
}
|
|
|
|
return VO_NOTIMPL;
|
|
}
|
|
|
|
const struct ra_ctx_fns ra_ctx_rpi = {
|
|
.type = "opengl",
|
|
.name = "rpi",
|
|
.reconfig = rpi_reconfig,
|
|
.control = rpi_control,
|
|
.init = rpi_init,
|
|
.uninit = rpi_uninit,
|
|
};
|