summaryrefslogtreecommitdiffstats
path: root/video
diff options
context:
space:
mode:
authorrr- <mkurczew@gmail.com>2015-11-07 19:06:57 +0100
committerwm4 <wm4@nowhere>2015-11-08 15:00:15 +0100
commitc3f2ef5491dc28d1f2f68d024b4be01c27b029e7 (patch)
tree9dcab5a7b2cbc594c35860fde5d91683f415a1f8 /video
parent67caea357c23443cf583ad401a38bbaae19e3df8 (diff)
downloadmpv-c3f2ef5491dc28d1f2f68d024b4be01c27b029e7.tar.bz2
mpv-c3f2ef5491dc28d1f2f68d024b4be01c27b029e7.tar.xz
vo_opengl: add DRM EGL backend
Notes: - Unfortunately the only way to talk to EGL from within DRM I could find involves linking with GBM (generic buffer management for Mesa.) Because of this, I'm pretty sure it won't work with proprietary NVidia drivers, but then again, last time I checked NVidia didn't offer proper screen resolution for VT. - VT switching doesn't seem to work at all. It's worth mentioning that using vo_drm before introduction of VT switcher had an anomaly where user could switch to another VT and input text to it, while video played on top of that VT. However, that isn't the case with drm_egl: I can't switch to other VT during playback like this. This makes me think that it's either a limitation coming from my firmware or from EGL/KMS itself rather than a bug with my code. Nonetheless, I still left (untestable) VT switching code in place, in case it's useful to someone else. - The mode_id, connector_id and device_path should be configurable for power users and people who wish to watch videos on nonprimary screen. Unfortunately I didn't see anything that would allow OpenGL backends to register their own set of options. At the same time, adding them to global namespace is pointless. - A few dozens of lines could be shared with vo_drm (setting up VT switching, most of code behind page flipping). I don't have any strong opinion on this. - Sometimes I get minor visual glitches. I'm not sure if there's a race condition of some sort, unitialized variable (doubtful), or if it's buggy driver. (I'm using integrated Intel HD Graphics 4400 with Mesa) - .config and .control are very minimal. Signed-off-by: wm4 <wm4@nowhere>
Diffstat (limited to 'video')
-rw-r--r--video/out/opengl/common.c4
-rw-r--r--video/out/opengl/drm_egl.c435
2 files changed, 439 insertions, 0 deletions
diff --git a/video/out/opengl/common.c b/video/out/opengl/common.c
index 34cf909cc5..afb732fa9e 100644
--- a/video/out/opengl/common.c
+++ b/video/out/opengl/common.c
@@ -507,6 +507,7 @@ void mpgl_load_functions(GL *gl, void *(*getProcAddress)(const GLubyte *),
extern const struct mpgl_driver mpgl_driver_x11;
extern const struct mpgl_driver mpgl_driver_x11egl;
+extern const struct mpgl_driver mpgl_driver_drm_egl;
extern const struct mpgl_driver mpgl_driver_cocoa;
extern const struct mpgl_driver mpgl_driver_wayland;
extern const struct mpgl_driver mpgl_driver_w32;
@@ -528,6 +529,9 @@ static const struct mpgl_driver *const backends[] = {
#if HAVE_EGL_X11
&mpgl_driver_x11egl,
#endif
+#if HAVE_EGL_DRM
+ &mpgl_driver_drm_egl,
+#endif
#if HAVE_GL_X11
&mpgl_driver_x11,
#endif
diff --git a/video/out/opengl/drm_egl.c b/video/out/opengl/drm_egl.c
new file mode 100644
index 0000000000..696785d975
--- /dev/null
+++ b/video/out/opengl/drm_egl.c
@@ -0,0 +1,435 @@
+/*
+ * OpenGL video output driver for libdrm
+ *
+ * by rr- <rr-@sakuya.pl>
+ *
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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 <sys/poll.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <gbm.h>
+#include <EGL/egl.h>
+#include <EGL/eglext.h>
+#include <GL/gl.h>
+
+#include "common.h"
+#include "common/common.h"
+#include "video/out/drm_common.h"
+
+#define USE_MASTER 0
+
+struct framebuffer
+{
+ struct gbm_bo *bo;
+ int width, height;
+ int fd;
+ int 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 {
+ struct kms *kms;
+
+ drmEventContext ev;
+ drmModeCrtc *old_crtc;
+
+ struct egl egl;
+ struct gbm gbm;
+ struct framebuffer fb;
+
+ bool active;
+ bool waiting_for_flip;
+
+ bool vt_switcher_active;
+ struct vt_switcher vt_switcher;
+};
+
+static EGLConfig select_fb_config_egl(struct MPGLContext *ctx, bool es)
+{
+ struct priv *p = ctx->priv;
+ const EGLint attributes[] = {
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 0,
+ EGL_DEPTH_SIZE, 1,
+ EGL_RENDERABLE_TYPE, es ? EGL_OPENGL_ES2_BIT : EGL_OPENGL_BIT,
+ EGL_NONE
+ };
+ EGLint config_count;
+ EGLConfig config;
+ if (!eglChooseConfig(p->egl.display, attributes, &config, 1, &config_count)) {
+ MP_FATAL(ctx->vo, "Failed to configure EGL.\n");
+ return NULL;
+ }
+ if (!config_count) {
+ MP_FATAL(ctx->vo, "Could not find EGL configuration!\n");
+ return NULL;
+ }
+ return config;
+}
+
+static bool init_egl(struct MPGLContext *ctx, bool es)
+{
+ struct priv *p = ctx->priv;
+ MP_VERBOSE(ctx->vo, "Initializing EGL\n");
+ p->egl.display = eglGetDisplay(p->gbm.device);
+ if (p->egl.display == EGL_NO_DISPLAY) {
+ MP_ERR(ctx->vo, "Failed to get EGL display.\n");
+ return false;
+ }
+ if (!eglInitialize(p->egl.display, NULL, NULL)) {
+ MP_ERR(ctx->vo, "Failed to initialize EGL.\n");
+ return false;
+ }
+ if (!eglBindAPI(es ? EGL_OPENGL_ES_API : EGL_OPENGL_API)) {
+ MP_ERR(ctx->vo, "Failed to set EGL API version.\n");
+ return false;
+ }
+ EGLConfig config = select_fb_config_egl(ctx, es);
+ if (!config) {
+ MP_ERR(ctx->vo, "Failed to configure EGL.\n");
+ return false;
+ }
+ p->egl.context = eglCreateContext(p->egl.display, config, EGL_NO_CONTEXT, NULL);
+ if (!p->egl.context) {
+ MP_ERR(ctx->vo, "Failed to create EGL context.\n");
+ return false;
+ }
+ MP_VERBOSE(ctx->vo, "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->vo, "Failed to create EGL surface.\n");
+ return false;
+ }
+ return true;
+}
+
+static bool init_gbm(struct MPGLContext *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->kms->mode.hdisplay, p->kms->mode.vdisplay);
+ p->gbm.surface = gbm_surface_create(
+ p->gbm.device,
+ p->kms->mode.hdisplay,
+ p->kms->mode.vdisplay,
+ GBM_BO_FORMAT_XRGB8888,
+ 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(
+ const struct MPGLContext *ctx, struct gbm_bo *bo)
+{
+ struct priv *p = ctx->priv;
+ p->fb.bo = bo;
+ p->fb.fd = p->kms->fd;
+ p->fb.width = gbm_bo_get_width(bo);
+ p->fb.height = gbm_bo_get_height(bo);
+ int stride = gbm_bo_get_stride(bo);
+ int handle = gbm_bo_get_handle(bo).u32;
+
+ int ret = drmModeAddFB(p->kms->fd, p->fb.width, p->fb.height,
+ 24, 32, stride, handle, &p->fb.id);
+ if (ret) {
+ MP_ERR(ctx->vo, "Failed to create framebuffer: %s\n", mp_strerror(errno));
+ }
+ gbm_bo_set_user_data(bo, &p->fb, framebuffer_destroy_callback);
+}
+
+static void page_flipped(int fd, unsigned int frame, unsigned int sec,
+ unsigned int usec, void *data)
+{
+ struct priv *p = data;
+ p->waiting_for_flip = false;
+}
+
+static bool crtc_setup(struct MPGLContext *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 MPGLContext *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 MPGLContext *ctx = data;
+ MP_VERBOSE(ctx->vo, "Releasing VT");
+ 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 MPGLContext *ctx = data;
+ MP_VERBOSE(ctx->vo, "Acquiring VT");
+ 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 void drm_egl_uninit(MPGLContext *ctx)
+{
+ struct priv *p = ctx->priv;
+ 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;
+ }
+}
+
+static int drm_egl_init(struct MPGLContext *ctx, int flags)
+{
+ struct priv *p = ctx->priv;
+ p->kms = NULL;
+ p->old_crtc = NULL;
+ p->gbm.surface = NULL;
+ p->gbm.device = NULL;
+ p->active = false;
+ p->waiting_for_flip = false;
+ p->ev.version = DRM_EVENT_CONTEXT_VERSION;
+ p->ev.page_flip_handler = page_flipped;
+
+ p->vt_switcher_active = vt_switcher_init(&p->vt_switcher, ctx->vo->log) == 0;
+ 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->vo, "Failed to set up VT switcher. Terminal switching will be unavailable.\n");
+ }
+
+ MP_VERBOSE(ctx->vo, "Initializing KMS\n");
+ p->kms = kms_create(ctx->vo->log);
+ if (!p->kms) {
+ MP_ERR(ctx->vo, "Failed to create KMS.\n");
+ return -1;
+ }
+
+ // TODO: arguments should be configurable
+ int ret = kms_setup(p->kms, "/dev/dri/card0", -1, 0);
+ if (ret) {
+ MP_ERR(ctx->vo, "Failed to configure KMS.\n");
+ return -1;
+ }
+
+ if (!init_gbm(ctx)) {
+ MP_ERR(ctx->vo, "Failed to setup GBM.\n");
+ return -1;
+ }
+
+ if (!init_egl(ctx, flags & VOFLAG_GLES)) {
+ MP_ERR(ctx->vo, "Failed to setup EGL.\n");
+ return -1;
+ }
+
+ 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 -1;
+ }
+
+ const char *egl_exts = eglQueryString(p->egl.display, EGL_EXTENSIONS);
+ void *(*gpa)(const GLubyte*) = (void *(*)(const GLubyte*))eglGetProcAddress;
+ mpgl_load_functions(ctx->gl, gpa, egl_exts, ctx->vo->log);
+
+ // required by gbm_surface_lock_front_buffer
+ eglSwapBuffers(p->egl.display, p->egl.surface);
+
+ MP_VERBOSE(ctx->vo, "Preparing framebuffer\n");
+ p->gbm.bo = gbm_surface_lock_front_buffer(p->gbm.surface);
+ if (!p->gbm.bo) {
+ MP_ERR(ctx->vo, "Failed to lock GBM surface.\n");
+ return -1;
+ }
+ update_framebuffer_from_bo(ctx, p->gbm.bo);
+ if (!p->fb.id) {
+ MP_ERR(ctx->vo, "Failed to create framebuffer.\n");
+ return -1;
+ }
+
+ if (!crtc_setup(ctx)) {
+ MP_ERR(
+ ctx->vo,
+ "Failed to set CRTC for connector %u: %s\n",
+ p->kms->connector->connector_id,
+ mp_strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int drm_egl_reconfig(struct MPGLContext *ctx)
+{
+ struct priv *p = ctx->priv;
+ ctx->vo->dwidth = p->fb.width;
+ ctx->vo->dheight = p->fb.height;
+ return 0;
+}
+
+static int drm_egl_control(struct MPGLContext *ctx, int *events, int request,
+ void *arg)
+{
+ return VO_NOTIMPL;
+}
+
+static void drm_egl_swap_buffers(MPGLContext *ctx)
+{
+ struct priv *p = ctx->priv;
+ 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);
+ int 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);
+ return;
+ }
+ }
+
+ gbm_surface_release_buffer(p->gbm.surface, p->gbm.bo);
+ p->gbm.bo = p->gbm.next_bo;
+}
+
+const struct mpgl_driver mpgl_driver_drm_egl = {
+ .name = "drm_egl",
+ .priv_size = sizeof(struct priv),
+ .init = drm_egl_init,
+ .reconfig = drm_egl_reconfig,
+ .swap_buffers = drm_egl_swap_buffers,
+ .control = drm_egl_control,
+ .uninit = drm_egl_uninit,
+};