1
0
mirror of https://github.com/mpv-player/mpv synced 2025-01-10 00:49:32 +00:00
mpv/video/out/opengl/context_drm_egl.c
LongChair 9f2970f28a drm/atomic: refactor hwdec_drmprime_drm with native resources
That new API was introduced and allows to have several native resources.
Thisuses that mechanisma for drm resources rather than the deprecated
opengl-cb structs.

This patch therefore add two structs that can be used with the drm atomic interop.
 - mpv_opengl_drm_params : which will hold all the drm handles
 - mpv_opengl_drm_osd_size : which will hold osd layer size

This commit adds a drm-osd-size=WxH parameter to commandline which
allows to define the OSD plane dimension. OSD can be upscaled to
screen resolution when having OSD at video resolution is too heavy.

This is especially useful for UHD modes on embedded devices where
the GPU cannot handle UHD modes at a decent framerate.
2018-05-01 20:48:02 +03:00

640 lines
19 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 <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <poll.h>
#include <time.h>
#include <unistd.h>
#include <gbm.h>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include "libmpv/render_gl.h"
#include "video/out/drm_common.h"
#include "common/common.h"
#include "osdep/timer.h"
#include "egl_helpers.h"
#include "common.h"
#include "context.h"
#define USE_MASTER 0
struct framebuffer
{
int fd;
uint32_t width, height;
uint32_t id;
};
struct gbm
{
struct gbm_surface *surface;
struct gbm_device *device;
struct gbm_bo *bo;
struct gbm_bo *next_bo;
};
struct egl
{
EGLDisplay display;
EGLContext context;
EGLSurface surface;
};
struct priv {
GL gl;
struct kms *kms;
drmEventContext ev;
drmModeCrtc *old_crtc;
struct egl egl;
struct gbm gbm;
struct framebuffer *fb;
uint32_t gbm_format;
bool active;
bool waiting_for_flip;
bool vt_switcher_active;
struct vt_switcher vt_switcher;
struct mpv_opengl_drm_params drm_params;
struct mpv_opengl_drm_osd_size osd_size;
};
// Not general. Limited to only the formats being used in this module
static const char *gbm_format_to_string(uint32_t format)
{
switch (format) {
case GBM_FORMAT_XRGB8888:
return "GBM_FORMAT_XRGB8888";
case GBM_FORMAT_ARGB8888:
return "GBM_FORMAT_ARGB8888";
case GBM_FORMAT_XRGB2101010:
return "GBM_FORMAT_XRGB2101010";
case GBM_FORMAT_ARGB2101010:
return "GBM_FORMAT_ARGB2101010";
default:
return "UNKNOWN";
}
}
// Allow falling back to an ARGB EGLConfig when we have an XRGB framebuffer.
// Also allow falling back to an XRGB EGLConfig for ARGB framebuffers, since
// this seems neccessary to work with broken Mali drivers that don't report
// their EGLConfigs as supporting alpha properly.
static uint32_t fallback_format_for(uint32_t format)
{
switch (format) {
case GBM_FORMAT_XRGB8888:
return GBM_FORMAT_ARGB8888;
case GBM_FORMAT_ARGB8888:
return GBM_FORMAT_XRGB8888;
case GBM_FORMAT_XRGB2101010:
return GBM_FORMAT_ARGB2101010;
case GBM_FORMAT_ARGB2101010:
return GBM_FORMAT_XRGB2101010;
default:
return 0;
}
}
static int match_config_to_visual(void *user_data, EGLConfig *configs, int num_configs)
{
struct ra_ctx *ctx = (struct ra_ctx*)user_data;
struct priv *p = ctx->priv;
const EGLint visual_id[] = {
(EGLint)p->gbm_format,
(EGLint)fallback_format_for(p->gbm_format),
0
};
for (unsigned int i = 0; visual_id[i] != 0; ++i) {
MP_VERBOSE(ctx, "Attempting to find EGLConfig matching %s\n",
gbm_format_to_string(visual_id[i]));
for (unsigned int j = 0; j < num_configs; ++j) {
EGLint id;
if (!eglGetConfigAttrib(p->egl.display, configs[j], EGL_NATIVE_VISUAL_ID, &id))
continue;
if (visual_id[i] == id) {
MP_VERBOSE(ctx, "Found matching EGLConfig for %s\n",
gbm_format_to_string(visual_id[i]));
return j;
}
}
MP_VERBOSE(ctx, "No matching EGLConfig for %s\n", gbm_format_to_string(visual_id[i]));
}
MP_ERR(ctx, "Could not find EGLConfig matching the GBM visual (%s).\n",
gbm_format_to_string(p->gbm_format));
return -1;
}
static bool init_egl(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
MP_VERBOSE(ctx, "Initializing EGL\n");
p->egl.display = eglGetDisplay(p->gbm.device);
if (p->egl.display == EGL_NO_DISPLAY) {
MP_ERR(ctx, "Failed to get EGL display.\n");
return false;
}
if (!eglInitialize(p->egl.display, NULL, NULL)) {
MP_ERR(ctx, "Failed to initialize EGL.\n");
return false;
}
EGLConfig config;
if (!mpegl_create_context_cb(ctx,
p->egl.display,
(struct mpegl_cb){match_config_to_visual, ctx},
&p->egl.context,
&config))
return false;
MP_VERBOSE(ctx, "Initializing EGL surface\n");
p->egl.surface
= eglCreateWindowSurface(p->egl.display, config, p->gbm.surface, NULL);
if (p->egl.surface == EGL_NO_SURFACE) {
MP_ERR(ctx, "Failed to create EGL surface.\n");
return false;
}
return true;
}
static bool init_gbm(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
MP_VERBOSE(ctx->vo, "Creating GBM device\n");
p->gbm.device = gbm_create_device(p->kms->fd);
if (!p->gbm.device) {
MP_ERR(ctx->vo, "Failed to create GBM device.\n");
return false;
}
MP_VERBOSE(ctx->vo, "Initializing GBM surface (%d x %d)\n",
p->osd_size.width, p->osd_size.height);
p->gbm.surface = gbm_surface_create(
p->gbm.device,
p->osd_size.width,
p->osd_size.height,
p->gbm_format,
GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING);
if (!p->gbm.surface) {
MP_ERR(ctx->vo, "Failed to create GBM surface.\n");
return false;
}
return true;
}
static void framebuffer_destroy_callback(struct gbm_bo *bo, void *data)
{
struct framebuffer *fb = data;
if (fb) {
drmModeRmFB(fb->fd, fb->id);
}
}
static void update_framebuffer_from_bo(struct ra_ctx *ctx, struct gbm_bo *bo)
{
struct priv *p = ctx->priv;
struct framebuffer *fb = gbm_bo_get_user_data(bo);
if (fb) {
p->fb = fb;
return;
}
fb = talloc_zero(ctx, struct framebuffer);
fb->fd = p->kms->fd;
fb->width = gbm_bo_get_width(bo);
fb->height = gbm_bo_get_height(bo);
uint32_t stride = gbm_bo_get_stride(bo);
uint32_t handle = gbm_bo_get_handle(bo).u32;
int ret = drmModeAddFB2(fb->fd, fb->width, fb->height,
p->gbm_format,
(uint32_t[4]){handle, 0, 0, 0},
(uint32_t[4]){stride, 0, 0, 0},
(uint32_t[4]){0, 0, 0, 0},
&fb->id, 0);
if (ret) {
MP_ERR(ctx->vo, "Failed to create framebuffer: %s\n", mp_strerror(errno));
}
gbm_bo_set_user_data(bo, fb, framebuffer_destroy_callback);
p->fb = fb;
}
static bool crtc_setup(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
if (p->active)
return true;
p->old_crtc = drmModeGetCrtc(p->kms->fd, p->kms->crtc_id);
int ret = drmModeSetCrtc(p->kms->fd, p->kms->crtc_id, p->fb->id,
0, 0, &p->kms->connector->connector_id, 1,
&p->kms->mode);
p->active = true;
return ret == 0;
}
static void crtc_release(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
if (!p->active)
return;
p->active = false;
// wait for current page flip
while (p->waiting_for_flip) {
int ret = drmHandleEvent(p->kms->fd, &p->ev);
if (ret) {
MP_ERR(ctx->vo, "drmHandleEvent failed: %i\n", ret);
break;
}
}
if (p->old_crtc) {
drmModeSetCrtc(p->kms->fd,
p->old_crtc->crtc_id, p->old_crtc->buffer_id,
p->old_crtc->x, p->old_crtc->y,
&p->kms->connector->connector_id, 1,
&p->old_crtc->mode);
drmModeFreeCrtc(p->old_crtc);
p->old_crtc = NULL;
}
}
static void release_vt(void *data)
{
struct ra_ctx *ctx = data;
MP_VERBOSE(ctx->vo, "Releasing VT\n");
crtc_release(ctx);
if (USE_MASTER) {
//this function enables support for switching to x, weston etc.
//however, for whatever reason, it can be called only by root users.
//until things change, this is commented.
struct priv *p = ctx->priv;
if (drmDropMaster(p->kms->fd)) {
MP_WARN(ctx->vo, "Failed to drop DRM master: %s\n",
mp_strerror(errno));
}
}
}
static void acquire_vt(void *data)
{
struct ra_ctx *ctx = data;
MP_VERBOSE(ctx->vo, "Acquiring VT\n");
if (USE_MASTER) {
struct priv *p = ctx->priv;
if (drmSetMaster(p->kms->fd)) {
MP_WARN(ctx->vo, "Failed to acquire DRM master: %s\n",
mp_strerror(errno));
}
}
crtc_setup(ctx);
}
static bool drm_atomic_egl_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo)
{
struct priv *p = sw->ctx->priv;
if (p->kms->atomic_context) {
if (!p->kms->atomic_context->request) {
p->kms->atomic_context->request = drmModeAtomicAlloc();
p->drm_params.atomic_request_ptr = &p->kms->atomic_context->request;
}
return ra_gl_ctx_start_frame(sw, out_fbo);
}
return false;
}
static const struct ra_swapchain_fns drm_atomic_swapchain = {
.start_frame = drm_atomic_egl_start_frame,
};
static void drm_egl_swap_buffers(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
struct drm_atomic_context *atomic_ctx = p->kms->atomic_context;
int ret;
if (!p->active)
return;
eglSwapBuffers(p->egl.display, p->egl.surface);
p->gbm.next_bo = gbm_surface_lock_front_buffer(p->gbm.surface);
p->waiting_for_flip = true;
update_framebuffer_from_bo(ctx, p->gbm.next_bo);
if (atomic_ctx) {
drm_object_set_property(atomic_ctx->request, atomic_ctx->primary_plane, "FB_ID", p->fb->id);
drm_object_set_property(atomic_ctx->request, atomic_ctx->primary_plane, "CRTC_ID", atomic_ctx->crtc->id);
drm_object_set_property(atomic_ctx->request, atomic_ctx->primary_plane, "ZPOS", 1);
ret = drmModeAtomicCommit(p->kms->fd, atomic_ctx->request,
DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, NULL);
if (ret)
MP_WARN(ctx->vo, "Failed to commit atomic request (%d)\n", ret);
} else {
ret = drmModePageFlip(p->kms->fd, p->kms->crtc_id, p->fb->id,
DRM_MODE_PAGE_FLIP_EVENT, p);
if (ret) {
MP_WARN(ctx->vo, "Failed to queue page flip: %s\n", mp_strerror(errno));
}
}
// poll page flip finish event
const int timeout_ms = 3000;
struct pollfd fds[1] = { { .events = POLLIN, .fd = p->kms->fd } };
poll(fds, 1, timeout_ms);
if (fds[0].revents & POLLIN) {
ret = drmHandleEvent(p->kms->fd, &p->ev);
if (ret != 0) {
MP_ERR(ctx->vo, "drmHandleEvent failed: %i\n", ret);
p->waiting_for_flip = false;
return;
}
}
p->waiting_for_flip = false;
if (atomic_ctx) {
drmModeAtomicFree(atomic_ctx->request);
atomic_ctx->request = drmModeAtomicAlloc();
}
gbm_surface_release_buffer(p->gbm.surface, p->gbm.bo);
p->gbm.bo = p->gbm.next_bo;
}
static void drm_egl_uninit(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
struct drm_atomic_context *atomic_ctx = p->kms->atomic_context;
if (atomic_ctx) {
int ret = drmModeAtomicCommit(p->kms->fd, atomic_ctx->request, 0, NULL);
if (ret)
MP_ERR(ctx->vo, "Failed to commit atomic request (%d)\n", ret);
drmModeAtomicFree(atomic_ctx->request);
}
ra_gl_ctx_uninit(ctx);
crtc_release(ctx);
if (p->vt_switcher_active)
vt_switcher_destroy(&p->vt_switcher);
eglMakeCurrent(p->egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
eglDestroyContext(p->egl.display, p->egl.context);
eglDestroySurface(p->egl.display, p->egl.surface);
gbm_surface_destroy(p->gbm.surface);
eglTerminate(p->egl.display);
gbm_device_destroy(p->gbm.device);
p->egl.context = EGL_NO_CONTEXT;
eglDestroyContext(p->egl.display, p->egl.context);
if (p->kms) {
kms_destroy(p->kms);
p->kms = 0;
}
}
// If primary plane supports ARGB we want to use that, but if it doesn't we fall
// back on XRGB. If the driver does not support atomic there is no particular
// reason to be using ARGB (drmprime hwdec will not work without atomic,
// anyway), so we fall back to XRGB (another reason is that we do not have the
// convenient atomic_ctx and its convenient primary_plane field).
static bool probe_gbm_format(struct ra_ctx *ctx, uint32_t argb_format, uint32_t xrgb_format)
{
struct priv *p = ctx->priv;
if (!p->kms->atomic_context) {
p->gbm_format = xrgb_format;
MP_VERBOSE(ctx->vo, "Not using DRM Atomic: Use %s for primary plane.\n",
gbm_format_to_string(xrgb_format));
return true;
}
drmModePlane *drmplane =
drmModeGetPlane(p->kms->fd, p->kms->atomic_context->primary_plane->id);
bool have_argb = false;
bool have_xrgb = false;
bool result = false;
for (unsigned int i = 0; i < drmplane->count_formats; ++i) {
if (drmplane->formats[i] == argb_format) {
have_argb = true;
} else if (drmplane->formats[i] == xrgb_format) {
have_xrgb = true;
}
}
if (have_argb) {
p->gbm_format = argb_format;
MP_VERBOSE(ctx->vo, "%s supported by primary plane.\n", gbm_format_to_string(argb_format));
result = true;
} else if (have_xrgb) {
p->gbm_format = xrgb_format;
MP_VERBOSE(ctx->vo, "%s not supported by primary plane: Falling back to %s.\n",
gbm_format_to_string(argb_format), gbm_format_to_string(xrgb_format));
result = true;
}
drmModeFreePlane(drmplane);
return result;
}
static bool drm_egl_init(struct ra_ctx *ctx)
{
if (ctx->opts.probing) {
MP_VERBOSE(ctx, "DRM EGL backend can be activated only manually.\n");
return false;
}
struct priv *p = ctx->priv = talloc_zero(ctx, struct priv);
p->ev.version = DRM_EVENT_CONTEXT_VERSION;
p->vt_switcher_active = vt_switcher_init(&p->vt_switcher, ctx->vo->log);
if (p->vt_switcher_active) {
vt_switcher_acquire(&p->vt_switcher, acquire_vt, ctx);
vt_switcher_release(&p->vt_switcher, release_vt, ctx);
} else {
MP_WARN(ctx, "Failed to set up VT switcher. Terminal switching will be unavailable.\n");
}
MP_VERBOSE(ctx, "Initializing KMS\n");
p->kms = kms_create(ctx->log, ctx->vo->opts->drm_opts->drm_connector_spec,
ctx->vo->opts->drm_opts->drm_mode_id,
ctx->vo->opts->drm_opts->drm_overlay_id);
if (!p->kms) {
MP_ERR(ctx, "Failed to create KMS.\n");
return false;
}
if (ctx->vo->opts->drm_opts->drm_osd_size.wh_valid) {
if (p->kms->atomic_context) {
p->osd_size.width = ctx->vo->opts->drm_opts->drm_osd_size.w;
p->osd_size.height = ctx->vo->opts->drm_opts->drm_osd_size.h;
} else {
p->osd_size.width = p->kms->mode.hdisplay;
p->osd_size.height = p->kms->mode.vdisplay;
MP_WARN(ctx, "Setting OSD size is only available with DRM atomic, defaulting to screen resolution\n");
}
} else {
p->osd_size.width = p->kms->mode.hdisplay;
p->osd_size.height = p->kms->mode.vdisplay;
}
uint32_t argb_format;
uint32_t xrgb_format;
if (DRM_OPTS_FORMAT_XRGB2101010 == ctx->vo->opts->drm_opts->drm_format) {
argb_format = GBM_FORMAT_ARGB2101010;
xrgb_format = GBM_FORMAT_XRGB2101010;
} else {
argb_format = GBM_FORMAT_ARGB8888;
xrgb_format = GBM_FORMAT_XRGB8888;
}
if (!probe_gbm_format(ctx, argb_format, xrgb_format)) {
MP_ERR(ctx->vo, "No suitable format found on DRM primary plane (tried: %s and %s).\n",
gbm_format_to_string(argb_format), gbm_format_to_string(xrgb_format));
return false;
}
if (!init_gbm(ctx)) {
MP_ERR(ctx->vo, "Failed to setup GBM.\n");
return false;
}
if (!init_egl(ctx)) {
MP_ERR(ctx->vo, "Failed to setup EGL.\n");
return false;
}
if (!eglMakeCurrent(p->egl.display, p->egl.surface, p->egl.surface,
p->egl.context)) {
MP_ERR(ctx->vo, "Failed to make context current.\n");
return false;
}
mpegl_load_functions(&p->gl, ctx->vo->log);
// required by gbm_surface_lock_front_buffer
eglSwapBuffers(p->egl.display, p->egl.surface);
MP_VERBOSE(ctx, "Preparing framebuffer\n");
p->gbm.bo = gbm_surface_lock_front_buffer(p->gbm.surface);
if (!p->gbm.bo) {
MP_ERR(ctx, "Failed to lock GBM surface.\n");
return false;
}
update_framebuffer_from_bo(ctx, p->gbm.bo);
if (!p->fb || !p->fb->id) {
MP_ERR(ctx, "Failed to create framebuffer.\n");
return false;
}
if (!crtc_setup(ctx)) {
MP_ERR(ctx, "Failed to set CRTC for connector %u: %s\n",
p->kms->connector->connector_id, mp_strerror(errno));
return false;
}
p->drm_params.fd = p->kms->fd;
p->drm_params.connector_id = p->kms->connector->connector_id;
p->drm_params.crtc_id = p->kms->crtc_id;
if (p->kms->atomic_context)
p->drm_params.atomic_request_ptr = &p->kms->atomic_context->request;
struct ra_gl_ctx_params params = {
.swap_buffers = drm_egl_swap_buffers,
.external_swapchain = p->kms->atomic_context ? &drm_atomic_swapchain :
NULL,
};
if (!ra_gl_ctx_init(ctx, &p->gl, params))
return false;
ra_add_native_resource(ctx->ra, "drm_params", &p->drm_params);
ra_add_native_resource(ctx->ra, "drm_osd_size", &p->osd_size);
return true;
}
static bool drm_egl_reconfig(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
ctx->vo->dwidth = p->fb->width;
ctx->vo->dheight = p->fb->height;
ra_gl_ctx_resize(ctx->swapchain, p->fb->width, p->fb->height, 0);
return true;
}
static int drm_egl_control(struct ra_ctx *ctx, int *events, int request,
void *arg)
{
struct priv *p = ctx->priv;
switch (request) {
case VOCTRL_GET_DISPLAY_FPS: {
double fps = kms_get_display_fps(p->kms);
if (fps <= 0)
break;
*(double*)arg = fps;
return VO_TRUE;
}
}
return VO_NOTIMPL;
}
static void wait_events(struct ra_ctx *ctx, int64_t until_time_us)
{
struct priv *p = ctx->priv;
if (p->vt_switcher_active) {
int64_t wait_us = until_time_us - mp_time_us();
int timeout_ms = MPCLAMP((wait_us + 500) / 1000, 0, 10000);
vt_switcher_poll(&p->vt_switcher, timeout_ms);
} else {
vo_wait_default(ctx->vo, until_time_us);
}
}
static void wakeup(struct ra_ctx *ctx)
{
struct priv *p = ctx->priv;
if (p->vt_switcher_active)
vt_switcher_interrupt_poll(&p->vt_switcher);
}
const struct ra_ctx_fns ra_ctx_drm_egl = {
.type = "opengl",
.name = "drm",
.reconfig = drm_egl_reconfig,
.control = drm_egl_control,
.init = drm_egl_init,
.uninit = drm_egl_uninit,
.wait_events = wait_events,
.wakeup = wakeup,
};