From f8a50e4fe75ac01a86c94bfedf6cfbaf07c391b2 Mon Sep 17 00:00:00 2001 From: emersion Date: Mon, 10 Sep 2018 23:35:22 +0200 Subject: [PATCH] backend/drm: steal CRTCs from disabled outputs This commit allows outputs that need a CRTC to steal it from user-disabled outputs. Note that in the case there are enough CRTCs, disabled outputs don't loose it (so there's no modeset and plane initialization needed after DPMS). CRTC allocation still prefers to keep the old configuration, even if that means allocating an extra CRTC to a disabled output. CRTC reallocation now happen when enabling/disabling an output as well as when trying to modeset. When enabling an output without a CRTC, we realloc to try to steal a CRTC from a disabled output (that doesn't really need the CRTC). When disabling an output, we try to give our CRTC to an output that needs one. Modesetting is similar to enabling. A new DRM connector field has been added: `desired_enabled`. Outputs without CRTCs get automatically disabled. This field keeps track of the state desired by the user, allowing to automatically re-enable outputs when a CRTC becomes free. This required some changes to the allocation algorithm. Previously, the algorithm tried to keep the previous configuration even if a new configuration with a better score was possible (it only changed configuration when the old one didn't work anymore). This is now changed and the old configuration (still preferred) is only retained without considering new possibilities when it's perfect (all outputs have CRTCs). User-disabled outputs now have `possible_crtcs` set to 0, meaning they can only retain a previous CRTC (not acquire a new one). The allocation algorithm has been updated to do not bump the score when assigning a CRTC to a disabled output. --- backend/drm/atomic.c | 3 + backend/drm/drm.c | 138 +++++++++++++++++++++++++------------- backend/drm/util.c | 22 +++--- include/backend/drm/drm.h | 1 + 4 files changed, 109 insertions(+), 55 deletions(-) diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index 335c9de1..90acac25 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -129,6 +129,9 @@ static bool atomic_crtc_pageflip(struct wlr_drm_backend *drm, static bool atomic_conn_enable(struct wlr_drm_backend *drm, struct wlr_drm_connector *conn, bool enable) { struct wlr_drm_crtc *crtc = conn->crtc; + if (crtc == NULL) { + return !enable; + } struct atomic atom; atomic_begin(crtc, &atom); diff --git a/backend/drm/drm.c b/backend/drm/drm.c index e61e624d..eac3a312 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -342,14 +342,39 @@ static void drm_connector_start_renderer(struct wlr_drm_connector *conn) { } } +static bool drm_connector_set_mode(struct wlr_output *output, + struct wlr_output_mode *mode); + +static void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs); + +static void attempt_enable_needs_modeset(struct wlr_drm_backend *drm) { + // Try to modeset any output that has a desired mode and a CRTC (ie. was + // lacking a CRTC on last modeset) + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + if (conn->state == WLR_DRM_CONN_NEEDS_MODESET && + conn->crtc != NULL && conn->desired_mode != NULL && + conn->desired_enabled) { + drm_connector_set_mode(&conn->output, conn->desired_mode); + } + } +} + bool enable_drm_connector(struct wlr_output *output, bool enable) { struct wlr_drm_connector *conn = (struct wlr_drm_connector *)output; + struct wlr_drm_backend *drm = (struct wlr_drm_backend *)output->backend; if (conn->state != WLR_DRM_CONN_CONNECTED && conn->state != WLR_DRM_CONN_NEEDS_MODESET) { return false; } - struct wlr_drm_backend *drm = (struct wlr_drm_backend *)output->backend; + conn->desired_enabled = enable; + + if (enable && conn->crtc == NULL) { + // Maybe we can steal a CRTC from a disabled output + realloc_crtcs(drm, NULL); + } + bool ok = drm->iface->conn_enable(drm, conn, enable); if (!ok) { return false; @@ -357,6 +382,10 @@ bool enable_drm_connector(struct wlr_output *output, bool enable) { if (enable) { drm_connector_start_renderer(conn); + } else { + realloc_crtcs(drm, NULL); + + attempt_enable_needs_modeset(drm); } wlr_output_update_enabled(&conn->output, enable); @@ -429,6 +458,10 @@ static bool drm_connector_set_mode(struct wlr_output *output, struct wlr_output_mode *mode) { struct wlr_drm_connector *conn = (struct wlr_drm_connector *)output; struct wlr_drm_backend *drm = (struct wlr_drm_backend *)output->backend; + if (conn->crtc == NULL) { + // Maybe we can steal a CRTC from a disabled output + realloc_crtcs(drm, NULL); + } if (conn->crtc == NULL) { wlr_log(WLR_ERROR, "Cannot modeset '%s': no CRTC for this connector", conn->output.name); @@ -450,6 +483,7 @@ static bool drm_connector_set_mode(struct wlr_output *output, conn->desired_mode = NULL; wlr_output_update_mode(&conn->output, mode); wlr_output_update_enabled(&conn->output, true); + conn->desired_enabled = true; drm_connector_start_renderer(conn); @@ -731,17 +765,27 @@ static void dealloc_crtc(struct wlr_drm_connector *conn) { conn->crtc = NULL; } -void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs) { +static void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs) { size_t num_outputs = wl_list_length(&drm->outputs); + if (changed_outputs == NULL) { + changed_outputs = calloc(num_outputs, sizeof(bool)); + if (changed_outputs == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return; + } + } + wlr_log(WLR_DEBUG, "Reallocating CRTCs"); - uint32_t crtc[drm->num_crtcs]; + uint32_t crtc[drm->num_crtcs + 1]; for (size_t i = 0; i < drm->num_crtcs; ++i) { crtc[i] = UNMATCHED; } - uint32_t possible_crtc[num_outputs]; + struct wlr_drm_connector *connectors[num_outputs + 1]; + + uint32_t possible_crtc[num_outputs + 1]; memset(possible_crtc, 0, sizeof(possible_crtc)); wlr_log(WLR_DEBUG, "State before reallocation:"); @@ -749,25 +793,31 @@ void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs) { struct wlr_drm_connector *conn; wl_list_for_each(conn, &drm->outputs, link) { i++; + connectors[i] = conn; - wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d", conn->output.name, - conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1, conn->state); + wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d desired_enabled=%d", + conn->output.name, + conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1, + conn->state, conn->desired_enabled); if (conn->crtc) { crtc[conn->crtc - drm->crtcs] = i; } - if (conn->state == WLR_DRM_CONN_CONNECTED || - conn->state == WLR_DRM_CONN_NEEDS_MODESET) { + // Only search CRTCs for user-enabled outputs (that are already + // connected or in need of a modeset) + if ((conn->state == WLR_DRM_CONN_CONNECTED || + conn->state == WLR_DRM_CONN_NEEDS_MODESET) && + conn->desired_enabled) { possible_crtc[i] = conn->possible_crtc; } } - uint32_t crtc_res[drm->num_crtcs]; + uint32_t crtc_res[drm->num_crtcs + 1]; match_obj(wl_list_length(&drm->outputs), possible_crtc, drm->num_crtcs, crtc, crtc_res); - bool matched[num_outputs]; + bool matched[num_outputs + 1]; memset(matched, false, sizeof(matched)); for (size_t i = 0; i < drm->num_crtcs; ++i) { if (crtc_res[i] != UNMATCHED) { @@ -777,34 +827,30 @@ void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs) { for (size_t i = 0; i < drm->num_crtcs; ++i) { // We don't want any of the current monitors to be deactivated - if (crtc[i] != UNMATCHED && !matched[crtc[i]]) { + if (crtc[i] != UNMATCHED && !matched[crtc[i]] && + connectors[crtc[i]]->desired_enabled) { wlr_log(WLR_DEBUG, "Could not match a CRTC for connected output %d", crtc[i]); return; } } - struct wlr_drm_connector *connectors[num_outputs]; - i = 0; - wl_list_for_each(conn, &drm->outputs, link) { - connectors[i] = conn; - i++; - } - for (size_t i = 0; i < drm->num_crtcs; ++i) { - if (crtc_res[i] == UNMATCHED) { - // De-allocate CRTCs we don't use anymore - if (crtc[i] != UNMATCHED) { - dealloc_crtc(connectors[crtc[i]]); - } + if (crtc_res[i] == crtc[i]) { continue; } - if (crtc_res[i] != crtc[i]) { + // De-allocate this CRTC on previous output + if (crtc[i] != UNMATCHED) { + changed_outputs[crtc[i]] = true; + dealloc_crtc(connectors[crtc[i]]); + } + + // Assign this CRTC to next output + if (crtc_res[i] != UNMATCHED) { changed_outputs[crtc_res[i]] = true; struct wlr_drm_connector *conn = connectors[crtc_res[i]]; - dealloc_crtc(conn); conn->crtc = &drm->crtcs[i]; @@ -815,8 +861,10 @@ void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs) { wlr_log(WLR_DEBUG, "State after reallocation:"); wl_list_for_each(conn, &drm->outputs, link) { - wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d", conn->output.name, - conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1, conn->state); + wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d desired_enabled=%d", + conn->output.name, + conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1, + conn->state, conn->desired_enabled); } realloc_planes(drm, crtc_res, changed_outputs); @@ -827,10 +875,10 @@ void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs) { i++; struct wlr_output_mode *mode = conn->output.current_mode; - if (conn->state != WLR_DRM_CONN_CONNECTED || !changed_outputs[i]) { + if (conn->state != WLR_DRM_CONN_CONNECTED || !changed_outputs[i] + || conn->crtc == NULL) { continue; } - assert(conn->crtc); if (!init_drm_plane_surfaces(conn->crtc->primary, drm, mode->width, mode->height, GBM_FORMAT_XRGB8888)) { @@ -1004,6 +1052,7 @@ void scan_drm_connectors(struct wlr_drm_backend *drm) { } wlr_output_update_enabled(&wlr_conn->output, wlr_conn->crtc != NULL); + wlr_conn->desired_enabled = true; wlr_conn->state = WLR_DRM_CONN_NEEDS_MODESET; new_outputs[new_outputs_len++] = wlr_conn; @@ -1066,14 +1115,7 @@ void scan_drm_connectors(struct wlr_drm_backend *drm) { &conn->output); } - // Try to modeset any output that has a desired mode and a CRTC (ie. was - // lacking a CRTC on last modeset) - wl_list_for_each(conn, &drm->outputs, link) { - if (conn->state == WLR_DRM_CONN_NEEDS_MODESET && conn->crtc != NULL && - conn->desired_mode != NULL) { - drm_connector_set_mode(&conn->output, conn->desired_mode); - } - } + attempt_enable_needs_modeset(drm); } static void page_flip_handler(int fd, unsigned seq, @@ -1082,7 +1124,7 @@ static void page_flip_handler(int fd, unsigned seq, struct wlr_drm_backend *drm = (struct wlr_drm_backend *)conn->output.backend; conn->pageflip_pending = false; - if (conn->state != WLR_DRM_CONN_CONNECTED) { + if (conn->state != WLR_DRM_CONN_CONNECTED || conn->crtc == NULL) { return; } @@ -1155,16 +1197,18 @@ static void drm_connector_cleanup(struct wlr_drm_connector *conn) { case WLR_DRM_CONN_CONNECTED: case WLR_DRM_CONN_CLEANUP:; struct wlr_drm_crtc *crtc = conn->crtc; - for (int i = 0; i < 3; ++i) { - if (!crtc->planes[i]) { - continue; - } + if (crtc != NULL) { + for (int i = 0; i < 3; ++i) { + if (!crtc->planes[i]) { + continue; + } - finish_drm_surface(&crtc->planes[i]->surf); - finish_drm_surface(&crtc->planes[i]->mgpu_surf); - if (crtc->planes[i]->id == 0) { - free(crtc->planes[i]); - crtc->planes[i] = NULL; + finish_drm_surface(&crtc->planes[i]->surf); + finish_drm_surface(&crtc->planes[i]->mgpu_surf); + if (crtc->planes[i]->id == 0) { + free(crtc->planes[i]); + crtc->planes[i] = NULL; + } } } diff --git a/backend/drm/util.c b/backend/drm/util.c index 050da2a6..f9637c33 100644 --- a/backend/drm/util.c +++ b/backend/drm/util.c @@ -223,7 +223,8 @@ struct match_state { static bool match_obj_(struct match_state *st, size_t skips, size_t score, size_t replaced, size_t i) { // Finished if (i >= st->num_res) { - if (score > st->score || (score == st->score && replaced < st->replaced)) { + if (score > st->score || + (score == st->score && replaced < st->replaced)) { st->score = score; st->replaced = replaced; memcpy(st->best, st->res, sizeof(st->best[0]) * st->num_res); @@ -243,29 +244,33 @@ static bool match_obj_(struct match_state *st, size_t skips, size_t score, size_ return match_obj_(st, skips + 1, score, replaced, i + 1); } + bool has_best = false; + /* * Attempt to use the current solution first, to try and avoid * recalculating everything */ if (st->orig[i] != UNMATCHED && !is_taken(i, st->res, st->orig[i])) { st->res[i] = st->orig[i]; - if (match_obj_(st, skips, score + 1, replaced, i + 1)) { - return true; + size_t obj_score = st->objs[st->res[i]] != 0 ? 1 : 0; + if (match_obj_(st, skips, score + obj_score, replaced, i + 1)) { + has_best = true; } } if (st->orig[i] == UNMATCHED) { st->res[i] = UNMATCHED; - match_obj_(st, skips, score, replaced, i + 1); - if (st->exit_early) { - return true; + if (match_obj_(st, skips, score, replaced, i + 1)) { + has_best = true; } } + if (st->exit_early) { + return true; + } if (st->orig[i] != UNMATCHED) { ++replaced; } - bool has_best = false; for (size_t candidate = 0; candidate < st->num_objs; ++candidate) { // We tried this earlier if (candidate == st->orig[i]) { @@ -283,7 +288,8 @@ static bool match_obj_(struct match_state *st, size_t skips, size_t score, size_ } st->res[i] = candidate; - if (match_obj_(st, skips, score + 1, replaced, i + 1)) { + size_t obj_score = st->objs[candidate] != 0 ? 1 : 0; + if (match_obj_(st, skips, score + obj_score, replaced, i + 1)) { has_best = true; } diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h index 25225227..6fe031e2 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -120,6 +120,7 @@ struct wlr_drm_connector { enum wlr_drm_connector_state state; struct wlr_output_mode *desired_mode; + bool desired_enabled; uint32_t id; struct wlr_drm_crtc *crtc;