summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--DOCS/man/vo.rst7
-rwxr-xr-xTOOLS/old-configure1
-rw-r--r--video/out/opengl/common.c4
-rw-r--r--video/out/opengl/drm_egl.c435
-rw-r--r--wscript27
-rw-r--r--wscript_build.py1
6 files changed, 467 insertions, 8 deletions
diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst
index 2f8bcb3661..d7074a54e6 100644
--- a/DOCS/man/vo.rst
+++ b/DOCS/man/vo.rst
@@ -778,6 +778,8 @@ Available video output drivers are:
X11/GLX
wayland
Wayland/EGL
+ drm_egl
+ DRM/EGL
x11egl
X11/EGL
@@ -1134,8 +1136,9 @@ Available video output drivers are:
``drm`` (Direct Rendering Manager)
Video output driver using Kernel Mode Setting / Direct Rendering Manager.
- Does not support hardware acceleration. Should be used when one doesn't
- want to install full-blown graphical environment (e.g. no X).
+ Should be used when one doesn't want to install full-blown graphical
+ environment (e.g. no X). Does not support hardware acceleration (if you
+ need this, check ``drm_egl`` backend for ``opengl`` VO).
``connector=<number>``
Select the connector to use (usually this is a monitor.) If set to -1,
diff --git a/TOOLS/old-configure b/TOOLS/old-configure
index a4d3cee0cb..8c2f73aaa9 100755
--- a/TOOLS/old-configure
+++ b/TOOLS/old-configure
@@ -970,6 +970,7 @@ cat > $TMPC << EOF
#define HAVE_RPI_GLES 0
#define HAVE_AV_PIX_FMT_MMAL 0
#define HAVE_DRM 0
+#define HAVE_EGL_DRM 0
#define HAVE_VIDEOTOOLBOX_HWACCEL 0
#define HAVE_VIDEOTOOLBOX_GL 0
#define HAVE_SSE4_INTRINSICS 1
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,
+};
diff --git a/wscript b/wscript
index 470c046b01..572be4b176 100644
--- a/wscript
+++ b/wscript
@@ -244,6 +244,10 @@ iconv support use --disable-iconv.",
'func': check_statement(['sys/vt.h', 'sys/ioctl.h'],
'int m; ioctl(0, VT_GETMODE, &m)'),
}, {
+ 'name': 'gbm.h',
+ 'desc': 'gbm.h',
+ 'func': check_cc(header_name=['stdio.h', 'gbm.h']),
+ }, {
'name': 'glibc-thread-name',
'desc': 'GLIBC API for setting thread name',
'func': check_statement('pthread.h',
@@ -569,6 +573,16 @@ video_output_features = [
'name': '--cocoa',
'desc': 'Cocoa',
'func': check_cocoa
+ }, {
+ 'name': '--drm',
+ 'desc': 'DRM',
+ 'deps': [ 'vt.h' ],
+ 'func': check_pkg_config('libdrm'),
+ }, {
+ 'name': '--gbm',
+ 'desc': 'GBM',
+ 'deps': [ 'gbm.h' ],
+ 'func': check_pkg_config('gbm'),
} , {
'name': '--wayland',
'desc': 'Wayland',
@@ -625,6 +639,12 @@ video_output_features = [
'groups': [ 'gl' ],
'func': check_pkg_config('egl', 'gl'),
} , {
+ 'name': '--egl-drm',
+ 'desc': 'OpenGL DRM EGL Backend',
+ 'deps': [ 'drm', 'gbm' ],
+ 'groups': [ 'gl' ],
+ 'func': check_pkg_config('egl', 'gl'),
+ } , {
'name': '--gl-wayland',
'desc': 'OpenGL Wayland Backend',
'deps': [ 'wayland' ],
@@ -686,11 +706,6 @@ video_output_features = [
'desc': 'CACA',
'func': check_pkg_config('caca', '>= 0.99.beta18'),
}, {
- 'name': '--drm',
- 'desc': 'DRM',
- 'deps': [ 'vt.h' ],
- 'func': check_pkg_config('libdrm'),
- }, {
'name': '--jpeg',
'desc': 'JPEG support',
'func': check_cc(header_name=['stdio.h', 'jpeglib.h'],
@@ -725,7 +740,7 @@ video_output_features = [
} , {
'name': '--gl',
'desc': 'OpenGL video outputs',
- 'deps_any': [ 'gl-cocoa', 'gl-x11', 'gl-win32', 'gl-wayland', 'rpi' ],
+ 'deps_any': [ 'gl-cocoa', 'gl-x11', 'egl-drm', 'gl-win32', 'gl-wayland', 'rpi' ],
'func': check_true
}
]
diff --git a/wscript_build.py b/wscript_build.py
index d33c07db68..4e654eee99 100644
--- a/wscript_build.py
+++ b/wscript_build.py
@@ -338,6 +338,7 @@ def build(ctx):
( "video/out/opengl/wayland.c", "gl-wayland" ),
( "video/out/opengl/x11.c", "gl-x11" ),
( "video/out/opengl/x11egl.c", "egl-x11" ),
+ ( "video/out/opengl/drm_egl.c", "egl-drm" ),
( "video/out/vo.c" ),
( "video/out/vo_caca.c", "caca" ),
( "video/out/vo_drm.c", "drm" ),