diff options
Diffstat (limited to 'sway/config/output.c')
-rw-r--r-- | sway/config/output.c | 331 |
1 files changed, 276 insertions, 55 deletions
diff --git a/sway/config/output.c b/sway/config/output.c index 54af5d8e..9a447388 100644 --- a/sway/config/output.c +++ b/sway/config/output.c | |||
@@ -13,6 +13,7 @@ | |||
13 | #include "sway/config.h" | 13 | #include "sway/config.h" |
14 | #include "sway/input/cursor.h" | 14 | #include "sway/input/cursor.h" |
15 | #include "sway/output.h" | 15 | #include "sway/output.h" |
16 | #include "sway/server.h" | ||
16 | #include "sway/tree/root.h" | 17 | #include "sway/tree/root.h" |
17 | #include "log.h" | 18 | #include "log.h" |
18 | #include "util.h" | 19 | #include "util.h" |
@@ -210,7 +211,7 @@ static void merge_output_config(struct output_config *dst, struct output_config | |||
210 | void store_output_config(struct output_config *oc) { | 211 | void store_output_config(struct output_config *oc) { |
211 | bool merged = false; | 212 | bool merged = false; |
212 | bool wildcard = strcmp(oc->name, "*") == 0; | 213 | bool wildcard = strcmp(oc->name, "*") == 0; |
213 | struct sway_output *output = wildcard ? NULL : output_by_name_or_id(oc->name); | 214 | struct sway_output *output = wildcard ? NULL : all_output_by_name_or_id(oc->name); |
214 | 215 | ||
215 | char id[128]; | 216 | char id[128]; |
216 | if (output) { | 217 | if (output) { |
@@ -386,22 +387,18 @@ static int compute_default_scale(struct wlr_output *output, | |||
386 | return 2; | 387 | return 2; |
387 | } | 388 | } |
388 | 389 | ||
389 | /* Lists of formats to try, in order, when a specific render bit depth has | 390 | static bool render_format_is_10bit(uint32_t render_format) { |
390 | * been asked for. The second to last format in each list should always | 391 | return render_format == DRM_FORMAT_XRGB2101010 || |
391 | * be XRGB8888, as a reliable backup in case the others are not available; | 392 | render_format == DRM_FORMAT_XBGR2101010; |
392 | * the last should be DRM_FORMAT_INVALID, to indicate the end of the list. */ | 393 | } |
393 | static const uint32_t *bit_depth_preferences[] = { | 394 | |
394 | [RENDER_BIT_DEPTH_8] = (const uint32_t []){ | 395 | static bool render_format_is_bgr(uint32_t fmt) { |
395 | DRM_FORMAT_XRGB8888, | 396 | return fmt == DRM_FORMAT_XBGR2101010 || fmt == DRM_FORMAT_XBGR8888; |
396 | DRM_FORMAT_INVALID, | 397 | } |
397 | }, | 398 | |
398 | [RENDER_BIT_DEPTH_10] = (const uint32_t []){ | 399 | static bool output_config_is_disabling(struct output_config *oc) { |
399 | DRM_FORMAT_XRGB2101010, | 400 | return oc && (!oc->enabled || oc->power == 0); |
400 | DRM_FORMAT_XBGR2101010, | 401 | } |
401 | DRM_FORMAT_XRGB8888, | ||
402 | DRM_FORMAT_INVALID, | ||
403 | }, | ||
404 | }; | ||
405 | 402 | ||
406 | static void queue_output_config(struct output_config *oc, | 403 | static void queue_output_config(struct output_config *oc, |
407 | struct sway_output *output, struct wlr_output_state *pending) { | 404 | struct sway_output *output, struct wlr_output_state *pending) { |
@@ -411,7 +408,7 @@ static void queue_output_config(struct output_config *oc, | |||
411 | 408 | ||
412 | struct wlr_output *wlr_output = output->wlr_output; | 409 | struct wlr_output *wlr_output = output->wlr_output; |
413 | 410 | ||
414 | if (oc && (!oc->enabled || oc->power == 0)) { | 411 | if (output_config_is_disabling(oc)) { |
415 | sway_log(SWAY_DEBUG, "Turning off output %s", wlr_output->name); | 412 | sway_log(SWAY_DEBUG, "Turning off output %s", wlr_output->name); |
416 | wlr_output_state_set_enabled(pending, false); | 413 | wlr_output_state_set_enabled(pending, false); |
417 | return; | 414 | return; |
@@ -434,22 +431,6 @@ static void queue_output_config(struct output_config *oc, | |||
434 | struct wlr_output_mode *preferred_mode = | 431 | struct wlr_output_mode *preferred_mode = |
435 | wlr_output_preferred_mode(wlr_output); | 432 | wlr_output_preferred_mode(wlr_output); |
436 | wlr_output_state_set_mode(pending, preferred_mode); | 433 | 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 | } | 434 | } |
454 | 435 | ||
455 | if (oc && (oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN || config->reloading)) { | 436 | if (oc && (oc->subpixel != WL_OUTPUT_SUBPIXEL_UNKNOWN || config->reloading)) { |
@@ -468,7 +449,7 @@ static void queue_output_config(struct output_config *oc, | |||
468 | #endif | 449 | #endif |
469 | } | 450 | } |
470 | if (wlr_output->transform != tr) { | 451 | if (wlr_output->transform != tr) { |
471 | sway_log(SWAY_DEBUG, "Set %s transform to %d", oc->name, tr); | 452 | sway_log(SWAY_DEBUG, "Set %s transform to %d", wlr_output->name, tr); |
472 | wlr_output_state_set_transform(pending, tr); | 453 | wlr_output_state_set_transform(pending, tr); |
473 | } | 454 | } |
474 | 455 | ||
@@ -500,25 +481,17 @@ static void queue_output_config(struct output_config *oc, | |||
500 | sway_log(SWAY_DEBUG, "Set %s adaptive sync to %d", wlr_output->name, | 481 | sway_log(SWAY_DEBUG, "Set %s adaptive sync to %d", wlr_output->name, |
501 | oc->adaptive_sync); | 482 | oc->adaptive_sync); |
502 | wlr_output_state_set_adaptive_sync_enabled(pending, oc->adaptive_sync == 1); | 483 | 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 | } | 484 | } |
508 | 485 | ||
509 | if (oc && oc->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { | 486 | if (oc && oc->render_bit_depth != RENDER_BIT_DEPTH_DEFAULT) { |
510 | const uint32_t *fmts = bit_depth_preferences[oc->render_bit_depth]; | 487 | if (oc->render_bit_depth == RENDER_BIT_DEPTH_10 && |
511 | assert(fmts); | 488 | render_format_is_10bit(output->wlr_output->render_format)) { |
512 | 489 | // 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++) { | 490 | wlr_output_state_set_render_format(pending, output->wlr_output->render_format); |
514 | wlr_output_state_set_render_format(pending, fmts[i]); | 491 | } else if (oc->render_bit_depth == RENDER_BIT_DEPTH_10) { |
515 | if (wlr_output_test_state(wlr_output, pending)) { | 492 | wlr_output_state_set_render_format(pending, DRM_FORMAT_XRGB2101010); |
516 | break; | 493 | } else { |
517 | } | 494 | 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 | } | 495 | } |
523 | } | 496 | } |
524 | } | 497 | } |
@@ -649,8 +622,245 @@ struct output_config *find_output_config(struct sway_output *sway_output) { | |||
649 | return result; | 622 | return result; |
650 | } | 623 | } |
651 | 624 | ||
625 | static bool config_has_auto_mode(struct output_config *oc) { | ||
626 | if (!oc) { | ||
627 | return true; | ||
628 | } | ||
629 | if (oc->drm_mode.type != 0 && oc->drm_mode.type != (uint32_t)-1) { | ||
630 | return true; | ||
631 | } else if (oc->width > 0 && oc->height > 0) { | ||
632 | return true; | ||
633 | } | ||
634 | return false; | ||
635 | } | ||
636 | |||
637 | struct search_context { | ||
638 | struct wlr_output_swapchain_manager *swapchain_mgr; | ||
639 | struct wlr_backend_output_state *states; | ||
640 | struct matched_output_config *configs; | ||
641 | size_t configs_len; | ||
642 | bool degrade_to_off; | ||
643 | }; | ||
644 | |||
645 | static void dump_output_state(struct wlr_output *wlr_output, struct wlr_output_state *state) { | ||
646 | sway_log(SWAY_DEBUG, "Output state for %s", wlr_output->name); | ||
647 | if (state->committed & WLR_OUTPUT_STATE_ENABLED) { | ||
648 | sway_log(SWAY_DEBUG, " enabled: %s", state->enabled ? "yes" : "no"); | ||
649 | } | ||
650 | if (state->committed & WLR_OUTPUT_STATE_RENDER_FORMAT) { | ||
651 | sway_log(SWAY_DEBUG, " render_format: %d", state->render_format); | ||
652 | } | ||
653 | if (state->committed & WLR_OUTPUT_STATE_MODE) { | ||
654 | if (state->mode_type == WLR_OUTPUT_STATE_MODE_CUSTOM) { | ||
655 | sway_log(SWAY_DEBUG, " custom mode: %dx%d@%dmHz", | ||
656 | state->custom_mode.width, state->custom_mode.height, state->custom_mode.refresh); | ||
657 | } else { | ||
658 | sway_log(SWAY_DEBUG, " mode: %dx%d@%dmHz%s", | ||
659 | state->mode->width, state->mode->height, state->mode->refresh, | ||
660 | state->mode->preferred ? " (preferred)" : ""); | ||
661 | } | ||
662 | } | ||
663 | if (state->committed & WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED) { | ||
664 | sway_log(SWAY_DEBUG, " adaptive_sync: %s", | ||
665 | state->adaptive_sync_enabled ? "enabled": "disabled"); | ||
666 | } | ||
667 | } | ||
668 | |||
669 | static bool search_valid_config(struct search_context *ctx, size_t output_idx); | ||
670 | |||
671 | static void reset_output_state(struct wlr_output_state *state) { | ||
672 | wlr_output_state_finish(state); | ||
673 | wlr_output_state_init(state); | ||
674 | state->committed = 0; | ||
675 | } | ||
676 | |||
677 | static void clear_later_output_states(struct wlr_backend_output_state *states, | ||
678 | size_t configs_len, size_t output_idx) { | ||
679 | |||
680 | // Clear and disable all output states after this one to avoid conflict | ||
681 | // with previous tests. | ||
682 | for (size_t idx = output_idx+1; idx < configs_len; idx++) { | ||
683 | struct wlr_backend_output_state *backend_state = &states[idx]; | ||
684 | struct wlr_output_state *state = &backend_state->base; | ||
685 | |||
686 | reset_output_state(state); | ||
687 | wlr_output_state_set_enabled(state, false); | ||
688 | } | ||
689 | } | ||
690 | |||
691 | static bool search_finish(struct search_context *ctx, size_t output_idx) { | ||
692 | struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; | ||
693 | struct wlr_output_state *state = &backend_state->base; | ||
694 | struct wlr_output *wlr_output = backend_state->output; | ||
695 | |||
696 | clear_later_output_states(ctx->states, ctx->configs_len, output_idx); | ||
697 | dump_output_state(wlr_output, state); | ||
698 | return wlr_output_swapchain_manager_prepare(ctx->swapchain_mgr, ctx->states, ctx->configs_len) && | ||
699 | search_valid_config(ctx, output_idx+1); | ||
700 | } | ||
701 | |||
702 | static bool search_adaptive_sync(struct search_context *ctx, size_t output_idx) { | ||
703 | struct matched_output_config *cfg = &ctx->configs[output_idx]; | ||
704 | struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; | ||
705 | struct wlr_output_state *state = &backend_state->base; | ||
706 | |||
707 | if (cfg->config && cfg->config->adaptive_sync == 1) { | ||
708 | wlr_output_state_set_adaptive_sync_enabled(state, true); | ||
709 | if (search_finish(ctx, output_idx)) { | ||
710 | return true; | ||
711 | } | ||
712 | } | ||
713 | if (!cfg->config || cfg->config->adaptive_sync != -1) { | ||
714 | wlr_output_state_set_adaptive_sync_enabled(state, false); | ||
715 | if (search_finish(ctx, output_idx)) { | ||
716 | return true; | ||
717 | } | ||
718 | } | ||
719 | // If adaptive sync has not been set, or fallback in case we are on a | ||
720 | // backend that cannot disable adaptive sync such as the wayland backend. | ||
721 | state->committed &= ~WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED; | ||
722 | return search_finish(ctx, output_idx); | ||
723 | } | ||
724 | |||
725 | static bool search_mode(struct search_context *ctx, size_t output_idx) { | ||
726 | struct matched_output_config *cfg = &ctx->configs[output_idx]; | ||
727 | struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; | ||
728 | struct wlr_output_state *state = &backend_state->base; | ||
729 | struct wlr_output *wlr_output = backend_state->output; | ||
730 | |||
731 | if (!config_has_auto_mode(cfg->config)) { | ||
732 | return search_adaptive_sync(ctx, output_idx); | ||
733 | } | ||
734 | |||
735 | struct wlr_output_mode *preferred_mode = wlr_output_preferred_mode(wlr_output); | ||
736 | if (preferred_mode) { | ||
737 | wlr_output_state_set_mode(state, preferred_mode); | ||
738 | if (search_adaptive_sync(ctx, output_idx)) { | ||
739 | return true; | ||
740 | } | ||
741 | } | ||
742 | |||
743 | if (wl_list_empty(&wlr_output->modes)) { | ||
744 | state->committed &= ~WLR_OUTPUT_STATE_MODE; | ||
745 | return search_adaptive_sync(ctx, output_idx); | ||
746 | } | ||
747 | |||
748 | struct wlr_output_mode *mode; | ||
749 | wl_list_for_each(mode, &backend_state->output->modes, link) { | ||
750 | if (mode == preferred_mode) { | ||
751 | continue; | ||
752 | } | ||
753 | wlr_output_state_set_mode(state, mode); | ||
754 | if (search_adaptive_sync(ctx, output_idx)) { | ||
755 | return true; | ||
756 | } | ||
757 | } | ||
758 | |||
759 | return false; | ||
760 | } | ||
761 | |||
762 | static bool search_render_format(struct search_context *ctx, size_t output_idx) { | ||
763 | struct matched_output_config *cfg = &ctx->configs[output_idx]; | ||
764 | struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; | ||
765 | struct wlr_output_state *state = &backend_state->base; | ||
766 | struct wlr_output *wlr_output = backend_state->output; | ||
767 | |||
768 | uint32_t fmts[] = { | ||
769 | DRM_FORMAT_XRGB2101010, | ||
770 | DRM_FORMAT_XBGR2101010, | ||
771 | DRM_FORMAT_XRGB8888, | ||
772 | DRM_FORMAT_INVALID, | ||
773 | }; | ||
774 | if (render_format_is_bgr(wlr_output->render_format)) { | ||
775 | // Start with BGR in the unlikely event that we previously required it. | ||
776 | fmts[0] = DRM_FORMAT_XBGR2101010; | ||
777 | fmts[1] = DRM_FORMAT_XRGB2101010; | ||
778 | } | ||
779 | |||
780 | const struct wlr_drm_format_set *primary_formats = | ||
781 | wlr_output_get_primary_formats(wlr_output, WLR_BUFFER_CAP_DMABUF); | ||
782 | bool need_10bit = cfg->config && cfg->config->render_bit_depth == RENDER_BIT_DEPTH_10; | ||
783 | for (size_t idx = 0; fmts[idx] != DRM_FORMAT_INVALID; idx++) { | ||
784 | if (!need_10bit && render_format_is_10bit(fmts[idx])) { | ||
785 | continue; | ||
786 | } | ||
787 | if (!wlr_drm_format_set_get(primary_formats, fmts[idx])) { | ||
788 | // This is not a supported format for this output | ||
789 | continue; | ||
790 | } | ||
791 | wlr_output_state_set_render_format(state, fmts[idx]); | ||
792 | if (search_mode(ctx, output_idx)) { | ||
793 | return true; | ||
794 | } | ||
795 | } | ||
796 | return false; | ||
797 | } | ||
798 | |||
799 | static bool search_valid_config(struct search_context *ctx, size_t output_idx) { | ||
800 | if (output_idx >= ctx->configs_len) { | ||
801 | // We reached the end of the search, all good! | ||
802 | return true; | ||
803 | } | ||
804 | |||
805 | struct matched_output_config *cfg = &ctx->configs[output_idx]; | ||
806 | struct wlr_backend_output_state *backend_state = &ctx->states[output_idx]; | ||
807 | struct wlr_output_state *state = &backend_state->base; | ||
808 | struct wlr_output *wlr_output = backend_state->output; | ||
809 | |||
810 | if (!output_config_is_disabling(cfg->config)) { | ||
811 | // Search through our possible configurations, doing a depth-first | ||
812 | // through render_format, modes, adaptive_sync and the next output's | ||
813 | // config. | ||
814 | queue_output_config(cfg->config, cfg->output, &backend_state->base); | ||
815 | if (search_render_format(ctx, output_idx)) { | ||
816 | return true; | ||
817 | } else if (!ctx->degrade_to_off) { | ||
818 | return false; | ||
819 | } | ||
820 | // We could not get anything to work, try to disable this output to see | ||
821 | // if we can at least make the outputs before us work. | ||
822 | sway_log(SWAY_DEBUG, "Unable to find valid config with output %s, disabling", | ||
823 | wlr_output->name); | ||
824 | reset_output_state(state); | ||
825 | } | ||
826 | |||
827 | wlr_output_state_set_enabled(state, false); | ||
828 | return search_finish(ctx, output_idx); | ||
829 | } | ||
830 | |||
831 | static int compare_matched_output_config_priority(const void *a, const void *b) { | ||
832 | |||
833 | const struct matched_output_config *amc = a; | ||
834 | const struct matched_output_config *bmc = b; | ||
835 | bool a_disabling = output_config_is_disabling(amc->config); | ||
836 | bool b_disabling = output_config_is_disabling(bmc->config); | ||
837 | bool a_enabled = amc->output->enabled; | ||
838 | bool b_enabled = bmc->output->enabled; | ||
839 | |||
840 | // We want to give priority to existing enabled outputs. To do so, we want | ||
841 | // the configuration order to be: | ||
842 | // 1. Existing, enabled outputs | ||
843 | // 2. Outputs that need to be enabled | ||
844 | // 3. Disabled or disabling outputs | ||
845 | if (a_enabled && !a_disabling) { | ||
846 | return -1; | ||
847 | } else if (b_enabled && !b_disabling) { | ||
848 | return 1; | ||
849 | } else if (b_disabling && !a_disabling) { | ||
850 | return -1; | ||
851 | } else if (a_disabling && !b_disabling) { | ||
852 | return 1; | ||
853 | } | ||
854 | return 0; | ||
855 | } | ||
856 | |||
857 | void sort_output_configs_by_priority(struct matched_output_config *configs, | ||
858 | size_t configs_len) { | ||
859 | qsort(configs, configs_len, sizeof(*configs), compare_matched_output_config_priority); | ||
860 | } | ||
861 | |||
652 | bool apply_output_configs(struct matched_output_config *configs, | 862 | bool apply_output_configs(struct matched_output_config *configs, |
653 | size_t configs_len, bool test_only) { | 863 | size_t configs_len, bool test_only, bool degrade_to_off) { |
654 | struct wlr_backend_output_state *states = calloc(configs_len, sizeof(*states)); | 864 | struct wlr_backend_output_state *states = calloc(configs_len, sizeof(*states)); |
655 | if (!states) { | 865 | if (!states) { |
656 | return false; | 866 | return false; |
@@ -674,8 +884,18 @@ bool apply_output_configs(struct matched_output_config *configs, | |||
674 | 884 | ||
675 | bool ok = wlr_output_swapchain_manager_prepare(&swapchain_mgr, states, configs_len); | 885 | bool ok = wlr_output_swapchain_manager_prepare(&swapchain_mgr, states, configs_len); |
676 | if (!ok) { | 886 | if (!ok) { |
677 | sway_log(SWAY_ERROR, "Swapchain prepare failed"); | 887 | sway_log(SWAY_ERROR, "Requested backend configuration failed, searching for valid fallbacks"); |
678 | goto out; | 888 | struct search_context ctx = { |
889 | .swapchain_mgr = &swapchain_mgr, | ||
890 | .states = states, | ||
891 | .configs = configs, | ||
892 | .configs_len = configs_len, | ||
893 | .degrade_to_off = degrade_to_off, | ||
894 | }; | ||
895 | if (!search_valid_config(&ctx, 0)) { | ||
896 | sway_log(SWAY_ERROR, "Search for valid config failed"); | ||
897 | goto out; | ||
898 | } | ||
679 | } | 899 | } |
680 | 900 | ||
681 | if (test_only) { | 901 | if (test_only) { |
@@ -761,7 +981,8 @@ void apply_all_output_configs(void) { | |||
761 | config->config = find_output_config(sway_output); | 981 | config->config = find_output_config(sway_output); |
762 | } | 982 | } |
763 | 983 | ||
764 | apply_output_configs(configs, configs_len, false); | 984 | sort_output_configs_by_priority(configs, configs_len); |
985 | apply_output_configs(configs, configs_len, false, true); | ||
765 | for (size_t idx = 0; idx < configs_len; idx++) { | 986 | for (size_t idx = 0; idx < configs_len; idx++) { |
766 | struct matched_output_config *cfg = &configs[idx]; | 987 | struct matched_output_config *cfg = &configs[idx]; |
767 | free_output_config(cfg->config); | 988 | free_output_config(cfg->config); |