/* * 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 . * * You can alternatively redistribute this file 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #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) { if (ctx->vo->probing) { MP_VERBOSE(ctx->vo, "DRM EGL backend can be activated only manually.\n"); return -1; } 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); 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 if (!kms_setup(p->kms, "/dev/dri/card0", -1, 0)) { 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, };