diff --git a/Utilities/Config.cpp b/Utilities/Config.cpp index 9f2387dae1..05977d4e0f 100644 --- a/Utilities/Config.cpp +++ b/Utilities/Config.cpp @@ -60,9 +60,15 @@ std::vector cfg::make_int_range(s64 min, s64 max) bool try_to_int64(s64* out, std::string_view value, s64 min, s64 max) { + if (value.empty()) + { + if (out) cfg_log.error("cfg::try_to_uint64(): called with an empty string"); + return false; + } + s64 result; - const char* start = &value.front(); - const char* end = &value.back() + 1; + const char* start = value.data(); + const char* end = start + value.size(); int base = 10; int sign = +1; @@ -72,7 +78,7 @@ bool try_to_int64(s64* out, std::string_view value, s64 min, s64 max) start += 1; } - if (start[0] == '0' && (start[1] == 'x' || start[1] == 'X')) + if (start[0] == '0' && value.size() >= 2 && (start[1] == 'x' || start[1] == 'X')) { // Limited hex support base = 16; @@ -106,12 +112,18 @@ std::vector cfg::make_uint_range(u64 min, u64 max) bool try_to_uint64(u64* out, std::string_view value, u64 min, u64 max) { + if (value.empty()) + { + if (out) cfg_log.error("cfg::try_to_uint64(): called with an empty string"); + return false; + } + u64 result; - const char* start = &value.front(); - const char* end = &value.back() + 1; + const char* start = value.data(); + const char* end = start + value.size(); int base = 10; - if (start[0] == '0' && (start[1] == 'x' || start[1] == 'X')) + if (start[0] == '0' && value.size() >= 2 && (start[1] == 'x' || start[1] == 'X')) { // Limited hex support base = 16; @@ -136,6 +148,42 @@ bool try_to_uint64(u64* out, std::string_view value, u64 min, u64 max) return true; } +std::vector cfg::make_float_range(f64 min, f64 max) +{ + return {std::to_string(min), std::to_string(max)}; +} + +bool try_to_float(f64* out, std::string_view value, f64 min, f64 max) +{ + if (value.empty()) + { + if (out) cfg_log.error("cfg::try_to_float(): called with an empty string"); + return false; + } + + // std::from_chars float is yet to be implemented on Xcode it seems + // And strtod doesn't support ranged view so we need to ensure it meets a null terminator + const std::string str = std::string{value}; + + char* end_check{}; + const double result = std::strtod(str.data(), &end_check); + + if (end_check != str.data() + str.size()) + { + if (out) cfg_log.error("cfg::try_to_float('%s'): invalid float", value); + return false; + } + + if (result < min || result > max) + { + if (out) cfg_log.error("cfg::try_to_float('%s'): out of bounds (%f..%f)", value, min, max); + return false; + } + + if (out) *out = result; + return true; +} + bool cfg::try_to_enum_value(u64* out, decltype(&fmt_class_string::format) func, std::string_view value) { u64 max = umax; @@ -162,8 +210,8 @@ bool cfg::try_to_enum_value(u64* out, decltype(&fmt_class_string::format) f } u64 result; - const char* start = &value.front(); - const char* end = &value.back() + 1; + const char* start = value.data(); + const char* end = start + value.size(); int base = 10; if (start[0] == '0' && (start[1] == 'x' || start[1] == 'X')) diff --git a/Utilities/Config.h b/Utilities/Config.h index 4fbe3f9524..af8ab18db9 100644 --- a/Utilities/Config.h +++ b/Utilities/Config.h @@ -20,6 +20,9 @@ namespace cfg // Format min and max unsigned values std::vector make_uint_range(u64 min, u64 max); + // Format min and max float values + std::vector make_float_range(f64 min, f64 max); + // Internal hack bool try_to_enum_value(u64* out, decltype(&fmt_class_string::format) func, std::string_view); @@ -309,6 +312,72 @@ namespace cfg } }; + // Float entry with custom Min/Max range. + template + class _float final : public _base + { + static_assert(Min < Max, "Invalid cfg::_float range"); + + using float_type = f64; + atomic_t m_value; + + public: + float_type def; + + // Expose range + static constexpr float_type max = Max; + static constexpr float_type min = Min; + + _float(node* owner, const std::string& name, float_type def = std::min(Max, std::max(Min, 0)), bool dynamic = false) + : _base(type::_int, owner, name, dynamic) + , m_value(def) + , def(def) + { + } + + operator float_type() const + { + return m_value; + } + + float_type get() const + { + return m_value; + } + + void from_default() override + { + m_value = def; + } + + std::string to_string() const override + { + return std::to_string(m_value); + } + + bool from_string(std::string_view value, bool /*dynamic*/ = false) override + { + f64 result; + if (try_to_float(&result, value, Min, Max)) + { + m_value = static_cast(result); + return true; + } + + return false; + } + + void set(const f64& value) + { + m_value = static_cast(value); + } + + std::vector to_list() const override + { + return make_float_range(Min, Max); + } + }; + // Alias for 32 bit int using int32 = _int; diff --git a/Utilities/StrUtil.h b/Utilities/StrUtil.h index c2815fd0d0..aefedc9556 100644 --- a/Utilities/StrUtil.h +++ b/Utilities/StrUtil.h @@ -27,6 +27,9 @@ bool try_to_int64(s64* out, std::string_view value, s64 min, s64 max); // Convert string to unsigned integer bool try_to_uint64(u64* out, std::string_view value, u64 min, u64 max); +// Convert string to float +bool try_to_float(f64* out, std::string_view value, f64 min, f64 max); + // Get the file extension of a file path ("png", "jpg", etc.) std::string get_file_extension(const std::string& file_path); diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 328bd1fb83..13e6b133cd 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -166,7 +166,7 @@ struct cfg_root : cfg::node cfg::_int<1, 8> consecutive_frames_to_skip{ this, "Consecutive Frames To Skip", 1, true}; cfg::_int<50, 800> resolution_scale_percent{ this, "Resolution Scale", 100 }; cfg::uint<0, 16> anisotropic_level_override{ this, "Anisotropic Filter Override", 0, true }; - cfg::_int<-32, 32> texture_lod_bias{ this, "Texture LOD Bias Addend", 0, true }; + cfg::_float<-32, 32> texture_lod_bias{ this, "Texture LOD Bias Addend", 0, true }; cfg::_int<1, 1024> min_scalable_dimension{ this, "Minimum Scalable Dimension", 16 }; cfg::_int<0, 16> shader_compiler_threads_count{ this, "Shader Compiler Threads", 0 }; cfg::_int<0, 30000000> driver_recovery_timeout{ this, "Driver Recovery Timeout", 1000000, true };