diff options
-rw-r--r-- | include/sway/config.h | 5 | ||||
-rw-r--r-- | sway/config/output.c | 308 | ||||
-rw-r--r-- | sway/desktop/output.c | 3 |
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 | |||
689 | struct output_config *new_output_config(const char *name); | 689 | struct output_config *new_output_config(const char *name); |
690 | 690 | ||
691 | bool apply_output_configs(struct matched_output_config *configs, | 691 | bool 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 | ||
694 | void apply_all_output_configs(void); | 694 | void apply_all_output_configs(void); |
695 | 695 | ||
696 | void 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 | 389 | static 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 | } |
393 | static const uint32_t *bit_depth_preferences[] = { | 393 | |
394 | [RENDER_BIT_DEPTH_8] = (const uint32_t []){ | 394 | static 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 []){ | 398 | static 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 | ||
406 | static void queue_output_config(struct output_config *oc, | 402 | static 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 | ||
624 | static 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 | |||
636 | struct 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 | |||
644 | static bool search_valid_config(struct search_context *ctx, size_t output_idx); | ||
645 | |||
646 | static 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 | |||
652 | static 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 | |||
666 | static 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 | |||
672 | static 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 | |||
699 | static 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 | |||
739 | static 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 | |||
778 | static 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 | |||
812 | static 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 | |||
838 | void 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 | |||
652 | bool apply_output_configs(struct matched_output_config *configs, | 843 | bool 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 | ||