feat(display): add display mode remapping option (#3529)
Co-authored-by: ReenigneArcher <42013603+ReenigneArcher@users.noreply.github.com>
This commit is contained in:
parent
012a99c26d
commit
1b94e9339a
11 changed files with 701 additions and 35 deletions
|
|
@ -1234,6 +1234,92 @@ editing the `conf` file in a text editor. Use the examples as reference.
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
### dd_mode_remapping
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>Description</td>
|
||||||
|
<td colspan="2">
|
||||||
|
Remap the requested resolution and FPS to another display mode.<br>
|
||||||
|
Depending on the [dd_resolution_option](#dd_resolution_option) and
|
||||||
|
[dd_refresh_rate_option](#dd_refresh_rate_option) values, the following mapping
|
||||||
|
groups are available:
|
||||||
|
<ul>
|
||||||
|
<li>`mixed` - both options are set to `auto`.</li>
|
||||||
|
<li>
|
||||||
|
`resolution_only` - only [dd_resolution_option](#dd_resolution_option) is set to `auto`.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
`refresh_rate_only` - only [dd_refresh_rate_option](#dd_refresh_rate_option) is set to `auto`.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
For each of those groups, a list of fields can be configured to perform remapping:
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
`requested_resolution` - resolution that needs to be matched in order to use this remapping entry.
|
||||||
|
</li>
|
||||||
|
<li>`requested_fps` - FPS that needs to be matched in order to use this remapping entry.</li>
|
||||||
|
<li>`final_resolution` - resolution value to be used if the entry was matched.</li>
|
||||||
|
<li>`final_refresh_rate` - refresh rate value to be used if the entry was matched.</li>
|
||||||
|
</ul>
|
||||||
|
If `requested_*` field is left empty, it will match <b>everything</b>.<br>
|
||||||
|
If `final_*` field is left empty, the original value will not be remapped and either a requested, manual
|
||||||
|
or current value is used. However, at least one `final_*` must be set, otherwise the entry is considered
|
||||||
|
invalid.<br>
|
||||||
|
@note{"Optimize game settings" must be enabled on client side for ANY entry with `resolution`
|
||||||
|
field to be considered.}
|
||||||
|
@note{First entry to be matched in the list is the one that will be used.}
|
||||||
|
@tip{`requested_resolution` and `final_resolution` can be omitted for `refresh_rate_only` group.}
|
||||||
|
@tip{`requested_fps` and `final_refresh_rate` can be omitted for `resolution_only` group.}
|
||||||
|
@note{Applies to Windows only.}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Default</td>
|
||||||
|
<td colspan="2">@code{}
|
||||||
|
dd_mode_remapping = {
|
||||||
|
"mixed": [],
|
||||||
|
"resolution_only": [],
|
||||||
|
"refresh_rate_only": []
|
||||||
|
}
|
||||||
|
@endcode
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Example</td>
|
||||||
|
<td colspan="2">@code{}
|
||||||
|
dd_mode_remapping = {
|
||||||
|
"mixed": [
|
||||||
|
{
|
||||||
|
"requested_fps": "60",
|
||||||
|
"final_refresh_rate": "119.95",
|
||||||
|
"requested_resolution": "1920x1080",
|
||||||
|
"final_resolution": "2560x1440"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"requested_fps": "60",
|
||||||
|
"final_refresh_rate": "120",
|
||||||
|
"requested_resolution": "",
|
||||||
|
"final_resolution": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resolution_only": [
|
||||||
|
{
|
||||||
|
"requested_resolution": "1920x1080",
|
||||||
|
"final_resolution": "2560x1440"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh_rate_only": [
|
||||||
|
{
|
||||||
|
"requested_fps": "60",
|
||||||
|
"final_refresh_rate": "119.95"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}@endcode
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
### min_fps_factor
|
### min_fps_factor
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
|
|
|
||||||
|
|
@ -379,6 +379,38 @@ namespace config {
|
||||||
#undef _CONVERT_2_ARG_
|
#undef _CONVERT_2_ARG_
|
||||||
return video_t::dd_t::hdr_option_e::disabled; // Default to this if value is invalid
|
return video_t::dd_t::hdr_option_e::disabled; // Default to this if value is invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
video_t::dd_t::mode_remapping_t
|
||||||
|
mode_remapping_from_view(const std::string_view value) {
|
||||||
|
const auto parse_entry_list { [](const auto &entry_list, auto &output_field) {
|
||||||
|
for (auto &[_, entry] : entry_list) {
|
||||||
|
auto requested_resolution = entry.template get_optional<std::string>("requested_resolution"s);
|
||||||
|
auto requested_fps = entry.template get_optional<std::string>("requested_fps"s);
|
||||||
|
auto final_resolution = entry.template get_optional<std::string>("final_resolution"s);
|
||||||
|
auto final_refresh_rate = entry.template get_optional<std::string>("final_refresh_rate"s);
|
||||||
|
|
||||||
|
output_field.push_back(video_t::dd_t::mode_remapping_entry_t {
|
||||||
|
requested_resolution.value_or(""),
|
||||||
|
requested_fps.value_or(""),
|
||||||
|
final_resolution.value_or(""),
|
||||||
|
final_refresh_rate.value_or("") });
|
||||||
|
}
|
||||||
|
} };
|
||||||
|
|
||||||
|
// We need to add a wrapping object to make it valid JSON, otherwise ptree cannot parse it.
|
||||||
|
std::stringstream json_stream;
|
||||||
|
json_stream << "{\"dd_mode_remapping\":" << value << "}";
|
||||||
|
|
||||||
|
boost::property_tree::ptree json_tree;
|
||||||
|
boost::property_tree::read_json(json_stream, json_tree);
|
||||||
|
|
||||||
|
video_t::dd_t::mode_remapping_t output;
|
||||||
|
parse_entry_list(json_tree.get_child("dd_mode_remapping.mixed"), output.mixed);
|
||||||
|
parse_entry_list(json_tree.get_child("dd_mode_remapping.resolution_only"), output.resolution_only);
|
||||||
|
parse_entry_list(json_tree.get_child("dd_mode_remapping.refresh_rate_only"), output.refresh_rate_only);
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
} // namespace dd
|
} // namespace dd
|
||||||
|
|
||||||
video_t video {
|
video_t video {
|
||||||
|
|
@ -447,6 +479,7 @@ namespace config {
|
||||||
{}, // manual_refresh_rate
|
{}, // manual_refresh_rate
|
||||||
video_t::dd_t::hdr_option_e::automatic, // hdr_option
|
video_t::dd_t::hdr_option_e::automatic, // hdr_option
|
||||||
3s, // config_revert_delay
|
3s, // config_revert_delay
|
||||||
|
{}, // mode_remapping
|
||||||
{} // wa
|
{} // wa
|
||||||
}, // display_device
|
}, // display_device
|
||||||
|
|
||||||
|
|
@ -1105,6 +1138,7 @@ namespace config {
|
||||||
video.dd.config_revert_delay = std::chrono::milliseconds { value };
|
video.dd.config_revert_delay = std::chrono::milliseconds { value };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
generic_f(vars, "dd_mode_remapping", video.dd.mode_remapping, dd::mode_remapping_from_view);
|
||||||
bool_f(vars, "dd_wa_hdr_toggle", video.dd.wa.hdr_toggle);
|
bool_f(vars, "dd_wa_hdr_toggle", video.dd.wa.hdr_toggle);
|
||||||
|
|
||||||
int_between_f(vars, "min_fps_factor", video.min_fps_factor, { 1, 3 });
|
int_between_f(vars, "min_fps_factor", video.min_fps_factor, { 1, 3 });
|
||||||
|
|
|
||||||
14
src/config.h
14
src/config.h
|
|
@ -110,6 +110,19 @@ namespace config {
|
||||||
automatic ///< Change HDR settings and use the state requested by Moonlight.
|
automatic ///< Change HDR settings and use the state requested by Moonlight.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct mode_remapping_entry_t {
|
||||||
|
std::string requested_resolution;
|
||||||
|
std::string requested_fps;
|
||||||
|
std::string final_resolution;
|
||||||
|
std::string final_refresh_rate;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct mode_remapping_t {
|
||||||
|
std::vector<mode_remapping_entry_t> mixed; ///< To be used when `resolution_option` and `refresh_rate_option` is set to `automatic`.
|
||||||
|
std::vector<mode_remapping_entry_t> resolution_only; ///< To be use when only `resolution_option` is set to `automatic`.
|
||||||
|
std::vector<mode_remapping_entry_t> refresh_rate_only; ///< To be use when only `refresh_rate_option` is set to `automatic`.
|
||||||
|
};
|
||||||
|
|
||||||
config_option_e configuration_option;
|
config_option_e configuration_option;
|
||||||
resolution_option_e resolution_option;
|
resolution_option_e resolution_option;
|
||||||
std::string manual_resolution; ///< Manual resolution in case `resolution_option == resolution_option_e::manual`.
|
std::string manual_resolution; ///< Manual resolution in case `resolution_option == resolution_option_e::manual`.
|
||||||
|
|
@ -117,6 +130,7 @@ namespace config {
|
||||||
std::string manual_refresh_rate; ///< Manual refresh rate in case `refresh_rate_option == refresh_rate_option_e::manual`.
|
std::string manual_refresh_rate; ///< Manual refresh rate in case `refresh_rate_option == refresh_rate_option_e::manual`.
|
||||||
hdr_option_e hdr_option;
|
hdr_option_e hdr_option;
|
||||||
std::chrono::milliseconds config_revert_delay; ///< Time to wait until settings are reverted (after stream ends/app exists).
|
std::chrono::milliseconds config_revert_delay; ///< Time to wait until settings are reverted (after stream ends/app exists).
|
||||||
|
mode_remapping_t mode_remapping;
|
||||||
workarounds_t wa;
|
workarounds_t wa;
|
||||||
} dd;
|
} dd;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,7 @@ namespace display_device {
|
||||||
* @brief Parse refresh rate value from the string.
|
* @brief Parse refresh rate value from the string.
|
||||||
* @param input String to be parsed.
|
* @param input String to be parsed.
|
||||||
* @param output Reference to output variable to fill in.
|
* @param output Reference to output variable to fill in.
|
||||||
|
* @param allow_decimal_point Specify whether the decimal point is allowed or not.
|
||||||
* @returns True on successful parsing (empty string allowed), false otherwise.
|
* @returns True on successful parsing (empty string allowed), false otherwise.
|
||||||
*
|
*
|
||||||
* @examples
|
* @examples
|
||||||
|
|
@ -203,10 +204,10 @@ namespace display_device {
|
||||||
* @examples_end
|
* @examples_end
|
||||||
*/
|
*/
|
||||||
bool
|
bool
|
||||||
parse_refresh_rate_string(const std::string &input, std::optional<FloatingPoint> &output) {
|
parse_refresh_rate_string(const std::string &input, std::optional<FloatingPoint> &output, const bool allow_decimal_point = true) {
|
||||||
static const auto is_zero { [](const auto &character) { return character == '0'; } };
|
static const auto is_zero { [](const auto &character) { return character == '0'; } };
|
||||||
const std::string trimmed_input { boost::algorithm::trim_copy(input) };
|
const std::string trimmed_input { boost::algorithm::trim_copy(input) };
|
||||||
const std::regex refresh_rate_regex { R"(^(\d+)(?:\.(\d+))?$)" };
|
const std::regex refresh_rate_regex { allow_decimal_point ? R"(^(\d+)(?:\.(\d+))?$)" : R"(^(\d+)$)" };
|
||||||
|
|
||||||
if (std::smatch match; std::regex_match(trimmed_input, match, refresh_rate_regex)) {
|
if (std::smatch match; std::regex_match(trimmed_input, match, refresh_rate_regex)) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -217,7 +218,7 @@ namespace display_device {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string trimmed_match_2;
|
std::string trimmed_match_2;
|
||||||
if (match[2].matched) {
|
if (allow_decimal_point && match[2].matched) {
|
||||||
trimmed_match_2 = boost::algorithm::trim_right_copy_if(match[2].str(), is_zero);
|
trimmed_match_2 = boost::algorithm::trim_right_copy_if(match[2].str(), is_zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,7 +262,7 @@ namespace display_device {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << R"(. Must have a pattern of "123" or "123.456"!)";
|
BOOST_LOG(error) << "Failed to parse refresh rate string " << trimmed_input << ". Must have a pattern of " << (allow_decimal_point ? R"("123" or "123.456")" : R"("123")") << "!";
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -435,6 +436,196 @@ namespace display_device {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Indicates which remapping fields and config structure shall be used.
|
||||||
|
*/
|
||||||
|
enum class remapping_type_e {
|
||||||
|
mixed, ///! Both reseolution and refresh rate may be remapped
|
||||||
|
resolution_only, ///! Only resolution will be remapped
|
||||||
|
refresh_rate_only ///! Only refresh rate will be remapped
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Determine the ramapping type from the user config.
|
||||||
|
* @param video_config User's video related configuration.
|
||||||
|
* @returns Enum value if remapping can be performed, null optional if remapping shall be skipped.
|
||||||
|
*/
|
||||||
|
std::optional<remapping_type_e>
|
||||||
|
determine_remapping_type(const config::video_t &video_config) {
|
||||||
|
using dd_t = config::video_t::dd_t;
|
||||||
|
const bool auto_resolution { video_config.dd.resolution_option == dd_t::resolution_option_e::automatic };
|
||||||
|
const bool auto_refresh_rate { video_config.dd.refresh_rate_option == dd_t::refresh_rate_option_e::automatic };
|
||||||
|
|
||||||
|
if (auto_resolution && auto_refresh_rate) {
|
||||||
|
return remapping_type_e::mixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto_resolution) {
|
||||||
|
return remapping_type_e::resolution_only;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto_refresh_rate) {
|
||||||
|
return remapping_type_e::refresh_rate_only;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Contains remapping data parsed from the string values.
|
||||||
|
*/
|
||||||
|
struct parsed_remapping_entry_t {
|
||||||
|
std::optional<Resolution> requested_resolution;
|
||||||
|
std::optional<FloatingPoint> requested_fps;
|
||||||
|
std::optional<Resolution> final_resolution;
|
||||||
|
std::optional<FloatingPoint> final_refresh_rate;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if resolution is to be mapped based on remmaping type.
|
||||||
|
* @param type Remapping type to check.
|
||||||
|
* @returns True if resolution is to be mapped, false otherwise.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
is_resolution_mapped(const remapping_type_e type) {
|
||||||
|
return type == remapping_type_e::resolution_only || type == remapping_type_e::mixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if FPS is to be mapped based on remmaping type.
|
||||||
|
* @param type Remapping type to check.
|
||||||
|
* @returns True if FPS is to be mapped, false otherwise.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
is_fps_mapped(const remapping_type_e type) {
|
||||||
|
return type == remapping_type_e::refresh_rate_only || type == remapping_type_e::mixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Parse the remapping entry from the config into an internal structure.
|
||||||
|
* @param entry Entry to parse.
|
||||||
|
* @param type Specify which entry fields should be parsed.
|
||||||
|
* @returns Parsed structure or null optional if a necessary field could not be parsed.
|
||||||
|
*/
|
||||||
|
std::optional<parsed_remapping_entry_t>
|
||||||
|
parse_remapping_entry(const config::video_t::dd_t::mode_remapping_entry_t &entry, const remapping_type_e type) {
|
||||||
|
parsed_remapping_entry_t result {};
|
||||||
|
|
||||||
|
if (is_resolution_mapped(type) && (!parse_resolution_string(entry.requested_resolution, result.requested_resolution) ||
|
||||||
|
!parse_resolution_string(entry.final_resolution, result.final_resolution))) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_fps_mapped(type) && (!parse_refresh_rate_string(entry.requested_fps, result.requested_fps, false) ||
|
||||||
|
!parse_refresh_rate_string(entry.final_refresh_rate, result.final_refresh_rate))) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remap the the requested display mode based on the config.
|
||||||
|
* @param video_config User's video related configuration.
|
||||||
|
* @param session Session information.
|
||||||
|
* @param config A reference to a config object that will be modified on success.
|
||||||
|
* @returns True if the remapping was performed or skipped, false if remapping has failed due to invalid config.
|
||||||
|
*
|
||||||
|
* @examples
|
||||||
|
* const std::shared_ptr<rtsp_stream::launch_session_t> launch_session;
|
||||||
|
* const config::video_t &video_config { config::video };
|
||||||
|
*
|
||||||
|
* SingleDisplayConfiguration config;
|
||||||
|
* const bool success = remap_display_mode_if_needed(video_config, *launch_session, config);
|
||||||
|
* @examples_end
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
remap_display_mode_if_needed(const config::video_t &video_config, const rtsp_stream::launch_session_t &session, SingleDisplayConfiguration &config) {
|
||||||
|
const auto remapping_type { determine_remapping_type(video_config) };
|
||||||
|
if (!remapping_type) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &remapping_list { [&]() {
|
||||||
|
using enum remapping_type_e;
|
||||||
|
|
||||||
|
switch (*remapping_type) {
|
||||||
|
case resolution_only:
|
||||||
|
return video_config.dd.mode_remapping.resolution_only;
|
||||||
|
case refresh_rate_only:
|
||||||
|
return video_config.dd.mode_remapping.refresh_rate_only;
|
||||||
|
case mixed:
|
||||||
|
default:
|
||||||
|
return video_config.dd.mode_remapping.mixed;
|
||||||
|
}
|
||||||
|
}() };
|
||||||
|
|
||||||
|
if (remapping_list.empty()) {
|
||||||
|
BOOST_LOG(debug) << "No values are available for display mode remapping.";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
BOOST_LOG(debug) << "Trying to remap display modes...";
|
||||||
|
|
||||||
|
const auto entry_to_string { [type = *remapping_type](const config::video_t::dd_t::mode_remapping_entry_t &entry) {
|
||||||
|
const bool mapping_resolution { is_resolution_mapped(type) };
|
||||||
|
const bool mapping_fps { is_fps_mapped(type) };
|
||||||
|
|
||||||
|
// clang-format off
|
||||||
|
return (mapping_resolution ? " - requested resolution: "s + entry.requested_resolution + "\n" : "") +
|
||||||
|
(mapping_fps ? " - requested FPS: "s + entry.requested_fps + "\n" : "") +
|
||||||
|
(mapping_resolution ? " - final resolution: "s + entry.final_resolution + "\n" : "") +
|
||||||
|
(mapping_fps ? " - final refresh rate: "s + entry.final_refresh_rate : "");
|
||||||
|
// clang-format on
|
||||||
|
} };
|
||||||
|
|
||||||
|
for (const auto &entry : remapping_list) {
|
||||||
|
const auto parsed_entry { parse_remapping_entry(entry, *remapping_type) };
|
||||||
|
if (!parsed_entry) {
|
||||||
|
BOOST_LOG(error) << "Failed to parse remapping entry from:\n"
|
||||||
|
<< entry_to_string(entry);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsed_entry->final_resolution && !parsed_entry->final_refresh_rate) {
|
||||||
|
BOOST_LOG(error) << "At least one final value must be set for remapping display modes! Entry:\n"
|
||||||
|
<< entry_to_string(entry);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session.enable_sops && (parsed_entry->requested_resolution || parsed_entry->final_resolution)) {
|
||||||
|
BOOST_LOG(warning) << R"(Skipping remapping entry, because the "Optimize game settings" is not set in the client! Entry:\n)"
|
||||||
|
<< entry_to_string(entry);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: at this point config should already have parsed resolution set.
|
||||||
|
if (parsed_entry->requested_resolution && parsed_entry->requested_resolution != config.m_resolution) {
|
||||||
|
BOOST_LOG(verbose) << "Skipping remapping because requested resolutions do not match! Entry:\n"
|
||||||
|
<< entry_to_string(entry);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: at this point config should already have parsed refresh rate set.
|
||||||
|
if (parsed_entry->requested_fps && parsed_entry->requested_fps != config.m_refresh_rate) {
|
||||||
|
BOOST_LOG(verbose) << "Skipping remapping because requested FPS do not match! Entry:\n"
|
||||||
|
<< entry_to_string(entry);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_LOG(info) << "Remapping requested display mode. Entry:\n"
|
||||||
|
<< entry_to_string(entry);
|
||||||
|
if (parsed_entry->final_resolution) {
|
||||||
|
config.m_resolution = parsed_entry->final_resolution;
|
||||||
|
}
|
||||||
|
if (parsed_entry->final_refresh_rate) {
|
||||||
|
config.m_refresh_rate = parsed_entry->final_refresh_rate;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Construct a settings manager interface to manage display device settings.
|
* @brief Construct a settings manager interface to manage display device settings.
|
||||||
* @param persistence_filepath File location for saving persistent state.
|
* @param persistence_filepath File location for saving persistent state.
|
||||||
|
|
@ -626,6 +817,11 @@ namespace display_device {
|
||||||
return failed_to_parse_tag_t {};
|
return failed_to_parse_tag_t {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!remap_display_mode_if_needed(video_config, session, config)) {
|
||||||
|
// Error already logged
|
||||||
|
return failed_to_parse_tag_t {};
|
||||||
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
} // namespace display_device
|
} // namespace display_device
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@
|
||||||
<general
|
<general
|
||||||
v-if="currentTab === 'general'"
|
v-if="currentTab === 'general'"
|
||||||
:config="config"
|
:config="config"
|
||||||
:global-prep-cmd="global_prep_cmd"
|
|
||||||
:platform="platform">
|
:platform="platform">
|
||||||
</general>
|
</general>
|
||||||
|
|
||||||
|
|
@ -127,7 +126,6 @@
|
||||||
restarted: false,
|
restarted: false,
|
||||||
config: null,
|
config: null,
|
||||||
currentTab: "general",
|
currentTab: "general",
|
||||||
global_prep_cmd: [],
|
|
||||||
tabs: [ // TODO: Move the options to each Component instead, encapsulate.
|
tabs: [ // TODO: Move the options to each Component instead, encapsulate.
|
||||||
{
|
{
|
||||||
id: "general",
|
id: "general",
|
||||||
|
|
@ -136,7 +134,7 @@
|
||||||
"locale": "en",
|
"locale": "en",
|
||||||
"sunshine_name": "",
|
"sunshine_name": "",
|
||||||
"min_log_level": 2,
|
"min_log_level": 2,
|
||||||
"global_prep_cmd": "[]",
|
"global_prep_cmd": [],
|
||||||
"notify_pre_releases": "disabled",
|
"notify_pre_releases": "disabled",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -178,6 +176,7 @@
|
||||||
"dd_manual_refresh_rate": "",
|
"dd_manual_refresh_rate": "",
|
||||||
"dd_hdr_option": "auto",
|
"dd_hdr_option": "auto",
|
||||||
"dd_config_revert_delay": 3000,
|
"dd_config_revert_delay": 3000,
|
||||||
|
"dd_mode_remapping": {"mixed": [], "resolution_only": [], "refresh_rate_only": []},
|
||||||
"dd_wa_hdr_toggle": "disabled",
|
"dd_wa_hdr_toggle": "disabled",
|
||||||
"min_fps_factor": 1,
|
"min_fps_factor": 1,
|
||||||
},
|
},
|
||||||
|
|
@ -320,17 +319,23 @@
|
||||||
|
|
||||||
// TODO: let each tab's Component handle it's own data instead of doing it here
|
// TODO: let each tab's Component handle it's own data instead of doing it here
|
||||||
|
|
||||||
|
// Parse the special options before population if available
|
||||||
|
const specialOptions = ["dd_mode_remapping", "global_prep_cmd"]
|
||||||
|
for (const optionKey of specialOptions) {
|
||||||
|
if (this.config.hasOwnProperty(optionKey)) {
|
||||||
|
this.config[optionKey] = JSON.parse(this.config[optionKey]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Populate default values from tabs options
|
// Populate default values from tabs options
|
||||||
this.tabs.forEach(tab => {
|
this.tabs.forEach(tab => {
|
||||||
Object.keys(tab.options).forEach(optionKey => {
|
Object.keys(tab.options).forEach(optionKey => {
|
||||||
if (this.config[optionKey] === undefined) {
|
if (this.config[optionKey] === undefined) {
|
||||||
this.config[optionKey] = tab.options[optionKey];
|
// Make sure to copy by value
|
||||||
|
this.config[optionKey] = JSON.parse(JSON.stringify(tab.options[optionKey]));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.config.global_prep_cmd = this.config.global_prep_cmd || [];
|
|
||||||
this.global_prep_cmd = JSON.parse(this.config.global_prep_cmd);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -338,26 +343,26 @@
|
||||||
this.$forceUpdate()
|
this.$forceUpdate()
|
||||||
},
|
},
|
||||||
serialize() {
|
serialize() {
|
||||||
this.config.global_prep_cmd = JSON.stringify(this.global_prep_cmd);
|
let config = JSON.parse(JSON.stringify(this.config));
|
||||||
|
config.global_prep_cmd = JSON.stringify(config.global_prep_cmd);
|
||||||
|
config.dd_mode_remapping = JSON.stringify(config.dd_mode_remapping);
|
||||||
|
return config;
|
||||||
},
|
},
|
||||||
save() {
|
save() {
|
||||||
this.saved = false;
|
this.saved = false;
|
||||||
this.restarted = false;
|
this.restarted = false;
|
||||||
this.serialize();
|
|
||||||
|
|
||||||
// create a temp copy of this.config to use for the post request
|
// create a temp copy of this.config to use for the post request
|
||||||
let config = JSON.parse(JSON.stringify(this.config))
|
let config = this.serialize();
|
||||||
|
|
||||||
// delete default values from this.config
|
// delete default values from this.config
|
||||||
this.tabs.forEach(tab => {
|
this.tabs.forEach(tab => {
|
||||||
Object.keys(tab.options).forEach(optionKey => {
|
Object.keys(tab.options).forEach(optionKey => {
|
||||||
let delete_value = false
|
let delete_value = false
|
||||||
|
|
||||||
if (["global_prep_cmd"].includes(optionKey)) {
|
if (["global_prep_cmd", "dd_mode_remapping"].includes(optionKey)) {
|
||||||
let config_value, default_value
|
const config_value = config[optionKey]
|
||||||
|
const default_value = JSON.stringify(tab.options[optionKey])
|
||||||
config_value = JSON.parse(config[optionKey])
|
|
||||||
default_value = JSON.parse(tab.options[optionKey])
|
|
||||||
|
|
||||||
if (config_value === default_value) {
|
if (config_value === default_value) {
|
||||||
delete_value = true
|
delete_value = true
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import Checkbox from "../../Checkbox.vue";
|
||||||
const props = defineProps([
|
const props = defineProps([
|
||||||
'platform',
|
'platform',
|
||||||
'config',
|
'config',
|
||||||
'min_fps_factor',
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const config = ref(props.config)
|
const config = ref(props.config)
|
||||||
|
|
@ -95,7 +94,6 @@ const config = ref(props.config)
|
||||||
<DisplayModesSettings
|
<DisplayModesSettings
|
||||||
:platform="platform"
|
:platform="platform"
|
||||||
:config="config"
|
:config="config"
|
||||||
:min_fps_factor="min_fps_factor"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,9 @@ import { ref } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
platform: String,
|
platform: String,
|
||||||
config: Object,
|
config: Object
|
||||||
globalPrepCmd: Array
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const config = ref(props.config)
|
const config = ref(props.config)
|
||||||
const globalPrepCmd = ref(props.globalPrepCmd)
|
|
||||||
|
|
||||||
function addCmd() {
|
function addCmd() {
|
||||||
let template = {
|
let template = {
|
||||||
|
|
@ -20,11 +17,11 @@ function addCmd() {
|
||||||
if (props.platform === 'windows') {
|
if (props.platform === 'windows') {
|
||||||
template = { ...template, elevated: false };
|
template = { ...template, elevated: false };
|
||||||
}
|
}
|
||||||
globalPrepCmd.value.push(template);
|
config.value.global_prep_cmd.push(template);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeCmd(index) {
|
function removeCmd(index) {
|
||||||
globalPrepCmd.value.splice(index,1)
|
config.value.global_prep_cmd.splice(index,1)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -83,7 +80,7 @@ function removeCmd(index) {
|
||||||
<div id="global_prep_cmd" class="mb-3 d-flex flex-column">
|
<div id="global_prep_cmd" class="mb-3 d-flex flex-column">
|
||||||
<label class="form-label">{{ $t('config.global_prep_cmd') }}</label>
|
<label class="form-label">{{ $t('config.global_prep_cmd') }}</label>
|
||||||
<div class="form-text">{{ $t('config.global_prep_cmd_desc') }}</div>
|
<div class="form-text">{{ $t('config.global_prep_cmd_desc') }}</div>
|
||||||
<table class="table" v-if="globalPrepCmd.length > 0">
|
<table class="table" v-if="config.global_prep_cmd.length > 0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col"><i class="fas fa-play"></i> {{ $t('_common.do_cmd') }}</th>
|
<th scope="col"><i class="fas fa-play"></i> {{ $t('_common.do_cmd') }}</th>
|
||||||
|
|
@ -95,7 +92,7 @@ function removeCmd(index) {
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(c, i) in globalPrepCmd">
|
<tr v-for="(c, i) in config.global_prep_cmd">
|
||||||
<td>
|
<td>
|
||||||
<input type="text" class="form-control monospace" v-model="c.do" />
|
<input type="text" class="form-control monospace" v-model="c.do" />
|
||||||
</td>
|
</td>
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,44 @@ const props = defineProps({
|
||||||
platform: String,
|
platform: String,
|
||||||
config: Object
|
config: Object
|
||||||
})
|
})
|
||||||
|
|
||||||
const config = ref(props.config)
|
const config = ref(props.config)
|
||||||
|
|
||||||
|
const REFRESH_RATE_ONLY = "refresh_rate_only"
|
||||||
|
const RESOLUTION_ONLY = "resolution_only"
|
||||||
|
const MIXED = "mixed"
|
||||||
|
|
||||||
|
function canBeRemapped() {
|
||||||
|
return (config.value.dd_resolution_option === "auto" || config.value.dd_refresh_rate_option === "auto")
|
||||||
|
&& config.value.dd_configuration_option !== "disabled";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRemappingType() {
|
||||||
|
// Assuming here that at least one setting is set to "auto" if other is not
|
||||||
|
if (config.value.dd_resolution_option !== "auto") {
|
||||||
|
return REFRESH_RATE_ONLY;
|
||||||
|
}
|
||||||
|
if (config.value.dd_refresh_rate_option !== "auto") {
|
||||||
|
return RESOLUTION_ONLY;
|
||||||
|
}
|
||||||
|
return MIXED;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addRemappingEntry() {
|
||||||
|
const type = getRemappingType();
|
||||||
|
let template = {};
|
||||||
|
|
||||||
|
if (type !== RESOLUTION_ONLY) {
|
||||||
|
template["requested_fps"] = "";
|
||||||
|
template["final_refresh_rate"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type !== REFRESH_RATE_ONLY) {
|
||||||
|
template["requested_resolution"] = "";
|
||||||
|
template["final_resolution"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
config.value.dd_mode_remapping[type].push(template);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -114,6 +150,76 @@ const config = ref(props.config)
|
||||||
{{ $t('config.dd_config_revert_delay_desc') }}
|
{{ $t('config.dd_config_revert_delay_desc') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Display mode remapping -->
|
||||||
|
<div class="mb-3" v-if="canBeRemapped()">
|
||||||
|
<label for="dd_mode_remapping" class="form-label">
|
||||||
|
{{ $t('config.dd_mode_remapping') }}
|
||||||
|
</label>
|
||||||
|
<div id="dd_mode_remapping" class="d-flex flex-column">
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t('config.dd_mode_remapping_desc_1') }}<br>
|
||||||
|
{{ $t('config.dd_mode_remapping_desc_2') }}<br>
|
||||||
|
{{ $t('config.dd_mode_remapping_desc_3') }}<br>
|
||||||
|
{{ $t(getRemappingType() === MIXED ? 'config.dd_mode_remapping_desc_4_final_values_mixed' : 'config.dd_mode_remapping_desc_4_final_values_non_mixed') }}<br>
|
||||||
|
<template v-if="getRemappingType() === MIXED">
|
||||||
|
{{ $t('config.dd_mode_remapping_desc_5_sops_mixed_only') }}<br>
|
||||||
|
</template>
|
||||||
|
<template v-if="getRemappingType() === RESOLUTION_ONLY">
|
||||||
|
{{ $t('config.dd_mode_remapping_desc_5_sops_resolution_only') }}<br>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table" v-if="config.dd_mode_remapping[getRemappingType()].length > 0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" v-if="getRemappingType() !== REFRESH_RATE_ONLY">
|
||||||
|
{{ $t('config.dd_mode_remapping_requested_resolution') }}
|
||||||
|
</th>
|
||||||
|
<th scope="col" v-if="getRemappingType() !== RESOLUTION_ONLY">
|
||||||
|
{{ $t('config.dd_mode_remapping_requested_fps') }}
|
||||||
|
</th>
|
||||||
|
<th scope="col" v-if="getRemappingType() !== REFRESH_RATE_ONLY">
|
||||||
|
{{ $t('config.dd_mode_remapping_final_resolution') }}
|
||||||
|
</th>
|
||||||
|
<th scope="col" v-if="getRemappingType() !== RESOLUTION_ONLY">
|
||||||
|
{{ $t('config.dd_mode_remapping_final_refresh_rate') }}
|
||||||
|
</th>
|
||||||
|
<!-- Additional columns for buttons-->
|
||||||
|
<th scope="col"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="(value, idx) in config.dd_mode_remapping[getRemappingType()]">
|
||||||
|
<td v-if="getRemappingType() !== REFRESH_RATE_ONLY">
|
||||||
|
<input type="text" class="form-control monospace" v-model="value.requested_resolution"
|
||||||
|
:placeholder="'1920x1080'" />
|
||||||
|
</td>
|
||||||
|
<td v-if="getRemappingType() !== RESOLUTION_ONLY">
|
||||||
|
<input type="text" class="form-control monospace" v-model="value.requested_fps"
|
||||||
|
:placeholder="'60'" />
|
||||||
|
</td>
|
||||||
|
<td v-if="getRemappingType() !== REFRESH_RATE_ONLY">
|
||||||
|
<input type="text" class="form-control monospace" v-model="value.final_resolution"
|
||||||
|
:placeholder="'2560x1440'" />
|
||||||
|
</td>
|
||||||
|
<td v-if="getRemappingType() !== RESOLUTION_ONLY">
|
||||||
|
<input type="text" class="form-control monospace" v-model="value.final_refresh_rate"
|
||||||
|
:placeholder="'119.95'" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-danger" @click="config.dd_mode_remapping[getRemappingType()].splice(idx, 1)">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button class="ms-0 mt-2 btn btn-success" style="margin: 0 auto" @click="addRemappingEntry()">
|
||||||
|
+ {{ $t('config.dd_mode_remapping_add') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,8 @@ import PlatformLayout from '../../../PlatformLayout.vue'
|
||||||
const props = defineProps([
|
const props = defineProps([
|
||||||
'platform',
|
'platform',
|
||||||
'config',
|
'config',
|
||||||
'min_fps_factor',
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const config = ref(props.config)
|
const config = ref(props.config)
|
||||||
|
|
||||||
const resIn = ref("")
|
|
||||||
const fpsIn = ref("")
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,19 @@
|
||||||
"dd_hdr_option": "HDR",
|
"dd_hdr_option": "HDR",
|
||||||
"dd_hdr_option_auto": "Switch on/off the HDR mode as requested by the client (default)",
|
"dd_hdr_option_auto": "Switch on/off the HDR mode as requested by the client (default)",
|
||||||
"dd_hdr_option_disabled": "Do not change HDR settings",
|
"dd_hdr_option_disabled": "Do not change HDR settings",
|
||||||
|
"dd_mode_remapping": "Display mode remapping",
|
||||||
|
"dd_mode_remapping_add": "Add remapping entry",
|
||||||
|
"dd_mode_remapping_desc_1": "Specify remapping entries to change the requested resolution and/or refresh rate to other values.",
|
||||||
|
"dd_mode_remapping_desc_2": "The list is iterated from top to bottom and the first match is used.",
|
||||||
|
"dd_mode_remapping_desc_3": "\"Requested\" fields can be left empty to match any requested value.",
|
||||||
|
"dd_mode_remapping_desc_4_final_values_mixed": "At least one \"Final\" field must be specified. The unspecified resolution or refresh rate will not be changed.",
|
||||||
|
"dd_mode_remapping_desc_4_final_values_non_mixed": "\"Final\" field must be specified and cannot be empty.",
|
||||||
|
"dd_mode_remapping_desc_5_sops_mixed_only": "\"Optimize game settings\" option must be enabled in the Moonlight client, otherwise entries with any resolution fields specified are skipped.",
|
||||||
|
"dd_mode_remapping_desc_5_sops_resolution_only": "\"Optimize game settings\" option must be enabled in the Moonlight client, otherwise the mapping is skipped.",
|
||||||
|
"dd_mode_remapping_final_refresh_rate": "Final refresh rate",
|
||||||
|
"dd_mode_remapping_final_resolution": "Final resolution",
|
||||||
|
"dd_mode_remapping_requested_fps": "Requested FPS",
|
||||||
|
"dd_mode_remapping_requested_resolution": "Requested resolution",
|
||||||
"dd_options_header": "Advanced display device options",
|
"dd_options_header": "Advanced display device options",
|
||||||
"dd_refresh_rate_option": "Refresh rate",
|
"dd_refresh_rate_option": "Refresh rate",
|
||||||
"dd_refresh_rate_option_auto": "Use FPS value provided by the client (default)",
|
"dd_refresh_rate_option_auto": "Use FPS value provided by the client (default)",
|
||||||
|
|
|
||||||
|
|
@ -274,3 +274,225 @@ TEST_P(ParseRefreshRateOption, IntegrationTest) {
|
||||||
EXPECT_EQ(std::get<display_device::SingleDisplayConfiguration>(result).m_refresh_rate, expected_refresh_rate);
|
EXPECT_EQ(std::get<display_device::SingleDisplayConfiguration>(result).m_refresh_rate, expected_refresh_rate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
using res_t = resolution_t;
|
||||||
|
using fps_t = client_fps_t;
|
||||||
|
using remap_entries_t = config::video_t::dd_t::mode_remapping_t;
|
||||||
|
|
||||||
|
struct no_value_t {
|
||||||
|
};
|
||||||
|
template <class T>
|
||||||
|
struct auto_value_t {
|
||||||
|
T value;
|
||||||
|
};
|
||||||
|
template <class T>
|
||||||
|
struct manual_value_t {
|
||||||
|
T value;
|
||||||
|
};
|
||||||
|
|
||||||
|
using resolution_variant_t = std::variant<no_value_t, auto_value_t<res_t>, manual_value_t<res_t>>;
|
||||||
|
using rational_variant_t = std::variant<no_value_t, auto_value_t<fps_t>, manual_value_t<fps_t>>;
|
||||||
|
|
||||||
|
struct failed_to_remap_t {};
|
||||||
|
struct final_values_t {
|
||||||
|
std::optional<resolution_t> resolution;
|
||||||
|
std::optional<rational_t> refresh_rate;
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::string INVALID_RES { "INVALID" };
|
||||||
|
const std::string INVALID_FPS { "1.23" };
|
||||||
|
const std::string INVALID_REFRESH_RATE { "INVALID" };
|
||||||
|
const remap_entries_t VALID_ENTRIES {
|
||||||
|
.mixed = {
|
||||||
|
{ "1920x1080", "11", "1024x720", "1.11" },
|
||||||
|
{ "1920x1080", "", "1024x720", "2" },
|
||||||
|
{ "", "33", "1024x720", "3" },
|
||||||
|
{ "1920x720", "44", "1024x720", "" },
|
||||||
|
{ "1920x720", "55", "", "5" },
|
||||||
|
{ "1920x720", "", "1024x720", "" },
|
||||||
|
{ "", "11", "", "7.77" } },
|
||||||
|
.resolution_only = { { "1920x1080", "", "720x720", "" }, { "1024x720", "", "1920x1920", "" } },
|
||||||
|
.refresh_rate_only = { { "", "11", "", "1.23" }, { "", "22", "", "2.34" } }
|
||||||
|
};
|
||||||
|
const remap_entries_t INVALID_REQ_RES {
|
||||||
|
.mixed = { { INVALID_RES, "11", "1024x720", "1.11" } },
|
||||||
|
.resolution_only = { { INVALID_RES, "", "720x720", "" } },
|
||||||
|
.refresh_rate_only = { { INVALID_RES, "11", "", "1.23" } }
|
||||||
|
};
|
||||||
|
const remap_entries_t INVALID_REQ_FPS {
|
||||||
|
.mixed = { { "1920x1080", INVALID_FPS, "1024x720", "1.11" } },
|
||||||
|
.resolution_only = { { "1920x1080", INVALID_FPS, "720x720", "" } },
|
||||||
|
.refresh_rate_only = { { "", INVALID_FPS, "", "1.23" } }
|
||||||
|
};
|
||||||
|
const remap_entries_t INVALID_FINAL_RES {
|
||||||
|
.mixed = { { "1920x1080", "11", INVALID_RES, "1.11" } },
|
||||||
|
.resolution_only = { { "1920x1080", "", INVALID_RES, "" } },
|
||||||
|
.refresh_rate_only = { { "", "11", INVALID_RES, "1.23" } }
|
||||||
|
};
|
||||||
|
const remap_entries_t INVALID_FINAL_REFRESH_RATE {
|
||||||
|
.mixed = { { "1920x1080", "11", "1024x720", INVALID_REFRESH_RATE } },
|
||||||
|
.resolution_only = { { "1920x1080", "", "720x720", INVALID_REFRESH_RATE } },
|
||||||
|
.refresh_rate_only = { { "", "11", "", INVALID_REFRESH_RATE } }
|
||||||
|
};
|
||||||
|
const remap_entries_t EMPTY_REQ_ENTRIES {
|
||||||
|
.mixed = { { "", "", "1024x720", "1.11" } },
|
||||||
|
.resolution_only = { { "", "", "720x720", "" } },
|
||||||
|
.refresh_rate_only = { { "", "", "", "1.23" } }
|
||||||
|
};
|
||||||
|
const remap_entries_t EMPTY_FINAL_ENTRIES {
|
||||||
|
.mixed = { { "1920x1080", "11", "", "" } },
|
||||||
|
.resolution_only = { { "1920x1080", "", "", "" } },
|
||||||
|
.refresh_rate_only = { { "", "11", "", "" } }
|
||||||
|
};
|
||||||
|
|
||||||
|
using DisplayModeRemapping = DisplayDeviceConfigTest<std::pair<std::tuple<resolution_variant_t, rational_variant_t, sops_enabled_t, remap_entries_t>, std::variant<failed_to_remap_t, final_values_t>>>;
|
||||||
|
INSTANTIATE_TEST_SUITE_P(
|
||||||
|
DisplayDeviceConfigTest,
|
||||||
|
DisplayModeRemapping,
|
||||||
|
testing::Values(
|
||||||
|
//---- Mixed (valid), SOPS enabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { true }, VALID_ENTRIES), final_values_t { { { 1024, 720 } }, { { 111, 100 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 120 }, sops_enabled_t { true }, VALID_ENTRIES), final_values_t { { { 1024, 720 } }, { { 2, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1, 1 }, auto_value_t<fps_t> { 33 }, sops_enabled_t { true }, VALID_ENTRIES), final_values_t { { { 1024, 720 } }, { { 3, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 720 }, auto_value_t<fps_t> { 44 }, sops_enabled_t { true }, VALID_ENTRIES), final_values_t { { { 1024, 720 } }, { { 44, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 720 }, auto_value_t<fps_t> { 55 }, sops_enabled_t { true }, VALID_ENTRIES), final_values_t { { { 1920, 720 } }, { { 5, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 720 }, auto_value_t<fps_t> { 60 }, sops_enabled_t { true }, VALID_ENTRIES), final_values_t { { { 1024, 720 } }, { { 60, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1, 1 }, auto_value_t<fps_t> { 123 }, sops_enabled_t { true }, VALID_ENTRIES), final_values_t { { { 1, 1 } }, { { 123, 1 } } }),
|
||||||
|
//---- Mixed (valid), SOPS disabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { false }, VALID_ENTRIES), final_values_t { std::nullopt, { { 777, 100 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 120 }, sops_enabled_t { false }, VALID_ENTRIES), final_values_t { std::nullopt, { { 120, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1, 1 }, auto_value_t<fps_t> { 33 }, sops_enabled_t { false }, VALID_ENTRIES), final_values_t { std::nullopt, { { 33, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 720 }, auto_value_t<fps_t> { 44 }, sops_enabled_t { false }, VALID_ENTRIES), final_values_t { std::nullopt, { { 44, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 720 }, auto_value_t<fps_t> { 55 }, sops_enabled_t { false }, VALID_ENTRIES), final_values_t { std::nullopt, { { 55, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 720 }, auto_value_t<fps_t> { 60 }, sops_enabled_t { false }, VALID_ENTRIES), final_values_t { std::nullopt, { { 60, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1, 1 }, auto_value_t<fps_t> { 123 }, sops_enabled_t { false }, VALID_ENTRIES), final_values_t { std::nullopt, { { 123, 1 } } }),
|
||||||
|
//---- Resolution only (valid), SOPS enabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { true }, VALID_ENTRIES), final_values_t { { { 720, 720 } }, { { 11, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1024, 720 }, no_value_t {}, sops_enabled_t { true }, VALID_ENTRIES), final_values_t { { { 1920, 1920 } }, std::nullopt }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 11, 11 }, manual_value_t<fps_t> { 33 }, sops_enabled_t { true }, VALID_ENTRIES), final_values_t { { { 11, 11 } }, { { 33, 1 } } }),
|
||||||
|
//---- Resolution only (valid), SOPS disabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { false }, VALID_ENTRIES), final_values_t { std::nullopt, { { 11, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1024, 720 }, no_value_t {}, sops_enabled_t { false }, VALID_ENTRIES), final_values_t { std::nullopt, std::nullopt }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 11, 11 }, manual_value_t<fps_t> { 33 }, sops_enabled_t { false }, VALID_ENTRIES), final_values_t { std::nullopt, { { 33, 1 } } }),
|
||||||
|
//---- Refresh rate only (valid), SOPS enabled ----
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { true }, VALID_ENTRIES), final_values_t { { { 1920, 1080 } }, { { 123, 100 } } }),
|
||||||
|
std::make_pair(std::make_tuple(no_value_t {}, auto_value_t<fps_t> { 22 }, sops_enabled_t { true }, VALID_ENTRIES), final_values_t { std::nullopt, { { 234, 100 } } }),
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 11, 11 }, auto_value_t<fps_t> { 33 }, sops_enabled_t { true }, VALID_ENTRIES), final_values_t { { { 11, 11 } }, { { 33, 1 } } }),
|
||||||
|
//---- Refresh rate only (valid), SOPS disabled ----
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { false }, VALID_ENTRIES), final_values_t { std::nullopt, { { 123, 100 } } }),
|
||||||
|
std::make_pair(std::make_tuple(no_value_t {}, auto_value_t<fps_t> { 22 }, sops_enabled_t { false }, VALID_ENTRIES), final_values_t { std::nullopt, { { 234, 100 } } }),
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 11, 11 }, auto_value_t<fps_t> { 33 }, sops_enabled_t { false }, VALID_ENTRIES), final_values_t { std::nullopt, { { 33, 1 } } }),
|
||||||
|
//---- No mapping (valid), SOPS enabled ----
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { true }, VALID_ENTRIES), final_values_t { { { 1920, 1080 } }, { { 11, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(no_value_t {}, no_value_t {}, sops_enabled_t { true }, VALID_ENTRIES), final_values_t { std::nullopt, std::nullopt }),
|
||||||
|
//---- No mapping (valid), SOPS disabled ----
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { false }, VALID_ENTRIES), final_values_t { std::nullopt, { { 11, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(no_value_t {}, no_value_t {}, sops_enabled_t { false }, VALID_ENTRIES), final_values_t { std::nullopt, std::nullopt }),
|
||||||
|
// ---- Invalid requested resolution, SOPS enabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { true }, INVALID_REQ_RES), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { true }, INVALID_REQ_RES), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { true }, INVALID_REQ_RES), final_values_t { { { 1920, 1080 } }, { { 123, 100 } } }),
|
||||||
|
// ---- Invalid requested resolution, SOPS disabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { false }, INVALID_REQ_RES), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { false }, INVALID_REQ_RES), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { false }, INVALID_REQ_RES), final_values_t { std::nullopt, { { 123, 100 } } }),
|
||||||
|
// ---- Invalid requested FPS, SOPS enabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { true }, INVALID_REQ_FPS), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { true }, INVALID_REQ_FPS), final_values_t { { { 720, 720 } }, { { 11, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { true }, INVALID_REQ_FPS), failed_to_remap_t {}),
|
||||||
|
// ---- Invalid requested FPS, SOPS disabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { false }, INVALID_REQ_FPS), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { false }, INVALID_REQ_FPS), final_values_t { std::nullopt, { { 11, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { false }, INVALID_REQ_FPS), failed_to_remap_t {}),
|
||||||
|
// ---- Invalid final resolution, SOPS enabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { true }, INVALID_FINAL_RES), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { true }, INVALID_FINAL_RES), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { true }, INVALID_FINAL_RES), final_values_t { { { 1920, 1080 } }, { { 123, 100 } } }),
|
||||||
|
// ---- Invalid final resolution, SOPS disabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { false }, INVALID_FINAL_RES), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { false }, INVALID_FINAL_RES), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { false }, INVALID_FINAL_RES), final_values_t { std::nullopt, { { 123, 100 } } }),
|
||||||
|
// ---- Invalid final refresh rate, SOPS enabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { true }, INVALID_FINAL_REFRESH_RATE), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { true }, INVALID_FINAL_REFRESH_RATE), final_values_t { { { 720, 720 } }, { { 11, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { true }, INVALID_FINAL_REFRESH_RATE), failed_to_remap_t {}),
|
||||||
|
// ---- Invalid final refresh rate, SOPS disabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { false }, INVALID_FINAL_REFRESH_RATE), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { false }, INVALID_FINAL_REFRESH_RATE), final_values_t { std::nullopt, { { 11, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { false }, INVALID_FINAL_REFRESH_RATE), failed_to_remap_t {}),
|
||||||
|
// ---- Empty req entries, SOPS enabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { true }, EMPTY_REQ_ENTRIES), final_values_t { { { 1024, 720 } }, { { 111, 100 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { true }, EMPTY_REQ_ENTRIES), final_values_t { { { 720, 720 } }, { { 11, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { true }, EMPTY_REQ_ENTRIES), final_values_t { { { 1920, 1080 } }, { { 123, 100 } } }),
|
||||||
|
// ---- Empty req entries, SOPS disabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { false }, EMPTY_REQ_ENTRIES), final_values_t { std::nullopt, { { 11, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { false }, EMPTY_REQ_ENTRIES), final_values_t { std::nullopt, { { 11, 1 } } }),
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { false }, EMPTY_REQ_ENTRIES), final_values_t { std::nullopt, { { 123, 100 } } }),
|
||||||
|
// ---- Empty final entries, SOPS enabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { true }, EMPTY_FINAL_ENTRIES), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { true }, EMPTY_FINAL_ENTRIES), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { true }, EMPTY_FINAL_ENTRIES), failed_to_remap_t {}),
|
||||||
|
// ---- Empty final entries, SOPS disabled ----
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { false }, EMPTY_FINAL_ENTRIES), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(auto_value_t<res_t> { 1920, 1080 }, manual_value_t<fps_t> { 11 }, sops_enabled_t { false }, EMPTY_FINAL_ENTRIES), failed_to_remap_t {}),
|
||||||
|
std::make_pair(std::make_tuple(manual_value_t<res_t> { 1920, 1080 }, auto_value_t<fps_t> { 11 }, sops_enabled_t { false }, EMPTY_FINAL_ENTRIES), failed_to_remap_t {})));
|
||||||
|
TEST_P(DisplayModeRemapping, IntegrationTest) {
|
||||||
|
const auto &[input_value, expected_value] = GetParam();
|
||||||
|
const auto &[input_res, input_fps, input_enable_sops, input_entries] = input_value;
|
||||||
|
|
||||||
|
config::video_t video_config {};
|
||||||
|
rtsp_stream::launch_session_t session {};
|
||||||
|
|
||||||
|
{ // resolution
|
||||||
|
using enum resolution_option_e;
|
||||||
|
|
||||||
|
if (const auto *no_res { std::get_if<no_value_t>(&input_res) }; no_res) {
|
||||||
|
video_config.dd.resolution_option = disabled;
|
||||||
|
}
|
||||||
|
else if (const auto *auto_res { std::get_if<auto_value_t<res_t>>(&input_res) }; auto_res) {
|
||||||
|
video_config.dd.resolution_option = automatic;
|
||||||
|
session.width = static_cast<int>(auto_res->value.m_width);
|
||||||
|
session.height = static_cast<int>(auto_res->value.m_height);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const auto [manual_res] = std::get<manual_value_t<res_t>>(input_res);
|
||||||
|
video_config.dd.resolution_option = manual;
|
||||||
|
video_config.dd.manual_resolution = std::to_string(manual_res.m_width) + "x"s + std::to_string(manual_res.m_height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // fps
|
||||||
|
using enum refresh_rate_option_e;
|
||||||
|
|
||||||
|
if (const auto *no_fps { std::get_if<no_value_t>(&input_fps) }; no_fps) {
|
||||||
|
video_config.dd.refresh_rate_option = disabled;
|
||||||
|
}
|
||||||
|
else if (const auto *auto_fps { std::get_if<auto_value_t<fps_t>>(&input_fps) }; auto_fps) {
|
||||||
|
video_config.dd.refresh_rate_option = automatic;
|
||||||
|
session.fps = auto_fps->value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const auto [manual_fps] = std::get<manual_value_t<fps_t>>(input_fps);
|
||||||
|
video_config.dd.refresh_rate_option = manual;
|
||||||
|
video_config.dd.manual_refresh_rate = std::to_string(manual_fps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
video_config.dd.configuration_option = config_option_e::verify_only;
|
||||||
|
video_config.dd.mode_remapping = input_entries;
|
||||||
|
session.enable_sops = input_enable_sops;
|
||||||
|
|
||||||
|
const auto result { display_device::parse_configuration(video_config, session) };
|
||||||
|
if (const auto *failed_option { std::get_if<failed_to_remap_t>(&expected_value) }; failed_option) {
|
||||||
|
EXPECT_NO_THROW(std::get<display_device::failed_to_parse_tag_t>(result));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const auto &[expected_resolution, expected_refresh_rate] = std::get<final_values_t>(expected_value);
|
||||||
|
const auto &parsed_config = std::get<display_device::SingleDisplayConfiguration>(result);
|
||||||
|
|
||||||
|
EXPECT_EQ(parsed_config.m_resolution, expected_resolution);
|
||||||
|
EXPECT_EQ(parsed_config.m_refresh_rate, expected_refresh_rate ? std::make_optional(display_device::FloatingPoint { *expected_refresh_rate }) : std::nullopt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue