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.
This commit is contained in:
emersion 2018-09-10 23:35:22 +02:00
parent df991a55ab
commit f8a50e4fe7
4 changed files with 109 additions and 55 deletions

View file

@ -129,6 +129,9 @@ static bool atomic_crtc_pageflip(struct wlr_drm_backend *drm,
static bool atomic_conn_enable(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_connector *conn, bool enable) {
struct wlr_drm_crtc *crtc = conn->crtc; struct wlr_drm_crtc *crtc = conn->crtc;
if (crtc == NULL) {
return !enable;
}
struct atomic atom; struct atomic atom;
atomic_begin(crtc, &atom); atomic_begin(crtc, &atom);

View file

@ -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) { bool enable_drm_connector(struct wlr_output *output, bool enable) {
struct wlr_drm_connector *conn = (struct wlr_drm_connector *)output; 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 if (conn->state != WLR_DRM_CONN_CONNECTED
&& conn->state != WLR_DRM_CONN_NEEDS_MODESET) { && conn->state != WLR_DRM_CONN_NEEDS_MODESET) {
return false; 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); bool ok = drm->iface->conn_enable(drm, conn, enable);
if (!ok) { if (!ok) {
return false; return false;
@ -357,6 +382,10 @@ bool enable_drm_connector(struct wlr_output *output, bool enable) {
if (enable) { if (enable) {
drm_connector_start_renderer(conn); drm_connector_start_renderer(conn);
} else {
realloc_crtcs(drm, NULL);
attempt_enable_needs_modeset(drm);
} }
wlr_output_update_enabled(&conn->output, enable); 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_output_mode *mode) {
struct wlr_drm_connector *conn = (struct wlr_drm_connector *)output; struct wlr_drm_connector *conn = (struct wlr_drm_connector *)output;
struct wlr_drm_backend *drm = (struct wlr_drm_backend *)output->backend; 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) { if (conn->crtc == NULL) {
wlr_log(WLR_ERROR, "Cannot modeset '%s': no CRTC for this connector", wlr_log(WLR_ERROR, "Cannot modeset '%s': no CRTC for this connector",
conn->output.name); conn->output.name);
@ -450,6 +483,7 @@ static bool drm_connector_set_mode(struct wlr_output *output,
conn->desired_mode = NULL; conn->desired_mode = NULL;
wlr_output_update_mode(&conn->output, mode); wlr_output_update_mode(&conn->output, mode);
wlr_output_update_enabled(&conn->output, true); wlr_output_update_enabled(&conn->output, true);
conn->desired_enabled = true;
drm_connector_start_renderer(conn); drm_connector_start_renderer(conn);
@ -731,17 +765,27 @@ static void dealloc_crtc(struct wlr_drm_connector *conn) {
conn->crtc = NULL; 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); 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"); 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) { for (size_t i = 0; i < drm->num_crtcs; ++i) {
crtc[i] = UNMATCHED; 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)); memset(possible_crtc, 0, sizeof(possible_crtc));
wlr_log(WLR_DEBUG, "State before reallocation:"); 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; struct wlr_drm_connector *conn;
wl_list_for_each(conn, &drm->outputs, link) { wl_list_for_each(conn, &drm->outputs, link) {
i++; i++;
connectors[i] = conn;
wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d", conn->output.name, wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d desired_enabled=%d",
conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1, conn->state); conn->output.name,
conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1,
conn->state, conn->desired_enabled);
if (conn->crtc) { if (conn->crtc) {
crtc[conn->crtc - drm->crtcs] = i; crtc[conn->crtc - drm->crtcs] = i;
} }
if (conn->state == WLR_DRM_CONN_CONNECTED || // Only search CRTCs for user-enabled outputs (that are already
conn->state == WLR_DRM_CONN_NEEDS_MODESET) { // 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; 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, match_obj(wl_list_length(&drm->outputs), possible_crtc,
drm->num_crtcs, crtc, crtc_res); drm->num_crtcs, crtc, crtc_res);
bool matched[num_outputs]; bool matched[num_outputs + 1];
memset(matched, false, sizeof(matched)); memset(matched, false, sizeof(matched));
for (size_t i = 0; i < drm->num_crtcs; ++i) { for (size_t i = 0; i < drm->num_crtcs; ++i) {
if (crtc_res[i] != UNMATCHED) { 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) { for (size_t i = 0; i < drm->num_crtcs; ++i) {
// We don't want any of the current monitors to be deactivated // 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", wlr_log(WLR_DEBUG, "Could not match a CRTC for connected output %d",
crtc[i]); crtc[i]);
return; 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) { for (size_t i = 0; i < drm->num_crtcs; ++i) {
if (crtc_res[i] == UNMATCHED) { if (crtc_res[i] == crtc[i]) {
// De-allocate CRTCs we don't use anymore
if (crtc[i] != UNMATCHED) {
dealloc_crtc(connectors[crtc[i]]);
}
continue; 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; changed_outputs[crtc_res[i]] = true;
struct wlr_drm_connector *conn = connectors[crtc_res[i]]; struct wlr_drm_connector *conn = connectors[crtc_res[i]];
dealloc_crtc(conn); dealloc_crtc(conn);
conn->crtc = &drm->crtcs[i]; 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:"); wlr_log(WLR_DEBUG, "State after reallocation:");
wl_list_for_each(conn, &drm->outputs, link) { wl_list_for_each(conn, &drm->outputs, link) {
wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d", conn->output.name, wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d desired_enabled=%d",
conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1, conn->state); conn->output.name,
conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1,
conn->state, conn->desired_enabled);
} }
realloc_planes(drm, crtc_res, changed_outputs); realloc_planes(drm, crtc_res, changed_outputs);
@ -827,10 +875,10 @@ void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs) {
i++; i++;
struct wlr_output_mode *mode = conn->output.current_mode; 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; continue;
} }
assert(conn->crtc);
if (!init_drm_plane_surfaces(conn->crtc->primary, drm, if (!init_drm_plane_surfaces(conn->crtc->primary, drm,
mode->width, mode->height, GBM_FORMAT_XRGB8888)) { 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_output_update_enabled(&wlr_conn->output, wlr_conn->crtc != NULL);
wlr_conn->desired_enabled = true;
wlr_conn->state = WLR_DRM_CONN_NEEDS_MODESET; wlr_conn->state = WLR_DRM_CONN_NEEDS_MODESET;
new_outputs[new_outputs_len++] = wlr_conn; new_outputs[new_outputs_len++] = wlr_conn;
@ -1066,14 +1115,7 @@ void scan_drm_connectors(struct wlr_drm_backend *drm) {
&conn->output); &conn->output);
} }
// Try to modeset any output that has a desired mode and a CRTC (ie. was attempt_enable_needs_modeset(drm);
// 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);
}
}
} }
static void page_flip_handler(int fd, unsigned seq, 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; struct wlr_drm_backend *drm = (struct wlr_drm_backend *)conn->output.backend;
conn->pageflip_pending = false; conn->pageflip_pending = false;
if (conn->state != WLR_DRM_CONN_CONNECTED) { if (conn->state != WLR_DRM_CONN_CONNECTED || conn->crtc == NULL) {
return; return;
} }
@ -1155,6 +1197,7 @@ static void drm_connector_cleanup(struct wlr_drm_connector *conn) {
case WLR_DRM_CONN_CONNECTED: case WLR_DRM_CONN_CONNECTED:
case WLR_DRM_CONN_CLEANUP:; case WLR_DRM_CONN_CLEANUP:;
struct wlr_drm_crtc *crtc = conn->crtc; struct wlr_drm_crtc *crtc = conn->crtc;
if (crtc != NULL) {
for (int i = 0; i < 3; ++i) { for (int i = 0; i < 3; ++i) {
if (!crtc->planes[i]) { if (!crtc->planes[i]) {
continue; continue;
@ -1167,6 +1210,7 @@ static void drm_connector_cleanup(struct wlr_drm_connector *conn) {
crtc->planes[i] = NULL; crtc->planes[i] = NULL;
} }
} }
}
conn->output.current_mode = NULL; conn->output.current_mode = NULL;
conn->desired_mode = NULL; conn->desired_mode = NULL;

View file

@ -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) { static bool match_obj_(struct match_state *st, size_t skips, size_t score, size_t replaced, size_t i) {
// Finished // Finished
if (i >= st->num_res) { 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->score = score;
st->replaced = replaced; st->replaced = replaced;
memcpy(st->best, st->res, sizeof(st->best[0]) * st->num_res); 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); 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 * Attempt to use the current solution first, to try and avoid
* recalculating everything * recalculating everything
*/ */
if (st->orig[i] != UNMATCHED && !is_taken(i, st->res, st->orig[i])) { if (st->orig[i] != UNMATCHED && !is_taken(i, st->res, st->orig[i])) {
st->res[i] = st->orig[i]; st->res[i] = st->orig[i];
if (match_obj_(st, skips, score + 1, replaced, i + 1)) { size_t obj_score = st->objs[st->res[i]] != 0 ? 1 : 0;
return true; if (match_obj_(st, skips, score + obj_score, replaced, i + 1)) {
has_best = true;
} }
} }
if (st->orig[i] == UNMATCHED) { if (st->orig[i] == UNMATCHED) {
st->res[i] = UNMATCHED; st->res[i] = UNMATCHED;
match_obj_(st, skips, score, replaced, i + 1); if (match_obj_(st, skips, score, replaced, i + 1)) {
has_best = true;
}
}
if (st->exit_early) { if (st->exit_early) {
return true; return true;
} }
}
if (st->orig[i] != UNMATCHED) { if (st->orig[i] != UNMATCHED) {
++replaced; ++replaced;
} }
bool has_best = false;
for (size_t candidate = 0; candidate < st->num_objs; ++candidate) { for (size_t candidate = 0; candidate < st->num_objs; ++candidate) {
// We tried this earlier // We tried this earlier
if (candidate == st->orig[i]) { 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; 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; has_best = true;
} }

View file

@ -120,6 +120,7 @@ struct wlr_drm_connector {
enum wlr_drm_connector_state state; enum wlr_drm_connector_state state;
struct wlr_output_mode *desired_mode; struct wlr_output_mode *desired_mode;
bool desired_enabled;
uint32_t id; uint32_t id;
struct wlr_drm_crtc *crtc; struct wlr_drm_crtc *crtc;