diff --git a/backend/drm/drm.c b/backend/drm/drm.c index bfbe6cbd..b0b36a4e 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -42,7 +42,8 @@ static const uint32_t COMMIT_OUTPUT_STATE = WLR_OUTPUT_STATE_MODE | WLR_OUTPUT_STATE_ENABLED | WLR_OUTPUT_STATE_GAMMA_LUT | - WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED; + WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED | + WLR_OUTPUT_STATE_LAYERS; static const uint32_t SUPPORTED_OUTPUT_STATE = WLR_OUTPUT_STATE_BACKEND_OPTIONAL | COMMIT_OUTPUT_STATE; @@ -276,6 +277,8 @@ bool init_drm_resources(struct wlr_drm_backend *drm) { if (!get_drm_crtc_props(drm->fd, crtc->id, &crtc->props)) { goto error_crtcs; } + + wl_list_init(&crtc->layers); } if (!init_planes(drm)) { @@ -336,6 +339,64 @@ static struct wlr_drm_connector *get_drm_connector_from_output( return (struct wlr_drm_connector *)wlr_output; } +static void layer_handle_addon_destroy(struct wlr_addon *addon) { + struct wlr_drm_layer *layer = wl_container_of(addon, layer, addon); + wlr_addon_finish(&layer->addon); + wl_list_remove(&layer->link); +#if HAVE_LIBLIFTOFF + liftoff_layer_destroy(layer->liftoff); +#endif + drm_fb_clear(&layer->pending_fb); + drm_fb_clear(&layer->queued_fb); + drm_fb_clear(&layer->current_fb); + free(layer); +} + +const struct wlr_addon_interface layer_impl = { + .name = "wlr_drm_layer", + .destroy = layer_handle_addon_destroy, +}; + +struct wlr_drm_layer *get_drm_layer(struct wlr_drm_backend *drm, + struct wlr_output_layer *wlr_layer) { + struct wlr_addon *addon = + wlr_addon_find(&wlr_layer->addons, drm, &layer_impl); + assert(addon != NULL); + struct wlr_drm_layer *layer = wl_container_of(addon, layer, addon); + return layer; +} + +static struct wlr_drm_layer *get_or_create_layer(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, struct wlr_output_layer *wlr_layer) { + struct wlr_drm_layer *layer; + struct wlr_addon *addon = + wlr_addon_find(&wlr_layer->addons, drm, &layer_impl); + if (addon != NULL) { + layer = wl_container_of(addon, layer, addon); + return layer; + } + + layer = calloc(1, sizeof(*layer)); + if (layer == NULL) { + return NULL; + } + +#if HAVE_LIBLIFTOFF + layer->liftoff = liftoff_layer_create(crtc->liftoff); + if (layer->liftoff == NULL) { + free(layer); + return NULL; + } +#else + abort(); // unreachable +#endif + + wlr_addon_init(&layer->addon, &wlr_layer->addons, drm, &layer_impl); + wl_list_insert(&crtc->layers, &layer->link); + + return layer; +} + static bool drm_crtc_commit(struct wlr_drm_connector *conn, const struct wlr_drm_connector_state *state, uint32_t flags, bool test_only) { @@ -353,6 +414,11 @@ static bool drm_crtc_commit(struct wlr_drm_connector *conn, if (crtc->cursor != NULL) { drm_fb_move(&crtc->cursor->queued_fb, &conn->cursor_pending_fb); } + + struct wlr_drm_layer *layer; + wl_list_for_each(layer, &crtc->layers, link) { + drm_fb_move(&layer->queued_fb, &layer->pending_fb); + } } else { // The set_cursor() hook is a bit special: it's not really synchronized // to commit() or test(). Once set_cursor() returns true, the new @@ -360,6 +426,11 @@ static bool drm_crtc_commit(struct wlr_drm_connector *conn, // risk ending up in a state where we don't have a cursor FB but // wlr_drm_connector.cursor_enabled is true. // TODO: fix our output interface to avoid this issue. + + struct wlr_drm_layer *layer; + wl_list_for_each(layer, &crtc->layers, link) { + drm_fb_clear(&layer->pending_fb); + } } return ok; } @@ -457,6 +528,42 @@ static bool drm_connector_state_update_primary_fb(struct wlr_drm_connector *conn return true; } +static bool drm_connector_set_pending_layer_fbs(struct wlr_drm_connector *conn, + const struct wlr_output_state *state) { + struct wlr_drm_backend *drm = conn->backend; + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc || drm->parent) { + return false; + } + + if (!crtc->liftoff) { + return true; // libliftoff is disabled + } + + assert(state->committed & WLR_OUTPUT_STATE_LAYERS); + + for (size_t i = 0; i < state->layers_len; i++) { + struct wlr_output_layer_state *layer_state = &state->layers[i]; + struct wlr_drm_layer *layer = + get_or_create_layer(drm, crtc, layer_state->layer); + if (!layer) { + return false; + } + + if (layer_state->buffer != NULL) { + layer->pending_width = layer_state->buffer->width; + layer->pending_height = layer_state->buffer->height; + drm_fb_import(&layer->pending_fb, drm, layer_state->buffer, NULL); + } else { + layer->pending_width = layer->pending_height = 0; + drm_fb_clear(&layer->pending_fb); + } + } + + return true; +} + static bool drm_connector_alloc_crtc(struct wlr_drm_connector *conn); static bool drm_connector_test(struct wlr_output *output, @@ -533,6 +640,11 @@ static bool drm_connector_test(struct wlr_output *output, goto out; } } + if (state->committed & WLR_OUTPUT_STATE_LAYERS) { + if (!drm_connector_set_pending_layer_fbs(conn, pending.base)) { + return false; + } + } ok = drm_crtc_commit(conn, &pending, 0, true); @@ -618,6 +730,11 @@ bool drm_connector_commit_state(struct wlr_drm_connector *conn, if (pending.modeset && pending.active) { flags |= DRM_MODE_PAGE_FLIP_EVENT; } + if (pending.base->committed & WLR_OUTPUT_STATE_LAYERS) { + if (!drm_connector_set_pending_layer_fbs(conn, pending.base)) { + return false; + } + } if (pending.modeset) { if (pending.active) { @@ -1564,6 +1681,11 @@ static void handle_page_flip(int fd, unsigned seq, &conn->crtc->cursor->queued_fb); } + struct wlr_drm_layer *layer; + wl_list_for_each(layer, &conn->crtc->layers, link) { + drm_fb_move(&layer->current_fb, &layer->queued_fb); + } + uint32_t present_flags = WLR_OUTPUT_PRESENT_VSYNC | WLR_OUTPUT_PRESENT_HW_CLOCK | WLR_OUTPUT_PRESENT_HW_COMPLETION; /* Don't report ZERO_COPY in multi-gpu situations, because we had to copy diff --git a/backend/drm/libliftoff.c b/backend/drm/libliftoff.c index 2b8cb124..1ba75416 100644 --- a/backend/drm/libliftoff.c +++ b/backend/drm/libliftoff.c @@ -142,10 +142,9 @@ static void rollback_blob(struct wlr_drm_backend *drm, } static bool set_plane_props(struct wlr_drm_plane *plane, - struct liftoff_layer *layer, int32_t x, int32_t y, uint64_t zpos) { - struct wlr_drm_fb *fb = plane_get_next_fb(plane); + struct liftoff_layer *layer, struct wlr_drm_fb *fb, int32_t x, int32_t y, uint64_t zpos) { if (fb == NULL) { - wlr_log(WLR_ERROR, "Failed to acquire FB"); + wlr_log(WLR_ERROR, "Failed to acquire FB for plane %"PRIu32, plane->id); return false; } @@ -169,6 +168,52 @@ static bool disable_plane(struct wlr_drm_plane *plane) { return liftoff_layer_set_property(plane->liftoff_layer, "FB_ID", 0) == 0; } +static uint64_t to_fp16(double v) { + return (uint64_t)round(v * (1 << 16)); +} + +static bool set_layer_props(struct wlr_drm_backend *drm, + const struct wlr_output_layer_state *state, uint64_t zpos) { + struct wlr_drm_layer *layer = get_drm_layer(drm, state->layer); + + struct wlr_drm_fb *fb = layer->pending_fb; + int ret = 0; + if (state->buffer == NULL) { + ret = liftoff_layer_set_property(layer->liftoff, "FB_ID", 0); + } else if (fb == NULL) { + liftoff_layer_set_fb_composited(layer->liftoff); + } else { + ret = liftoff_layer_set_property(layer->liftoff, "FB_ID", fb->id); + } + if (ret != 0) { + return false; + } + + uint32_t width = layer->pending_width; + uint32_t height = layer->pending_height; + + uint64_t crtc_x = (uint64_t)state->x; + uint64_t crtc_y = (uint64_t)state->y; + uint64_t crtc_w = (uint64_t)width; + uint64_t crtc_h = (uint64_t)height; + + uint64_t src_x = to_fp16(0); + uint64_t src_y = to_fp16(0); + uint64_t src_w = to_fp16(width); + uint64_t src_h = to_fp16(height); + + return + liftoff_layer_set_property(layer->liftoff, "zpos", zpos) == 0 && + liftoff_layer_set_property(layer->liftoff, "CRTC_X", crtc_x) == 0 && + liftoff_layer_set_property(layer->liftoff, "CRTC_Y", crtc_y) == 0 && + liftoff_layer_set_property(layer->liftoff, "CRTC_W", crtc_w) == 0 && + liftoff_layer_set_property(layer->liftoff, "CRTC_H", crtc_h) == 0 && + liftoff_layer_set_property(layer->liftoff, "SRC_X", src_x) == 0 && + liftoff_layer_set_property(layer->liftoff, "SRC_Y", src_y) == 0 && + liftoff_layer_set_property(layer->liftoff, "SRC_W", src_w) == 0 && + liftoff_layer_set_property(layer->liftoff, "SRC_H", src_h) == 0; +} + static bool crtc_commit(struct wlr_drm_connector *conn, const struct wlr_drm_connector_state *state, uint32_t flags, bool test_only) { @@ -272,16 +317,25 @@ static bool crtc_commit(struct wlr_drm_connector *conn, ok = ok && add_prop(req, crtc->id, crtc->props.vrr_enabled, vrr_enabled); } ok = ok && - set_plane_props(crtc->primary, crtc->primary->liftoff_layer, 0, 0, 0) && - set_plane_props(crtc->primary, crtc->liftoff_composition_layer, 0, 0, 0); + set_plane_props(crtc->primary, crtc->primary->liftoff_layer, state->primary_fb, 0, 0, 0) && + set_plane_props(crtc->primary, crtc->liftoff_composition_layer, state->primary_fb, 0, 0, 0); liftoff_layer_set_property(crtc->primary->liftoff_layer, "FB_DAMAGE_CLIPS", fb_damage_clips); liftoff_layer_set_property(crtc->liftoff_composition_layer, "FB_DAMAGE_CLIPS", fb_damage_clips); + + if (state->base->committed & WLR_OUTPUT_STATE_LAYERS) { + for (size_t i = 0; i < state->base->layers_len; i++) { + const struct wlr_output_layer_state *layer_state = &state->base->layers[i]; + ok = ok && set_layer_props(drm, layer_state, i + 1); + } + } + if (crtc->cursor) { if (drm_connector_is_cursor_visible(conn)) { ok = ok && set_plane_props(crtc->cursor, crtc->cursor->liftoff_layer, - conn->cursor_x, conn->cursor_y, 1); + get_next_cursor_fb(conn), conn->cursor_x, conn->cursor_y, + wl_list_length(&crtc->layers) + 1); } else { ok = ok && disable_plane(crtc->cursor); } @@ -320,6 +374,15 @@ static bool crtc_commit(struct wlr_drm_connector *conn, goto out; } + if (state->base->committed & WLR_OUTPUT_STATE_LAYERS) { + for (size_t i = 0; i < state->base->layers_len; i++) { + struct wlr_output_layer_state *layer_state = &state->base->layers[i]; + struct wlr_drm_layer *layer = get_drm_layer(drm, layer_state->layer); + layer_state->accepted = + !liftoff_layer_needs_composition(layer->liftoff); + } + } + out: drmModeAtomicFree(req); diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h index f61be026..36e72b7a 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "backend/drm/iface.h" #include "backend/drm/properties.h" @@ -36,11 +37,27 @@ struct wlr_drm_plane { struct liftoff_layer *liftoff_layer; }; +struct wlr_drm_layer { + struct liftoff_layer *liftoff; + struct wlr_addon addon; // wlr_output_layer.addons + struct wl_list link; // wlr_drm_crtc.layers + + /* Buffer to be submitted to the kernel on the next page-flip */ + struct wlr_drm_fb *pending_fb; + /* Buffer submitted to the kernel, will be presented on next vblank */ + struct wlr_drm_fb *queued_fb; + /* Buffer currently displayed on screen */ + struct wlr_drm_fb *current_fb; + + int pending_width, pending_height; +}; + struct wlr_drm_crtc { uint32_t id; struct wlr_drm_lease *lease; struct liftoff_output *liftoff; struct liftoff_layer *liftoff_composition_layer; + struct wl_list layers; // wlr_drm_layer.link // Atomic modesetting only uint32_t mode_id; @@ -163,6 +180,8 @@ size_t drm_crtc_get_gamma_lut_size(struct wlr_drm_backend *drm, void drm_lease_destroy(struct wlr_drm_lease *lease); struct wlr_drm_fb *get_next_cursor_fb(struct wlr_drm_connector *conn); +struct wlr_drm_layer *get_drm_layer(struct wlr_drm_backend *drm, + struct wlr_output_layer *layer); #define wlr_drm_conn_log(conn, verb, fmt, ...) \ wlr_log(verb, "connector %s: " fmt, conn->name, ##__VA_ARGS__)