aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Kenny Levinsen <kl@kl.wtf>2024-03-28 14:26:05 +0100
committerLibravatar Kenny Levinsen <kl@kl.wtf>2024-05-02 16:16:42 +0200
commit4c28916d685714a74048d798096a6f853bf61000 (patch)
treeaa1cb463e987c4fc5f51655260847f7c861ef9ff
parentconfig/output: Use all outputs for config merge (diff)
downloadsway-4c28916d685714a74048d798096a6f853bf61000.tar.gz
sway-4c28916d685714a74048d798096a6f853bf61000.tar.zst
sway-4c28916d685714a74048d798096a6f853bf61000.zip
config/output: Search for output config fallbacks
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.
-rw-r--r--include/sway/config.h5
-rw-r--r--sway/config/output.c308
-rw-r--r--sway/desktop/output.c3
3 files changed, 261 insertions, 55 deletions
diff --git a/include/sway/config.h b/include/sway/config.h
index 0be1cd22..5ccc3e77 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
689struct output_config *new_output_config(const char *name); 689struct output_config *new_output_config(const char *name);
690 690
691bool apply_output_configs(struct matched_output_config *configs, 691bool apply_output_configs(struct matched_output_config *configs,
692 size_t configs_len, bool test_only); 692 size_t configs_len, bool test_only, bool degrade_to_off);
693 693
694void apply_all_output_configs(void); 694void apply_all_output_configs(void);
695 695
696void sort_output_configs_by_priority(struct matched_output_config *configs,
697 size_t configs_len);
698
696/** 699/**
697 * store_output_config stores a new output config. An output may be matched by 700 * store_output_config stores a new output config. An output may be matched by
698 * three different config types, in order of precedence: Identifier, name and 701 * three different config types, in order of precedence: Identifier, name and
diff --git a/sway/config/output.c b/sway/config/output.c
index 7e037676..3ec5d77b 100644
--- a/sway/config/output.c
+++ b/sway/config/output.c
@@ -386,22 +386,18 @@ static int compute_default_scale(struct wlr_output *output,
386 return 2; 386 return 2;
387} 387}
388 388
389/* Lists of formats to try, in order, when a specific render bit depth has 389static bool render_format_is_10bit(uint32_t render_format) {
390 * been asked for. The second to last format in each list should always 390 return render_format == DRM_FORMAT_XRGB2101010 ||
391 * be XRGB8888, as a reliable backup in case the others are not available; 391 render_format == DRM_FORMAT_XBGR2101010;
392 * the last should be DRM_FORMAT_INVALID, to indicate the end of the list. */ 392}
393static const uint32_t *bit_depth_preferences[] = { 393
394 [RENDER_BIT_DEPTH_8] = (const uint32_t []){ 394static bool render_format_is_bgr(uint32_t fmt) {
395 DRM_FORMAT_XRGB8888, 395 return fmt == DRM_FORMAT_XBGR2101010 || fmt == DRM_FORMAT_XBGR8888;
396 DRM_FORMAT_INVALID, 396}
397 }, 397
398 [RENDER_BIT_DEPTH_10] = (const uint32_t []){ 398static bool output_config_is_disabling(struct output_config *oc) {
399 DRM_FORMAT_XRGB2101010, 399 return oc && (!oc->enabled || oc->power == 0);
400 DRM_FORMAT_XBGR2101010, 400}
401 DRM_FORMAT_XRGB8888,
402 DRM_FORMAT_INVALID,
403 },
404};
405 401
406static void queue_output_config(struct output_config *oc, 402static void queue_output_config(struct output_config *oc,
407 struct sway_output *output, struct wlr_output_state *pending) { 403 struct sway_output *output, struct wlr_output_state *pending) {
@@ -411,7 +407,7 @@ static void queue_output_config(struct output_config *oc,
411 407
412 struct wlr_output *wlr_output = output->wlr_output; 408 struct wlr_output *wlr_output = output->wlr_output;
413 409
414 if (oc && (!oc->enabled || oc->power == 0)) { 410 if (output_config_is_disabling(oc)) {
415 sway_log(SWAY_DEBUG, "Turning off output %s", wlr_output->name); 411 sway_log(SWAY_DEBUG, "Turning off output %s", wlr_output->name);
416 wlr_output_state_set_enabled(pending, false); 412 wlr_output_state_set_enabled(pending, false);
417 return; 413 return;
@@ -434,22 +430,6 @@ static void queue_output_config(struct output_config *oc,
434 struct wlr_output_mode *preferred_mode = 430 struct wlr_output_mode *preferred_mode =
435 wlr_output_preferred_mode(wlr_output); 431 wlr_output_preferred_mode(wlr_output);
436 wlr_output_state_set_mode(pending, preferred_mode); 432 wlr_output_state_set_mode(pending, preferred_mode);
437
438 if (!wlr_output_test_state(wlr_output, pending)) {
439 sway_log(SWAY_DEBUG, "Preferred mode rejected, "
440 "falling back to another mode");
441 struct wlr_output_mode *mode;
442 wl_list_for_each(mode, &wlr_output->modes, link) {
443 if (mode == preferred_mode) {
444 continue;
445 }
446
447 wlr_output_state_set_mode(pending, mode);
448 if (wlr_output_test_state(wlr_output, pending)) {
449 break;
450 }
451 }
452 }
453 } 433 }
454 434
455 if (oc && (oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN || config->reloading)) { 435 if (oc && (oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN || config->reloading)) {
@@ -500,25 +480,17 @@ static void queue_output_config(struct output_config *oc,
500 sway_log(SWAY_DEBUG, "Set %s adaptive sync to %d", wlr_output->name, 480 sway_log(SWAY_DEBUG, "Set %s adaptive sync to %d", wlr_output->name,
501 oc->adaptive_sync); 481 oc->adaptive_sync);
502 wlr_output_state_set_adaptive_sync_enabled(pending, oc->adaptive_sync == 1); 482 wlr_output_state_set_adaptive_sync_enabled(pending, oc->adaptive_sync == 1);
503 if (oc->adaptive_sync == 1 && !wlr_output_test_state(wlr_output, pending)) {
504 sway_log(SWAY_DEBUG, "Adaptive sync failed, ignoring");
505 wlr_output_state_set_adaptive_sync_enabled(pending, false);
506 }
507 } 483 }
508 484
509 if (oc && oc->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { 485 if (oc && oc->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) {
510 const uint32_t *fmts = bit_depth_preferences[oc->render_bit_depth]; 486 if (oc->render_bit_depth == RENDER_BIT_DEPTH_10 &&
511 assert(fmts); 487 render_format_is_10bit(output->wlr_output->render_format)) {
512 488 // 10-bit was set successfully before, try to save some tests by reusing the format
513 for (size_t i = 0; fmts[i] != DRM_FORMAT_INVALID; i++) { 489 wlr_output_state_set_render_format(pending, output->wlr_output->render_format);
514 wlr_output_state_set_render_format(pending, fmts[i]); 490 } else if (oc->render_bit_depth == RENDER_BIT_DEPTH_10) {
515 if (wlr_output_test_state(wlr_output, pending)) { 491 wlr_output_state_set_render_format(pending, DRM_FORMAT_XRGB2101010);
516 break; 492 } else {
517 } 493 wlr_output_state_set_render_format(pending, DRM_FORMAT_XRGB8888);
518
519 sway_log(SWAY_DEBUG, "Preferred output format 0x%08x "
520 "failed to work, falling back to next in "
521 "list, 0x%08x", fmts[i], fmts[i + 1]);
522 } 494 }
523 } 495 }
524} 496}
@@ -649,8 +621,227 @@ struct output_config *find_output_config(struct sway_output *sway_output) {
649 return result; 621 return result;
650} 622}
651 623
624static bool config_has_auto_mode(struct output_config *oc) {
625 if (!oc) {
626 return true;
627 }
628 if (oc->drm_mode.type != 0 && oc->drm_mode.type != (uint32_t)-1) {
629 return true;
630 } else if (oc->width > 0 && oc->height > 0) {
631 return true;
632 }
633 return false;
634}
635
636struct search_context {
637 struct wlr_output_swapchain_manager *swapchain_mgr;
638 struct wlr_backend_output_state *states;
639 struct matched_output_config *configs;
640 size_t configs_len;
641 bool degrade_to_off;
642};
643
644static bool search_valid_config(struct search_context *ctx, size_t output_idx);
645
646static void reset_output_state(struct wlr_output_state *state) {
647 wlr_output_state_finish(state);
648 wlr_output_state_init(state);
649 state->committed = 0;
650}
651
652static void clear_later_output_states(struct wlr_backend_output_state *states,
653 size_t configs_len, size_t output_idx) {
654
655 // Clear and disable all output states after this one to avoid conflict
656 // with previous tests.
657 for (size_t idx = output_idx+1; idx < configs_len; idx++) {
658 struct wlr_backend_output_state *backend_state = &states[idx];
659 struct wlr_output_state *state = &backend_state->base;
660
661 reset_output_state(state);
662 wlr_output_state_set_enabled(state, false);
663 }
664}
665
666static bool search_finish(struct search_context *ctx, size_t output_idx) {
667 clear_later_output_states(ctx->states, ctx->configs_len, output_idx);
668 return wlr_output_swapchain_manager_prepare(ctx->swapchain_mgr, ctx->states, ctx->configs_len) &&
669 search_valid_config(ctx, output_idx+1);
670}
671
672static bool search_adaptive_sync(struct search_context *ctx, size_t output_idx) {
673 struct matched_output_config *cfg = &ctx->configs[output_idx];
674 struct wlr_backend_output_state *backend_state = &ctx->states[output_idx];
675 struct wlr_output_state *state = &backend_state->base;
676
677 if (cfg->config && cfg->config->adaptive_sync == 1) {
678 wlr_output_state_set_adaptive_sync_enabled(state, true);
679 if (search_finish(ctx, output_idx)) {
680 return true;
681 }
682 }
683 if (!cfg->config || cfg->config->adaptive_sync != -1) {
684 sway_log(SWAY_DEBUG, "Trying with adaptive sync disabled for: %s",
685 backend_state->output->name);
686 wlr_output_state_set_adaptive_sync_enabled(state, false);
687 if (search_finish(ctx, output_idx)) {
688 return true;
689 }
690 }
691 // If adaptive sync has not been set, or fallback in case we are on a
692 // backend that cannot disable adaptive sync such as the wayland backend.
693 sway_log(SWAY_DEBUG, "Trying with adaptive sync unset for: %s",
694 backend_state->output->name);
695 state->committed &= ~WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED;
696 return search_finish(ctx, output_idx);
697}
698
699static bool search_mode(struct search_context *ctx, size_t output_idx) {
700 struct matched_output_config *cfg = &ctx->configs[output_idx];
701 struct wlr_backend_output_state *backend_state = &ctx->states[output_idx];
702 struct wlr_output_state *state = &backend_state->base;
703 struct wlr_output *wlr_output = backend_state->output;
704
705 if (!config_has_auto_mode(cfg->config)) {
706 return search_adaptive_sync(ctx, output_idx);
707 }
708
709 struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output);
710 if (preferred_mode) {
711 sway_log(SWAY_DEBUG, "Trying with preferred mode for: %s", backend_state->output->name);
712 wlr_output_state_set_mode(state, preferred_mode);
713 if (search_adaptive_sync(ctx, output_idx)) {
714 return true;
715 }
716 }
717
718 if (wl_list_empty(&wlr_output->modes)) {
719 state->committed &= ~WLR_OUTPUT_STATE_MODE;
720 return search_adaptive_sync(ctx, output_idx);
721 }
722
723 struct wlr_output_mode *mode;
724 wl_list_for_each(mode, &backend_state->output->modes, link) {
725 if (mode == preferred_mode) {
726 continue;
727 }
728 sway_log(SWAY_DEBUG, "Trying with mode %dx%d@%dmHz for: %s",
729 mode->width, mode->height, mode->refresh, backend_state->output->name);
730 wlr_output_state_set_mode(state, mode);
731 if (search_adaptive_sync(ctx, output_idx)) {
732 return true;
733 }
734 }
735
736 return false;
737}
738
739static bool search_render_format(struct search_context *ctx, size_t output_idx) {
740 struct matched_output_config *cfg = &ctx->configs[output_idx];
741 struct wlr_backend_output_state *backend_state = &ctx->states[output_idx];
742 struct wlr_output_state *state = &backend_state->base;
743 struct wlr_output *wlr_output = backend_state->output;
744
745 uint32_t fmts[] = {
746 DRM_FORMAT_XRGB2101010,
747 DRM_FORMAT_XBGR2101010,
748 DRM_FORMAT_XRGB8888,
749 DRM_FORMAT_INVALID,
750 };
751 if (render_format_is_bgr(wlr_output->render_format)) {
752 // Start with BGR in the unlikely event that we previously required it.
753 fmts[0] = DRM_FORMAT_XBGR2101010;
754 fmts[1] = DRM_FORMAT_XRGB2101010;
755 }
756
757 const struct wlr_drm_format_set *primary_formats =
758 wlr_output_get_primary_formats(wlr_output, WLR_BUFFER_CAP_DMABUF);
759 bool need_10bit = cfg->config && cfg->config->render_bit_depth == RENDER_BIT_DEPTH_10;
760 for (size_t idx = 0; fmts[idx] != DRM_FORMAT_INVALID; idx++) {
761 if (!need_10bit && render_format_is_10bit(fmts[idx])) {
762 continue;
763 }
764 if (!wlr_drm_format_set_get(primary_formats, fmts[idx])) {
765 // This is not a supported format for this output
766 continue;
767 }
768 sway_log(SWAY_DEBUG, "Trying with render format %d for: %s", fmts[idx],
769 wlr_output->name);
770 wlr_output_state_set_render_format(state, fmts[idx]);
771 if (search_mode(ctx, output_idx)) {
772 return true;
773 }
774 }
775 return false;
776}
777
778static bool search_valid_config(struct search_context *ctx, size_t output_idx) {
779 if (output_idx >= ctx->configs_len) {
780 // We reached the end of the search, all good!
781 return true;
782 }
783
784 struct matched_output_config *cfg = &ctx->configs[output_idx];
785 struct wlr_backend_output_state *backend_state = &ctx->states[output_idx];
786 struct wlr_output_state *state = &backend_state->base;
787
788 sway_log(SWAY_DEBUG, "Finding valid config for: %s",
789 backend_state->output->name);
790
791 if (!output_config_is_disabling(cfg->config)) {
792 // Search through our possible configurations, doing a depth-first
793 // through render_format, modes, adaptive_sync and the next output's
794 // config.
795 queue_output_config(cfg->config, cfg->output, &backend_state->base);
796 if (search_render_format(ctx, output_idx)) {
797 return true;
798 } else if (!ctx->degrade_to_off) {
799 return false;
800 }
801 // We could not get anything to work, try to disable this output to see
802 // if we can at least make the outputs before us work.
803 sway_log(SWAY_DEBUG, "Trying with disabled output for: %s",
804 backend_state->output->name);
805 reset_output_state(state);
806 }
807
808 wlr_output_state_set_enabled(state, false);
809 return search_finish(ctx, output_idx);
810}
811
812static int compare_matched_output_config_priority(const void *a, const void *b) {
813
814 const struct matched_output_config *amc = a;
815 const struct matched_output_config *bmc = b;
816 bool a_disabling = output_config_is_disabling(amc->config);
817 bool b_disabling = output_config_is_disabling(bmc->config);
818 bool a_enabled = amc->output->enabled;
819 bool b_enabled = bmc->output->enabled;
820
821 // We want to give priority to existing enabled outputs. To do so, we want
822 // the configuration order to be:
823 // 1. Existing, enabled outputs
824 // 2. Outputs that need to be enabled
825 // 3. Disabled or disabling outputs
826 if (a_enabled && !a_disabling) {
827 return -1;
828 } else if (b_enabled && !b_disabling) {
829 return 1;
830 } else if (b_disabling && !a_disabling) {
831 return -1;
832 } else if (a_disabling && !b_disabling) {
833 return 1;
834 }
835 return 0;
836}
837
838void sort_output_configs_by_priority(struct matched_output_config *configs,
839 size_t configs_len) {
840 qsort(configs, configs_len, sizeof(*configs), compare_matched_output_config_priority);
841}
842
652bool apply_output_configs(struct matched_output_config *configs, 843bool apply_output_configs(struct matched_output_config *configs,
653 size_t configs_len, bool test_only) { 844 size_t configs_len, bool test_only, bool degrade_to_off) {
654 struct wlr_backend_output_state *states = calloc(configs_len, sizeof(*states)); 845 struct wlr_backend_output_state *states = calloc(configs_len, sizeof(*states));
655 if (!states) { 846 if (!states) {
656 return false; 847 return false;
@@ -674,8 +865,18 @@ bool apply_output_configs(struct matched_output_config *configs,
674 865
675 bool ok = wlr_output_swapchain_manager_prepare(&swapchain_mgr, states, configs_len); 866 bool ok = wlr_output_swapchain_manager_prepare(&swapchain_mgr, states, configs_len);
676 if (!ok) { 867 if (!ok) {
677 sway_log(SWAY_ERROR, "Swapchain prepare failed"); 868 sway_log(SWAY_ERROR, "Requested backend configuration failed, searching for valid fallbacks");
678 goto out; 869 struct search_context ctx = {
870 .swapchain_mgr = &swapchain_mgr,
871 .states = states,
872 .configs = configs,
873 .configs_len = configs_len,
874 .degrade_to_off = degrade_to_off,
875 };
876 if (!search_valid_config(&ctx, 0)) {
877 sway_log(SWAY_ERROR, "Search for valid config failed");
878 goto out;
879 }
679 } 880 }
680 881
681 if (test_only) { 882 if (test_only) {
@@ -761,7 +962,8 @@ void apply_all_output_configs(void) {
761 config->config = find_output_config(sway_output); 962 config->config = find_output_config(sway_output);
762 } 963 }
763 964
764 apply_output_configs(configs, configs_len, false); 965 sort_output_configs_by_priority(configs, configs_len);
966 apply_output_configs(configs, configs_len, false, true);
765 for (size_t idx = 0; idx < configs_len; idx++) { 967 for (size_t idx = 0; idx < configs_len; idx++) {
766 struct matched_output_config *cfg = &configs[idx]; 968 struct matched_output_config *cfg = &configs[idx];
767 free_output_config(cfg->config); 969 free_output_config(cfg->config);
diff --git a/sway/desktop/output.c b/sway/desktop/output.c
index 70987feb..2722e556 100644
--- a/sway/desktop/output.c
+++ b/sway/desktop/output.c
@@ -609,7 +609,8 @@ static void output_manager_apply(struct sway_server *server,
609 } 609 }
610 } 610 }
611 611
612 bool ok = apply_output_configs(configs, configs_len, test_only); 612 sort_output_configs_by_priority(configs, configs_len);
613 bool ok = apply_output_configs(configs, configs_len, test_only, false);
613 for (size_t idx = 0; idx < configs_len; idx++) { 614 for (size_t idx = 0; idx < configs_len; idx++) {
614 struct matched_output_config *cfg = &configs[idx]; 615 struct matched_output_config *cfg = &configs[idx];
615 616