diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake index b22eadc13..af637abad 100644 --- a/cmake/compile_definitions/windows.cmake +++ b/cmake/compile_definitions/windows.cmake @@ -14,6 +14,11 @@ list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-misleading-indentation) # can remove after https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120495 is available in mingw-w64 list(APPEND SUNSHINE_COMPILE_OPTIONS -Wno-template-body) +# Template-heavy TUs (e.g. confighttp.cpp) exceed the 32767-section COFF limit; +# without big-obj the assembler writes a corrupt symbol table whose COMDAT +# symbols (typeinfo, inline members) resolve as undefined at link time. +list(APPEND SUNSHINE_COMPILE_OPTIONS -Wa,-mbig-obj) + # see gcc bug 98723 add_definitions(-DUSE_BOOST_REGEX) diff --git a/src/confighttp.cpp b/src/confighttp.cpp index 889ce1bea..396dfe682 100644 --- a/src/confighttp.cpp +++ b/src/confighttp.cpp @@ -1238,32 +1238,193 @@ namespace confighttp { return tokens; } + std::string + trim_vdd_token(std::string value) { + boost::algorithm::trim(value); + if (value.size() >= 2 && + ((value.front() == '"' && value.back() == '"') || + (value.front() == '\'' && value.back() == '\''))) { + value = value.substr(1, value.size() - 2); + boost::algorithm::trim(value); + } + return value; + } + + std::vector + parse_vdd_array(std::string value) { + boost::algorithm::trim(value); + if (value.size() >= 2 && value.front() == '[' && value.back() == ']') { + value = value.substr(1, value.size() - 2); + } + + std::vector result; + for (auto token : split(value, ',')) { + token = trim_vdd_token(token); + if (!token.empty()) { + result.push_back(std::move(token)); + } + } + return result; + } + + std::vector + normalize_vdd_modes(const std::vector &modes, bool enforce_global_combination_limit = true) { + std::vector normalized; + + for (const auto &mode : modes) { + auto resolution = trim_vdd_token(mode.first); + auto refresh_rate = trim_vdd_token(mode.second); + if (resolution.empty() || refresh_rate.empty() || resolution.find('x') == std::string::npos) { + continue; + } + + const auto key = resolution + "@" + refresh_rate; + normalized.erase(std::remove_if(normalized.begin(), normalized.end(), [&](const auto &existing_mode) { + return existing_mode.first + "@" + existing_mode.second == key; + }), + normalized.end()); + normalized.emplace_back(std::move(resolution), std::move(refresh_rate)); + } + + while (enforce_global_combination_limit && !normalized.empty()) { + std::set resolutions; + std::set refresh_rates; + for (const auto &mode : normalized) { + resolutions.insert(mode.first); + refresh_rates.insert(mode.second); + } + + const auto combination_limit = vdd_max_mode_combination_count(refresh_rates.size()); + if (resolutions.size() * refresh_rates.size() <= combination_limit) { + break; + } + normalized.erase(normalized.begin()); + } + return normalized; + } + bool - saveVddSettings(std::string resArray, std::string fpsArray, std::string gpu_name) { - pt::ptree iddOptionTree; - pt::ptree global_node; - pt::ptree resolutions_nodes; + append_vdd_resolution_node(pt::ptree &resolutions_nodes, const std::string &resolution, const std::string *refresh_rate = nullptr) { + const auto index = resolution.find('x'); + if (index == std::string::npos) { + return false; + } - // prepare resolutions setting for vdd - boost::regex pattern("\\[|\\]|\\s+"); - char delimiter = ','; + auto width = resolution.substr(0, index); + auto height = resolution.substr(index + 1); + boost::algorithm::trim(width); + boost::algorithm::trim(height); - // 添加全局刷新率到global节点 - for (const auto &fps : split(boost::regex_replace(fpsArray, pattern, ""), delimiter)) { - global_node.add("g_refresh_rate", fps); + if (width.empty() || height.empty()) { + return false; } - std::string str = boost::regex_replace(resArray, pattern, ""); - boost::algorithm::trim(str); - for (const auto &resolution : split(str, delimiter)) { - auto index = resolution.find('x'); - if(index == std::string::npos) { + pt::ptree res_node; + res_node.put("width", width); + res_node.put("height", height); + if (refresh_rate) { + auto refresh_rate_value = trim_vdd_token(*refresh_rate); + if (refresh_rate_value.empty()) { + return false; + } + res_node.put("refresh_rate", refresh_rate_value); + } + resolutions_nodes.push_back(std::make_pair("resolution"s, res_node)); + return true; + } + + void + append_vdd_global_refresh_rates(pt::ptree &global_node, const std::vector &refresh_rates) { + for (const auto &refresh_rate : refresh_rates) { + global_node.add("g_refresh_rate", refresh_rate); + } + } + + void + collect_vdd_mode_lists(const std::vector &modes, + std::vector &resolutions, + std::vector &refresh_rates) { + std::set seen_resolutions; + std::set seen_refresh_rates; + + for (const auto &mode : modes) { + if (seen_resolutions.insert(mode.first).second) { + resolutions.push_back(mode.first); + } + if (seen_refresh_rates.insert(mode.second).second) { + refresh_rates.push_back(mode.second); + } + } + } + + void + trim_vdd_temporary_modes(const std::vector &global_modes, std::vector &temporary_modes) { + std::set global_resolutions; + std::set global_refresh_rates; + for (const auto &mode : global_modes) { + global_resolutions.insert(mode.first); + global_refresh_rates.insert(mode.second); + } + + temporary_modes.erase(std::remove_if(temporary_modes.begin(), temporary_modes.end(), [&](const auto &mode) { + return global_resolutions.count(mode.first) != 0 && + global_refresh_rates.count(mode.second) != 0; + }), + temporary_modes.end()); + + const auto temporary_mode_limit = std::min( + VDD_MAX_CACHED_TEMPORARY_MODES, + vdd_max_temporary_mode_count( + global_resolutions.size(), + global_refresh_rates.size())); + while (temporary_modes.size() > temporary_mode_limit) { + temporary_modes.erase(temporary_modes.begin()); + } + } + + void + put_vdd_common_nodes(pt::ptree &iddOptionTree, const std::string &gpu_name) { + pt::ptree monitor_node; + monitor_node.put("count", 1); + + pt::ptree gpu_node; + gpu_node.put("friendlyname", gpu_name.empty() ? "default" : gpu_name); + + iddOptionTree.put_child("monitors", monitor_node); + iddOptionTree.put_child("gpu", gpu_node); + } + + bool + saveVddModeSettings(const std::vector &modes, std::string gpu_name) { + return saveVddModeSettings(modes, {}, std::move(gpu_name)); + } + + bool + saveVddModeSettings(const std::vector &global_modes, const std::vector &temporary_modes, std::string gpu_name) { + auto normalized_global_modes = normalize_vdd_modes(global_modes); + if (normalized_global_modes.empty()) { + return false; + } + auto normalized_temporary_modes = normalize_vdd_modes(temporary_modes, false); + trim_vdd_temporary_modes(normalized_global_modes, normalized_temporary_modes); + + std::vector resolutions; + std::vector refresh_rates; + collect_vdd_mode_lists(normalized_global_modes, resolutions, refresh_rates); + + pt::ptree global_node; + append_vdd_global_refresh_rates(global_node, refresh_rates); + + pt::ptree resolutions_nodes; + for (const auto &resolution : resolutions) { + if (!append_vdd_resolution_node(resolutions_nodes, resolution)) { + return false; + } + } + for (const auto &mode : normalized_temporary_modes) { + if (!append_vdd_resolution_node(resolutions_nodes, mode.first, &mode.second)) { return false; } - pt::ptree res_node; - res_node.put("width", resolution.substr(0, index)); - res_node.put("height", resolution.substr(index + 1)); - resolutions_nodes.push_back(std::make_pair("resolution"s, res_node)); } // 类似于 config.cpp 中的 path_f 函数逻辑,使用相对路径 @@ -1272,60 +1433,29 @@ namespace confighttp { BOOST_LOG(info) << "VDD配置文件路径: " << idd_option_path.string(); if (!fs::exists(idd_option_path)) { - return false; + return false; } // 先读取现有配置文件 + pt::ptree iddOptionTree; pt::ptree existing_root; pt::ptree root; try { pt::read_xml(idd_option_path.string(), existing_root); - // 如果现有配置文件中已有vdd_settings节点 if (existing_root.get_child_optional("vdd_settings")) { - // 复制现有配置 iddOptionTree = existing_root.get_child("vdd_settings"); - - // 更新需要更改的部分 - pt::ptree monitor_node; - monitor_node.put("count", 1); - - pt::ptree gpu_node; - gpu_node.put("friendlyname", gpu_name.empty() ? "default" : gpu_name); - - // 替换配置 - iddOptionTree.put_child("monitors", monitor_node); - iddOptionTree.put_child("gpu", gpu_node); - iddOptionTree.put_child("global", global_node); - iddOptionTree.put_child("resolutions", resolutions_nodes); - } else { - // 如果没有vdd_settings节点,创建新的 - pt::ptree monitor_node; - monitor_node.put("count", 1); - - pt::ptree gpu_node; - gpu_node.put("friendlyname", gpu_name.empty() ? "default" : gpu_name); - - iddOptionTree.add_child("monitors", monitor_node); - iddOptionTree.add_child("gpu", gpu_node); - iddOptionTree.add_child("global", global_node); - iddOptionTree.add_child("resolutions", resolutions_nodes); } - } catch(std::exception &e) { - // 读取失败,创建新的配置 + } + catch (std::exception &e) { BOOST_LOG(warning) << "读取现有VDD配置失败,创建新配置: " << e.what(); + } - pt::ptree monitor_node; - monitor_node.put("count", 1); - - pt::ptree gpu_node; - gpu_node.put("friendlyname", gpu_name.empty() ? "default" : gpu_name); + put_vdd_common_nodes(iddOptionTree, gpu_name); - iddOptionTree.add_child("monitors", monitor_node); - iddOptionTree.add_child("gpu", gpu_node); - iddOptionTree.add_child("global", global_node); - iddOptionTree.add_child("resolutions", resolutions_nodes); - } + iddOptionTree.put_child("global", global_node); + iddOptionTree.put_child("resolutions", resolutions_nodes); + iddOptionTree.erase("sunshine_mode_cache"); root.add_child("vdd_settings", iddOptionTree); try { @@ -1351,6 +1481,25 @@ namespace confighttp { } } + bool + saveVddSettings(std::string resArray, std::string fpsArray, std::string gpu_name) { + std::vector modes; + const auto resolutions = parse_vdd_array(std::move(resArray)); + const auto refresh_rates = parse_vdd_array(std::move(fpsArray)); + + for (const auto &resolution : resolutions) { + if (resolution.find('x') == std::string::npos) { + return false; + } + + for (const auto &refresh_rate : refresh_rates) { + modes.emplace_back(resolution, refresh_rate); + } + } + + return saveVddModeSettings(modes, std::move(gpu_name)); + } + void saveConfig(resp_https_t response, req_https_t request) { if (!check_content_type(response, request, "application/json")) return; diff --git a/src/confighttp.h b/src/confighttp.h index 307503e3b..3ed596806 100644 --- a/src/confighttp.h +++ b/src/confighttp.h @@ -4,8 +4,11 @@ */ #pragma once +#include #include #include +#include +#include #include "thread_safe.h" @@ -13,12 +16,102 @@ namespace confighttp { constexpr auto PORT_HTTPS = 1; + struct VddModeCombinationLimit { + std::size_t refresh_rate_count; + std::size_t max_combination_count; + }; + + struct VddTemporaryModeLimit { + std::size_t global_resolution_count; + std::size_t global_refresh_rate_count; + std::size_t max_temporary_mode_count; + }; + + // Empirically stable Windows/IddCx mode-list budgets for VDD XML reloads. + // Measured with ZakoVDD disable/enable + CREATEMONITOR probes on 2026-06-05. + // Effective mode count is unique resolutions * global refresh rates. + inline constexpr VddModeCombinationLimit VDD_MODE_COMBINATION_LIMITS[] = { + { 1, 342 }, // 342 accepted, 343 failed. + { 2, 134 }, // 134 accepted in targeted create probe. + { 3, 69 }, // 69 accepted, 72 failed in the longer-wait probe. + { 4, 72 }, // 72 accepted, 76 failed. + { 5, 65 }, // 65 accepted in targeted create probe. + { 6, 66 }, // 66 accepted; 72 was intermittent, 78 failed. + { 7, 70 }, // 70 accepted in targeted create probe. + { 8, 80 }, // 80 accepted, 104 failed. + { 9, 72 }, // 72 accepted in targeted create probe. + { 10, 80 }, // 80 accepted, 90 failed. + }; + + inline constexpr std::size_t VDD_FALLBACK_MODE_COMBINATION_LIMIT = 66; + + // Per-resolution refresh_rate nodes do not behave like a pure global Cartesian product. + // These are max temporary exact modes, measured with CREATEMONITOR probes. + inline constexpr VddTemporaryModeLimit VDD_TEMPORARY_MODE_LIMITS[] = { + { 0, 1, 171 }, // 171 accepted, 172 failed when every node had refresh_rate and one global rate existed. + { 0, 6, 43 }, // 43 accepted, 44 failed when every node had refresh_rate and six global rates existed. + { 8, 6, 27 }, // Original 8 global resolutions: 27 temporary exact modes accepted, 28 failed. + }; + + inline constexpr std::size_t VDD_FALLBACK_TEMPORARY_MODE_LIMIT = 0; + + // 临时模式缓存上限:最多保留最近使用的5个会话模式。 + inline constexpr std::size_t VDD_MAX_CACHED_TEMPORARY_MODES = 5; + + inline constexpr std::size_t + vdd_max_mode_combination_count(std::size_t refresh_rate_count) { + if (refresh_rate_count == 0) { + return 0; + } + + for (const auto &limit : VDD_MODE_COMBINATION_LIMITS) { + if (refresh_rate_count == limit.refresh_rate_count) { + return limit.max_combination_count; + } + } + + for (const auto &limit : VDD_MODE_COMBINATION_LIMITS) { + if (refresh_rate_count <= limit.refresh_rate_count) { + return limit.max_combination_count; + } + } + + return VDD_FALLBACK_MODE_COMBINATION_LIMIT; + } + + inline constexpr std::size_t + vdd_max_temporary_mode_count(std::size_t global_resolution_count, std::size_t global_refresh_rate_count) { + if (global_refresh_rate_count == 0) { + return 0; + } + + for (const auto &limit : VDD_TEMPORARY_MODE_LIMITS) { + if (global_resolution_count == limit.global_resolution_count && + global_refresh_rate_count == limit.global_refresh_rate_count) { + return limit.max_temporary_mode_count; + } + } + + const auto global_mode_budget = vdd_max_mode_combination_count(global_refresh_rate_count); + const auto global_mode_count = global_resolution_count * global_refresh_rate_count; + return global_mode_count < global_mode_budget ? global_mode_budget - global_mode_count : VDD_FALLBACK_TEMPORARY_MODE_LIMIT; + } + void start(); + // Pair of "WIDTHxHEIGHT" and refresh-rate text, e.g. {"1920x1080", "60"}. + using VddMode = std::pair; + bool saveVddSettings(std::string resArray, std::string fpsArray, std::string gpu_name); + bool + saveVddModeSettings(const std::vector &modes, std::string gpu_name); + + bool + saveVddModeSettings(const std::vector &global_modes, const std::vector &temporary_modes, std::string gpu_name); + // AI LLM Proxy — shared interface for nvhttp struct AiProxyResult { int httpCode; // HTTP status code to return (200, 400, 403, 502, 500) diff --git a/src/display_device/session.cpp b/src/display_device/session.cpp index 14b876eb0..0091c3538 100644 --- a/src/display_device/session.cpp +++ b/src/display_device/session.cpp @@ -7,6 +7,7 @@ // local includes #include "parsed_config.h" #include "session.h" +#include "src/confighttp.h" #include "src/globals.h" #include "src/platform/common.h" #include "src/platform/windows/display_device/session_listener.h" @@ -555,7 +556,7 @@ namespace display_device { const auto setmodes_result = vdd_utils::set_vdd_session_mode(config, vdd_settings); switch (setmodes_result) { case vdd_utils::set_vdd_result::ok: { - BOOST_LOG(info) << "VDD会话模式列表更新完成(未写入XML): " << new_setting; + BOOST_LOG(info) << "VDD会话模式列表更新完成: " << new_setting; const display_mode_t requested_mode {*config.resolution, *config.refresh_rate}; const bool mode_advertised = !vdd_device_id.empty() && vdd_utils::is_mode_advertised(vdd_device_id, requested_mode); @@ -662,15 +663,23 @@ namespace display_device { config.resolution && config.refresh_rate) { const auto mode_update = update_vdd_resolution(config, vdd_settings, device_zako); if (mode_update == vdd_mode_update_e::failed) { + vdd_utils::clear_vdd_mode_cache(); return false; } + // SETMODES已被驱动接受,持久化模式缓存供驱动重载/重启后使用 + if (vdd_settings.needs_cache_update && + !confighttp::saveVddModeSettings(vdd_settings.global_modes, vdd_settings.temporary_modes, config::video.adapter_name)) { + BOOST_LOG(warning) << "VDD模式XML缓存保存失败 [mode_count: " << vdd_settings.mode_combination_count << "]"; + } + verify_mode_publication_after_create = device_zako.empty(); if (mode_update == vdd_mode_update_e::recreate_monitor) { mode_rebuild_old_vdd_id = device_zako; verify_mode_publication_after_create = true; if (!vdd_utils::destroy_vdd_monitor()) { BOOST_LOG(error) << "Failed to destroy the VDD monitor for an IOCTL mode-list rebuild"; + vdd_utils::clear_vdd_mode_cache(); return false; } if (!wait_for_vdd_device_departure(5, 100ms, 1000ms)) { @@ -697,6 +706,7 @@ namespace display_device { : current_client_id; // 为每个客户端生成不同GUID if (!vdd_utils::create_vdd_monitor(vdd_identifier, hdr_brightness, physical_size)) { BOOST_LOG(error) << "VDD monitor creation command failed"; + vdd_utils::clear_vdd_mode_cache(); return false; } std::this_thread::sleep_for(200ms); @@ -726,11 +736,13 @@ namespace display_device { else { BOOST_LOG(warning) << "VDD IOCTL 仍可用,跳过 disable/enable,避免制造 phantom monitor"; } + vdd_utils::clear_vdd_mode_cache(); return false; } } if (device_zako.empty()) { + vdd_utils::clear_vdd_mode_cache(); return false; } @@ -747,6 +759,7 @@ namespace display_device { if (!mode_advertised) { BOOST_LOG(error) << "VDD monitor did not publish " << to_string(*config.resolution) << "@" << to_string(*config.refresh_rate); + vdd_utils::clear_vdd_mode_cache(); return false; } } diff --git a/src/display_device/vdd_utils.cpp b/src/display_device/vdd_utils.cpp index c05d0e624..648dc1c09 100644 --- a/src/display_device/vdd_utils.cpp +++ b/src/display_device/vdd_utils.cpp @@ -24,6 +24,7 @@ #include #include +#include "src/confighttp.h" #include "src/globals.h" #include "src/platform/common.h" #include "src/platform/run_command.h" @@ -69,6 +70,174 @@ namespace display_device { return status; } + using vdd_mode_t = std::pair; + + std::string + make_vdd_mode_key(const std::string &resolution, const std::string &refresh_rate) { + return resolution + "@" + refresh_rate; + } + + void + append_vdd_mode_latest(std::vector &modes, std::string resolution, std::string refresh_rate) { + boost::algorithm::trim(resolution); + boost::algorithm::trim(refresh_rate); + if (resolution.empty() || refresh_rate.empty()) { + return; + } + + const auto key = make_vdd_mode_key(resolution, refresh_rate); + modes.erase(std::remove_if(modes.begin(), modes.end(), [&](const auto &mode) { + return make_vdd_mode_key(mode.first, mode.second) == key; + }), + modes.end()); + modes.emplace_back(std::move(resolution), std::move(refresh_rate)); + } + + void + collect_vdd_mode_dimensions(const std::vector &modes, + std::unordered_set &resolutions, + std::unordered_set &refresh_rates) { + for (const auto &mode : modes) { + resolutions.insert(mode.first); + refresh_rates.insert(mode.second); + } + } + + std::size_t + count_vdd_mode_combinations(const std::vector &modes) { + std::unordered_set resolutions; + std::unordered_set refresh_rates; + collect_vdd_mode_dimensions(modes, resolutions, refresh_rates); + return resolutions.size() * refresh_rates.size(); + } + + bool + vdd_modes_equal(const std::vector &lhs, const std::vector &rhs) { + if (lhs.size() != rhs.size()) { + return false; + } + + for (std::size_t index = 0; index < lhs.size(); ++index) { + if (lhs[index] != rhs[index]) { + return false; + } + } + return true; + } + + bool + contains_vdd_mode(const std::vector &modes, const std::string &resolution, const std::string &refresh_rate) { + const auto key = make_vdd_mode_key(resolution, refresh_rate); + return std::any_of(modes.begin(), modes.end(), [&](const auto &mode) { + return make_vdd_mode_key(mode.first, mode.second) == key; + }); + } + + void + trim_temporary_vdd_modes_to_limit(const std::vector &global_modes, std::vector &temporary_modes) { + std::unordered_set global_resolutions; + std::unordered_set global_refresh_rates; + collect_vdd_mode_dimensions(global_modes, global_resolutions, global_refresh_rates); + + const auto temporary_mode_limit = std::min( + confighttp::VDD_MAX_CACHED_TEMPORARY_MODES, + confighttp::vdd_max_temporary_mode_count( + global_resolutions.size(), + global_refresh_rates.size())); + + while (temporary_modes.size() > temporary_mode_limit) { + temporary_modes.erase(temporary_modes.begin()); + } + } + + void + read_modes_from_vdd_settings(const pt::ptree &vdd_settings, + std::vector &global_modes, + std::vector &temporary_modes) { + global_modes.clear(); + temporary_modes.clear(); + std::vector resolutions; + std::vector global_refresh_rates; + + if (const auto global_node = vdd_settings.get_child_optional("global")) { + for (const auto &child : *global_node) { + if (child.first != "g_refresh_rate") { + continue; + } + + auto refresh_rate = child.second.data(); + boost::algorithm::trim(refresh_rate); + if (!refresh_rate.empty()) { + global_refresh_rates.push_back(std::move(refresh_rate)); + } + } + } + + if (const auto resolutions_node = vdd_settings.get_child_optional("resolutions")) { + for (const auto &child : *resolutions_node) { + if (child.first != "resolution") { + continue; + } + + auto width = child.second.get("width", ""); + auto height = child.second.get("height", ""); + boost::algorithm::trim(width); + boost::algorithm::trim(height); + if (width.empty() || height.empty()) { + continue; + } + + const auto resolution = width + "x" + height; + if (auto refresh_rate = child.second.get_optional("refresh_rate")) { + append_vdd_mode_latest(temporary_modes, resolution, *refresh_rate); + } + else { + resolutions.push_back(resolution); + } + } + } + + for (const auto &refresh_rate : global_refresh_rates) { + for (const auto &resolution : resolutions) { + append_vdd_mode_latest(global_modes, resolution, refresh_rate); + } + } + } + + void + read_vdd_settings_modes(std::vector &global_modes, + std::vector &temporary_modes) { + global_modes.clear(); + temporary_modes.clear(); + const auto settings_path = std::filesystem::path(platf::appdata()) / "vdd_settings.xml"; + if (!std::filesystem::exists(settings_path)) { + return; + } + + try { + pt::ptree root; + pt::read_xml(settings_path.string(), root); + const auto vdd_settings = root.get_child_optional("vdd_settings"); + if (!vdd_settings) { + return; + } + + read_modes_from_vdd_settings(*vdd_settings, global_modes, temporary_modes); + } + catch (const std::exception &e) { + BOOST_LOG(warning) << "读取VDD模式列表失败: " << e.what(); + } + } + + void + append_configured_vdd_modes(std::vector &modes) { + for (const auto &resolution : config::nvhttp.resolutions) { + for (const auto &refresh_rate : config::nvhttp.fps) { + append_vdd_mode_latest(modes, resolution, refresh_rate); + } + } + } + std::chrono::milliseconds calculate_exponential_backoff(int attempt) { auto delay = kInitialRetryDelay * (1 << attempt); @@ -729,31 +898,83 @@ namespace display_device { VddSettings prepare_vdd_settings(const parsed_config_t &config) { - std::vector resolution_modes; - std::vector refresh_rates_hz; + VddSettings settings; + + // XML缓存模式列表:全局模式来自用户配置,临时模式来自缓存+本次会话 + std::vector existing_global_modes; + std::vector existing_temporary_modes; + read_vdd_settings_modes(existing_global_modes, existing_temporary_modes); + + auto global_modes = existing_global_modes; + if (!config::nvhttp.resolutions.empty() && !config::nvhttp.fps.empty()) { + global_modes.clear(); + append_configured_vdd_modes(global_modes); + } + + auto temporary_modes = existing_temporary_modes; + if (config.resolution && config.refresh_rate) { + const auto current_resolution = to_string(*config.resolution); + const auto current_refresh_rate = to_string(*config.refresh_rate); + if (!contains_vdd_mode(global_modes, current_resolution, current_refresh_rate)) { + append_vdd_mode_latest(temporary_modes, current_resolution, current_refresh_rate); + } + } + trim_temporary_vdd_modes_to_limit(global_modes, temporary_modes); + + // SETMODES类型化模式列表:配置模式 + 缓存临时模式 + 本次会话模式 for (const auto &res : config::nvhttp.resolutions) { if (const auto parsed_resolution = parse_vdd_resolution(res)) { - append_unique_resolution(resolution_modes, *parsed_resolution); + append_unique_resolution(settings.resolution_modes, *parsed_resolution); } } for (const auto &fps : config::nvhttp.fps) { if (const auto parsed_refresh_hz = parse_vdd_refresh_hz(fps)) { - append_unique_refresh_rate(refresh_rates_hz, *parsed_refresh_hz); + append_unique_refresh_rate(settings.refresh_rates_hz, *parsed_refresh_hz); + } + } + + for (const auto &mode : temporary_modes) { + if (const auto parsed_resolution = parse_vdd_resolution(mode.first)) { + append_unique_resolution(settings.resolution_modes, *parsed_resolution); + } + if (const auto parsed_refresh_hz = parse_vdd_refresh_hz(mode.second)) { + append_unique_refresh_rate(settings.refresh_rates_hz, *parsed_refresh_hz); } } if (config.resolution) { - append_unique_resolution(resolution_modes, *config.resolution); + append_unique_resolution(settings.resolution_modes, *config.resolution); } if (config.refresh_rate) { if (const auto session_refresh_hz = rounded_refresh_hz(*config.refresh_rate)) { - append_unique_refresh_rate(refresh_rates_hz, *session_refresh_hz); + append_unique_refresh_rate(settings.refresh_rates_hz, *session_refresh_hz); } } - return { std::move(resolution_modes), std::move(refresh_rates_hz) }; + settings.needs_cache_update = config.resolution && config.refresh_rate && + (!vdd_modes_equal(existing_global_modes, global_modes) || + !vdd_modes_equal(existing_temporary_modes, temporary_modes)); + settings.mode_combination_count = count_vdd_mode_combinations(global_modes) + temporary_modes.size(); + settings.global_modes = std::move(global_modes); + settings.temporary_modes = std::move(temporary_modes); + return settings; + } + + void + clear_vdd_mode_cache() { + std::vector global_modes; + std::vector temporary_modes; + read_vdd_settings_modes(global_modes, temporary_modes); + if (temporary_modes.empty()) { + return; + } + + BOOST_LOG(info) << "VDD启动失败,清除临时模式缓存 [count: " << temporary_modes.size() << "]"; + if (!confighttp::saveVddModeSettings(global_modes, {}, config::video.adapter_name)) { + BOOST_LOG(warning) << "清除VDD临时模式缓存失败"; + } } bool diff --git a/src/display_device/vdd_utils.h b/src/display_device/vdd_utils.h index fe615a795..dcd402da5 100644 --- a/src/display_device/vdd_utils.h +++ b/src/display_device/vdd_utils.h @@ -2,9 +2,11 @@ #define WIN32_LEAN_AND_MEAN +#include #include #include #include +#include #include #include #include @@ -48,8 +50,15 @@ namespace display_device::vdd_utils { // VDD设置结构 struct VddSettings { + // Typed mode lists pushed to the driver via SETMODES. std::vector resolution_modes; std::vector refresh_rates_hz; + // Fixed modes written through global.g_refresh_rate plus resolution nodes. + std::vector> global_modes; + // Temporary exact modes written as resolution.refresh_rate nodes. + std::vector> temporary_modes; + std::size_t mode_combination_count = 0; + bool needs_cache_update = false; }; constexpr bool @@ -257,6 +266,14 @@ namespace display_device::vdd_utils { VddSettings prepare_vdd_settings(const parsed_config_t &config); + /** + * @brief Drop cached temporary modes from vdd_settings.xml. + * @details Called on driver bring-up failure paths so a bad cached mode list + * cannot keep breaking every subsequent VDD start. + */ + void + clear_vdd_mode_cache(); + // 重试函数模板 template bool diff --git a/src/main.cpp b/src/main.cpp index e7042fae8..d60d94d0f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -125,7 +125,9 @@ mainThreadLoop(const std::shared_ptr> &shutdown_event) { // Main thread event loop BOOST_LOG(info) << "Starting main loop"sv; +#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 while (system_tray::process_tray_events() == 0); +#endif BOOST_LOG(info) << "Main loop has exited"sv; } @@ -442,15 +444,17 @@ main(int argc, char *argv[]) { if (tray_is_enabled && config::sunshine.system_tray) { BOOST_LOG(info) << "Starting system tray"sv; -#ifdef _WIN32 +#if defined SUNSHINE_TRAY && SUNSHINE_TRAY >= 1 + #ifdef _WIN32 // TODO: Windows has a weird bug where when running as a service and on the first Windows boot, // he tray icon would not appear even though Sunshine is running correctly otherwise. // Restarting the service would allow the icon to appear normally. // For now we will keep the Windows tray icon on a separate thread. // Ideally, we would run the system tray on the main thread for all platforms. system_tray::init_tray_threaded(); -#else + #else system_tray::init_tray(); + #endif #endif }