Files
ARMSX3/Utilities/Config.h
T
jpolo1224 1eb63137e3 ARMSX3: Android port of RPCS3, proof of concept
Adds an Android build of the RPCS3 core plus a Compose UI, and fixes several
things that stopped it working on ARM64.

Renderer:
- Emit concrete bounds for runtime sized arrays in uniform blocks when
  VK_EXT_shader_uniform_buffer_unsized_array is missing. Adreno does not have
  the extension, so every game pipeline failed with VK_ERROR_UNKNOWN and only
  overlays drew.
- Probe and request that extension properly instead of chaining its feature
  struct unconditionally.
- Hand VMA the Vulkan function pointers it needs under VK_NO_PROTOTYPES.
- Rebuild the surface and swapchain when the window is lost instead of killing
  the RSX thread.
- Only create a GLES context when the GL renderer is actually selected.

SPU:
- Sum instead of taking an absolute difference in the ARM64 block verification
  checksum. The difference collides on the near identical job binaries an SPU
  job manager streams through one local store address, so a cached block could
  run against another job's code.

Threading:
- Implement thread affinity on Android using sched_setaffinity.
- Add an ARM big.LITTLE core arrangement so SPU and RSX threads land on the
  fast cores.

Misc:
- Detect the host CPU for the LLVM JIT instead of pinning cortex-a34.
- Fall back to the default audio device when cubeb cannot enumerate.
2026-08-06 08:59:52 -04:00

927 lines
18 KiB
C++

#pragma once
#include "util/types.hpp"
#include "Utilities/StrUtil.h"
#include "util/logs.hpp"
#include "util/atomic.hpp"
#include "util/shared_ptr.hpp"
#include <algorithm>
#include <utility>
#include <string>
#include <vector>
#include <set>
#include <map>
namespace cfg
{
// Format min and max values
std::vector<std::string> make_int_range(s64 min, s64 max);
// Format min and max unsigned values
std::vector<std::string> make_uint_range(u64 min, u64 max);
// Format min and max float values
std::vector<std::string> make_float_range(f64 min, f64 max);
// Internal hack
bool try_to_enum_value(u64* out, decltype(&fmt_class_string<int>::format) func, std::string_view value, std::string_view name = {});
// Internal hack
std::vector<std::string> try_to_enum_list(decltype(&fmt_class_string<int>::format) func);
// Internal hack
size_t try_to_enum_size(decltype(&fmt_class_string<int>::format) func);
// Config tree entry type.
enum class type : unsigned
{
node = 0, // cfg::node type
_bool, // cfg::_bool type
_enum, // cfg::_enum type
_int, // cfg::_int type
uint, // cfg::uint type
uint128, // cfg::uint128 type
string, // cfg::string type
set, // cfg::set_entry type
map, // cfg::map_entry type
node_map, // cfg::node_map_entry type
log, // cfg::log_entry type
device, // cfg::device_entry type
};
// Config tree entry abstract base class
class _base
{
const type m_type{};
protected:
_base* m_parent = nullptr;
bool m_dynamic = true;
const std::string m_name{};
static u32 id_counter;
u32 m_id = 0;
// Ownerless entry constructor
_base(type _type);
// Owned entry constructor
_base(type _type, class node* owner, std::string name, bool dynamic);
public:
_base(const _base&) = delete;
_base& operator=(const _base&) = delete;
virtual ~_base() = default;
// Get unique ID
u32 get_id() const { return m_id; }
// Get parent
_base* get_parent() const { return m_parent; }
// Get type
type get_type() const { return m_type; }
// Get name
const std::string& get_name() const { return m_name; }
// Get dynamic property for reloading configs during games
bool get_is_dynamic() const { return m_dynamic; }
// Reset defaults
virtual void from_default() = 0;
// Restore default members
virtual void restore_defaults() = 0;
// Convert to string (optional)
virtual std::string to_string() const
{
return {};
}
// Convert default to string (optional)
virtual std::string def_to_string() const
{
return {};
}
// ARMSX3: expose the numeric range through the base interface.
// _int/_float/uint carry Min/Max as static constexpr TEMPLATE parameters,
// which are unreachable from a _base* -- so a generic settings UI cannot
// tell a slider from a free-text field without these. Mirrors the
// to_string/def_to_string pattern; empty means "not a bounded numeric".
virtual std::string min_to_string() const
{
return {};
}
virtual std::string max_to_string() const
{
return {};
}
// Try to convert from string (optional)
virtual bool from_string(std::string_view value, bool dynamic = false);
// Get string list (optional)
virtual std::vector<std::string> to_list() const
{
return {};
}
// Set multiple values. Implementation-specific, optional.
virtual bool from_list(std::vector<std::string>&&);
bool save(std::string_view cfg_name) const;
};
// Config tree node which contains another nodes
class node : public _base
{
std::vector<_base*> m_nodes{};
friend class _base;
public:
// Root node constructor
node()
: _base(type::node)
{
}
// Registered node constructor
node(node* owner, std::string name, bool dynamic = true)
: _base(type::node, owner, std::move(name), dynamic)
{
}
// Get child nodes
const auto& get_nodes() const
{
return m_nodes;
}
// Serialize node
std::string to_string() const override;
// Deserialize node
bool from_string(std::string_view value, bool dynamic = false) override;
// Set default values
void from_default() override;
// Restore default members
void restore_defaults() override;
// Try to convert from string and validate
bool validate(std::string_view value);
};
class _bool final : public _base
{
atomic_t<bool> m_value;
bool original_def;
public:
bool def;
_bool(node* owner, std::string name, bool def = false, bool dynamic = false)
: _base(type::_bool, owner, std::move(name), dynamic)
, m_value(def)
, original_def(def)
, def(def)
{
}
explicit operator bool() const
{
return m_value;
}
bool get() const
{
return m_value;
}
void from_default() override
{
m_value = def;
}
void restore_defaults() override
{
def = original_def;
}
std::string to_string() const override
{
return m_value ? "true" : "false";
}
std::string def_to_string() const override
{
return def ? "true" : "false";
}
bool from_string(std::string_view value, bool /*dynamic*/ = false) override
{
if (value.size() != 4 && value.size() != 5)
{
return false;
}
char copy[5];
std::transform(value.begin(), value.end(), std::begin(copy), ::tolower);
if (value.size() == 5 && std::string_view{copy, 5} == "false")
m_value = false;
else if (value.size() == 4 && std::string_view{copy, 4} == "true")
m_value = true;
else
return false;
return true;
}
void set(const bool& value)
{
m_value = value;
}
};
// Value node with fixed set of possible values, each maps to an enum value of type T.
template <typename T>
class _enum : public _base
{
atomic_t<T> m_value;
T original_def;
public:
T def;
_enum(node* owner, const std::string& name, T def = {}, bool dynamic = false)
: _base(type::_enum, owner, name, dynamic)
, m_value(def)
, original_def(def)
, def(def)
{
}
operator T() const
{
return m_value;
}
T get() const
{
return m_value;
}
T get_default() const
{
return def;
}
void set(T value)
{
m_value = value;
}
void from_default() override
{
m_value = def;
}
void restore_defaults() override
{
def = original_def;
}
std::string to_string() const override
{
std::string result;
fmt_class_string<T>::format(result, fmt_unveil<T>::get(m_value.load()));
return result; // TODO: ???
}
std::string def_to_string() const override
{
std::string result;
fmt_class_string<T>::format(result, fmt_unveil<T>::get(def));
return result; // TODO: ???
}
bool from_string(std::string_view value, bool /*dynamic*/ = false) override
{
u64 result;
if (try_to_enum_value(&result, &fmt_class_string<T>::format, value, m_name))
{
// No narrowing check, it's hard to do right there
m_value = static_cast<T>(static_cast<std::underlying_type_t<T>>(result));
return true;
}
return false;
}
std::vector<std::string> to_list() const override
{
return try_to_enum_list(&fmt_class_string<T>::format);
}
size_t size() const
{
return try_to_enum_size(&fmt_class_string<T>::format);
}
};
// Signed 32/64-bit integer entry with custom Min/Max range.
template <s64 Min, s64 Max>
class _int final : public _base
{
static_assert(Min < Max, "Invalid cfg::_int range");
// Prefer 32 bit type if possible
using int_type = std::conditional_t<Min >= s32{smin} && Max <= s32{smax}, s32, s64>;
atomic_t<int_type> m_value;
int_type original_def;
public:
int_type def;
// Expose range
static constexpr s64 max = Max;
static constexpr s64 min = Min;
_int(node* owner, const std::string& name, int_type def = std::min<int_type>(Max, std::max<int_type>(Min, 0)), bool dynamic = false)
: _base(type::_int, owner, name, dynamic)
, m_value(def)
, original_def(def)
, def(def)
{
}
operator int_type() const
{
return m_value;
}
int_type get() const
{
return m_value;
}
void from_default() override
{
m_value = def;
}
void restore_defaults() override
{
def = original_def;
}
std::string to_string() const override
{
return std::to_string(m_value);
}
std::string def_to_string() const override
{
return std::to_string(def);
}
std::string min_to_string() const override
{
return std::to_string(min);
}
std::string max_to_string() const override
{
return std::to_string(max);
}
bool from_string(std::string_view value, bool /*dynamic*/ = false) override
{
s64 result;
if (try_to_int64(&result, value, Min, Max, m_name))
{
m_value = static_cast<int_type>(result);
return true;
}
return false;
}
void set(const s64& value)
{
if (value < Min || value > Max) fmt::throw_exception("'%s': value %d out of bounds (min=%d, max=%d)", m_name, value, Min, Max);
m_value = static_cast<int_type>(value);
}
std::vector<std::string> to_list() const override
{
return make_int_range(Min, Max);
}
};
// Float entry with custom Min/Max range.
template <s32 Min, s32 Max>
class _float final : public _base
{
static_assert(Min < Max, "Invalid cfg::_float range");
using float_type = f64;
atomic_t<float_type> m_value;
float_type original_def;
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<float_type>(Max, std::max<float_type>(Min, 0)), bool dynamic = false)
: _base(type::_int, owner, name, dynamic)
, m_value(def)
, original_def(def)
, def(def)
{
}
operator float_type() const
{
return m_value;
}
float_type get() const
{
return m_value;
}
void from_default() override
{
m_value = def;
}
void restore_defaults() override
{
def = original_def;
}
std::string to_string() const override
{
std::string result;
if (try_to_string(&result, m_value, m_name))
{
return result;
}
return "0.0";
}
std::string def_to_string() const override
{
std::string result;
if (try_to_string(&result, def, m_name))
{
return result;
}
return "0.0";
}
std::string min_to_string() const override
{
return std::to_string(min);
}
std::string max_to_string() const override
{
return std::to_string(max);
}
bool from_string(std::string_view value, bool /*dynamic*/ = false) override
{
f64 result;
if (try_to_float(&result, value, Min, Max, m_name))
{
m_value = static_cast<float_type>(result);
return true;
}
return false;
}
void set(const f64& value)
{
if (value < Min || value > Max) fmt::throw_exception("'%s': value %d out of bounds (min=%d, max=%d)", m_name, value, Min, Max);
m_value = static_cast<float_type>(value);
}
std::vector<std::string> to_list() const override
{
return make_float_range(Min, Max);
}
};
// Alias for 32 bit int
using int32 = _int<s32{smin}, s32{smax}>;
// Alias for 64 bit int
using int64 = _int<s64{smin}, s64{smax}>;
// Unsigned 32/64-bit integer entry with custom Min/Max range.
template <u64 Min, u64 Max>
class uint final : public _base
{
static_assert(Min < Max, "Invalid cfg::uint range");
// Prefer 32 bit type if possible
using int_type = std::conditional_t<Max <= u32{umax}, u32, u64>;
atomic_t<int_type> m_value;
int_type original_def;
public:
int_type def;
// Expose range
static constexpr u64 max = Max;
static constexpr u64 min = Min;
uint(node* owner, const std::string& name, int_type def = std::max<int_type>(Min, 0), bool dynamic = false)
: _base(type::uint, owner, name, dynamic)
, m_value(def)
, original_def(def)
, def(def)
{
}
operator int_type() const
{
return m_value;
}
int_type get() const
{
return m_value;
}
void from_default() override
{
m_value = def;
}
void restore_defaults() override
{
def = original_def;
}
std::string to_string() const override
{
return std::to_string(m_value);
}
std::string def_to_string() const override
{
return std::to_string(def);
}
std::string min_to_string() const override
{
return std::to_string(min);
}
std::string max_to_string() const override
{
return std::to_string(max);
}
bool from_string(std::string_view value, bool /*dynamic*/ = false) override
{
u64 result;
if (try_to_uint64(&result, value, Min, Max, m_name))
{
m_value = static_cast<int_type>(result);
return true;
}
return false;
}
void set(const u64& value)
{
if (value < Min || value > Max) fmt::throw_exception("'%s': value %d out of bounds (min=%d, max=%d)", m_name, value, Min, Max);
m_value = static_cast<int_type>(value);
}
std::vector<std::string> to_list() const override
{
return make_uint_range(Min, Max);
}
};
// Alias for 32 bit uint
using uint32 = uint<0, u32{umax}>;
// Alias for 64 bit int
using uint64 = uint<0, u64{umax}>;
// Unsigned 128-bit integer entry.
class uint128 final : public _base
{
using int_type = u128;
atomic_t<int_type> m_value{};
int_type original_def = 0;
public:
int_type def;
uint128(node* owner, const std::string& name, int_type def = 0, bool dynamic = false)
: _base(type::uint128, owner, name, dynamic)
, m_value(def)
, original_def(def)
, def(def)
{
}
operator int_type() const
{
return m_value;
}
operator ullong() const
{
return static_cast<ullong>(m_value.load());
}
int_type get() const
{
return m_value;
}
void from_default() override
{
m_value = def;
}
void restore_defaults() override
{
def = original_def;
}
static std::string to_string(u128 value) noexcept;
std::string to_string() const override
{
return to_string(m_value.load());
}
std::string def_to_string() const override
{
return to_string(def);
}
bool from_string(std::string_view value, bool /*dynamic*/ = false) override
{
u128 result;
if (try_to_uint128(&result, value, m_name))
{
m_value = result;
return true;
}
return false;
}
void set(u128 value)
{
m_value = value;
}
std::vector<std::string> to_list() const override
{
// Should not be used
return make_uint_range(0, 1);
}
};
// Simple string entry with mutex
class string : public _base
{
atomic_ptr<std::string> m_value;
std::string original_def;
public:
std::string def;
string(node* owner, std::string name, std::string def = {}, bool dynamic = false)
: _base(type::string, owner, std::move(name), dynamic)
, m_value(def)
, original_def(def)
, def(std::move(def))
{
}
operator std::string() const
{
return *m_value.load().get();
}
void from_default() override
{
m_value = def;
}
void restore_defaults() override
{
def = original_def;
}
std::string to_string() const override
{
return *m_value.load().get();
}
std::string get() const
{
return *m_value.load().get();
}
std::string def_to_string() const override
{
return def;
}
bool from_string(std::string_view value, bool /*dynamic*/ = false) override
{
m_value = std::string(value);
return true;
}
void set(std::string_view value)
{
m_value = std::string(value);
}
};
// Simple set entry (TODO: template for various types)
class set_entry final : public _base
{
std::set<std::string> m_set{};
public:
// Default value is empty list in current implementation
set_entry(node* owner, const std::string& name)
: _base(type::set, owner, name, false)
{
}
const std::set<std::string>& get_set() const
{
return m_set;
}
void set_set(std::set<std::string>&& set)
{
m_set = std::move(set);
}
void from_default() override
{
m_set = {};
}
void restore_defaults() override
{
}
std::vector<std::string> to_list() const override
{
return { m_set.begin(), m_set.end() };
}
bool from_list(std::vector<std::string>&& list) override
{
m_set = { std::make_move_iterator(list.begin()), std::make_move_iterator(list.end()) };
return true;
}
};
template<typename T>
using map_of_type = std::map<std::string, T, std::less<>>;
class map_entry : public _base
{
map_of_type<std::string> m_map{};
public:
map_entry(node* owner, const std::string& name, type _type = type::map)
: _base(_type, owner, name, true)
{
}
const map_of_type<std::string>& get_map() const
{
return m_map;
}
std::string get_value(std::string_view key);
void set_value(std::string key, std::string value);
void set_map(map_of_type<std::string>&& map);
void erase(std::string_view key);
void from_default() override;
void restore_defaults() override
{
}
};
class node_map_entry final : public map_entry
{
public:
node_map_entry(node* owner, const std::string& name)
: map_entry(owner, name, type::node_map)
{
}
};
class log_entry final : public _base
{
map_of_type<logs::level> m_map{};
public:
log_entry(node* owner, const std::string& name)
: _base(type::log, owner, name, true)
{
}
const map_of_type<logs::level>& get_map() const
{
return m_map;
}
void set_map(map_of_type<logs::level>&& map);
void from_default() override;
void restore_defaults() override
{
}
};
struct device_info
{
std::string path;
std::string serial;
std::string vid;
std::string pid;
std::pair<u16, u16> get_usb_ids() const;
};
class device_entry final : public _base
{
map_of_type<device_info> m_map{};
map_of_type<device_info> m_default{};
public:
device_entry(node* owner, const std::string& name, map_of_type<device_info> def = {})
: _base(type::device, owner, name, true)
, m_map(std::move(def))
{
m_default = m_map;
}
const map_of_type<device_info>& get_map() const
{
return m_map;
}
const map_of_type<device_info>& get_default() const
{
return m_default;
}
void set_map(map_of_type<device_info>&& map);
void from_default() override;
void restore_defaults() override
{
}
};
}