diff --git a/include/sway/config.h b/include/sway/config.h index 40710199ad..0216b3dd7e 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -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); diff --git a/sway/config/output.c b/sway/config/output.c index 3f1c3126b7..9c734e1fe8 100644 --- a/sway/config/output.c +++ b/sway/config/output.c @@ -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) { @@ -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)) { @@ -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); } } } @@ -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; @@ -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) { @@ -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); diff --git a/sway/desktop/output.c b/sway/desktop/output.c index bd3de3fe1c..284646e1a4 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -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];