From 92a6f2d687e90452c9080a2e0b471ced7557518f Mon Sep 17 00:00:00 2001 From: Dudemanguy Date: Wed, 4 Jan 2023 21:34:26 -0600 Subject: [PATCH] drm: rewrite based around vo_drm_state A longstanding pain point of the drm VOs is the relative lack of state sharing. While drm_common does provide some sharing, it's far less than other platforms like x11 or wayland. What we do here is essentially copy them by creating a new vo_drm_state struct and using it in vo_drm and context_drm_egl. Much of the functionality that was essentially duplicated in both VOs/contexts is now reduced simple functions in drm_common. The usage of the term 'kms' was also mostly eliminated since this is libdrm nowadays from a userspace perspective. --- options/options.c | 11 +- options/options.h | 4 +- video/drmprime.c | 2 +- video/out/drm_atomic.c | 10 +- video/out/drm_atomic.h | 9 +- video/out/drm_common.c | 1391 +++++++++++++--------- video/out/drm_common.h | 115 +- video/out/hwdec/hwdec_drmprime.c | 2 +- video/out/hwdec/hwdec_drmprime_overlay.c | 5 +- video/out/opengl/context_drm_egl.c | 429 ++----- video/out/vo.h | 1 + video/out/vo_drm.c | 475 +++----- 12 files changed, 1116 insertions(+), 1338 deletions(-) diff --git a/options/options.c b/options/options.c index b4cccd3348..dbd2a3f9bd 100644 --- a/options/options.c +++ b/options/options.c @@ -48,10 +48,6 @@ #include "stream/stream.h" #include "demux/demux.h" -#if HAVE_DRM -#include "video/out/drm_common.h" -#endif - static void print_version(struct mp_log *log) { mp_print_version(log, true); @@ -187,9 +183,6 @@ static const m_option_t mp_vo_opt_list[] = { #if HAVE_WIN32_DESKTOP {"vo-mmcss-profile", OPT_STRING(mmcss_profile)}, #endif -#if HAVE_DRM - {"", OPT_SUBSTRUCT(drm_opts, drm_conf)}, -#endif #if HAVE_EGL_ANDROID {"android-surface-size", OPT_SIZE_BOX(android_surface_size)}, #endif @@ -829,6 +822,10 @@ static const m_option_t mp_opts[] = { {"", OPT_SUBSTRUCT(macos_opts, macos_conf)}, #endif +#if HAVE_DRM + {"", OPT_SUBSTRUCT(drm_opts, drm_conf)}, +#endif + #if HAVE_WAYLAND {"", OPT_SUBSTRUCT(wayland_opts, wayland_conf)}, #endif diff --git a/options/options.h b/options/options.h index 5560f40554..86e95dc826 100644 --- a/options/options.h +++ b/options/options.h @@ -66,9 +66,6 @@ typedef struct mp_vo_opts { double timing_offset; int video_sync; - // vo_drm - struct drm_opts *drm_opts; - struct m_geometry android_surface_size; int swapchain_depth; // max number of images to render ahead @@ -360,6 +357,7 @@ typedef struct MPOpts { struct d3d11va_opts *d3d11va_opts; struct cocoa_opts *cocoa_opts; struct macos_opts *macos_opts; + struct drm_opts *drm_opts; struct wayland_opts *wayland_opts; struct dvd_opts *dvd_opts; struct vaapi_opts *vaapi_opts; diff --git a/video/drmprime.c b/video/drmprime.c index b8fda3b726..c7bd11a8fa 100644 --- a/video/drmprime.c +++ b/video/drmprime.c @@ -29,7 +29,7 @@ static struct AVBufferRef *drm_create_standalone(struct mpv_global *global, { void *tmp = talloc_new(NULL); struct drm_opts *drm_opts = mp_get_config_group(tmp, global, &drm_conf); - const char *opt_path = drm_opts->drm_device_path; + const char *opt_path = drm_opts->device_path; const char *device_path = opt_path ? opt_path : "/dev/dri/renderD128"; AVBufferRef* ref = NULL; diff --git a/video/out/drm_atomic.c b/video/out/drm_atomic.c index dfd27c6f52..5754504e98 100644 --- a/video/out/drm_atomic.c +++ b/video/out/drm_atomic.c @@ -107,14 +107,14 @@ int drm_object_set_property(drmModeAtomicReq *request, struct drm_object *object return -EINVAL; } -struct drm_object * drm_object_create(struct mp_log *log, int fd, - uint32_t object_id, uint32_t type) +struct drm_object *drm_object_create(struct mp_log *log, int fd, + uint32_t object_id, uint32_t type) { struct drm_object *obj = NULL; obj = talloc_zero(NULL, struct drm_object); + obj->fd = fd; obj->id = object_id; obj->type = type; - obj->fd = fd; if (drm_object_create_properties(log, fd, obj)) { talloc_free(obj); @@ -195,7 +195,6 @@ struct drm_atomic_context *drm_atomic_create_context(struct mp_log *log, int fd, if (connector->connector_id == connector_id) ctx->connector = drm_object_create(log, ctx->fd, connector->connector_id, DRM_MODE_OBJECT_CONNECTOR); - drmModeFreeConnector(connector); if (ctx->connector) break; @@ -211,8 +210,7 @@ struct drm_atomic_context *drm_atomic_create_context(struct mp_log *log, int fd, drmplane = NULL; if (possible_crtcs & (1 << crtc_index)) { - plane = drm_object_create(log, ctx->fd, plane_id, - DRM_MODE_OBJECT_PLANE); + plane = drm_object_create(log, ctx->fd, plane_id, DRM_MODE_OBJECT_PLANE); if (!plane) { mp_err(log, "Failed to create Plane object from plane ID %d\n", diff --git a/video/out/drm_atomic.h b/video/out/drm_atomic.h index 32e56c7f97..499aa33319 100644 --- a/video/out/drm_atomic.h +++ b/video/out/drm_atomic.h @@ -24,15 +24,11 @@ #include #include "common/msg.h" +#include "drm_common.h" #define DRM_OPTS_PRIMARY_PLANE -1 #define DRM_OPTS_OVERLAY_PLANE -2 -struct drm_mode { - drmModeModeInfo mode; - uint32_t blob_id; -}; - struct drm_atomic_plane_state { uint64_t fb_id; uint64_t crtc_id; @@ -83,13 +79,12 @@ struct drm_atomic_context { struct drm_atomic_state old_state; }; - int drm_object_create_properties(struct mp_log *log, int fd, struct drm_object *object); void drm_object_free_properties(struct drm_object *object); int drm_object_get_property(struct drm_object *object, char *name, uint64_t *value); int drm_object_set_property(drmModeAtomicReq *request, struct drm_object *object, char *name, uint64_t value); drmModePropertyBlobPtr drm_object_get_property_blob(struct drm_object *object, char *name); -struct drm_object * drm_object_create(struct mp_log *log, int fd, uint32_t object_id, uint32_t type); +struct drm_object *drm_object_create(struct mp_log *log, int fd, uint32_t object_id, uint32_t type); void drm_object_free(struct drm_object *object); void drm_object_print_info(struct mp_log *log, struct drm_object *object); struct drm_atomic_context *drm_atomic_create_context(struct mp_log *log, int fd, int crtc_id, int connector_id, diff --git a/video/out/drm_common.c b/video/out/drm_common.c index 7845edb734..09494d6317 100644 --- a/video/out/drm_common.c +++ b/video/out/drm_common.c @@ -35,10 +35,12 @@ #include #endif +#include "drm_atomic.h" #include "drm_common.h" #include "common/common.h" #include "common/msg.h" +#include "options/m_config.h" #include "osdep/io.h" #include "osdep/timer.h" #include "misc/ctype.h" @@ -55,38 +57,36 @@ static int vt_switcher_pipe[2]; -static int drm_connector_opt_help( - struct mp_log *log, const struct m_option *opt, struct bstr name); +static int drm_connector_opt_help(struct mp_log *log, const struct m_option *opt, + struct bstr name); -static int drm_mode_opt_help( - struct mp_log *log, const struct m_option *opt, struct bstr name); +static int drm_mode_opt_help(struct mp_log *log, const struct m_option *opt, + struct bstr name); -static int drm_validate_mode_opt( - struct mp_log *log, const struct m_option *opt, struct bstr name, - const char **value); +static int drm_validate_mode_opt(struct mp_log *log, const struct m_option *opt, + struct bstr name, const char **value); -static void kms_show_available_modes( - struct mp_log *log, const drmModeConnector *connector); +static void drm_show_available_modes(struct mp_log *log, const drmModeConnector *connector); -static void kms_show_available_connectors(struct mp_log *log, int card_no, +static void drm_show_available_connectors(struct mp_log *log, int card_no, const char *card_path); static double mode_get_Hz(const drmModeModeInfo *mode); #define OPT_BASE_STRUCT struct drm_opts const struct m_sub_options drm_conf = { .opts = (const struct m_option[]) { - {"drm-device", OPT_STRING(drm_device_path), .flags = M_OPT_FILE}, - {"drm-connector", OPT_STRING(drm_connector_spec), + {"drm-device", OPT_STRING(device_path), .flags = M_OPT_FILE}, + {"drm-connector", OPT_STRING(connector_spec), .help = drm_connector_opt_help}, - {"drm-mode", OPT_STRING_VALIDATE(drm_mode_spec, drm_validate_mode_opt), + {"drm-mode", OPT_STRING_VALIDATE(mode_spec, drm_validate_mode_opt), .help = drm_mode_opt_help}, {"drm-atomic", OPT_CHOICE(drm_atomic, {"no", 0}, {"auto", 1}), .deprecation_message = "this option is deprecated: DRM Atomic is required"}, - {"drm-draw-plane", OPT_CHOICE(drm_draw_plane, + {"drm-draw-plane", OPT_CHOICE(draw_plane, {"primary", DRM_OPTS_PRIMARY_PLANE}, {"overlay", DRM_OPTS_OVERLAY_PLANE}), M_RANGE(0, INT_MAX)}, - {"drm-drmprime-video-plane", OPT_CHOICE(drm_drmprime_video_plane, + {"drm-drmprime-video-plane", OPT_CHOICE(drmprime_video_plane, {"primary", DRM_OPTS_PRIMARY_PLANE}, {"overlay", DRM_OPTS_OVERLAY_PLANE}), M_RANGE(0, INT_MAX)}, @@ -95,21 +95,21 @@ const struct m_sub_options drm_conf = { {"xrgb2101010", DRM_OPTS_FORMAT_XRGB2101010}, {"xbgr8888", DRM_OPTS_FORMAT_XBGR8888}, {"xbgr2101010", DRM_OPTS_FORMAT_XBGR2101010})}, - {"drm-draw-surface-size", OPT_SIZE_BOX(drm_draw_surface_size)}, + {"drm-draw-surface-size", OPT_SIZE_BOX(draw_surface_size)}, + {"drm-vrr-enabled", OPT_CHOICE(vrr_enabled, + {"no", 0}, {"yes", 1}, {"auto", -1})}, {"drm-osd-plane-id", OPT_REPLACED("drm-draw-plane")}, {"drm-video-plane-id", OPT_REPLACED("drm-drmprime-video-plane")}, {"drm-osd-size", OPT_REPLACED("drm-draw-surface-size")}, - {"drm-vrr-enabled", OPT_CHOICE(drm_vrr_enabled, - {"no", 0}, {"yes", 1}, {"auto", -1})}, {0}, }, .defaults = &(const struct drm_opts) { - .drm_mode_spec = "preferred", + .mode_spec = "preferred", .drm_atomic = 1, - .drm_draw_plane = DRM_OPTS_PRIMARY_PLANE, - .drm_drmprime_video_plane = DRM_OPTS_OVERLAY_PLANE, - .drm_vrr_enabled = 0, + .draw_plane = DRM_OPTS_PRIMARY_PLANE, + .drmprime_video_plane = DRM_OPTS_OVERLAY_PLANE, + .vrr_enabled = 0, }, .size = sizeof(struct drm_opts), }; @@ -151,8 +151,299 @@ struct drm_mode_spec { double refresh; }; -// KMS ------------------------------------------------------------------------ +/* VT Switcher */ +static void vt_switcher_sighandler(int sig) +{ + unsigned char event = sig == RELEASE_SIGNAL ? EVT_RELEASE : EVT_ACQUIRE; + (void)write(vt_switcher_pipe[1], &event, sizeof(event)); +} +static bool has_signal_installed(int signo) +{ + struct sigaction act = { 0 }; + sigaction(signo, 0, &act); + return act.sa_handler != 0; +} + +static int install_signal(int signo, void (*handler)(int)) +{ + struct sigaction act = { 0 }; + act.sa_handler = handler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART; + return sigaction(signo, &act, NULL); +} + +static void release_vt(void *data) +{ + struct vo_drm_state *drm = data; + MP_VERBOSE(drm, "Releasing VT\n"); + vo_drm_release_crtc(drm); +} + +static void acquire_vt(void *data) +{ + struct vo_drm_state *drm = data; + MP_VERBOSE(drm, "Acquiring VT\n"); + vo_drm_acquire_crtc(drm); +} + +static void vt_switcher_acquire(struct vt_switcher *s, + void (*handler)(void*), void *user_data) +{ + s->handlers[HANDLER_ACQUIRE] = handler; + s->handler_data[HANDLER_ACQUIRE] = user_data; +} + +static void vt_switcher_release(struct vt_switcher *s, + void (*handler)(void*), void *user_data) +{ + s->handlers[HANDLER_RELEASE] = handler; + s->handler_data[HANDLER_RELEASE] = user_data; +} + +static bool vt_switcher_init(struct vt_switcher *s, struct mp_log *log) +{ + s->tty_fd = -1; + s->log = log; + vt_switcher_pipe[0] = -1; + vt_switcher_pipe[1] = -1; + + if (mp_make_cloexec_pipe(vt_switcher_pipe)) { + mp_err(log, "Creating pipe failed: %s\n", mp_strerror(errno)); + return false; + } + + s->tty_fd = open("/dev/tty", O_RDWR | O_CLOEXEC); + if (s->tty_fd < 0) { + mp_err(log, "Can't open TTY for VT control: %s\n", mp_strerror(errno)); + return false; + } + + if (has_signal_installed(RELEASE_SIGNAL)) { + mp_err(log, "Can't handle VT release - signal already used\n"); + return false; + } + if (has_signal_installed(ACQUIRE_SIGNAL)) { + mp_err(log, "Can't handle VT acquire - signal already used\n"); + return false; + } + + if (install_signal(RELEASE_SIGNAL, vt_switcher_sighandler)) { + mp_err(log, "Failed to install release signal: %s\n", mp_strerror(errno)); + return false; + } + if (install_signal(ACQUIRE_SIGNAL, vt_switcher_sighandler)) { + mp_err(log, "Failed to install acquire signal: %s\n", mp_strerror(errno)); + return false; + } + + struct vt_mode vt_mode = { 0 }; + if (ioctl(s->tty_fd, VT_GETMODE, &vt_mode) < 0) { + mp_err(log, "VT_GETMODE failed: %s\n", mp_strerror(errno)); + return false; + } + + vt_mode.mode = VT_PROCESS; + vt_mode.relsig = RELEASE_SIGNAL; + vt_mode.acqsig = ACQUIRE_SIGNAL; + // frsig is a signal for forced release. Not implemented on Linux, + // Solaris, BSDs but must be set to a valid signal on some of those. + vt_mode.frsig = SIGIO; // unused + if (ioctl(s->tty_fd, VT_SETMODE, &vt_mode) < 0) { + mp_err(log, "VT_SETMODE failed: %s\n", mp_strerror(errno)); + return false; + } + + // Block the VT switching signals from interrupting the VO thread (they will + // still be picked up by other threads, which will fill vt_switcher_pipe for us) + sigset_t set; + sigemptyset(&set); + sigaddset(&set, RELEASE_SIGNAL); + sigaddset(&set, ACQUIRE_SIGNAL); + pthread_sigmask(SIG_BLOCK, &set, NULL); + + return true; +} + +static void vt_switcher_interrupt_poll(struct vt_switcher *s) +{ + unsigned char event = EVT_INTERRUPT; + (void)write(vt_switcher_pipe[1], &event, sizeof(event)); +} + +static void vt_switcher_destroy(struct vt_switcher *s) +{ + struct vt_mode vt_mode = {0}; + vt_mode.mode = VT_AUTO; + if (ioctl(s->tty_fd, VT_SETMODE, &vt_mode) < 0) { + MP_ERR(s, "VT_SETMODE failed: %s\n", mp_strerror(errno)); + return; + } + + install_signal(RELEASE_SIGNAL, SIG_DFL); + install_signal(ACQUIRE_SIGNAL, SIG_DFL); + close(s->tty_fd); + close(vt_switcher_pipe[0]); + close(vt_switcher_pipe[1]); +} + +static void vt_switcher_poll(struct vt_switcher *s, int timeout_ms) +{ + struct pollfd fds[1] = { + { .events = POLLIN, .fd = vt_switcher_pipe[0] }, + }; + poll(fds, 1, timeout_ms); + if (!fds[0].revents) + return; + + unsigned char event; + if (read(fds[0].fd, &event, sizeof(event)) != sizeof(event)) + return; + + switch (event) { + case EVT_RELEASE: + s->handlers[HANDLER_RELEASE](s->handler_data[HANDLER_RELEASE]); + if (ioctl(s->tty_fd, VT_RELDISP, 1) < 0) { + MP_ERR(s, "Failed to release virtual terminal\n"); + } + break; + case EVT_ACQUIRE: + s->handlers[HANDLER_ACQUIRE](s->handler_data[HANDLER_ACQUIRE]); + if (ioctl(s->tty_fd, VT_RELDISP, VT_ACKACQ) < 0) { + MP_ERR(s, "Failed to acquire virtual terminal\n"); + } + break; + case EVT_INTERRUPT: + break; + } +} + +bool vo_drm_acquire_crtc(struct vo_drm_state *drm) +{ + if (drm->active) + return true; + drm->active = true; + + if (drmSetMaster(drm->fd)) { + MP_WARN(drm, "Failed to acquire DRM master: %s\n", + mp_strerror(errno)); + } + + struct drm_atomic_context *atomic_ctx = drm->atomic_context; + + if (!drm_atomic_save_old_state(atomic_ctx)) + MP_WARN(drm, "Failed to save old DRM atomic state\n"); + + drmModeAtomicReqPtr request = drmModeAtomicAlloc(); + if (!request) { + MP_ERR(drm, "Failed to allocate drm atomic request\n"); + goto err; + } + + if (drm_object_set_property(request, atomic_ctx->connector, "CRTC_ID", drm->crtc_id) < 0) { + MP_ERR(drm, "Could not set CRTC_ID on connector\n"); + goto err; + } + + if (!drm_mode_ensure_blob(drm->fd, &drm->mode)) { + MP_ERR(drm, "Failed to create DRM mode blob\n"); + goto err; + } + if (drm_object_set_property(request, atomic_ctx->crtc, "MODE_ID", drm->mode.blob_id) < 0) { + MP_ERR(drm, "Could not set MODE_ID on crtc\n"); + goto err; + } + if (drm_object_set_property(request, atomic_ctx->crtc, "ACTIVE", 1) < 0) { + MP_ERR(drm, "Could not set ACTIVE on crtc\n"); + goto err; + } + + /* + * VRR related properties were added in kernel 5.0. We will not fail if we + * cannot query or set the value, but we will log as appropriate. + */ + uint64_t vrr_capable = 0; + drm_object_get_property(atomic_ctx->connector, "VRR_CAPABLE", &vrr_capable); + MP_VERBOSE(drm, "crtc is%s VRR capable\n", vrr_capable ? "" : " not"); + + uint64_t vrr_requested = drm->opts->vrr_enabled; + if (vrr_requested == 1 || (vrr_capable && vrr_requested == -1)) { + if (drm_object_set_property(request, atomic_ctx->crtc, "VRR_ENABLED", 1) < 0) { + MP_WARN(drm, "Could not enable VRR on crtc\n"); + } else { + MP_VERBOSE(drm, "Enabled VRR on crtc\n"); + } + } + + drm_object_set_property(request, atomic_ctx->draw_plane, "FB_ID", drm->fb->id); + drm_object_set_property(request, atomic_ctx->draw_plane, "CRTC_ID", drm->crtc_id); + drm_object_set_property(request, atomic_ctx->draw_plane, "SRC_X", 0); + drm_object_set_property(request, atomic_ctx->draw_plane, "SRC_Y", 0); + drm_object_set_property(request, atomic_ctx->draw_plane, "SRC_W", drm->width << 16); + drm_object_set_property(request, atomic_ctx->draw_plane, "SRC_H", drm->height << 16); + drm_object_set_property(request, atomic_ctx->draw_plane, "CRTC_X", 0); + drm_object_set_property(request, atomic_ctx->draw_plane, "CRTC_Y", 0); + drm_object_set_property(request, atomic_ctx->draw_plane, "CRTC_W", drm->mode.mode.hdisplay); + drm_object_set_property(request, atomic_ctx->draw_plane, "CRTC_H", drm->mode.mode.vdisplay); + + if (drmModeAtomicCommit(drm->fd, request, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL)) { + MP_ERR(drm, "Failed to commit ModeSetting atomic request: %s\n", strerror(errno)); + goto err; + } + + drmModeAtomicFree(request); + return true; + +err: + drmModeAtomicFree(request); + return false; +} + + +void vo_drm_release_crtc(struct vo_drm_state *drm) +{ + if (!drm->active) + return; + drm->active = false; + + if (!drm->atomic_context->old_state.saved) + return; + + bool success = true; + struct drm_atomic_context *atomic_ctx = drm->atomic_context; + drmModeAtomicReqPtr request = drmModeAtomicAlloc(); + if (!request) { + MP_ERR(drm, "Failed to allocate drm atomic request\n"); + success = false; + } + + if (request && !drm_atomic_restore_old_state(request, atomic_ctx)) { + MP_WARN(drm, "Got error while restoring old state\n"); + success = false; + } + + if (request) { + if (drmModeAtomicCommit(drm->fd, request, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL)) { + MP_WARN(drm, "Failed to commit ModeSetting atomic request: %s\n", + mp_strerror(errno)); + success = false; + } + } + + if (request) + drmModeAtomicFree(request); + + if (!success) + MP_ERR(drm, "Failed to restore previous mode\n"); + + if (drmDropMaster(drm->fd)) { + MP_WARN(drm, "Failed to drop DRM master: %s\n", + mp_strerror(errno)); + } +} + +/* libdrm */ static void get_connector_name(const drmModeConnector *connector, char ret[MAX_CONNECTOR_NAME_LEN]) { @@ -171,13 +462,13 @@ static void get_connector_name(const drmModeConnector *connector, // Gets the first connector whose name matches the input parameter. // The returned connector may be disconnected. // Result must be freed with drmModeFreeConnector. -static drmModeConnector *get_connector_by_name(const struct kms *kms, - const drmModeRes *res, - const char *connector_name) +static drmModeConnector *get_connector_by_name(const drmModeRes *res, + const char *connector_name, + int fd) { for (int i = 0; i < res->count_connectors; i++) { drmModeConnector *connector - = drmModeGetConnector(kms->fd, res->connectors[i]); + = drmModeGetConnector(fd, res->connectors[i]); if (!connector) continue; char other_connector_name[MAX_CONNECTOR_NAME_LEN]; @@ -191,16 +482,14 @@ static drmModeConnector *get_connector_by_name(const struct kms *kms, // Gets the first connected connector. // Result must be freed with drmModeFreeConnector. -static drmModeConnector *get_first_connected_connector(const struct kms *kms, - const drmModeRes *res) +static drmModeConnector *get_first_connected_connector(const drmModeRes *res, + int fd) { for (int i = 0; i < res->count_connectors; i++) { - drmModeConnector *connector - = drmModeGetConnector(kms->fd, res->connectors[i]); + drmModeConnector *connector = drmModeGetConnector(fd, res->connectors[i]); if (!connector) continue; - if (connector->connection == DRM_MODE_CONNECTED - && connector->count_modes > 0) { + if (connector->connection == DRM_MODE_CONNECTED && connector->count_modes > 0) { return connector; } drmModeFreeConnector(connector); @@ -208,61 +497,58 @@ static drmModeConnector *get_first_connected_connector(const struct kms *kms, return NULL; } -static bool setup_connector(struct kms *kms, const drmModeRes *res, +static bool setup_connector(struct vo_drm_state *drm, const drmModeRes *res, const char *connector_name) { drmModeConnector *connector; - if (connector_name - && strcmp(connector_name, "") - && strcmp(connector_name, "auto")) { - connector = get_connector_by_name(kms, res, connector_name); + if (connector_name && strcmp(connector_name, "") && strcmp(connector_name, "auto")) { + connector = get_connector_by_name(res, connector_name, drm->fd); if (!connector) { - MP_ERR(kms, "No connector with name %s found\n", connector_name); - kms_show_available_connectors(kms->log, kms->card_no, - kms->primary_node_path); + MP_ERR(drm, "No connector with name %s found\n", connector_name); + drm_show_available_connectors(drm->log, drm->card_no, drm->card_path); return false; } } else { - connector = get_first_connected_connector(kms, res); + connector = get_first_connected_connector(res, drm->fd); if (!connector) { - MP_ERR(kms, "No connected connectors found\n"); + MP_ERR(drm, "No connected connectors found\n"); return false; } } if (connector->connection != DRM_MODE_CONNECTED) { drmModeFreeConnector(connector); - MP_ERR(kms, "Chosen connector is disconnected\n"); + MP_ERR(drm, "Chosen connector is disconnected\n"); return false; } if (connector->count_modes == 0) { drmModeFreeConnector(connector); - MP_ERR(kms, "Chosen connector has no valid modes\n"); + MP_ERR(drm, "Chosen connector has no valid modes\n"); return false; } - kms->connector = connector; + drm->connector = connector; return true; } -static bool setup_crtc(struct kms *kms, const drmModeRes *res) +static bool setup_crtc(struct vo_drm_state *drm, const drmModeRes *res) { // First try to find currently connected encoder and its current CRTC for (unsigned int i = 0; i < res->count_encoders; i++) { - drmModeEncoder *encoder = drmModeGetEncoder(kms->fd, res->encoders[i]); + drmModeEncoder *encoder = drmModeGetEncoder(drm->fd, res->encoders[i]); if (!encoder) { - MP_WARN(kms, "Cannot retrieve encoder %u:%u: %s\n", + MP_WARN(drm, "Cannot retrieve encoder %u:%u: %s\n", i, res->encoders[i], mp_strerror(errno)); continue; } - if (encoder->encoder_id == kms->connector->encoder_id && encoder->crtc_id != 0) { - MP_VERBOSE(kms, "Connector %u currently connected to encoder %u\n", - kms->connector->connector_id, kms->connector->encoder_id); - kms->encoder = encoder; - kms->crtc_id = encoder->crtc_id; + if (encoder->encoder_id == drm->connector->encoder_id && encoder->crtc_id != 0) { + MP_VERBOSE(drm, "Connector %u currently connected to encoder %u\n", + drm->connector->connector_id, drm->connector->encoder_id); + drm->encoder = encoder; + drm->crtc_id = encoder->crtc_id; goto success; } @@ -270,12 +556,12 @@ static bool setup_crtc(struct kms *kms, const drmModeRes *res) } // Otherwise pick first legal encoder and CRTC combo for the connector - for (unsigned int i = 0; i < kms->connector->count_encoders; ++i) { + for (unsigned int i = 0; i < drm->connector->count_encoders; ++i) { drmModeEncoder *encoder - = drmModeGetEncoder(kms->fd, kms->connector->encoders[i]); + = drmModeGetEncoder(drm->fd, drm->connector->encoders[i]); if (!encoder) { - MP_WARN(kms, "Cannot retrieve encoder %u:%u: %s\n", - i, kms->connector->encoders[i], mp_strerror(errno)); + MP_WARN(drm, "Cannot retrieve encoder %u:%u: %s\n", + i, drm->connector->encoders[i], mp_strerror(errno)); continue; } @@ -285,21 +571,21 @@ static bool setup_crtc(struct kms *kms, const drmModeRes *res) if (!(encoder->possible_crtcs & (1 << j))) continue; - kms->encoder = encoder; - kms->crtc_id = res->crtcs[j]; + drm->encoder = encoder; + drm->crtc_id = res->crtcs[j]; goto success; } drmModeFreeEncoder(encoder); } - MP_ERR(kms, "Connector %u has no suitable CRTC\n", - kms->connector->connector_id); + MP_ERR(drm, "Connector %u has no suitable CRTC\n", + drm->connector->connector_id); return false; success: - MP_VERBOSE(kms, "Selected Encoder %u with CRTC %u\n", - kms->encoder->encoder_id, kms->crtc_id); + MP_VERBOSE(drm, "Selected Encoder %u with CRTC %u\n", + drm->encoder->encoder_id, drm->crtc_id); return true; } @@ -388,15 +674,15 @@ static bool parse_mode_spec(const char *spec, struct drm_mode_spec *parse_result return true; } -static bool setup_mode_by_idx(struct kms *kms, unsigned int mode_idx) +static bool setup_mode_by_idx(struct vo_drm_state *drm, unsigned int mode_idx) { - if (mode_idx >= kms->connector->count_modes) { - MP_ERR(kms, "Bad mode index (max = %d).\n", - kms->connector->count_modes - 1); + if (mode_idx >= drm->connector->count_modes) { + MP_ERR(drm, "Bad mode index (max = %d).\n", + drm->connector->count_modes - 1); return false; } - kms->mode.mode = kms->connector->modes[mode_idx]; + drm->mode.mode = drm->connector->modes[mode_idx]; return true; } @@ -418,46 +704,45 @@ static bool mode_match(const drmModeModeInfo *mode, } } -static bool setup_mode_by_numbers(struct kms *kms, +static bool setup_mode_by_numbers(struct vo_drm_state *drm, unsigned int width, unsigned int height, - double refresh, - const char *mode_spec) + double refresh) { - for (unsigned int i = 0; i < kms->connector->count_modes; ++i) { - drmModeModeInfo *current_mode = &kms->connector->modes[i]; + for (unsigned int i = 0; i < drm->connector->count_modes; ++i) { + drmModeModeInfo *current_mode = &drm->connector->modes[i]; if (mode_match(current_mode, width, height, refresh)) { - kms->mode.mode = *current_mode; + drm->mode.mode = *current_mode; return true; } } - MP_ERR(kms, "Could not find mode matching %s\n", mode_spec); + MP_ERR(drm, "Could not find mode matching %s\n", drm->opts->mode_spec); return false; } -static bool setup_mode_preferred(struct kms *kms) +static bool setup_mode_preferred(struct vo_drm_state *drm) { - for (unsigned int i = 0; i < kms->connector->count_modes; ++i) { - drmModeModeInfo *current_mode = &kms->connector->modes[i]; + for (unsigned int i = 0; i < drm->connector->count_modes; ++i) { + drmModeModeInfo *current_mode = &drm->connector->modes[i]; if (current_mode->type & DRM_MODE_TYPE_PREFERRED) { - kms->mode.mode = *current_mode; + drm->mode.mode = *current_mode; return true; } } // Fall back to first mode - MP_WARN(kms, "Could not find any preferred mode. Picking the first mode.\n"); - kms->mode.mode = kms->connector->modes[0]; + MP_WARN(drm, "Could not find any preferred mode. Picking the first mode.\n"); + drm->mode.mode = drm->connector->modes[0]; return true; } -static bool setup_mode_highest(struct kms *kms) +static bool setup_mode_highest(struct vo_drm_state *drm) { unsigned int area = 0; - drmModeModeInfo *highest_resolution_mode = &kms->connector->modes[0]; - for (unsigned int i = 0; i < kms->connector->count_modes; ++i) { - drmModeModeInfo *current_mode = &kms->connector->modes[i]; + drmModeModeInfo *highest_resolution_mode = &drm->connector->modes[0]; + for (unsigned int i = 0; i < drm->connector->count_modes; ++i) { + drmModeModeInfo *current_mode = &drm->connector->modes[i]; const unsigned int current_area = current_mode->hdisplay * current_mode->vdisplay; @@ -467,55 +752,54 @@ static bool setup_mode_highest(struct kms *kms) } } - kms->mode.mode = *highest_resolution_mode; + drm->mode.mode = *highest_resolution_mode; return true; } -static bool setup_mode(struct kms *kms, const char *mode_spec) +static bool setup_mode(struct vo_drm_state *drm) { - if (kms->connector->count_modes <= 0) { - MP_ERR(kms, "No available modes\n"); + if (drm->connector->count_modes <= 0) { + MP_ERR(drm, "No available modes\n"); return false; } struct drm_mode_spec parsed; - if (!parse_mode_spec(mode_spec, &parsed)) { - MP_ERR(kms, "Parse error\n"); + if (!parse_mode_spec(drm->opts->mode_spec, &parsed)) { + MP_ERR(drm, "Parse error\n"); goto err; } switch (parsed.type) { case DRM_MODE_SPEC_BY_IDX: - if (!setup_mode_by_idx(kms, parsed.idx)) + if (!setup_mode_by_idx(drm, parsed.idx)) goto err; break; case DRM_MODE_SPEC_BY_NUMBERS: - if (!setup_mode_by_numbers(kms, parsed.width, parsed.height, parsed.refresh, - mode_spec)) + if (!setup_mode_by_numbers(drm, parsed.width, parsed.height, parsed.refresh)) goto err; break; case DRM_MODE_SPEC_PREFERRED: - if (!setup_mode_preferred(kms)) + if (!setup_mode_preferred(drm)) goto err; break; case DRM_MODE_SPEC_HIGHEST: - if (!setup_mode_highest(kms)) + if (!setup_mode_highest(drm)) goto err; break; default: - MP_ERR(kms, "setup_mode: Internal error\n"); + MP_ERR(drm, "setup_mode: Internal error\n"); goto err; } - drmModeModeInfo *mode = &kms->mode.mode; - MP_VERBOSE(kms, "Selected mode: %s (%dx%d@%.2fHz)\n", + drmModeModeInfo *mode = &drm->mode.mode; + MP_VERBOSE(drm, "Selected mode: %s (%dx%d@%.2fHz)\n", mode->name, mode->hdisplay, mode->vdisplay, mode_get_Hz(mode)); return true; err: - MP_INFO(kms, "Available modes:\n"); - kms_show_available_modes(kms->log, kms->connector); + MP_INFO(drm, "Available modes:\n"); + drm_show_available_modes(drm->log, drm->connector); return false; } @@ -537,32 +821,35 @@ static bool card_supports_kms(const char *path) #endif } -static char *get_primary_device_path(struct mp_log *log, int *card_no) +static void get_primary_device_path(struct vo_drm_state *drm) { + if (drm->opts->device_path) { + drm->card_path = talloc_strdup(drm, drm->opts->device_path); + return; + } + drmDevice *devices[DRM_MAX_MINOR] = { 0 }; int card_count = drmGetDevices2(0, devices, MP_ARRAY_SIZE(devices)); - char *device_path = NULL; - bool card_no_given = (*card_no >= 0); + bool card_no_given = drm->card_no >= 0; if (card_count < 0) { - mp_err(log, "Listing DRM devices with drmGetDevices failed! (%s)\n", + MP_ERR(drm, "Listing DRM devices with drmGetDevices failed! (%s)\n", mp_strerror(errno)); goto err; } - if (card_no_given && *card_no > (card_count - 1)) { - mp_err(log, "Card number %d given too high! %d devices located.\n", - *card_no, card_count); + if (card_no_given && drm->card_no > (card_count - 1)) { + MP_ERR(drm, "Card number %d given too high! %d devices located.\n", + drm->card_no, card_count); goto err; } - for (int i = card_no_given ? *card_no : 0; i < card_count; i++) { + for (int i = card_no_given ? drm->card_no : 0; i < card_count; i++) { drmDevice *dev = devices[i]; if (!(dev->available_nodes & (1 << DRM_NODE_PRIMARY))) { if (card_no_given) { - mp_err(log, - "DRM card number %d given, yet it does not have " + MP_ERR(drm, "DRM card number %d given, but it does not have " "a primary node!\n", i); break; } @@ -570,12 +857,12 @@ static char *get_primary_device_path(struct mp_log *log, int *card_no) continue; } - const char *primary_node_path = dev->nodes[DRM_NODE_PRIMARY]; + const char *card_path = dev->nodes[DRM_NODE_PRIMARY]; - if (!card_supports_kms(primary_node_path)) { + if (!card_supports_kms(card_path)) { if (card_no_given) { - mp_err(log, - "DRM card number %d given, yet it does not support " + MP_ERR(drm, + "DRM card number %d given, but it does not support " "KMS!\n", i); break; } @@ -583,495 +870,40 @@ static char *get_primary_device_path(struct mp_log *log, int *card_no) continue; } - mp_verbose(log, "Picked DRM card %d, primary node %s%s.\n", - i, primary_node_path, + MP_VERBOSE(drm, "Picked DRM card %d, primary node %s%s.\n", + i, card_path, card_no_given ? "" : " as the default"); - device_path = talloc_strdup(log, primary_node_path); - *card_no = i; + drm->card_path = talloc_strdup(drm, card_path); + drm->card_no = i; break; } - if (!device_path) - mp_err(log, "No primary DRM device could be picked!\n"); + if (!drm->card_path) + MP_ERR(drm, "No primary DRM device could be picked!\n"); err: drmFreeDevices(devices, card_count); - - return device_path; } -static void parse_connector_spec(struct mp_log *log, - const char *connector_spec, - int *card_no, char **connector_name) + +static char *parse_connector_spec(struct vo_drm_state *drm) { - if (!connector_spec) { - *card_no = -1; - *connector_name = NULL; - return; - } - char *dot_ptr = strchr(connector_spec, '.'); - if (dot_ptr) { - mp_warn(log, "Warning: Selecting a connector by index with drm-connector " - "is deprecated. Use the drm-device option instead.\n"); - *card_no = atoi(connector_spec); - *connector_name = talloc_strdup(log, dot_ptr + 1); - } else { - *card_no = -1; - *connector_name = talloc_strdup(log, connector_spec); - } -} - -struct kms *kms_create(struct mp_log *log, - const char *drm_device_path, - const char *connector_spec, - const char* mode_spec, - int draw_plane, int drmprime_video_plane) -{ - int card_no = -1; - char *connector_name = NULL; - - parse_connector_spec(log, connector_spec, &card_no, &connector_name); - if (drm_device_path && card_no != -1) - mp_warn(log, "Both DRM device and card number (as part of " - "drm-connector) are set! Will prefer given device path " - "'%s'!\n", - drm_device_path); - - char *primary_node_path = drm_device_path ? - talloc_strdup(log, drm_device_path) : - get_primary_device_path(log, &card_no); - - if (!primary_node_path) { - mp_err(log, - "Failed to find a usable DRM primary node!\n"); + if (!drm->opts->connector_spec) return NULL; - } - - struct kms *kms = talloc(NULL, struct kms); - *kms = (struct kms) { - .log = mp_log_new(kms, log, "kms"), - .primary_node_path = primary_node_path, - .fd = open_card_path(primary_node_path), - .connector = NULL, - .encoder = NULL, - .mode = {{0}}, - .crtc_id = -1, - .card_no = card_no, - }; - - drmModeRes *res = NULL; - - if (kms->fd < 0) { - mp_err(log, "Cannot open card \"%d\": %s.\n", - card_no, mp_strerror(errno)); - goto err; - } - - drmVersionPtr ver = drmGetVersion(kms->fd); - if (ver) { - mp_verbose(log, "Driver: %s %d.%d.%d (%s)\n", ver->name, - ver->version_major, ver->version_minor, ver->version_patchlevel, - ver->date); - drmFreeVersion(ver); - } - - res = drmModeGetResources(kms->fd); - if (!res) { - mp_err(log, "Cannot retrieve DRM resources: %s\n", mp_strerror(errno)); - goto err; - } - - if (!setup_connector(kms, res, connector_name)) - goto err; - if (!setup_crtc(kms, res)) - goto err; - if (!setup_mode(kms, mode_spec)) - goto err; - - // Universal planes allows accessing all the planes (including primary) - if (drmSetClientCap(kms->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { - mp_err(log, "Failed to set Universal planes capability\n"); - } - - if (drmSetClientCap(kms->fd, DRM_CLIENT_CAP_ATOMIC, 1)) { - mp_err(log, "Failed to create DRM atomic context, no DRM Atomic support\n"); - goto err; + char *dot_ptr = strchr(drm->opts->connector_spec, '.'); + if (dot_ptr) { + MP_WARN(drm, "Warning: Selecting a connector by index with drm-connector " + "is deprecated. Use the drm-device option instead.\n"); + drm->card_no = strtoul(drm->opts->connector_spec, NULL, 10); + return talloc_strdup(drm, dot_ptr + 1); } else { - mp_verbose(log, "DRM Atomic support found\n"); - kms->atomic_context = drm_atomic_create_context(kms->log, kms->fd, kms->crtc_id, - kms->connector->connector_id, - draw_plane, drmprime_video_plane); - if (!kms->atomic_context) { - mp_err(log, "Failed to create DRM atomic context\n"); - goto err; - } - } - - drmModeFreeResources(res); - return kms; - -err: - if (res) - drmModeFreeResources(res); - if (connector_name) - talloc_free(connector_name); - - kms_destroy(kms); - return NULL; -} - -void kms_destroy(struct kms *kms) -{ - if (!kms) - return; - drm_mode_destroy_blob(kms->fd, &kms->mode); - if (kms->connector) { - drmModeFreeConnector(kms->connector); - kms->connector = NULL; - } - if (kms->encoder) { - drmModeFreeEncoder(kms->encoder); - kms->encoder = NULL; - } - if (kms->atomic_context) { - drm_atomic_destroy_context(kms->atomic_context); - } - - close(kms->fd); - talloc_free(kms); -} - -static double mode_get_Hz(const drmModeModeInfo *mode) -{ - double rate = mode->clock * 1000.0 / mode->htotal / mode->vtotal; - if (mode->flags & DRM_MODE_FLAG_INTERLACE) - rate *= 2.0; - return rate; -} - -static void kms_show_available_modes( - struct mp_log *log, const drmModeConnector *connector) -{ - for (unsigned int i = 0; i < connector->count_modes; i++) { - mp_info(log, " Mode %d: %s (%dx%d@%.2fHz)\n", i, - connector->modes[i].name, - connector->modes[i].hdisplay, - connector->modes[i].vdisplay, - mode_get_Hz(&connector->modes[i])); + return talloc_strdup(drm, drm->opts->connector_spec); } } -static void kms_show_foreach_connector(struct mp_log *log, int card_no, - const char *card_path, - void (*show_fn)(struct mp_log*, int, - const drmModeConnector*)) -{ - int fd = open_card_path(card_path); - if (fd < 0) { - mp_err(log, "Failed to open card %d (%s)\n", card_no, card_path); - return; - } - - drmModeRes *res = drmModeGetResources(fd); - if (!res) { - mp_err(log, "Cannot retrieve DRM resources: %s\n", mp_strerror(errno)); - goto err; - } - - for (int i = 0; i < res->count_connectors; i++) { - drmModeConnector *connector - = drmModeGetConnector(fd, res->connectors[i]); - if (!connector) - continue; - show_fn(log, card_no, connector); - drmModeFreeConnector(connector); - } - -err: - if (fd >= 0) - close(fd); - if (res) - drmModeFreeResources(res); -} - -static void kms_show_connector_name_and_state_callback( - struct mp_log *log, int card_no, const drmModeConnector *connector) -{ - char other_connector_name[MAX_CONNECTOR_NAME_LEN]; - get_connector_name(connector, other_connector_name); - const char *connection_str = - (connector->connection == DRM_MODE_CONNECTED) ? "connected" : "disconnected"; - mp_info(log, " %s (%s)\n", other_connector_name, connection_str); -} - -static void kms_show_available_connectors(struct mp_log *log, int card_no, - const char *card_path) -{ - mp_info(log, "Available connectors for card %d (%s):\n", card_no, - card_path); - kms_show_foreach_connector( - log, card_no, card_path, kms_show_connector_name_and_state_callback); - mp_info(log, "\n"); -} - -static void kms_show_connector_modes_callback(struct mp_log *log, int card_no, - const drmModeConnector *connector) -{ - if (connector->connection != DRM_MODE_CONNECTED) - return; - - char other_connector_name[MAX_CONNECTOR_NAME_LEN]; - get_connector_name(connector, other_connector_name); - mp_info(log, "Available modes for drm-connector=%d.%s\n", - card_no, other_connector_name); - kms_show_available_modes(log, connector); - mp_info(log, "\n"); -} - -static void kms_show_available_connectors_and_modes(struct mp_log *log, - int card_no, - const char *card_path) -{ - kms_show_foreach_connector(log, card_no, card_path, - kms_show_connector_modes_callback); -} - -static void kms_show_foreach_card( - struct mp_log *log, void (*show_fn)(struct mp_log*,int,const char *)) -{ - drmDevice *devices[DRM_MAX_MINOR] = { 0 }; - int card_count = drmGetDevices2(0, devices, MP_ARRAY_SIZE(devices)); - if (card_count < 0) { - mp_err(log, "Listing DRM devices with drmGetDevices failed! (%s)\n", - mp_strerror(errno)); - return; - } - - for (int i = 0; i < card_count; i++) { - drmDevice *dev = devices[i]; - - if (!(dev->available_nodes & (1 << DRM_NODE_PRIMARY))) - continue; - - const char *primary_node_path = dev->nodes[DRM_NODE_PRIMARY]; - - int fd = open_card_path(primary_node_path); - if (fd < 0) { - mp_err(log, "Failed to open primary DRM node path %s!\n", - primary_node_path); - continue; - } - - close(fd); - show_fn(log, i, primary_node_path); - } - - drmFreeDevices(devices, card_count); -} - -static void kms_show_available_cards_and_connectors(struct mp_log *log) -{ - kms_show_foreach_card(log, kms_show_available_connectors); -} - -static void kms_show_available_cards_connectors_and_modes(struct mp_log *log) -{ - kms_show_foreach_card(log, kms_show_available_connectors_and_modes); -} - -double kms_get_display_fps(const struct kms *kms) -{ - return mode_get_Hz(&kms->mode.mode); -} - -static int drm_connector_opt_help(struct mp_log *log, const struct m_option *opt, - struct bstr name) -{ - kms_show_available_cards_and_connectors(log); - return M_OPT_EXIT; -} - -static int drm_mode_opt_help(struct mp_log *log, const struct m_option *opt, - struct bstr name) -{ - kms_show_available_cards_connectors_and_modes(log); - return M_OPT_EXIT; -} - -static int drm_validate_mode_opt(struct mp_log *log, const struct m_option *opt, - struct bstr name, const char **value) -{ - const char *param = *value; - if (!parse_mode_spec(param, NULL)) { - mp_fatal(log, "Invalid value for option drm-mode. Must be a positive number, a string of the format WxH[@R] or 'help'\n"); - return M_OPT_INVALID; - } - - return 1; -} - -// VT switcher ---------------------------------------------------------------- - -static void vt_switcher_sighandler(int sig) -{ - unsigned char event = sig == RELEASE_SIGNAL ? EVT_RELEASE : EVT_ACQUIRE; - (void)write(vt_switcher_pipe[1], &event, sizeof(event)); -} - -static bool has_signal_installed(int signo) -{ - struct sigaction act = { 0 }; - sigaction(signo, 0, &act); - return act.sa_handler != 0; -} - -static int install_signal(int signo, void (*handler)(int)) -{ - struct sigaction act = { 0 }; - act.sa_handler = handler; - sigemptyset(&act.sa_mask); - act.sa_flags = SA_RESTART; - return sigaction(signo, &act, NULL); -} - -bool vt_switcher_init(struct vt_switcher *s, struct mp_log *log) -{ - s->log = log; - s->tty_fd = -1; - vt_switcher_pipe[0] = -1; - vt_switcher_pipe[1] = -1; - - if (mp_make_cloexec_pipe(vt_switcher_pipe)) { - MP_ERR(s, "Creating pipe failed: %s\n", mp_strerror(errno)); - return false; - } - - s->tty_fd = open("/dev/tty", O_RDWR | O_CLOEXEC); - if (s->tty_fd < 0) { - MP_ERR(s, "Can't open TTY for VT control: %s\n", mp_strerror(errno)); - return false; - } - - if (has_signal_installed(RELEASE_SIGNAL)) { - MP_ERR(s, "Can't handle VT release - signal already used\n"); - return false; - } - if (has_signal_installed(ACQUIRE_SIGNAL)) { - MP_ERR(s, "Can't handle VT acquire - signal already used\n"); - return false; - } - - if (install_signal(RELEASE_SIGNAL, vt_switcher_sighandler)) { - MP_ERR(s, "Failed to install release signal: %s\n", mp_strerror(errno)); - return false; - } - if (install_signal(ACQUIRE_SIGNAL, vt_switcher_sighandler)) { - MP_ERR(s, "Failed to install acquire signal: %s\n", mp_strerror(errno)); - return false; - } - - struct vt_mode vt_mode = { 0 }; - if (ioctl(s->tty_fd, VT_GETMODE, &vt_mode) < 0) { - MP_ERR(s, "VT_GETMODE failed: %s\n", mp_strerror(errno)); - return false; - } - - vt_mode.mode = VT_PROCESS; - vt_mode.relsig = RELEASE_SIGNAL; - vt_mode.acqsig = ACQUIRE_SIGNAL; - // frsig is a signal for forced release. Not implemented on Linux, - // Solaris, BSDs but must be set to a valid signal on some of those. - vt_mode.frsig = SIGIO; // unused - if (ioctl(s->tty_fd, VT_SETMODE, &vt_mode) < 0) { - MP_ERR(s, "VT_SETMODE failed: %s\n", mp_strerror(errno)); - return false; - } - - // Block the VT switching signals from interrupting the VO thread (they will - // still be picked up by other threads, which will fill vt_switcher_pipe for us) - sigset_t set; - sigemptyset(&set); - sigaddset(&set, RELEASE_SIGNAL); - sigaddset(&set, ACQUIRE_SIGNAL); - pthread_sigmask(SIG_BLOCK, &set, NULL); - - return true; -} - -void vt_switcher_acquire(struct vt_switcher *s, - void (*handler)(void*), void *user_data) -{ - s->handlers[HANDLER_ACQUIRE] = handler; - s->handler_data[HANDLER_ACQUIRE] = user_data; -} - -void vt_switcher_release(struct vt_switcher *s, - void (*handler)(void*), void *user_data) -{ - s->handlers[HANDLER_RELEASE] = handler; - s->handler_data[HANDLER_RELEASE] = user_data; -} - -void vt_switcher_interrupt_poll(struct vt_switcher *s) -{ - unsigned char event = EVT_INTERRUPT; - (void)write(vt_switcher_pipe[1], &event, sizeof(event)); -} - -void vt_switcher_destroy(struct vt_switcher *s) -{ - struct vt_mode vt_mode = {0}; - vt_mode.mode = VT_AUTO; - if (ioctl(s->tty_fd, VT_SETMODE, &vt_mode) < 0) { - MP_ERR(s, "VT_SETMODE failed: %s\n", mp_strerror(errno)); - return; - } - - install_signal(RELEASE_SIGNAL, SIG_DFL); - install_signal(ACQUIRE_SIGNAL, SIG_DFL); - close(s->tty_fd); - close(vt_switcher_pipe[0]); - close(vt_switcher_pipe[1]); -} - -void vt_switcher_poll(struct vt_switcher *s, int timeout_ms) -{ - struct pollfd fds[1] = { - { .events = POLLIN, .fd = vt_switcher_pipe[0] }, - }; - poll(fds, 1, timeout_ms); - if (!fds[0].revents) - return; - - unsigned char event; - if (read(fds[0].fd, &event, sizeof(event)) != sizeof(event)) - return; - - switch (event) { - case EVT_RELEASE: - s->handlers[HANDLER_RELEASE](s->handler_data[HANDLER_RELEASE]); - - if (ioctl(s->tty_fd, VT_RELDISP, 1) < 0) { - MP_ERR(s, "Failed to release virtual terminal\n"); - } - break; - - case EVT_ACQUIRE: - s->handlers[HANDLER_ACQUIRE](s->handler_data[HANDLER_ACQUIRE]); - - if (ioctl(s->tty_fd, VT_RELDISP, VT_ACKACQ) < 0) { - MP_ERR(s, "Failed to acquire virtual terminal\n"); - } - break; - - case EVT_INTERRUPT: - break; - } -} - -void drm_pflip_cb(int fd, unsigned int msc, unsigned int sec, - unsigned int usec, void *data) +static void drm_pflip_cb(int fd, unsigned int msc, unsigned int sec, + unsigned int usec, void *data) { struct drm_pflip_cb_closure *closure = data; @@ -1123,3 +955,384 @@ fail: *closure->waiting_for_flip = false; talloc_free(closure); } + +int vo_drm_control(struct vo *vo, int *events, int request, void *arg) +{ + struct vo_drm_state *drm = vo->drm; + switch (request) { + case VOCTRL_GET_DISPLAY_FPS: { + double fps = vo_drm_get_display_fps(drm); + if (fps <= 0) + break; + *(double*)arg = fps; + return VO_TRUE; + } + case VOCTRL_GET_DISPLAY_RES: { + ((int *)arg)[0] = drm->mode.mode.hdisplay; + ((int *)arg)[1] = drm->mode.mode.vdisplay; + return VO_TRUE; + } + case VOCTRL_PAUSE: + vo->want_redraw = true; + drm->paused = true; + return VO_TRUE; + case VOCTRL_RESUME: + drm->paused = false; + drm->vsync_info.last_queue_display_time = -1; + drm->vsync_info.skipped_vsyncs = 0; + drm->vsync.ust = 0; + drm->vsync.msc = 0; + return VO_TRUE; + } + return VO_NOTIMPL; +} + +bool vo_drm_init(struct vo *vo) +{ + vo->drm = talloc_zero(NULL, struct vo_drm_state); + struct vo_drm_state *drm = vo->drm; + + *drm = (struct vo_drm_state) { + .vo = vo, + .log = mp_log_new(drm, vo->log, "drm"), + .mode = {{0}}, + .crtc_id = -1, + .card_no = -1, + }; + + drm->vt_switcher_active = vt_switcher_init(&drm->vt_switcher, drm->log); + if (drm->vt_switcher_active) { + vt_switcher_acquire(&drm->vt_switcher, acquire_vt, drm); + vt_switcher_release(&drm->vt_switcher, release_vt, drm); + } else { + MP_WARN(drm, "Failed to set up VT switcher. Terminal switching will be unavailable.\n"); + } + + drm->opts = mp_get_config_group(drm, drm->vo->global, &drm_conf); + + drmModeRes *res = NULL; + char *connector_name = parse_connector_spec(drm); + get_primary_device_path(drm); + + if (!drm->card_path) { + MP_ERR(drm, "Failed to find a usable DRM primary node!\n"); + goto err; + } + + drm->fd = open_card_path(drm->card_path); + if (drm->fd < 0) { + MP_ERR(drm, "Cannot open card \"%d\": %s.\n", drm->card_no, mp_strerror(errno)); + goto err; + } + + drmVersionPtr ver = drmGetVersion(drm->fd); + if (ver) { + MP_VERBOSE(drm, "Driver: %s %d.%d.%d (%s)\n", ver->name, ver->version_major, + ver->version_minor, ver->version_patchlevel, ver->date); + drmFreeVersion(ver); + } + + res = drmModeGetResources(drm->fd); + if (!res) { + MP_ERR(drm, "Cannot retrieve DRM resources: %s\n", mp_strerror(errno)); + goto err; + } + + if (!setup_connector(drm, res, connector_name)) + goto err; + if (!setup_crtc(drm, res)) + goto err; + if (!setup_mode(drm)) + goto err; + + // Universal planes allows accessing all the planes (including primary) + if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { + MP_ERR(drm, "Failed to set Universal planes capability\n"); + } + + if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_ATOMIC, 1)) { + MP_ERR(drm, "Failed to create DRM atomic context, no DRM Atomic support\n"); + goto err; + } else { + MP_VERBOSE(drm, "DRM Atomic support found\n"); + drm->atomic_context = drm_atomic_create_context(drm->log, drm->fd, drm->crtc_id, + drm->connector->connector_id, + drm->opts->draw_plane, + drm->opts->drmprime_video_plane); + if (!drm->atomic_context) { + MP_ERR(drm, "Failed to create DRM atomic context\n"); + goto err; + } + } + + drmModeFreeResources(res); + + drm->ev.version = DRM_EVENT_CONTEXT_VERSION; + drm->ev.page_flip_handler = &drm_pflip_cb; + + drm->vsync_info.vsync_duration = 0; + drm->vsync_info.skipped_vsyncs = -1; + drm->vsync_info.last_queue_display_time = -1; + + return true; + +err: + if (res) + drmModeFreeResources(res); + + vo_drm_uninit(vo); + return false; +} + +void vo_drm_uninit(struct vo *vo) +{ + struct vo_drm_state *drm = vo->drm; + if (!drm) + return; + + vo_drm_release_crtc(drm); + if (drm->vt_switcher_active) + vt_switcher_destroy(&drm->vt_switcher); + + drm_mode_destroy_blob(drm->fd, &drm->mode); + + if (drm->connector) { + drmModeFreeConnector(drm->connector); + drm->connector = NULL; + } + if (drm->encoder) { + drmModeFreeEncoder(drm->encoder); + drm->encoder = NULL; + } + if (drm->atomic_context) { + drm_atomic_destroy_context(drm->atomic_context); + } + + close(drm->fd); + talloc_free(drm); + vo->drm = NULL; +} + +static double mode_get_Hz(const drmModeModeInfo *mode) +{ + double rate = mode->clock * 1000.0 / mode->htotal / mode->vtotal; + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + rate *= 2.0; + return rate; +} + +static void drm_show_available_modes(struct mp_log *log, + const drmModeConnector *connector) +{ + for (unsigned int i = 0; i < connector->count_modes; i++) { + mp_info(log, " Mode %d: %s (%dx%d@%.2fHz)\n", i, + connector->modes[i].name, + connector->modes[i].hdisplay, + connector->modes[i].vdisplay, + mode_get_Hz(&connector->modes[i])); + } +} + +static void drm_show_foreach_connector(struct mp_log *log, int card_no, + const char *card_path, + void (*show_fn)(struct mp_log*, int, + const drmModeConnector*)) +{ + int fd = open_card_path(card_path); + if (fd < 0) { + mp_err(log, "Failed to open card %d (%s)\n", card_no, card_path); + return; + } + + drmModeRes *res = drmModeGetResources(fd); + if (!res) { + mp_err(log, "Cannot retrieve DRM resources: %s\n", mp_strerror(errno)); + goto err; + } + + for (int i = 0; i < res->count_connectors; i++) { + drmModeConnector *connector = drmModeGetConnector(fd, res->connectors[i]); + if (!connector) + continue; + show_fn(log, card_no, connector); + drmModeFreeConnector(connector); + } + +err: + if (fd >= 0) + close(fd); + if (res) + drmModeFreeResources(res); +} + +static void drm_show_connector_name_and_state_callback(struct mp_log *log, int card_no, + const drmModeConnector *connector) +{ + char other_connector_name[MAX_CONNECTOR_NAME_LEN]; + get_connector_name(connector, other_connector_name); + const char *connection_str = (connector->connection == DRM_MODE_CONNECTED) ? + "connected" : "disconnected"; + mp_info(log, " %s (%s)\n", other_connector_name, connection_str); +} + +static void drm_show_available_connectors(struct mp_log *log, int card_no, + const char *card_path) +{ + mp_info(log, "Available connectors for card %d (%s):\n", card_no, + card_path); + drm_show_foreach_connector(log, card_no, card_path, + drm_show_connector_name_and_state_callback); + mp_info(log, "\n"); +} + +static void drm_show_connector_modes_callback(struct mp_log *log, int card_no, + const drmModeConnector *connector) +{ + if (connector->connection != DRM_MODE_CONNECTED) + return; + + char other_connector_name[MAX_CONNECTOR_NAME_LEN]; + get_connector_name(connector, other_connector_name); + mp_info(log, "Available modes for drm-connector=%d.%s\n", + card_no, other_connector_name); + drm_show_available_modes(log, connector); + mp_info(log, "\n"); +} + +static void drm_show_available_connectors_and_modes(struct mp_log *log, + int card_no, + const char *card_path) +{ + drm_show_foreach_connector(log, card_no, card_path, + drm_show_connector_modes_callback); +} + +static void drm_show_foreach_card(struct mp_log *log, + void (*show_fn)(struct mp_log *, int, + const char *)) +{ + drmDevice *devices[DRM_MAX_MINOR] = { 0 }; + int card_count = drmGetDevices2(0, devices, MP_ARRAY_SIZE(devices)); + if (card_count < 0) { + mp_err(log, "Listing DRM devices with drmGetDevices failed! (%s)\n", + mp_strerror(errno)); + return; + } + + for (int i = 0; i < card_count; i++) { + drmDevice *dev = devices[i]; + + if (!(dev->available_nodes & (1 << DRM_NODE_PRIMARY))) + continue; + + const char *card_path = dev->nodes[DRM_NODE_PRIMARY]; + + int fd = open_card_path(card_path); + if (fd < 0) { + mp_err(log, "Failed to open primary DRM node path %s!\n", + card_path); + continue; + } + + close(fd); + show_fn(log, i, card_path); + } + + drmFreeDevices(devices, card_count); +} + +static void drm_show_available_cards_and_connectors(struct mp_log *log) +{ + drm_show_foreach_card(log, drm_show_available_connectors); +} + +static void drm_show_available_cards_connectors_and_modes(struct mp_log *log) +{ + drm_show_foreach_card(log, drm_show_available_connectors_and_modes); +} + +static int drm_connector_opt_help(struct mp_log *log, const struct m_option *opt, + struct bstr name) +{ + drm_show_available_cards_and_connectors(log); + return M_OPT_EXIT; +} + +static int drm_mode_opt_help(struct mp_log *log, const struct m_option *opt, + struct bstr name) +{ + drm_show_available_cards_connectors_and_modes(log); + return M_OPT_EXIT; +} + +static int drm_validate_mode_opt(struct mp_log *log, const struct m_option *opt, + struct bstr name, const char **value) +{ + const char *param = *value; + if (!parse_mode_spec(param, NULL)) { + mp_fatal(log, "Invalid value for option drm-mode. Must be a positive number, a string of the format WxH[@R] or 'help'\n"); + return M_OPT_INVALID; + } + + return 1; +} + +/* Helpers */ +double vo_drm_get_display_fps(struct vo_drm_state *drm) +{ + return mode_get_Hz(&drm->mode.mode); +} + +void vo_drm_get_vsync(struct vo *vo, struct vo_vsync_info *info) +{ + struct vo_drm_state *drm = vo->drm; + *info = drm->vsync_info; +} + +void vo_drm_set_monitor_par(struct vo *vo) +{ + struct vo_drm_state *drm = vo->drm; + if (vo->opts->force_monitor_aspect != 0.0) { + vo->monitor_par = drm->fb->width / (double) drm->fb->height / + vo->opts->force_monitor_aspect; + } else { + vo->monitor_par = 1 / vo->opts->monitor_pixel_aspect; + } + MP_VERBOSE(drm, "Monitor pixel aspect: %g\n", vo->monitor_par); +} + +void vo_drm_wait_events(struct vo *vo, int64_t until_time_us) +{ + struct vo_drm_state *drm = vo->drm; + if (drm->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(&drm->vt_switcher, timeout_ms); + } else { + vo_wait_default(vo, until_time_us); + } +} + +void vo_drm_wait_on_flip(struct vo_drm_state *drm) +{ + // poll page flip finish event + while (drm->waiting_for_flip) { + const int timeout_ms = 3000; + struct pollfd fds[1] = { { .events = POLLIN, .fd = drm->fd } }; + poll(fds, 1, timeout_ms); + if (fds[0].revents & POLLIN) { + const int ret = drmHandleEvent(drm->fd, &drm->ev); + if (ret != 0) { + MP_ERR(drm, "drmHandleEvent failed: %i\n", ret); + return; + } + } + } +} + +void vo_drm_wakeup(struct vo *vo) +{ + struct vo_drm_state *drm = vo->drm; + if (drm->vt_switcher_active) + vt_switcher_interrupt_poll(&drm->vt_switcher); +} diff --git a/video/out/drm_common.h b/video/out/drm_common.h index 9a8b20427c..ac91584899 100644 --- a/video/out/drm_common.h +++ b/video/out/drm_common.h @@ -21,24 +21,45 @@ #include #include #include -#include "options/m_option.h" -#include "drm_atomic.h" +#include "vo.h" #define DRM_OPTS_FORMAT_XRGB8888 0 #define DRM_OPTS_FORMAT_XRGB2101010 1 #define DRM_OPTS_FORMAT_XBGR8888 2 #define DRM_OPTS_FORMAT_XBGR2101010 3 -struct kms { - struct mp_log *log; - char *primary_node_path; +struct framebuffer { int fd; - drmModeConnector *connector; - drmModeEncoder *encoder; - struct drm_mode mode; - uint32_t crtc_id; - int card_no; - struct drm_atomic_context *atomic_context; + uint32_t width; + uint32_t height; + uint32_t stride; + uint32_t size; + uint32_t handle; + uint8_t *map; + uint32_t id; +}; + +struct drm_vsync_tuple { + uint64_t ust; + unsigned int msc; + unsigned int sbc; +}; + +struct drm_mode { + drmModeModeInfo mode; + uint32_t blob_id; +}; + +struct drm_opts { + char *device_path; + char *connector_spec; + char *mode_spec; + int drm_atomic; + int draw_plane; + int drmprime_video_plane; + int drm_format; + struct m_geometry draw_surface_size; + int vrr_enabled; }; struct vt_switcher { @@ -48,22 +69,34 @@ struct vt_switcher { void *handler_data[2]; }; -struct drm_opts { - char *drm_device_path; - char *drm_connector_spec; - char *drm_mode_spec; - int drm_atomic; - int drm_draw_plane; - int drm_drmprime_video_plane; - int drm_format; - struct m_geometry drm_draw_surface_size; - int drm_vrr_enabled; -}; +struct vo_drm_state { + drmModeConnector *connector; + drmModeEncoder *encoder; + drmEventContext ev; -struct drm_vsync_tuple { - uint64_t ust; - unsigned int msc; - unsigned int sbc; + struct drm_atomic_context *atomic_context; + struct drm_mode mode; + struct drm_opts *opts; + struct drm_vsync_tuple vsync; + struct framebuffer *fb; + struct mp_log *log; + struct vo *vo; + struct vt_switcher vt_switcher; + struct vo_vsync_info vsync_info; + + bool active; + bool paused; + bool still; + bool vt_switcher_active; + bool waiting_for_flip; + + char *card_path; + int card_no; + int fd; + + uint32_t crtc_id; + uint32_t height; + uint32_t width; }; struct drm_pflip_cb_closure { @@ -74,26 +107,18 @@ struct drm_pflip_cb_closure { struct mp_log *log; // Needed to print error messages that shame bad drivers }; -bool vt_switcher_init(struct vt_switcher *s, struct mp_log *log); -void vt_switcher_destroy(struct vt_switcher *s); -void vt_switcher_poll(struct vt_switcher *s, int timeout_ms); -void vt_switcher_interrupt_poll(struct vt_switcher *s); +bool vo_drm_init(struct vo *vo); +int vo_drm_control(struct vo *vo, int *events, int request, void *arg); -void vt_switcher_acquire(struct vt_switcher *s, void (*handler)(void*), - void *user_data); -void vt_switcher_release(struct vt_switcher *s, void (*handler)(void*), - void *user_data); +double vo_drm_get_display_fps(struct vo_drm_state *drm); +void vo_drm_get_vsync(struct vo *vo, struct vo_vsync_info *info); +void vo_drm_set_monitor_par(struct vo *vo); +void vo_drm_uninit(struct vo *vo); +void vo_drm_wait_events(struct vo *vo, int64_t until_time_us); +void vo_drm_wait_on_flip(struct vo_drm_state *drm); +void vo_drm_wakeup(struct vo *vo); -struct kms *kms_create(struct mp_log *log, - const char *drm_device_path, - const char *connector_spec, - const char *mode_spec, - int draw_plane, int drmprime_video_plane); -void kms_destroy(struct kms *kms); -double kms_get_display_fps(const struct kms *kms); - -// DRM Page Flip callback -void drm_pflip_cb(int fd, unsigned int msc, unsigned int sec, - unsigned int usec, void *data); +bool vo_drm_acquire_crtc(struct vo_drm_state *drm); +void vo_drm_release_crtc(struct vo_drm_state *drm); #endif diff --git a/video/out/hwdec/hwdec_drmprime.c b/video/out/hwdec/hwdec_drmprime.c index e78857ce21..6ecacbb1ce 100644 --- a/video/out/hwdec/hwdec_drmprime.c +++ b/video/out/hwdec/hwdec_drmprime.c @@ -93,7 +93,7 @@ static int init(struct ra_hwdec *hw) */ void *tmp = talloc_new(NULL); struct drm_opts *drm_opts = mp_get_config_group(tmp, hw->global, &drm_conf); - const char *opt_path = drm_opts->drm_device_path; + const char *opt_path = drm_opts->device_path; const char *device_path = params && params->render_fd > -1 ? drmGetRenderDeviceNameFromFd(params->render_fd) : diff --git a/video/out/hwdec/hwdec_drmprime_overlay.c b/video/out/hwdec/hwdec_drmprime_overlay.c index 7e3c0b276d..c9725f2e7e 100644 --- a/video/out/hwdec/hwdec_drmprime_overlay.c +++ b/video/out/hwdec/hwdec_drmprime_overlay.c @@ -30,6 +30,7 @@ #include "common/msg.h" #include "options/m_config.h" #include "libmpv/render_gl.h" +#include "video/out/drm_atomic.h" #include "video/out/drm_common.h" #include "video/out/drm_prime.h" #include "video/out/gpu/hwdec.h" @@ -252,8 +253,8 @@ static int init(struct ra_hwdec *hw) void *tmp = talloc_new(NULL); struct drm_opts *opts = mp_get_config_group(tmp, hw->global, &drm_conf); - draw_plane = opts->drm_draw_plane; - drmprime_video_plane = opts->drm_drmprime_video_plane; + draw_plane = opts->draw_plane; + drmprime_video_plane = opts->drmprime_video_plane; talloc_free(tmp); struct mpv_opengl_drm_params_v2 *drm_params; diff --git a/video/out/opengl/context_drm_egl.c b/video/out/opengl/context_drm_egl.c index 022a2d2a45..79ef10f2f6 100644 --- a/video/out/opengl/context_drm_egl.c +++ b/video/out/opengl/context_drm_egl.c @@ -29,6 +29,7 @@ #include #include "libmpv/render_gl.h" +#include "video/out/drm_atomic.h" #include "video/out/drm_common.h" #include "common/common.h" #include "osdep/timer.h" @@ -45,28 +46,19 @@ #define EGL_PLATFORM_GBM_KHR 0x31D7 #endif -struct framebuffer -{ - int fd; - uint32_t width, height; - uint32_t id; -}; - struct gbm_frame { struct gbm_bo *bo; struct drm_vsync_tuple vsync; }; -struct gbm -{ +struct gbm { struct gbm_surface *surface; struct gbm_device *device; struct gbm_frame **bo_queue; unsigned int num_bos; }; -struct egl -{ +struct egl { EGLDisplay display; EGLContext context; EGLSurface surface; @@ -74,13 +66,9 @@ struct egl struct priv { GL gl; - struct kms *kms; - - drmEventContext ev; struct egl egl; struct gbm gbm; - struct framebuffer *fb; GLsync *vsync_fences; unsigned int num_vsync_fences; @@ -89,18 +77,6 @@ struct priv { uint64_t *gbm_modifiers; unsigned int num_gbm_modifiers; - bool active; - bool waiting_for_flip; - - bool vt_switcher_active; - struct vt_switcher vt_switcher; - - bool still; - bool paused; - - struct drm_vsync_tuple vsync; - struct vo_vsync_info vsync_info; - struct mpv_opengl_drm_params_v2 drm_params; struct mpv_opengl_drm_draw_surface_size draw_surface_size; }; @@ -245,8 +221,9 @@ static bool init_egl(struct ra_ctx *ctx) static bool init_gbm(struct ra_ctx *ctx) { struct priv *p = ctx->priv; + struct vo_drm_state *drm = ctx->vo->drm; MP_VERBOSE(ctx->vo, "Creating GBM device\n"); - p->gbm.device = gbm_create_device(p->kms->fd); + p->gbm.device = gbm_create_device(drm->fd); if (!p->gbm.device) { MP_ERR(ctx->vo, "Failed to create GBM device.\n"); return false; @@ -288,14 +265,15 @@ static void framebuffer_destroy_callback(struct gbm_bo *bo, void *data) static void update_framebuffer_from_bo(struct ra_ctx *ctx, struct gbm_bo *bo) { struct priv *p = ctx->priv; + struct vo_drm_state *drm = ctx->vo->drm; struct framebuffer *fb = gbm_bo_get_user_data(bo); if (fb) { - p->fb = fb; + drm->fb = fb; return; } fb = talloc_zero(ctx, struct framebuffer); - fb->fd = p->kms->fd; + fb->fd = drm->fd; fb->width = gbm_bo_get_width(bo); fb->height = gbm_bo_get_height(bo); uint64_t modifier = gbm_bo_get_modifier(bo); @@ -335,210 +313,50 @@ static void update_framebuffer_from_bo(struct ra_ctx *ctx, struct gbm_bo *bo) 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->active = true; - - struct drm_atomic_context *atomic_ctx = p->kms->atomic_context; - - if (!drm_atomic_save_old_state(atomic_ctx)) { - MP_WARN(ctx->vo, "Failed to save old DRM atomic state\n"); - } - - drmModeAtomicReqPtr request = drmModeAtomicAlloc(); - if (!request) { - MP_ERR(ctx->vo, "Failed to allocate drm atomic request\n"); - return false; - } - - if (drm_object_set_property(request, atomic_ctx->connector, "CRTC_ID", p->kms->crtc_id) < 0) { - MP_ERR(ctx->vo, "Could not set CRTC_ID on connector\n"); - return false; - } - - if (!drm_mode_ensure_blob(p->kms->fd, &p->kms->mode)) { - MP_ERR(ctx->vo, "Failed to create DRM mode blob\n"); - goto err; - } - if (drm_object_set_property(request, atomic_ctx->crtc, "MODE_ID", p->kms->mode.blob_id) < 0) { - MP_ERR(ctx->vo, "Could not set MODE_ID on crtc\n"); - goto err; - } - if (drm_object_set_property(request, atomic_ctx->crtc, "ACTIVE", 1) < 0) { - MP_ERR(ctx->vo, "Could not set ACTIVE on crtc\n"); - goto err; - } - - /* - * VRR related properties were added in kernel 5.0. We will not fail if we - * cannot query or set the value, but we will log as appropriate. - */ - uint64_t vrr_capable = 0; - drm_object_get_property(atomic_ctx->connector, "VRR_CAPABLE", &vrr_capable); - MP_VERBOSE(ctx->vo, "crtc is%s VRR capable\n", vrr_capable ? "" : " not"); - - uint64_t vrr_requested = ctx->vo->opts->drm_opts->drm_vrr_enabled; - if (vrr_requested == 1 || (vrr_capable && vrr_requested == -1)) { - if (drm_object_set_property(request, atomic_ctx->crtc, "VRR_ENABLED", 1) < 0) { - MP_WARN(ctx->vo, "Could not enable VRR on crtc\n"); - } else { - MP_VERBOSE(ctx->vo, "Enabled VRR on crtc\n"); - } - } - - drm_object_set_property(request, atomic_ctx->draw_plane, "FB_ID", p->fb->id); - drm_object_set_property(request, atomic_ctx->draw_plane, "CRTC_ID", p->kms->crtc_id); - drm_object_set_property(request, atomic_ctx->draw_plane, "SRC_X", 0); - drm_object_set_property(request, atomic_ctx->draw_plane, "SRC_Y", 0); - drm_object_set_property(request, atomic_ctx->draw_plane, "SRC_W", p->draw_surface_size.width << 16); - drm_object_set_property(request, atomic_ctx->draw_plane, "SRC_H", p->draw_surface_size.height << 16); - drm_object_set_property(request, atomic_ctx->draw_plane, "CRTC_X", 0); - drm_object_set_property(request, atomic_ctx->draw_plane, "CRTC_Y", 0); - drm_object_set_property(request, atomic_ctx->draw_plane, "CRTC_W", p->kms->mode.mode.hdisplay); - drm_object_set_property(request, atomic_ctx->draw_plane, "CRTC_H", p->kms->mode.mode.vdisplay); - - int ret = drmModeAtomicCommit(p->kms->fd, request, DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); - if (ret) - MP_ERR(ctx->vo, "Failed to commit ModeSetting atomic request (%d)\n", ret); - - drmModeAtomicFree(request); - return ret == 0; - - err: - drmModeAtomicFree(request); - return false; -} - -static void crtc_release(struct ra_ctx *ctx) -{ - struct priv *p = ctx->priv; - if (!p->active) - return; - p->active = false; - - if (!p->kms->atomic_context->old_state.saved) - return; - - bool success = true; - struct drm_atomic_context *atomic_ctx = p->kms->atomic_context; - drmModeAtomicReqPtr request = drmModeAtomicAlloc(); - if (!request) { - MP_ERR(ctx->vo, "Failed to allocate drm atomic request\n"); - success = false; - } - - if (request && !drm_atomic_restore_old_state(request, atomic_ctx)) { - MP_WARN(ctx->vo, "Got error while restoring old state\n"); - success = false; - } - - if (request) { - int ret = drmModeAtomicCommit(p->kms->fd, request, - DRM_MODE_ATOMIC_ALLOW_MODESET, NULL); - success = ret == 0; - if (!success) - MP_WARN(ctx->vo, "Failed to commit ModeSetting atomic request (%d)\n", ret); - } - - if (request) - drmModeAtomicFree(request); - - if (!success) - MP_ERR(ctx->vo, "Failed to restore previous mode\n"); -} - -static void release_vt(void *data) -{ - struct ra_ctx *ctx = data; - MP_VERBOSE(ctx->vo, "Releasing VT\n"); - crtc_release(ctx); - - const 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"); - - const 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); + drm->fb = fb; } static void queue_flip(struct ra_ctx *ctx, struct gbm_frame *frame) { - struct priv *p = ctx->priv; - struct drm_atomic_context *atomic_ctx = p->kms->atomic_context; - int ret; + struct vo_drm_state *drm = ctx->vo->drm; update_framebuffer_from_bo(ctx, frame->bo); // Alloc and fill the data struct for the page flip callback struct drm_pflip_cb_closure *data = talloc(ctx, struct drm_pflip_cb_closure); data->frame_vsync = &frame->vsync; - data->vsync = &p->vsync; - data->vsync_info = &p->vsync_info; - data->waiting_for_flip = &p->waiting_for_flip; - data->log = ctx->log; + data->vsync = &drm->vsync; + data->vsync_info = &drm->vsync_info; + data->waiting_for_flip = &drm->waiting_for_flip; + data->log = drm->log; - drm_object_set_property(atomic_ctx->request, atomic_ctx->draw_plane, "FB_ID", p->fb->id); + struct drm_atomic_context *atomic_ctx = drm->atomic_context; + drm_object_set_property(atomic_ctx->request, atomic_ctx->draw_plane, "FB_ID", drm->fb->id); drm_object_set_property(atomic_ctx->request, atomic_ctx->draw_plane, "CRTC_ID", atomic_ctx->crtc->id); drm_object_set_property(atomic_ctx->request, atomic_ctx->draw_plane, "ZPOS", 1); - ret = drmModeAtomicCommit(p->kms->fd, atomic_ctx->request, - DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, data); + int ret = drmModeAtomicCommit(drm->fd, atomic_ctx->request, + DRM_MODE_ATOMIC_NONBLOCK | DRM_MODE_PAGE_FLIP_EVENT, data); + if (ret) { - MP_WARN(ctx->vo, "Failed to commit atomic request (%d)\n", ret); + MP_WARN(ctx->vo, "Failed to commit atomic request: %s\n", mp_strerror(ret)); talloc_free(data); } - p->waiting_for_flip = !ret; + drm->waiting_for_flip = !ret; drmModeAtomicFree(atomic_ctx->request); atomic_ctx->request = drmModeAtomicAlloc(); } -static void wait_on_flip(struct ra_ctx *ctx) -{ - struct priv *p = ctx->priv; - - // poll page flip finish event - while (p->waiting_for_flip) { - 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) { - const int ret = drmHandleEvent(p->kms->fd, &p->ev); - if (ret != 0) { - MP_ERR(ctx->vo, "drmHandleEvent failed: %i\n", ret); - return; - } - } - } -} - static void enqueue_bo(struct ra_ctx *ctx, struct gbm_bo *bo) { struct priv *p = ctx->priv; + struct vo_drm_state *drm = ctx->vo->drm; - p->vsync.sbc++; + drm->vsync.sbc++; struct gbm_frame *new_frame = talloc(p, struct gbm_frame); new_frame->bo = bo; - new_frame->vsync = p->vsync; + new_frame->vsync = drm->vsync; MP_TARRAY_APPEND(p, p->gbm.bo_queue, p->gbm.num_bos, new_frame); } @@ -588,10 +406,11 @@ static bool drm_egl_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo) { struct ra_ctx *ctx = sw->ctx; struct priv *p = ctx->priv; + struct vo_drm_state *drm = ctx->vo->drm; - if (!p->kms->atomic_context->request) { - p->kms->atomic_context->request = drmModeAtomicAlloc(); - p->drm_params.atomic_request_ptr = &p->kms->atomic_context->request; + if (!drm->atomic_context->request) { + drm->atomic_context->request = drmModeAtomicAlloc(); + p->drm_params.atomic_request_ptr = &drm->atomic_context->request; } return ra_gl_ctx_start_frame(sw, out_fbo); @@ -600,9 +419,9 @@ static bool drm_egl_start_frame(struct ra_swapchain *sw, struct ra_fbo *out_fbo) static bool drm_egl_submit_frame(struct ra_swapchain *sw, const struct vo_frame *frame) { struct ra_ctx *ctx = sw->ctx; - struct priv *p = ctx->priv; + struct vo_drm_state *drm = ctx->vo->drm; - p->still = frame->still; + drm->still = frame->still; return ra_gl_ctx_submit_frame(sw, frame); } @@ -611,9 +430,10 @@ static void drm_egl_swap_buffers(struct ra_swapchain *sw) { struct ra_ctx *ctx = sw->ctx; struct priv *p = ctx->priv; - const bool drain = p->paused || p->still; // True when we need to drain the swapchain + struct vo_drm_state *drm = ctx->vo->drm; + const bool drain = drm->paused || drm->still; // True when we need to drain the swapchain - if (!p->active) + if (!drm->active) return; wait_fence(ctx); @@ -630,8 +450,8 @@ static void drm_egl_swap_buffers(struct ra_swapchain *sw) while (drain || p->gbm.num_bos > ctx->vo->opts->swapchain_depth || !gbm_surface_has_free_buffers(p->gbm.surface)) { - if (p->waiting_for_flip) { - wait_on_flip(ctx); + if (drm->waiting_for_flip) { + vo_drm_wait_on_flip(drm); swapchain_step(ctx); } if (p->gbm.num_bos <= 1) @@ -654,19 +474,17 @@ static const struct ra_swapchain_fns drm_egl_swapchain = { static void drm_egl_uninit(struct ra_ctx *ctx) { struct priv *p = ctx->priv; - struct drm_atomic_context *atomic_ctx = p->kms->atomic_context; + struct vo_drm_state *drm = ctx->vo->drm; + struct drm_atomic_context *atomic_ctx = drm->atomic_context; + + if (drmModeAtomicCommit(drm->fd, atomic_ctx->request, 0, NULL)) + MP_ERR(ctx->vo, "Failed to commit atomic request: %s\n", mp_strerror(errno)); - 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); + vo_drm_uninit(ctx->vo); ra_gl_ctx_uninit(ctx); - crtc_release(ctx); - if (p->vt_switcher_active) - vt_switcher_destroy(&p->vt_switcher); - // According to GBM documentation all BO:s must be released before // gbm_surface_destroy can be called on the surface. while (p->gbm.num_bos) { @@ -684,11 +502,6 @@ static void drm_egl_uninit(struct ra_ctx *ctx) eglDestroyContext(p->egl.display, p->egl.context); close(p->drm_params.render_fd); - - if (p->kms) { - kms_destroy(p->kms); - p->kms = 0; - } } // If the draw plane supports ARGB we want to use that, but if it doesn't we @@ -699,9 +512,9 @@ static void drm_egl_uninit(struct ra_ctx *ctx) static bool probe_gbm_format(struct ra_ctx *ctx, uint32_t argb_format, uint32_t xrgb_format) { struct priv *p = ctx->priv; + struct vo_drm_state *drm = ctx->vo->drm; - drmModePlane *drmplane = - drmModeGetPlane(p->kms->fd, p->kms->atomic_context->draw_plane->id); + drmModePlane *drmplane = drmModeGetPlane(drm->fd, drm->atomic_context->draw_plane->id); bool have_argb = false; bool have_xrgb = false; bool result = false; @@ -731,9 +544,10 @@ static bool probe_gbm_format(struct ra_ctx *ctx, uint32_t argb_format, uint32_t static bool probe_gbm_modifiers(struct ra_ctx *ctx) { struct priv *p = ctx->priv; + struct vo_drm_state *drm = ctx->vo->drm; - drmModePropertyBlobPtr blob = - drm_object_get_property_blob(p->kms->atomic_context->draw_plane, "IN_FORMATS"); + drmModePropertyBlobPtr blob = drm_object_get_property_blob(drm->atomic_context->draw_plane, + "IN_FORMATS"); if (!blob) { MP_VERBOSE(ctx->vo, "Failed to find IN_FORMATS property\n"); return false; @@ -769,47 +583,31 @@ static bool probe_gbm_modifiers(struct ra_ctx *ctx) static void drm_egl_get_vsync(struct ra_ctx *ctx, struct vo_vsync_info *info) { - struct priv *p = ctx->priv; - *info = p->vsync_info; + vo_drm_get_vsync(ctx->vo, info); } static bool drm_egl_init(struct ra_ctx *ctx) { + if (!vo_drm_init(ctx->vo)) + goto err; + struct priv *p = ctx->priv = talloc_zero(ctx, struct priv); - p->ev.version = DRM_EVENT_CONTEXT_VERSION; - p->ev.page_flip_handler = &drm_pflip_cb; + struct vo_drm_state *drm = ctx->vo->drm; - 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); + if (ctx->vo->drm->opts->draw_surface_size.wh_valid) { + p->draw_surface_size.width = ctx->vo->drm->opts->draw_surface_size.w; + p->draw_surface_size.height = ctx->vo->drm->opts->draw_surface_size.h; } else { - MP_WARN(ctx, "Failed to set up VT switcher. Terminal switching will be unavailable.\n"); + p->draw_surface_size.width = drm->mode.mode.hdisplay; + p->draw_surface_size.height = drm->mode.mode.vdisplay; } - MP_VERBOSE(ctx, "Initializing KMS\n"); - p->kms = kms_create(ctx->log, - ctx->vo->opts->drm_opts->drm_device_path, - ctx->vo->opts->drm_opts->drm_connector_spec, - ctx->vo->opts->drm_opts->drm_mode_spec, - ctx->vo->opts->drm_opts->drm_draw_plane, - ctx->vo->opts->drm_opts->drm_drmprime_video_plane); - if (!p->kms) { - MP_ERR(ctx, "Failed to create KMS.\n"); - return false; - } - - if (ctx->vo->opts->drm_opts->drm_draw_surface_size.wh_valid) { - p->draw_surface_size.width = ctx->vo->opts->drm_opts->drm_draw_surface_size.w; - p->draw_surface_size.height = ctx->vo->opts->drm_opts->drm_draw_surface_size.h; - } else { - p->draw_surface_size.width = p->kms->mode.mode.hdisplay; - p->draw_surface_size.height = p->kms->mode.mode.vdisplay; - } + drm->width = p->draw_surface_size.width; + drm->height = p->draw_surface_size.height; uint32_t argb_format; uint32_t xrgb_format; - switch (ctx->vo->opts->drm_opts->drm_format) { + switch (ctx->vo->drm->opts->drm_format) { case DRM_OPTS_FORMAT_XRGB2101010: argb_format = GBM_FORMAT_ARGB2101010; xrgb_format = GBM_FORMAT_XRGB2101010; @@ -831,7 +629,7 @@ static bool drm_egl_init(struct ra_ctx *ctx) if (!probe_gbm_format(ctx, argb_format, xrgb_format)) { MP_ERR(ctx->vo, "No suitable format found on draw plane (tried: %s and %s).\n", gbm_format_to_string(argb_format), gbm_format_to_string(xrgb_format)); - return false; + goto err; } // It is not fatal if this fails. We'll just try without modifiers. @@ -839,18 +637,18 @@ static bool drm_egl_init(struct ra_ctx *ctx) if (!init_gbm(ctx)) { MP_ERR(ctx->vo, "Failed to setup GBM.\n"); - return false; + goto err; } if (!init_egl(ctx)) { MP_ERR(ctx->vo, "Failed to setup EGL.\n"); - return false; + goto err; } 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; + goto err; } mpegl_load_functions(&p->gl, ctx->vo->log); @@ -861,27 +659,29 @@ static bool drm_egl_init(struct ra_ctx *ctx) struct gbm_bo *new_bo = gbm_surface_lock_front_buffer(p->gbm.surface); if (!new_bo) { MP_ERR(ctx, "Failed to lock GBM surface.\n"); - return false; + goto err; } enqueue_bo(ctx, new_bo); update_framebuffer_from_bo(ctx, new_bo); - if (!p->fb || !p->fb->id) { + if (!drm->fb || !drm->fb->id) { MP_ERR(ctx, "Failed to create framebuffer.\n"); - return false; + goto err; } - if (!crtc_setup(ctx)) { + if (!vo_drm_acquire_crtc(ctx->vo->drm)) { MP_ERR(ctx, "Failed to set CRTC for connector %u: %s\n", - p->kms->connector->connector_id, mp_strerror(errno)); - return false; + drm->connector->connector_id, mp_strerror(errno)); + goto err; } - p->drm_params.fd = p->kms->fd; - p->drm_params.crtc_id = p->kms->crtc_id; - p->drm_params.connector_id = p->kms->connector->connector_id; - p->drm_params.atomic_request_ptr = &p->kms->atomic_context->request; - char *rendernode_path = drmGetRenderDeviceNameFromFd(p->kms->fd); + vo_drm_set_monitor_par(ctx->vo); + + p->drm_params.fd = drm->fd; + p->drm_params.crtc_id = drm->crtc_id; + p->drm_params.connector_id = drm->connector->connector_id; + p->drm_params.atomic_request_ptr = &drm->atomic_context->request; + char *rendernode_path = drmGetRenderDeviceNameFromFd(drm->fd); if (rendernode_path) { MP_VERBOSE(ctx, "Opening render node \"%s\"\n", rendernode_path); p->drm_params.render_fd = open(rendernode_path, O_RDWR | O_CLOEXEC); @@ -900,85 +700,42 @@ static bool drm_egl_init(struct ra_ctx *ctx) .get_vsync = &drm_egl_get_vsync, }; if (!ra_gl_ctx_init(ctx, &p->gl, params)) - return false; + goto err; ra_add_native_resource(ctx->ra, "drm_params_v2", &p->drm_params); ra_add_native_resource(ctx->ra, "drm_draw_surface_size", &p->draw_surface_size); - if (ctx->vo->opts->force_monitor_aspect != 0.0) { - ctx->vo->monitor_par = p->fb->width / (double) p->fb->height / - ctx->vo->opts->force_monitor_aspect; - } else { - ctx->vo->monitor_par = 1 / ctx->vo->opts->monitor_pixel_aspect; - } - - mp_verbose(ctx->vo->log, "Monitor pixel aspect: %g\n", ctx->vo->monitor_par); - - p->vsync_info.vsync_duration = 0; - p->vsync_info.skipped_vsyncs = -1; - p->vsync_info.last_queue_display_time = -1; - return true; + +err: + drm_egl_uninit(ctx); + return false; } 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); + struct vo_drm_state *drm = ctx->vo->drm; + ctx->vo->dwidth = drm->fb->width; + ctx->vo->dheight = drm->fb->height; + ra_gl_ctx_resize(ctx->swapchain, drm->fb->width, drm->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; - } - case VOCTRL_GET_DISPLAY_RES: { - ((int *)arg)[0] = p->kms->mode.mode.hdisplay; - ((int *)arg)[1] = p->kms->mode.mode.vdisplay; - return VO_TRUE; - } - case VOCTRL_PAUSE: - ctx->vo->want_redraw = true; - p->paused = true; - return VO_TRUE; - case VOCTRL_RESUME: - p->paused = false; - p->vsync_info.last_queue_display_time = -1; - p->vsync_info.skipped_vsyncs = 0; - p->vsync.ust = 0; - p->vsync.msc = 0; - return VO_TRUE; - } - return VO_NOTIMPL; + int ret = vo_drm_control(ctx->vo, events, request, arg); + return ret; } -static void wait_events(struct ra_ctx *ctx, int64_t until_time_us) +static void drm_egl_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); - } + vo_drm_wait_events(ctx->vo, until_time_us); } -static void wakeup(struct ra_ctx *ctx) +static void drm_egl_wakeup(struct ra_ctx *ctx) { - struct priv *p = ctx->priv; - if (p->vt_switcher_active) - vt_switcher_interrupt_poll(&p->vt_switcher); + vo_drm_wakeup(ctx->vo); } const struct ra_ctx_fns ra_ctx_drm_egl = { @@ -988,6 +745,6 @@ const struct ra_ctx_fns ra_ctx_drm_egl = { .control = drm_egl_control, .init = drm_egl_init, .uninit = drm_egl_uninit, - .wait_events = wait_events, - .wakeup = wakeup, + .wait_events = drm_egl_wait_events, + .wakeup = drm_egl_wakeup, }; diff --git a/video/out/vo.h b/video/out/vo.h index 98e13cd890..05882064a2 100644 --- a/video/out/vo.h +++ b/video/out/vo.h @@ -458,6 +458,7 @@ struct vo { struct vo_cocoa_state *cocoa; struct vo_wayland_state *wl; struct vo_android_state *android; + struct vo_drm_state *drm; struct mp_hwdec_devices *hwdec_devs; struct input_ctx *input_ctx; struct osd_state *osd; diff --git a/video/out/vo_drm.c b/video/out/vo_drm.c index d2ec8355e5..1b1fe81e05 100644 --- a/video/out/vo_drm.c +++ b/video/out/vo_drm.c @@ -26,9 +26,9 @@ #include #include -#include "drm_common.h" - #include "common/msg.h" +#include "drm_atomic.h" +#include "drm_common.h" #include "osdep/timer.h" #include "sub/osd.h" #include "video/fmt-conversion.h" @@ -46,50 +46,18 @@ #define BYTES_PER_PIXEL 4 #define BITS_PER_PIXEL 32 -struct framebuffer { - uint32_t width; - uint32_t height; - uint32_t stride; - uint32_t size; - uint32_t handle; - uint8_t *map; - uint32_t fb; -}; - -struct kms_frame { +struct drm_frame { struct framebuffer *fb; struct drm_vsync_tuple vsync; }; struct priv { - char *connector_spec; - int mode_id; - - struct kms *kms; - drmModeCrtc *old_crtc; - drmEventContext ev; - - bool vt_switcher_active; - struct vt_switcher vt_switcher; - - int swapchain_depth; - unsigned int buf_count; - struct framebuffer *bufs; - int front_buf; - bool active; - bool waiting_for_flip; - bool still; - bool paused; - - struct kms_frame **fb_queue; + struct drm_frame **fb_queue; unsigned int fb_queue_len; - struct framebuffer *cur_fb; uint32_t drm_format; enum mp_imgfmt imgfmt; - int32_t screen_w; - int32_t screen_h; struct mp_image *last_input; struct mp_image *cur_frame; struct mp_image *cur_frame_cropped; @@ -98,53 +66,73 @@ struct priv { struct mp_osd_res osd; struct mp_sws_context *sws; - struct drm_vsync_tuple vsync; - struct vo_vsync_info vsync_info; + struct framebuffer **bufs; + int front_buf; + int buf_count; }; -static void fb_destroy(int fd, struct framebuffer *buf) +static void destroy_framebuffer(int fd, struct framebuffer *fb) { - if (buf->map) { - munmap(buf->map, buf->size); + if (!fb) + return; + + if (fb->map) { + munmap(fb->map, fb->size); } - if (buf->fb) { - drmModeRmFB(fd, buf->fb); + if (fb->id) { + drmModeRmFB(fd, fb->id); } - if (buf->handle) { + if (fb->handle) { struct drm_mode_destroy_dumb dreq = { - .handle = buf->handle, + .handle = fb->handle, }; drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &dreq); } } -static bool fb_setup_single(struct vo *vo, int fd, struct framebuffer *buf) +static struct framebuffer *setup_framebuffer(struct vo *vo) { struct priv *p = vo->priv; + struct vo_drm_state *drm = vo->drm; - buf->handle = 0; + struct framebuffer *fb = talloc_zero(drm, struct framebuffer); + fb->width = drm->mode.mode.hdisplay; + fb->height = drm->mode.mode.vdisplay; + fb->fd = drm->fd; + fb->handle = 0; // create dumb buffer struct drm_mode_create_dumb creq = { - .width = buf->width, - .height = buf->height, + .width = fb->width, + .height = fb->height, .bpp = BITS_PER_PIXEL, }; - if (drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq) < 0) { + + if (drmIoctl(drm->fd, DRM_IOCTL_MODE_CREATE_DUMB, &creq) < 0) { MP_ERR(vo, "Cannot create dumb buffer: %s\n", mp_strerror(errno)); goto err; } - buf->stride = creq.pitch; - buf->size = creq.size; - buf->handle = creq.handle; + + fb->stride = creq.pitch; + fb->size = creq.size; + fb->handle = creq.handle; + + // select format + if (drm->opts->drm_format == DRM_OPTS_FORMAT_XRGB2101010) { + p->drm_format = DRM_FORMAT_XRGB2101010; + p->imgfmt = IMGFMT_XRGB2101010; + } else { + p->drm_format = DRM_FORMAT_XRGB8888;; + p->imgfmt = IMGFMT_XRGB8888; + } // create framebuffer object for the dumb-buffer - int ret = drmModeAddFB2(fd, buf->width, buf->height, + int ret = drmModeAddFB2(fb->fd, fb->width, fb->height, p->drm_format, - (uint32_t[4]){buf->handle, 0, 0, 0}, - (uint32_t[4]){buf->stride, 0, 0, 0}, + (uint32_t[4]){fb->handle, 0, 0, 0}, + (uint32_t[4]){fb->stride, 0, 0, 0}, (uint32_t[4]){0, 0, 0, 0}, - &buf->fb, 0); + &fb->id, 0); if (ret) { MP_ERR(vo, "Cannot create framebuffer: %s\n", mp_strerror(errno)); goto err; @@ -152,151 +140,36 @@ static bool fb_setup_single(struct vo *vo, int fd, struct framebuffer *buf) // prepare buffer for memory mapping struct drm_mode_map_dumb mreq = { - .handle = buf->handle, + .handle = fb->handle, }; - if (drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq)) { + if (drmIoctl(drm->fd, DRM_IOCTL_MODE_MAP_DUMB, &mreq)) { MP_ERR(vo, "Cannot map dumb buffer: %s\n", mp_strerror(errno)); goto err; } // perform actual memory mapping - buf->map = mmap(0, buf->size, PROT_READ | PROT_WRITE, MAP_SHARED, - fd, mreq.offset); - if (buf->map == MAP_FAILED) { + fb->map = mmap(0, fb->size, PROT_READ | PROT_WRITE, MAP_SHARED, + drm->fd, mreq.offset); + if (fb->map == MAP_FAILED) { MP_ERR(vo, "Cannot map dumb buffer: %s\n", mp_strerror(errno)); goto err; } - memset(buf->map, 0, buf->size); - return true; + memset(fb->map, 0, fb->size); + return fb; err: - fb_destroy(fd, buf); - return false; -} - -static bool fb_setup_buffers(struct vo *vo) -{ - struct priv *p = vo->priv; - - p->bufs = talloc_zero_array(p, struct framebuffer, p->buf_count); - - p->front_buf = 0; - for (unsigned int i = 0; i < p->buf_count; i++) { - p->bufs[i].width = p->kms->mode.mode.hdisplay; - p->bufs[i].height = p->kms->mode.mode.vdisplay; - } - - for (unsigned int i = 0; i < p->buf_count; i++) { - if (!fb_setup_single(vo, p->kms->fd, &p->bufs[i])) { - MP_ERR(vo, "Cannot create framebuffer\n"); - for (unsigned int j = 0; j < i; j++) { - fb_destroy(p->kms->fd, &p->bufs[j]); - } - return false; - } - } - - p->cur_fb = &p->bufs[0]; - - return true; -} - -static void get_vsync(struct vo *vo, struct vo_vsync_info *info) -{ - struct priv *p = vo->priv; - *info = p->vsync_info; -} - -static bool crtc_setup(struct vo *vo) -{ - struct priv *p = vo->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->cur_fb->fb, - 0, 0, &p->kms->connector->connector_id, 1, - &p->kms->mode.mode); - p->active = true; - return ret == 0; -} - -static void crtc_release(struct vo *vo) -{ - struct priv *p = vo->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(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 vo *vo = data; - crtc_release(vo); - - const struct priv *p = vo->priv; - if (drmDropMaster(p->kms->fd)) { - MP_WARN(vo, "Failed to drop DRM master: %s\n", mp_strerror(errno)); - } -} - -static void acquire_vt(void *data) -{ - struct vo *vo = data; - const struct priv *p = vo->priv; - if (drmSetMaster(p->kms->fd)) { - MP_WARN(vo, "Failed to acquire DRM master: %s\n", mp_strerror(errno)); - } - - crtc_setup(vo); -} - -static void wait_events(struct vo *vo, int64_t until_time_us) -{ - struct priv *p = vo->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(vo, until_time_us); - } -} - -static void wakeup(struct vo *vo) -{ - struct priv *p = vo->priv; - if (p->vt_switcher_active) - vt_switcher_interrupt_poll(&p->vt_switcher); + destroy_framebuffer(drm->fd, fb); + return NULL; } static int reconfig(struct vo *vo, struct mp_image_params *params) { struct priv *p = vo->priv; + struct vo_drm_state *drm = vo->drm; - vo->dwidth = p->screen_w; - vo->dheight = p->screen_h; + vo->dwidth =drm->fb->width; + vo->dheight = drm->fb->height; vo_get_src_dst_rects(vo, &p->src, &p->dst, &p->osd); int w = p->dst.x1 - p->dst.x0; @@ -312,10 +185,10 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) }; talloc_free(p->cur_frame); - p->cur_frame = mp_image_alloc(p->imgfmt, p->screen_w, p->screen_h); + p->cur_frame = mp_image_alloc(p->imgfmt, drm->fb->width, drm->fb->height); mp_image_params_guess_csp(&p->sws->dst); mp_image_set_params(p->cur_frame, &p->sws->dst); - mp_image_set_size(p->cur_frame, p->screen_w, p->screen_h); + mp_image_set_size(p->cur_frame, drm->fb->width, drm->fb->height); talloc_free(p->cur_frame_cropped); p->cur_frame_cropped = mp_image_new_dummy_ref(p->cur_frame); @@ -327,33 +200,14 @@ static int reconfig(struct vo *vo, struct mp_image_params *params) if (mp_sws_reinit(p->sws) < 0) return -1; - p->vsync_info.vsync_duration = 0; - p->vsync_info.skipped_vsyncs = -1; - p->vsync_info.last_queue_display_time = -1; + drm->vsync_info.vsync_duration = 0; + drm->vsync_info.skipped_vsyncs = -1; + drm->vsync_info.last_queue_display_time = -1; vo->want_redraw = true; return 0; } -static void wait_on_flip(struct vo *vo) -{ - struct priv *p = vo->priv; - - // poll page flip finish event - while (p->waiting_for_flip) { - 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) { - const int ret = drmHandleEvent(p->kms->fd, &p->ev); - if (ret != 0) { - MP_ERR(vo, "drmHandleEvent failed: %i\n", ret); - return; - } - } - } -} - static struct framebuffer *get_new_fb(struct vo *vo) { struct priv *p = vo->priv; @@ -361,14 +215,15 @@ static struct framebuffer *get_new_fb(struct vo *vo) p->front_buf++; p->front_buf %= p->buf_count; - return &p->bufs[p->front_buf]; + return p->bufs[p->front_buf]; } -static void draw_image(struct vo *vo, mp_image_t *mpi, struct framebuffer *front_buf) +static void draw_image(struct vo *vo, mp_image_t *mpi, struct framebuffer *buf) { struct priv *p = vo->priv; + struct vo_drm_state *drm = vo->drm; - if (p->active && front_buf != NULL) { + if (drm->active && buf != NULL) { if (mpi) { struct mp_image src = *mpi; struct mp_rect src_rc = p->src; @@ -396,12 +251,12 @@ static void draw_image(struct vo *vo, mp_image_t *mpi, struct framebuffer *front const int g_padding = p->cur_frame->stride[0]/sizeof(uint16_t) - w; const int b_padding = p->cur_frame->stride[1]/sizeof(uint16_t) - w; const int r_padding = p->cur_frame->stride[2]/sizeof(uint16_t) - w; - const int fbuf_padding = front_buf->stride/sizeof(uint32_t) - w; + const int fbuf_padding = buf->stride/sizeof(uint32_t) - w; uint16_t *g_ptr = (uint16_t*)p->cur_frame->planes[0]; uint16_t *b_ptr = (uint16_t*)p->cur_frame->planes[1]; uint16_t *r_ptr = (uint16_t*)p->cur_frame->planes[2]; - uint32_t *fbuf_ptr = (uint32_t*)front_buf->map; + uint32_t *fbuf_ptr = (uint32_t*)buf->map; for (unsigned y = 0; y < h; ++y) { for (unsigned x = 0; x < w; ++x) { *fbuf_ptr++ = (*r_ptr++ << 20) | (*g_ptr++ << 10) | (*b_ptr++); @@ -412,9 +267,9 @@ static void draw_image(struct vo *vo, mp_image_t *mpi, struct framebuffer *front fbuf_ptr += fbuf_padding; } } else { // p->drm_format == DRM_FORMAT_XRGB8888 - memcpy_pic(front_buf->map, p->cur_frame->planes[0], + memcpy_pic(buf->map, p->cur_frame->planes[0], p->cur_frame->w * BYTES_PER_PIXEL, p->cur_frame->h, - front_buf->stride, + buf->stride, p->cur_frame->stride[0]); } } @@ -428,11 +283,12 @@ static void draw_image(struct vo *vo, mp_image_t *mpi, struct framebuffer *front static void enqueue_frame(struct vo *vo, struct framebuffer *fb) { struct priv *p = vo->priv; + struct vo_drm_state *drm = vo->drm; - p->vsync.sbc++; - struct kms_frame *new_frame = talloc(p, struct kms_frame); + drm->vsync.sbc++; + struct drm_frame *new_frame = talloc(p, struct drm_frame); new_frame->fb = fb; - new_frame->vsync = p->vsync; + new_frame->vsync = drm->vsync; MP_TARRAY_APPEND(p, p->fb_queue, p->fb_queue_len, new_frame); } @@ -455,17 +311,17 @@ static void swapchain_step(struct vo *vo) static void draw_frame(struct vo *vo, struct vo_frame *frame) { + struct vo_drm_state *drm = vo->drm; struct priv *p = vo->priv; - if (!p->active) + if (!drm->active) return; - p->still = frame->still; + drm->still = frame->still; // we redraw the entire image when OSD needs to be redrawn + struct framebuffer *fb = p->bufs[p->front_buf]; const bool repeat = frame->repeat && !frame->redraw; - - struct framebuffer *fb = &p->bufs[p->front_buf]; if (!repeat) { fb = get_new_fb(vo); draw_image(vo, mp_image_new_ref(frame->current), fb); @@ -474,42 +330,42 @@ static void draw_frame(struct vo *vo, struct vo_frame *frame) enqueue_frame(vo, fb); } -static void queue_flip(struct vo *vo, struct kms_frame *frame) +static void queue_flip(struct vo *vo, struct drm_frame *frame) { - int ret = 0; struct priv *p = vo->priv; + struct vo_drm_state *drm = vo->drm; - p->cur_fb = frame->fb; + drm->fb = frame->fb; // Alloc and fill the data struct for the page flip callback struct drm_pflip_cb_closure *data = talloc(p, struct drm_pflip_cb_closure); data->frame_vsync = &frame->vsync; - data->vsync = &p->vsync; - data->vsync_info = &p->vsync_info; - data->waiting_for_flip = &p->waiting_for_flip; + data->vsync = &drm->vsync; + data->vsync_info = &drm->vsync_info; + data->waiting_for_flip = &drm->waiting_for_flip; data->log = vo->log; - ret = drmModePageFlip(p->kms->fd, p->kms->crtc_id, - p->cur_fb->fb, - DRM_MODE_PAGE_FLIP_EVENT, data); + int ret = drmModePageFlip(drm->fd, drm->crtc_id, + drm->fb->id, DRM_MODE_PAGE_FLIP_EVENT, data); if (ret) { MP_WARN(vo, "Failed to queue page flip: %s\n", mp_strerror(errno)); - } else { - p->waiting_for_flip = true; + talloc_free(data); } + drm->waiting_for_flip = !ret; } static void flip_page(struct vo *vo) { struct priv *p = vo->priv; - const bool drain = p->paused || p->still; + struct vo_drm_state *drm = vo->drm; + const bool drain = drm->paused || drm->still; - if (!p->active) + if (!drm->active) return; - while (drain || p->fb_queue_len > p->swapchain_depth) { - if (p->waiting_for_flip) { - wait_on_flip(vo); + while (drain || p->fb_queue_len > vo->opts->swapchain_depth) { + if (drm->waiting_for_flip) { + vo_drm_wait_on_flip(vo->drm); swapchain_step(vo); } if (p->fb_queue_len <= 1) @@ -526,22 +382,17 @@ static void flip_page(struct vo *vo) static void uninit(struct vo *vo) { struct priv *p = vo->priv; + struct vo_drm_state *drm = vo->drm; + int fd = drm->fd; - crtc_release(vo); + vo_drm_uninit(vo); while (p->fb_queue_len > 0) { swapchain_step(vo); } - if (p->kms) { - for (unsigned int i = 0; i < p->buf_count; i++) - fb_destroy(p->kms->fd, &p->bufs[i]); - kms_destroy(p->kms); - p->kms = NULL; - } - - if (p->vt_switcher_active) - vt_switcher_destroy(&p->vt_switcher); + for (int i = 0; i < p->buf_count; ++i) + destroy_framebuffer(fd, p->bufs[i]); talloc_free(p->last_input); talloc_free(p->cur_frame); @@ -551,73 +402,35 @@ static void uninit(struct vo *vo) static int preinit(struct vo *vo) { struct priv *p = vo->priv; + + if (!vo_drm_init(vo)) + goto err; + + struct vo_drm_state *drm = vo->drm; + p->buf_count = vo->opts->swapchain_depth + 1; + p->bufs = talloc_zero_array(p, struct framebuffer *, p->buf_count); + + p->front_buf = 0; + for (int i = 0; i < p->buf_count; i++) { + p->bufs[i] = setup_framebuffer(vo); + if (!p->bufs[i]) + goto err; + } + drm->fb = p->bufs[0]; + + vo->drm->width = vo->drm->fb->width; + vo->drm->height = vo->drm->fb->height; + + if (!vo_drm_acquire_crtc(vo->drm)) { + MP_ERR(vo, "Failed to set CRTC for connector %u: %s\n", + vo->drm->connector->connector_id, mp_strerror(errno)); + goto err; + } + + vo_drm_set_monitor_par(vo); p->sws = mp_sws_alloc(vo); p->sws->log = vo->log; mp_sws_enable_cmdline_opts(p->sws, vo->global); - p->ev.version = DRM_EVENT_CONTEXT_VERSION; - p->ev.page_flip_handler = &drm_pflip_cb; - - p->vt_switcher_active = vt_switcher_init(&p->vt_switcher, vo->log); - if (p->vt_switcher_active) { - vt_switcher_acquire(&p->vt_switcher, acquire_vt, vo); - vt_switcher_release(&p->vt_switcher, release_vt, vo); - } else { - MP_WARN(vo, "Failed to set up VT switcher. Terminal switching will be unavailable.\n"); - } - - p->kms = kms_create(vo->log, - vo->opts->drm_opts->drm_device_path, - vo->opts->drm_opts->drm_connector_spec, - vo->opts->drm_opts->drm_mode_spec, - 0, 0); - if (!p->kms) { - MP_ERR(vo, "Failed to create KMS.\n"); - goto err; - } - - if (vo->opts->drm_opts->drm_format == DRM_OPTS_FORMAT_XRGB2101010) { - p->drm_format = DRM_FORMAT_XRGB2101010; - p->imgfmt = IMGFMT_XRGB2101010; - } else { - p->drm_format = DRM_FORMAT_XRGB8888;; - p->imgfmt = IMGFMT_XRGB8888; - } - - p->swapchain_depth = vo->opts->swapchain_depth; - p->buf_count = p->swapchain_depth + 1; - if (!fb_setup_buffers(vo)) { - MP_ERR(vo, "Failed to set up buffers.\n"); - goto err; - } - - uint64_t has_dumb = 0; - if (drmGetCap(p->kms->fd, DRM_CAP_DUMB_BUFFER, &has_dumb) < 0 - || has_dumb == 0) { - MP_ERR(vo, "Card \"%d\" does not support dumb buffers.\n", - p->kms->card_no); - goto err; - } - - p->screen_w = p->bufs[0].width; - p->screen_h = p->bufs[0].height; - - if (!crtc_setup(vo)) { - MP_ERR(vo, "Cannot set CRTC: %s\n", mp_strerror(errno)); - goto err; - } - - if (vo->opts->force_monitor_aspect != 0.0) { - vo->monitor_par = p->screen_w / (double) p->screen_h / - vo->opts->force_monitor_aspect; - } else { - vo->monitor_par = 1 / vo->opts->monitor_pixel_aspect; - } - mp_verbose(vo->log, "Monitor pixel aspect: %g\n", vo->monitor_par); - - p->vsync_info.vsync_duration = 0; - p->vsync_info.skipped_vsyncs = -1; - p->vsync_info.last_queue_display_time = -1; - return 0; err: @@ -633,6 +446,7 @@ static int query_format(struct vo *vo, int format) static int control(struct vo *vo, uint32_t request, void *arg) { struct priv *p = vo->priv; + switch (request) { case VOCTRL_SCREENSHOT_WIN: *(struct mp_image**)arg = mp_image_new_copy(p->cur_frame); @@ -641,34 +455,13 @@ static int control(struct vo *vo, uint32_t request, void *arg) if (vo->config_ok) reconfig(vo, vo->params); return VO_TRUE; - case VOCTRL_GET_DISPLAY_FPS: { - double fps = kms_get_display_fps(p->kms); - if (fps <= 0) - break; - *(double*)arg = fps; - return VO_TRUE; } - case VOCTRL_GET_DISPLAY_RES: { - ((int *)arg)[0] = p->kms->mode.mode.hdisplay; - ((int *)arg)[1] = p->kms->mode.mode.vdisplay; - return VO_TRUE; - } - case VOCTRL_PAUSE: - vo->want_redraw = true; - p->paused = true; - return VO_TRUE; - case VOCTRL_RESUME: - p->paused = false; - p->vsync_info.last_queue_display_time = -1; - p->vsync_info.skipped_vsyncs = 0; - p->vsync.ust = 0; - p->vsync.msc = 0; - return VO_TRUE; - } - return VO_NOTIMPL; -} -#define OPT_BASE_STRUCT struct priv + int events = 0; + int ret = vo_drm_control(vo, &events, request, arg); + vo_event(vo, events); + return ret; +} const struct vo_driver video_out_drm = { .name = "drm", @@ -679,9 +472,9 @@ const struct vo_driver video_out_drm = { .control = control, .draw_frame = draw_frame, .flip_page = flip_page, - .get_vsync = get_vsync, + .get_vsync = vo_drm_get_vsync, .uninit = uninit, - .wait_events = wait_events, - .wakeup = wakeup, + .wait_events = vo_drm_wait_events, + .wakeup = vo_drm_wakeup, .priv_size = sizeof(struct priv), };