diff --git a/.builds/alpine.yml b/.builds/alpine.yml index 68679403..6df07fab 100644 --- a/.builds/alpine.yml +++ b/.builds/alpine.yml @@ -4,6 +4,7 @@ packages: - ffmpeg-dev - glslang - libinput-dev + - libliftoff-dev - libxkbcommon-dev - mesa-dev - meson diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml index 2d62d0d1..16d89331 100644 --- a/.builds/archlinux.yml +++ b/.builds/archlinux.yml @@ -3,6 +3,7 @@ packages: - clang - ffmpeg - libinput + - libliftoff - libxkbcommon - mesa - meson diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml index a9650d64..872e01fe 100644 --- a/.builds/freebsd.yml +++ b/.builds/freebsd.yml @@ -7,6 +7,7 @@ packages: - devel/pkgconf - graphics/glslang - graphics/libdrm + - graphics/libliftoff - graphics/mesa-libs - graphics/png - graphics/vulkan-headers diff --git a/README.md b/README.md index ae8a6ad9..e269ff14 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ Install dependencies: * pixman * [libseat] (optional, for the session) * [hwdata] (optional, for the DRM backend) +* [libliftoff] (optional, for the DRM backend) If you choose to enable X11 support: @@ -80,4 +81,5 @@ See [CONTRIBUTING.md]. [wrapper libraries]: https://gitlab.freedesktop.org/wlroots/wlroots/-/wikis/Projects-which-use-wlroots#wrapper-libraries [libseat]: https://git.sr.ht/~kennylevinsen/seatd [hwdata]: https://github.com/vcrhonek/hwdata +[libliftoff]: https://gitlab.freedesktop.org/emersion/libliftoff [CONTRIBUTING.md]: https://gitlab.freedesktop.org/wlroots/wlroots/-/blob/master/CONTRIBUTING.md diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index 0dacff23..c8ffaff6 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -93,7 +93,7 @@ static void atomic_add(struct atomic *atom, uint32_t id, uint32_t prop, uint64_t } } -static bool create_mode_blob(struct wlr_drm_backend *drm, +bool create_mode_blob(struct wlr_drm_backend *drm, struct wlr_drm_connector *conn, const struct wlr_drm_connector_state *state, uint32_t *blob_id) { if (!state->active) { @@ -110,7 +110,7 @@ static bool create_mode_blob(struct wlr_drm_backend *drm, return true; } -static bool create_gamma_lut_blob(struct wlr_drm_backend *drm, +bool create_gamma_lut_blob(struct wlr_drm_backend *drm, size_t size, const uint16_t *lut, uint32_t *blob_id) { if (size == 0) { *blob_id = 0; diff --git a/backend/drm/drm.c b/backend/drm/drm.c index f342249f..bfbe6cbd 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -30,6 +30,11 @@ #include "render/swapchain.h" #include "render/wlr_renderer.h" #include "util/env.h" +#include "config.h" + +#if HAVE_LIBLIFTOFF +#include +#endif // Output state which needs a KMS commit to be applied static const uint32_t COMMIT_OUTPUT_STATE = @@ -76,7 +81,20 @@ bool check_drm_features(struct wlr_drm_backend *drm) { return false; } - if (env_parse_bool("WLR_DRM_NO_ATOMIC")) { + if (env_parse_bool("WLR_DRM_FORCE_LIBLIFTOFF")) { +#if HAVE_LIBLIFTOFF + wlr_log(WLR_INFO, + "WLR_DRM_FORCE_LIBLIFTOFF set, forcing libliftoff interface"); + if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_ATOMIC, 1) != 0) { + wlr_log_errno(WLR_ERROR, "drmSetClientCap(ATOMIC) failed"); + return false; + } + drm->iface = &liftoff_iface; +#else + wlr_log(WLR_ERROR, "libliftoff interface not available"); + return false; +#endif + } else if (env_parse_bool("WLR_DRM_NO_ATOMIC")) { wlr_log(WLR_DEBUG, "WLR_DRM_NO_ATOMIC set, forcing legacy DRM interface"); drm->iface = &legacy_iface; @@ -121,6 +139,7 @@ static bool init_plane(struct wlr_drm_backend *drm, p->type = type; p->id = drm_plane->plane_id; p->props = props; + p->initial_crtc_id = drm_plane->crtc_id; for (size_t i = 0; i < drm_plane->count_formats; ++i) { // Force a LINEAR layout for the cursor if the driver doesn't support @@ -263,6 +282,10 @@ bool init_drm_resources(struct wlr_drm_backend *drm) { goto error_crtcs; } + if (drm->iface->init != NULL && !drm->iface->init(drm)) { + goto error_crtcs; + } + drmModeFreeResources(res); return true; @@ -279,6 +302,10 @@ void finish_drm_resources(struct wlr_drm_backend *drm) { return; } + if (drm->iface->finish != NULL) { + drm->iface->finish(drm); + } + for (size_t i = 0; i < drm->num_crtcs; ++i) { struct wlr_drm_crtc *crtc = &drm->crtcs[i]; @@ -295,6 +322,9 @@ void finish_drm_resources(struct wlr_drm_backend *drm) { for (size_t i = 0; i < drm->num_planes; ++i) { struct wlr_drm_plane *plane = &drm->planes[i]; wlr_drm_format_set_finish(&plane->formats); +#if HAVE_LIBLIFTOFF + liftoff_plane_destroy(plane->liftoff); +#endif } free(drm->planes); diff --git a/backend/drm/libliftoff.c b/backend/drm/libliftoff.c new file mode 100644 index 00000000..c6322c73 --- /dev/null +++ b/backend/drm/libliftoff.c @@ -0,0 +1,347 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include + +#include "backend/drm/drm.h" +#include "backend/drm/iface.h" + +static bool init(struct wlr_drm_backend *drm) { + // TODO: lower log level + liftoff_log_set_priority(LIFTOFF_DEBUG); + + int drm_fd = fcntl(drm->fd, F_DUPFD_CLOEXEC, 0); + if (drm_fd < 0) { + wlr_log_errno(WLR_ERROR, "fcntl(F_DUPFD_CLOEXEC) failed"); + return false; + } + + drm->liftoff = liftoff_device_create(drm_fd); + if (!drm->liftoff) { + wlr_log(WLR_ERROR, "Failed to create liftoff device"); + close(drm_fd); + return false; + } + + for (size_t i = 0; i < drm->num_planes; i++) { + struct wlr_drm_plane *plane = &drm->planes[i]; + if (plane->initial_crtc_id != 0) { + continue; + } + plane->liftoff = liftoff_plane_create(drm->liftoff, plane->id); + if (plane->liftoff == NULL) { + wlr_log(WLR_ERROR, "Failed to create liftoff plane"); + return false; + } + } + + for (size_t i = 0; i < drm->num_crtcs; i++) { + struct wlr_drm_crtc *crtc = &drm->crtcs[i]; + + crtc->liftoff = liftoff_output_create(drm->liftoff, crtc->id); + if (!crtc->liftoff) { + wlr_log(WLR_ERROR, "Failed to create liftoff output"); + return false; + } + + if (crtc->primary) { + crtc->primary->liftoff_layer = liftoff_layer_create(crtc->liftoff); + if (!crtc->primary->liftoff_layer) { + wlr_log(WLR_ERROR, "Failed to create liftoff layer for primary plane"); + return false; + } + } + + if (crtc->cursor) { + crtc->cursor->liftoff_layer = liftoff_layer_create(crtc->liftoff); + if (!crtc->cursor->liftoff_layer) { + wlr_log(WLR_ERROR, "Failed to create liftoff layer for cursor plane"); + return false; + } + } + } + + return true; +} + +static bool register_planes_for_crtc(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc) { + // When performing the first modeset on a CRTC, we need to be a bit careful + // when it comes to planes: we don't want to allow libliftoff to make use + // of planes currently already in-use on another CRTC. We need to wait for + // a modeset to happen on the other CRTC before being able to use these. + for (size_t i = 0; i < drm->num_planes; i++) { + struct wlr_drm_plane *plane = &drm->planes[i]; + if (plane->liftoff != NULL || plane->initial_crtc_id != crtc->id) { + continue; + } + plane->liftoff = liftoff_plane_create(drm->liftoff, plane->id); + if (plane->liftoff == NULL) { + wlr_log(WLR_ERROR, "Failed to create liftoff plane"); + return false; + } + } + return true; +} + +static void finish(struct wlr_drm_backend *drm) { + for (size_t i = 0; i < drm->num_crtcs; i++) { + struct wlr_drm_crtc *crtc = &drm->crtcs[i]; + + if (crtc->primary) { + liftoff_layer_destroy(crtc->primary->liftoff_layer); + } + if (crtc->cursor) { + liftoff_layer_destroy(crtc->cursor->liftoff_layer); + } + + liftoff_output_destroy(crtc->liftoff); + } + + liftoff_device_destroy(drm->liftoff); +} + +static bool add_prop(drmModeAtomicReq *req, uint32_t obj, + uint32_t prop, uint64_t val) { + if (drmModeAtomicAddProperty(req, obj, prop, val) < 0) { + wlr_log_errno(WLR_ERROR, "drmModeAtomicAddProperty failed"); + return false; + } + return true; +} + +static void commit_blob(struct wlr_drm_backend *drm, + uint32_t *current, uint32_t next) { + if (*current == next) { + return; + } + if (*current != 0) { + drmModeDestroyPropertyBlob(drm->fd, *current); + } + *current = next; +} + +static void rollback_blob(struct wlr_drm_backend *drm, + uint32_t *current, uint32_t next) { + if (*current == next) { + return; + } + if (next != 0) { + drmModeDestroyPropertyBlob(drm->fd, next); + } +} + +static bool set_plane_props(struct wlr_drm_plane *plane, + int32_t x, int32_t y, uint64_t zpos) { + struct wlr_drm_fb *fb = plane_get_next_fb(plane); + if (fb == NULL) { + wlr_log(WLR_ERROR, "Failed to acquire FB"); + return false; + } + + uint32_t width = fb->wlr_buf->width; + uint32_t height = fb->wlr_buf->height; + + // The SRC_* properties are in 16.16 fixed point + struct liftoff_layer *layer = plane->liftoff_layer; + return liftoff_layer_set_property(layer, "zpos", zpos) == 0 && + liftoff_layer_set_property(layer, "SRC_X", 0) == 0 && + liftoff_layer_set_property(layer, "SRC_Y", 0) == 0 && + liftoff_layer_set_property(layer, "SRC_W", (uint64_t)width << 16) == 0 && + liftoff_layer_set_property(layer, "SRC_H", (uint64_t)height << 16) == 0 && + liftoff_layer_set_property(layer, "CRTC_X", (uint64_t)x) == 0 && + liftoff_layer_set_property(layer, "CRTC_Y", (uint64_t)y) == 0 && + liftoff_layer_set_property(layer, "CRTC_W", width) == 0 && + liftoff_layer_set_property(layer, "CRTC_H", height) == 0 && + liftoff_layer_set_property(layer, "FB_ID", fb->id) == 0; +} + +static bool disable_plane(struct wlr_drm_plane *plane) { + return liftoff_layer_set_property(plane->liftoff_layer, "FB_ID", 0) == 0; +} + +static bool crtc_commit(struct wlr_drm_connector *conn, + const struct wlr_drm_connector_state *state, uint32_t flags, + bool test_only) { + struct wlr_drm_backend *drm = conn->backend; + struct wlr_output *output = &conn->output; + struct wlr_drm_crtc *crtc = conn->crtc; + + bool modeset = state->modeset; + bool active = state->active; + + if (modeset && !register_planes_for_crtc(drm, crtc)) { + return false; + } + + uint32_t mode_id = crtc->mode_id; + if (modeset) { + if (!create_mode_blob(drm, conn, state, &mode_id)) { + return false; + } + } + + uint32_t gamma_lut = crtc->gamma_lut; + if (state->base->committed & WLR_OUTPUT_STATE_GAMMA_LUT) { + // Fallback to legacy gamma interface when gamma properties are not + // available (can happen on older Intel GPUs that support gamma but not + // degamma). + if (crtc->props.gamma_lut == 0) { + if (!drm_legacy_crtc_set_gamma(drm, crtc, + state->base->gamma_lut_size, + state->base->gamma_lut)) { + return false; + } + } else { + if (!create_gamma_lut_blob(drm, state->base->gamma_lut_size, + state->base->gamma_lut, &gamma_lut)) { + return false; + } + } + } + + uint32_t fb_damage_clips = 0; + if ((state->base->committed & WLR_OUTPUT_STATE_DAMAGE) && + pixman_region32_not_empty((pixman_region32_t *)&state->base->damage) && + crtc->primary->props.fb_damage_clips != 0) { + int rects_len; + const pixman_box32_t *rects = pixman_region32_rectangles( + (pixman_region32_t *)&state->base->damage, &rects_len); + if (drmModeCreatePropertyBlob(drm->fd, rects, + sizeof(*rects) * rects_len, &fb_damage_clips) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to create FB_DAMAGE_CLIPS property blob"); + } + } + + bool prev_vrr_enabled = + output->adaptive_sync_status == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED; + bool vrr_enabled = prev_vrr_enabled; + if ((state->base->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) && + drm_connector_supports_vrr(conn)) { + vrr_enabled = state->base->adaptive_sync_enabled; + } + + if (test_only) { + flags |= DRM_MODE_ATOMIC_TEST_ONLY; + } + if (modeset) { + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } else if (!test_only && (state->base->committed & WLR_OUTPUT_STATE_BUFFER)) { + // The wlr_output API requires non-modeset commits with a new buffer to + // wait for the frame event. However compositors often perform + // non-modesets commits without a new buffer without waiting for the + // frame event. In that case we need to make the KMS commit blocking, + // otherwise the kernel will error out with EBUSY. + flags |= DRM_MODE_ATOMIC_NONBLOCK; + } + + drmModeAtomicReq *req = drmModeAtomicAlloc(); + if (req == NULL) { + wlr_log(WLR_ERROR, "drmModeAtomicAlloc failed"); + return false; + } + + bool ok = add_prop(req, conn->id, conn->props.crtc_id, + active ? crtc->id : 0); + if (modeset && active && conn->props.link_status != 0) { + ok = ok && add_prop(req, conn->id, conn->props.link_status, + DRM_MODE_LINK_STATUS_GOOD); + } + if (active && conn->props.content_type != 0) { + ok = ok && add_prop(req, conn->id, conn->props.content_type, + DRM_MODE_CONTENT_TYPE_GRAPHICS); + } + // TODO: set "max bpc" + ok = ok && + add_prop(req, crtc->id, crtc->props.mode_id, mode_id) && + add_prop(req, crtc->id, crtc->props.active, active); + if (active) { + if (crtc->props.gamma_lut != 0) { + ok = ok && add_prop(req, crtc->id, crtc->props.gamma_lut, gamma_lut); + } + if (crtc->props.vrr_enabled != 0) { + ok = ok && add_prop(req, crtc->id, crtc->props.vrr_enabled, vrr_enabled); + } + ok = ok && set_plane_props(crtc->primary, 0, 0, 0); + liftoff_layer_set_property(crtc->primary->liftoff_layer, + "FB_DAMAGE_CLIPS", fb_damage_clips); + if (crtc->cursor) { + if (drm_connector_is_cursor_visible(conn)) { + ok = ok && set_plane_props(crtc->cursor, + conn->cursor_x, conn->cursor_y, 1); + } else { + ok = ok && disable_plane(crtc->cursor); + } + } + } else { + ok = ok && disable_plane(crtc->primary); + if (crtc->cursor) { + ok = ok && disable_plane(crtc->cursor); + } + } + + if (!ok) { + goto out; + } + + int ret = liftoff_output_apply(crtc->liftoff, req, flags); + if (ret != 0) { + wlr_drm_conn_log(conn, test_only ? WLR_DEBUG : WLR_ERROR, + "liftoff_output_apply failed: %s", strerror(-ret)); + ok = false; + goto out; + } + + if (liftoff_layer_needs_composition(crtc->primary->liftoff_layer)) { + wlr_drm_conn_log(conn, WLR_DEBUG, "Failed to scan-out primary plane"); + ok = false; + goto out; + } + if (crtc->cursor && + liftoff_layer_needs_composition(crtc->cursor->liftoff_layer)) { + wlr_drm_conn_log(conn, WLR_DEBUG, "Failed to scan-out cursor plane"); + ok = false; + goto out; + } + + ret = drmModeAtomicCommit(drm->fd, req, flags, drm); + if (ret != 0) { + wlr_drm_conn_log_errno(conn, test_only ? WLR_DEBUG : WLR_ERROR, + "Atomic commit failed"); + ok = false; + goto out; + } + +out: + drmModeAtomicFree(req); + + if (ok && !test_only) { + commit_blob(drm, &crtc->mode_id, mode_id); + commit_blob(drm, &crtc->gamma_lut, gamma_lut); + + if (vrr_enabled != prev_vrr_enabled) { + output->adaptive_sync_status = vrr_enabled ? + WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED : + WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED; + wlr_drm_conn_log(conn, WLR_DEBUG, "VRR %s", + vrr_enabled ? "enabled" : "disabled"); + } + } else { + rollback_blob(drm, &crtc->mode_id, mode_id); + rollback_blob(drm, &crtc->gamma_lut, gamma_lut); + } + + if (fb_damage_clips != 0 && + drmModeDestroyPropertyBlob(drm->fd, fb_damage_clips) != 0) { + wlr_log_errno(WLR_ERROR, "Failed to destroy FB_DAMAGE_CLIPS property blob"); + } + + return ok; +} + +const struct wlr_drm_interface liftoff_iface = { + .init = init, + .finish = finish, + .crtc_commit = crtc_commit, +}; diff --git a/backend/drm/meson.build b/backend/drm/meson.build index 9abf1350..8d1c1845 100644 --- a/backend/drm/meson.build +++ b/backend/drm/meson.build @@ -5,6 +5,13 @@ hwdata = dependency( not_found_message: 'Required for the DRM backend.', ) +libliftoff = dependency( + 'libliftoff', + version: '>=0.2.0', + fallback: 'libliftoff', + required: false, +) + if not (hwdata.found() and features['session']) subdir_done() endif @@ -32,4 +39,10 @@ wlr_files += files( 'util.c', ) +if libliftoff.found() + wlr_files += files('libliftoff.c') +endif + features += { 'drm-backend': true } +internal_features += { 'libliftoff': libliftoff.found() } +wlr_deps += libliftoff diff --git a/docs/env_vars.md b/docs/env_vars.md index 39daf9fd..e36cdc78 100644 --- a/docs/env_vars.md +++ b/docs/env_vars.md @@ -24,6 +24,8 @@ wlroots reads these environment variables mode setting * *WLR_DRM_NO_MODIFIERS*: set to 1 to always allocate planes without modifiers, this can fix certain modeset failures because of bandwidth restrictions. +* *WLR_DRM_FORCE_LIBLIFTOFF*: set to 1 to force libliftoff (by default, + libliftoff is never used) ## Headless backend diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h index 0c07811b..f2cd9c50 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -30,11 +30,16 @@ struct wlr_drm_plane { struct wlr_drm_format_set formats; union wlr_drm_plane_props props; + + uint32_t initial_crtc_id; + struct liftoff_plane *liftoff; + struct liftoff_layer *liftoff_layer; }; struct wlr_drm_crtc { uint32_t id; struct wlr_drm_lease *lease; + struct liftoff_output *liftoff; // Atomic modesetting only uint32_t mode_id; @@ -60,6 +65,7 @@ struct wlr_drm_backend { int fd; char *name; struct wlr_device *dev; + struct liftoff_device *liftoff; size_t num_crtcs; struct wlr_drm_crtc *crtcs; diff --git a/include/backend/drm/iface.h b/include/backend/drm/iface.h index d52bbd3d..c4bd62e4 100644 --- a/include/backend/drm/iface.h +++ b/include/backend/drm/iface.h @@ -13,6 +13,8 @@ struct wlr_drm_connector_state; // Used to provide atomic or legacy DRM functions struct wlr_drm_interface { + bool (*init)(struct wlr_drm_backend *drm); + void (*finish)(struct wlr_drm_backend *drm); // Commit all pending changes on a CRTC. bool (*crtc_commit)(struct wlr_drm_connector *conn, const struct wlr_drm_connector_state *state, uint32_t flags, @@ -21,8 +23,15 @@ struct wlr_drm_interface { extern const struct wlr_drm_interface atomic_iface; extern const struct wlr_drm_interface legacy_iface; +extern const struct wlr_drm_interface liftoff_iface; bool drm_legacy_crtc_set_gamma(struct wlr_drm_backend *drm, struct wlr_drm_crtc *crtc, size_t size, uint16_t *lut); +bool create_mode_blob(struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, + const struct wlr_drm_connector_state *state, uint32_t *blob_id); +bool create_gamma_lut_blob(struct wlr_drm_backend *drm, + size_t size, const uint16_t *lut, uint32_t *blob_id); + #endif