Skip to content

Commit

Permalink
config/output: Search for output config fallbacks
Browse files Browse the repository at this point in the history
The original sway output config implementation enabled one output at a
time, testing modes, render formats and VRR support as it went along.
While this sort of fallback is easy to do, it has the downside of not
considering the effect of neighbor outputs on the configuration
viability.

With backend-wide commits, we can now better consider the effect of
neighbor outputs, but to handle the fact that we commit all outputs at
once we need to perform a more elaborate search of viable
configurations.

Implement a recursive configuration search for when the primary
configuration failed to apply.
  • Loading branch information
kennylevinsen committed Mar 28, 2024
1 parent 9e14651 commit 731312e
Show file tree
Hide file tree
Showing 3 changed files with 264 additions and 54 deletions.
5 changes: 4 additions & 1 deletion include/sway/config.h
Expand Up @@ -689,10 +689,13 @@ const char *sway_output_scale_filter_to_string(enum scale_filter_mode scale_filt
struct output_config *new_output_config(const char *name);

bool apply_output_configs(struct matched_output_config *configs,
size_t configs_len, bool test_only);
size_t configs_len, bool test_only, bool degrade_to_off);

void apply_all_output_configs(void);

void sort_output_configs_by_priority(struct matched_output_config *configs,
size_t configs_len);

struct output_config *store_output_config(struct output_config *oc);

struct output_config *find_output_config(struct sway_output *output);
Expand Down
310 changes: 258 additions & 52 deletions sway/config/output.c
Expand Up @@ -367,22 +367,14 @@ static int compute_default_scale(struct wlr_output *output,
return 2;
}

/* Lists of formats to try, in order, when a specific render bit depth has
* been asked for. The second to last format in each list should always
* be XRGB8888, as a reliable backup in case the others are not available;
* the last should be DRM_FORMAT_INVALID, to indicate the end of the list. */
static const uint32_t *bit_depth_preferences[] = {
[RENDER_BIT_DEPTH_8] = (const uint32_t []){
DRM_FORMAT_XRGB8888,
DRM_FORMAT_INVALID,
},
[RENDER_BIT_DEPTH_10] = (const uint32_t []){
DRM_FORMAT_XRGB2101010,
DRM_FORMAT_XBGR2101010,
DRM_FORMAT_XRGB8888,
DRM_FORMAT_INVALID,
},
};
static bool render_format_is_10bit(uint32_t render_format) {
return render_format == DRM_FORMAT_XRGB2101010 ||
render_format == DRM_FORMAT_XBGR2101010;
}

static bool output_config_is_disabling(struct output_config *oc) {
return oc && (!oc->enabled || oc->power == 0);
}

static void queue_output_config(struct output_config *oc,
struct sway_output *output, struct wlr_output_state *pending) {
Expand Down Expand Up @@ -415,22 +407,6 @@ static void queue_output_config(struct output_config *oc,
struct wlr_output_mode *preferred_mode =
wlr_output_preferred_mode(wlr_output);
wlr_output_state_set_mode(pending, preferred_mode);

if (!wlr_output_test_state(wlr_output, pending)) {
sway_log(SWAY_DEBUG, "Preferred mode rejected, "
"falling back to another mode");
struct wlr_output_mode *mode;
wl_list_for_each(mode, &wlr_output->modes, link) {
if (mode == preferred_mode) {
continue;
}

wlr_output_state_set_mode(pending, mode);
if (wlr_output_test_state(wlr_output, pending)) {
break;
}
}
}
}

if (oc && (oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN || config->reloading)) {
Expand Down Expand Up @@ -481,25 +457,17 @@ static void queue_output_config(struct output_config *oc,
sway_log(SWAY_DEBUG, "Set %s adaptive sync to %d", wlr_output->name,
oc->adaptive_sync);
wlr_output_state_set_adaptive_sync_enabled(pending, oc->adaptive_sync == 1);
if (oc->adaptive_sync == 1 && !wlr_output_test_state(wlr_output, pending)) {
sway_log(SWAY_DEBUG, "Adaptive sync failed, ignoring");
wlr_output_state_set_adaptive_sync_enabled(pending, false);
}
}

if (oc && oc->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) {
const uint32_t *fmts = bit_depth_preferences[oc->render_bit_depth];
assert(fmts);

for (size_t i = 0; fmts[i] != DRM_FORMAT_INVALID; i++) {
wlr_output_state_set_render_format(pending, fmts[i]);
if (wlr_output_test_state(wlr_output, pending)) {
break;
}

sway_log(SWAY_DEBUG, "Preferred output format 0x%08x "
"failed to work, falling back to next in "
"list, 0x%08x", fmts[i], fmts[i + 1]);
if (oc->render_bit_depth == RENDER_BIT_DEPTH_10 &&
render_format_is_10bit(output->wlr_output->render_format)) {
// 10-bit was set successfully before, try to save some tests by reusing the format
wlr_output_state_set_render_format(pending, output->wlr_output->render_format);
} else if (oc->render_bit_depth == RENDER_BIT_DEPTH_10) {
wlr_output_state_set_render_format(pending, DRM_FORMAT_XRGB2101010);
} else {
wlr_output_state_set_render_format(pending, DRM_FORMAT_XRGB8888);
}
}
}
Expand Down Expand Up @@ -677,8 +645,241 @@ struct output_config *find_output_config(struct sway_output *output) {
return get_output_config(id, output);
}

static bool config_has_auto_mode(struct output_config *oc) {
if (!oc) {
return true;
}
if (oc->drm_mode.type != 0 && oc->drm_mode.type != (uint32_t)-1) {
return true;
} else if (oc->width > 0 && oc->height > 0) {
return true;
}
return false;
}

static bool search_valid_config(struct wlr_output_swapchain_manager *swapchain_mgr,
struct wlr_backend_output_state *states, struct matched_output_config *configs,
size_t configs_len, bool degrade_to_off, size_t output_idx);

static void clear_later_output_states(struct wlr_backend_output_state *states,
size_t configs_len, size_t output_idx) {

// Clear and disable all output states after this one to avoid conflict
// with previous tests.
for (size_t idx = output_idx+1; idx < configs_len; idx++) {
struct wlr_backend_output_state *backend_state = &states[idx];
struct wlr_output_state *state = &backend_state->base;

wlr_output_state_finish(state);
wlr_output_state_init(state);
state->committed = 0;
wlr_output_state_set_enabled(state, false);
}
}

static bool search_finish(struct wlr_output_swapchain_manager *swapchain_mgr,
struct wlr_backend_output_state *states, struct matched_output_config *configs,
size_t configs_len, bool degrade_to_off, size_t output_idx) {

clear_later_output_states(states, configs_len, output_idx);
return wlr_output_swapchain_manager_prepare(swapchain_mgr, states, configs_len) &&
search_valid_config(swapchain_mgr, states, configs, configs_len,
degrade_to_off, output_idx+1);
}

static bool search_mode(struct wlr_output_swapchain_manager *swapchain_mgr,
struct wlr_backend_output_state *states, struct matched_output_config *configs,
size_t configs_len, bool degrade_to_off, size_t output_idx) {

struct matched_output_config *cfg = &configs[output_idx];
struct wlr_backend_output_state *backend_state = &states[output_idx];
struct wlr_output_state *state = &backend_state->base;
struct wlr_output *wlr_output = backend_state->output;

if (!config_has_auto_mode(cfg->config)) {
return search_finish(swapchain_mgr, states, configs, configs_len,
degrade_to_off, output_idx);
}

sway_log(SWAY_DEBUG, "Trying with preferred mode for: %s", backend_state->output->name);
struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output);
wlr_output_state_set_mode(state, preferred_mode);
if (search_finish(swapchain_mgr, states, configs, configs_len,
degrade_to_off, output_idx)) {
return true;
}

struct wlr_output_mode *mode;
wl_list_for_each(mode, &backend_state->output->modes, link) {
if (mode == preferred_mode) {
continue;
}
sway_log(SWAY_DEBUG, "Trying with mode %dx%d@%dmHz for: %s",
mode->width, mode->height, mode->refresh, backend_state->output->name);
wlr_output_state_set_mode(state, mode);
if (search_finish(swapchain_mgr, states, configs, configs_len,
degrade_to_off, output_idx)) {
return true;
}
}

return false;
}

static bool search_render_format(struct wlr_output_swapchain_manager *swapchain_mgr,
struct wlr_backend_output_state *states, struct matched_output_config *configs,
size_t configs_len, bool degrade_to_off, size_t output_idx) {

struct matched_output_config *cfg = &configs[output_idx];
struct wlr_backend_output_state *backend_state = &states[output_idx];
struct wlr_output_state *state = &backend_state->base;
struct wlr_output *wlr_output = backend_state->output;

if (cfg->config->render_bit_depth == RENDER_BIT_DEPTH_DEFAULT ||
cfg->config->render_bit_depth == RENDER_BIT_DEPTH_8) {
wlr_output_state_set_render_format(state, DRM_FORMAT_XRGB8888);
return search_mode(swapchain_mgr, states, configs, configs_len,
degrade_to_off, output_idx);
}

// We only need to test 10-bit formats from here
if (render_format_is_10bit(wlr_output->render_format)) {
// 10-bit was set successfully before, try to save some tests by reusing the format
wlr_output_state_set_render_format(state, wlr_output->render_format);
sway_log(SWAY_DEBUG, "Trying with render format %d for: %s", wlr_output->render_format,
wlr_output->name);
if (search_mode(swapchain_mgr, states, configs, configs_len,
degrade_to_off, output_idx)) {
return true;
}
}

const uint32_t fmts[] = {
DRM_FORMAT_XRGB2101010,
DRM_FORMAT_XBGR2101010,
DRM_FORMAT_INVALID,
};
for (size_t idx = 0; fmts[idx] != DRM_FORMAT_INVALID; idx++) {
if (fmts[idx] == wlr_output->render_format) {
// We already tried this one above
continue;
}
sway_log(SWAY_DEBUG, "Trying with render format %d for: %s", fmts[idx],
wlr_output->name);
wlr_output_state_set_render_format(state, fmts[idx]);
if (search_mode(swapchain_mgr, states, configs, configs_len,
degrade_to_off, output_idx)) {
return true;
}
}
return false;
}

static bool search_adaptive_sync(struct wlr_output_swapchain_manager *swapchain_mgr,
struct wlr_backend_output_state *states, struct matched_output_config *configs,
size_t configs_len, bool degrade_to_off, size_t output_idx) {

struct matched_output_config *cfg = &configs[output_idx];
struct wlr_backend_output_state *backend_state = &states[output_idx];
struct wlr_output_state *state = &backend_state->base;

if (cfg->config->adaptive_sync == 1) {
sway_log(SWAY_DEBUG, "Trying with adaptive sync enabled for: %s",
backend_state->output->name);
wlr_output_state_set_adaptive_sync_enabled(state, true);
if (search_render_format(swapchain_mgr, states, configs, configs_len,
degrade_to_off, output_idx)) {
return true;
}
}
if (cfg->config->adaptive_sync != -1) {
sway_log(SWAY_DEBUG, "Trying with adaptive sync disabled for: %s",
backend_state->output->name);
wlr_output_state_set_adaptive_sync_enabled(state, false);
if (search_render_format(swapchain_mgr, states, configs, configs_len,
degrade_to_off, output_idx)) {
return true;
}
}
// If adaptive sync has not been set, or fallback in case we are on a
// backend that cannot disable adaptive sync such as the wayland backend.
sway_log(SWAY_DEBUG, "Trying with adaptive sync unset for: %s",
backend_state->output->name);
state->committed &= ~WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED;
return search_render_format(swapchain_mgr, states, configs, configs_len,
degrade_to_off, output_idx);
}

static bool search_valid_config(struct wlr_output_swapchain_manager *swapchain_mgr,
struct wlr_backend_output_state *states, struct matched_output_config *configs,
size_t configs_len, bool degrade_to_off, size_t output_idx) {

if (output_idx >= configs_len) {
// We reached the end of the search, all good!
return true;
}

struct matched_output_config *cfg = &configs[output_idx];
struct wlr_backend_output_state *backend_state = &states[output_idx];
struct wlr_output_state *state = &backend_state->base;

sway_log(SWAY_DEBUG, "Finding valid config for: %s",
backend_state->output->name);

queue_output_config(cfg->config, cfg->output, state);
if (!cfg->config->enabled) {
return search_valid_config(swapchain_mgr, states, configs, configs_len,
degrade_to_off, output_idx+1);
}

// Search through our possible configurations, doing a depth-first through
// adaptive_sync, render_format, modes and the next output's config.
if (search_adaptive_sync(swapchain_mgr, states, configs, configs_len,
degrade_to_off, output_idx)) {
return true;
}

// We could not get anything to work, try to disable this output to see if
// we can at least make the outputs before us work.
sway_log(SWAY_DEBUG, "Trying with disabled output for: %s", backend_state->output->name);
wlr_output_state_set_enabled(state, false);
return search_finish(swapchain_mgr, states, configs, configs_len,
degrade_to_off, output_idx);
}

static int compare_matched_output_config_priority(const void *a, const void *b) {

const struct matched_output_config *amc = a;
const struct matched_output_config *bmc = b;
bool a_disabling = output_config_is_disabling(amc->config);
bool b_disabling = output_config_is_disabling(bmc->config);
bool a_enabled = amc->output->enabled;
bool b_enabled = bmc->output->enabled;

// We want to give priority to existing enabled outputs. To do so, we want
// the configuration order to be:
// 1. Existing, enabled outputs
// 2. Outputs that need to be enabled
// 3. Disabled or disabling outputs
if (a_enabled && !a_disabling) {
return -1;
} else if (b_enabled && !b_disabling) {
return 1;
} else if (b_disabling && !a_disabling) {
return -1;
} else if (a_disabling && !b_disabling) {
return 1;
}
return 0;
}

void sort_output_configs_by_priority(struct matched_output_config *configs,
size_t configs_len) {
qsort(configs, configs_len, sizeof(*configs), compare_matched_output_config_priority);
}

bool apply_output_configs(struct matched_output_config *configs,
size_t configs_len, bool test_only) {
size_t configs_len, bool test_only, bool degrade_to_off) {
struct wlr_backend_output_state *states = calloc(configs_len, sizeof(*states));
if (!states) {
return false;
Expand All @@ -702,8 +903,12 @@ bool apply_output_configs(struct matched_output_config *configs,

bool ok = wlr_output_swapchain_manager_prepare(&swapchain_mgr, states, configs_len);
if (!ok) {
sway_log(SWAY_ERROR, "Swapchain prepare failed");
goto out;
sway_log(SWAY_ERROR, "Requested backend configuration failed, searching for valid fallbacks");
if (!search_valid_config(&swapchain_mgr, states, configs, configs_len,
degrade_to_off, 0)) {
sway_log(SWAY_ERROR, "Search for valid config failed");
goto out;
}
}

if (test_only) {
Expand Down Expand Up @@ -789,7 +994,8 @@ void apply_all_output_configs(void) {
config->config = find_output_config(sway_output);
}

apply_output_configs(configs, configs_len, false);
sort_output_configs_by_priority(configs, configs_len);
apply_output_configs(configs, configs_len, false, true);
for (size_t idx = 0; idx < configs_len; idx++) {
struct matched_output_config *cfg = &configs[idx];
free_output_config(cfg->config);
Expand Down
3 changes: 2 additions & 1 deletion sway/desktop/output.c
Expand Up @@ -609,7 +609,8 @@ static void output_manager_apply(struct sway_server *server,
}
}

bool ok = apply_output_configs(configs, configs_len, test_only);
sort_output_configs_by_priority(configs, configs_len);
bool ok = apply_output_configs(configs, configs_len, test_only, false);
for (size_t idx = 0; idx < configs_len; idx++) {
struct matched_output_config *cfg = &configs[idx];

Expand Down

0 comments on commit 731312e

Please sign in to comment.