diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 3031d09b2..99331ada9 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -5,12 +5,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -72,8 +74,17 @@ bool check_drm_features(struct wlr_drm_backend *drm) { return false; } + const char *force_libliftoff = getenv("WLR_DRM_FORCE_LIBLIFTOFF"); const char *no_atomic = getenv("WLR_DRM_NO_ATOMIC"); - if (no_atomic && strcmp(no_atomic, "1") == 0) { + if (force_libliftoff && strcmp(force_libliftoff, "1") == 0) { + wlr_log(WLR_DEBUG, + "WLR_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 = &libliftoff_iface; + } else if (no_atomic && strcmp(no_atomic, "1") == 0) { wlr_log(WLR_DEBUG, "WLR_DRM_NO_ATOMIC set, forcing legacy DRM interface"); drm->iface = &legacy_iface; @@ -250,6 +261,57 @@ error: return false; } +static bool init_libliftoff(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; + } + + if (liftoff_device_register_all_planes(drm->liftoff) != 0) { + wlr_log(WLR_ERROR, "Failed to register liftoff planes"); + 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; +} + bool init_drm_resources(struct wlr_drm_backend *drm) { drmModeRes *res = drmModeGetResources(drm->fd); if (!res) { @@ -284,6 +346,10 @@ bool init_drm_resources(struct wlr_drm_backend *drm) { drmModeFreeResources(res); + if (drm->iface == &libliftoff_iface && !init_libliftoff(drm)) { + goto error_crtcs; + } + return true; error_crtcs: @@ -311,15 +377,20 @@ void finish_drm_resources(struct wlr_drm_backend *drm) { } if (crtc->primary) { + liftoff_layer_destroy(crtc->primary->liftoff_layer); wlr_drm_format_set_finish(&crtc->primary->formats); free(crtc->primary); } if (crtc->cursor) { + liftoff_layer_destroy(crtc->cursor->liftoff_layer); wlr_drm_format_set_finish(&crtc->cursor->formats); free(crtc->cursor); } + + liftoff_output_destroy(crtc->liftoff); } + liftoff_device_destroy(drm->liftoff); free(drm->crtcs); } diff --git a/backend/drm/libliftoff.c b/backend/drm/libliftoff.c new file mode 100644 index 000000000..951300180 --- /dev/null +++ b/backend/drm/libliftoff.c @@ -0,0 +1,178 @@ +#include +#include +#include "backend/drm/drm.h" +#include "backend/drm/iface.h" + +static bool create_mode_blob(struct wlr_drm_backend *drm, + const struct wlr_drm_connector_state *state, uint32_t *blob_id) { + if (!state->active) { + *blob_id = 0; + return true; + } + + if (drmModeCreatePropertyBlob(drm->fd, &state->mode, + sizeof(state->mode), blob_id)) { + wlr_log_errno(WLR_ERROR, "Unable to create mode property blob"); + return false; + } + + return true; +} + +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; + + 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 liftoff_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_drm_crtc *crtc = conn->crtc; + + if (test_only) { + flags |= DRM_MODE_ATOMIC_TEST_ONLY; + } + if (state->modeset) { + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } else if (!test_only) { + flags |= DRM_MODE_ATOMIC_NONBLOCK; + } + + uint32_t mode_id = crtc->mode_id; + if (state->modeset) { + if (!create_mode_blob(drm, state, &mode_id)) { + return false; + } + } + + drmModeAtomicReq *req = drmModeAtomicAlloc(); + if (req == NULL) { + wlr_log(WLR_ERROR, "drmModeAtomicAlloc failed"); + return false; + } + + uint32_t crtc_id = state->active ? crtc->id : 0; + bool ok = add_prop(req, conn->id, conn->props.crtc_id, crtc_id) && + add_prop(req, crtc->id, crtc->props.mode_id, mode_id) && + add_prop(req, crtc->id, crtc->props.active, state->active); + + if (state->active) { + ok = ok && set_plane_props(crtc->primary, 0, 0, 0); + 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 %s failed (%s)", + test_only ? "test" : "commit", + state->modeset ? "modeset" : "pageflip"); + ok = false; + } + +out: + + drmModeAtomicFree(req); + + if (ok && !test_only) { + commit_blob(drm, &crtc->mode_id, mode_id); + } else { + rollback_blob(drm, &crtc->mode_id, mode_id); + } + + return ok; +} + +const struct wlr_drm_interface libliftoff_iface = { + .crtc_commit = liftoff_crtc_commit, +}; diff --git a/backend/drm/meson.build b/backend/drm/meson.build index b076b4728..1d0ab58eb 100644 --- a/backend/drm/meson.build +++ b/backend/drm/meson.build @@ -1,12 +1,16 @@ +libliftoff = dependency('libliftoff', version: '>=0.2.0', fallback: ['libliftoff', 'liftoff']) + wlr_files += files( 'atomic.c', 'backend.c', 'cvt.c', 'drm.c', 'legacy.c', + 'libliftoff.c', 'properties.c', 'renderer.c', 'util.c', ) features += { 'drm-backend': true } +wlr_deps += libliftoff diff --git a/docs/env_vars.md b/docs/env_vars.md index 1d39a6249..1f2669d2a 100644 --- a/docs/env_vars.md +++ b/docs/env_vars.md @@ -21,6 +21,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 e045a72ee..1659a4f9c 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -32,6 +32,9 @@ struct wlr_drm_plane { struct wlr_drm_format_set formats; union wlr_drm_plane_props props; + + // TODO: shouldn't be part of wlr_drm_plane + struct liftoff_layer *liftoff_layer; }; struct wlr_drm_crtc { @@ -49,6 +52,8 @@ struct wlr_drm_crtc { struct wlr_drm_plane *cursor; union wlr_drm_crtc_props props; + + struct liftoff_output *liftoff; }; struct wlr_drm_backend { @@ -83,6 +88,7 @@ struct wlr_drm_backend { struct wlr_drm_renderer mgpu_renderer; struct wlr_session *session; + struct liftoff_device *liftoff; uint64_t cursor_width, cursor_height; diff --git a/include/backend/drm/iface.h b/include/backend/drm/iface.h index d52bbd3d9..159cce580 100644 --- a/include/backend/drm/iface.h +++ b/include/backend/drm/iface.h @@ -21,6 +21,7 @@ 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 libliftoff_iface; bool drm_legacy_crtc_set_gamma(struct wlr_drm_backend *drm, struct wlr_drm_crtc *crtc, size_t size, uint16_t *lut);