From 704d8764afa0074a4ee996850858e482afbefbeb Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 17 Mar 2026 21:30:18 +0100 Subject: [PATCH 001/108] input: refactor combos into a struct for future use Also update some terminology in the code --- rpcs3/Emu/Io/PadHandler.cpp | 61 +++++-------------------- rpcs3/Emu/Io/PadHandler.h | 11 ++--- rpcs3/Emu/Io/pad_config.cpp | 47 +++++++++---------- rpcs3/Emu/Io/pad_config.h | 36 ++++++++++++++- rpcs3/Input/evdev_joystick_handler.cpp | 26 +++++------ rpcs3/Input/keyboard_pad_handler.cpp | 8 ++-- rpcs3/Input/mm_joystick_handler.cpp | 2 +- rpcs3/rpcs3qt/pad_settings_dialog.cpp | 62 +++++++++++++------------- rpcs3/rpcs3qt/pad_settings_dialog.h | 8 ++-- 9 files changed, 121 insertions(+), 140 deletions(-) diff --git a/rpcs3/Emu/Io/PadHandler.cpp b/rpcs3/Emu/Io/PadHandler.cpp index 1e22da3fc8..ccd81d2805 100644 --- a/rpcs3/Emu/Io/PadHandler.cpp +++ b/rpcs3/Emu/Io/PadHandler.cpp @@ -11,30 +11,15 @@ PadHandlerBase::PadHandlerBase(pad_handler type) : m_type(type) { } -std::vector> PadHandlerBase::find_key_combos(const std::unordered_map& map, const std::string& cfg_string, const std::string& fallback) +std::vector> PadHandlerBase::find_key_combos(const std::unordered_map& map, const std::string& cfg_string) { std::vector> key_codes; - const std::vector> combos = cfg_pad::get_buttons(cfg_string); - u32 def_code = umax; + const std::vector combos = cfg_pad::get_combos(cfg_string); - for (const std::vector& names : combos) + for (const pad::combo& combo : combos) { - std::set keys; - - for (const std::string& nam : names) - { - for (const auto& [code, name] : map) - { - if (name == nam) - { - keys.insert(code); - } - - if (!fallback.empty() && name == fallback) - def_code = code; - } - } + std::set keys = find_key_codes(map, combo); if (!keys.empty()) { @@ -42,39 +27,18 @@ std::vector> PadHandlerBase::find_key_combos(const std::unordered_ } } - if (!key_codes.empty()) - { - return key_codes; - } - - if (!fallback.empty()) - { - if (!combos.empty()) - input_log.error("FindKeyCode for [name = %s] returned with [def_code = %d] for [fallback = %s]", cfg_string, def_code, fallback); - - if (def_code != umax) - { - return {{ def_code }}; - } - } - - return {}; + return key_codes; } -std::vector> PadHandlerBase::find_key_combos(const std::unordered_map& map, const cfg::string& cfg_string, bool fallback) -{ - return find_key_combos(map, cfg_string.to_string(), fallback ? cfg_string.def : ""); -} - -std::set PadHandlerBase::find_key_codes(const std::unordered_map& map, const std::vector& names) +std::set PadHandlerBase::find_key_codes(const std::unordered_map& map, const pad::combo& combo) { std::set key_codes; - for (const std::string& name : names) + for (const std::string& button_name : combo.buttons()) { - for (const auto& [code, nam] : map) + for (const auto& [code, name] : map) { - if (nam == name) + if (button_name == name) { key_codes.insert(code); break; @@ -82,12 +46,7 @@ std::set PadHandlerBase::find_key_codes(const std::unordered_map m_pad_for_pad_settings; - // Search an unordered map for a string value and return the found combo - static std::vector> find_key_combos(const std::unordered_map& map, const std::string& cfg_string, const std::string& fallback); + // Search an unordered map for a string value and return the found combos + static std::vector> find_key_combos(const std::unordered_map& map, const std::string& cfg_string); - // Search an unordered map for a string value and return the found combo - static std::vector> find_key_combos(const std::unordered_map& map, const cfg::string& cfg_string, bool fallback = true); - - // Search an unordered map for string values and return the found key codes - static std::set find_key_codes(const std::unordered_map& map, const std::vector& names); + // Search an unordered map for a combo and return the found key codes + static std::set find_key_codes(const std::unordered_map& map, const pad::combo& combo); // Get normalized trigger value based on the range defined by a threshold u16 NormalizeTriggerInput(u16 value, u32 threshold) const; diff --git a/rpcs3/Emu/Io/pad_config.cpp b/rpcs3/Emu/Io/pad_config.cpp index ce64513659..5b89ca709d 100644 --- a/rpcs3/Emu/Io/pad_config.cpp +++ b/rpcs3/Emu/Io/pad_config.cpp @@ -5,15 +5,15 @@ extern std::string g_input_config_override; -std::vector> cfg_pad::get_buttons(std::string_view str) +std::vector cfg_pad::get_combos(std::string_view button_string) { - if (str.empty()) + if (button_string.empty()) return {}; // Handle special case: string contains separator itself as configured value (it's why I don't use fmt::split here) const auto split = [](std::string_view str, char sep) { - std::vector vec; + std::set buttons; bool was_sep = true; usz btn_start = 0ULL; usz i = 0ULL; @@ -27,7 +27,7 @@ std::vector> cfg_pad::get_buttons(std::string_view str) if (!was_sep) { was_sep = true; - vec.push_back(std::string(str.substr(btn_start, i - btn_start))); + buttons.insert(std::string(str.substr(btn_start, i - btn_start))); continue; } } @@ -40,54 +40,47 @@ std::vector> cfg_pad::get_buttons(std::string_view str) if (i == (str.size() - 1)) { - vec.push_back(std::string(str.substr(btn_start, i - btn_start + 1))); + buttons.insert(std::string(str.substr(btn_start, i - btn_start + 1))); } } - // Remove duplicates - std::sort(vec.begin(), vec.end()); - vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); - - return vec; + return buttons; }; - std::vector> res; + std::vector combos; // Get all combos (seperated by ',') - const std::vector vec = split(str, ','); + const std::set combo_strings = split(button_string, ','); - for (const std::string& combo : vec) + for (const std::string& combo_string : combo_strings) { // Get all keys for this combo (seperated by '&') - std::vector keys = split(combo, '&'); - if (!keys.empty()) + std::set combo = split(combo_string, '&'); + if (!combo.empty()) { - res.push_back(std::move(keys)); + combos.push_back(pad::combo{std::move(combo)}); } } - return res; + return combos; } -std::string cfg_pad::get_buttons(std::vector> vec) +std::string cfg_pad::get_button_string(std::vector& combos) { - std::vector combos; + std::vector combo_strings; // Remove duplicates - std::sort(vec.begin(), vec.end()); - vec.erase(std::unique(vec.begin(), vec.end()), vec.end()); + std::sort(combos.begin(), combos.end()); + combos.erase(std::unique(combos.begin(), combos.end()), combos.end()); - for (std::vector& combo : vec) + for (const pad::combo& combo : combos) { - std::sort(combo.begin(), combo.end()); - combo.erase(std::unique(combo.begin(), combo.end()), combo.end()); - // Merge all keys for this combo (seperated by '&') - combos.push_back(fmt::merge(combo, "&")); + combo_strings.push_back(fmt::merge(combo.buttons(), "&")); } // Merge combos (seperated by ',') - return fmt::merge(combos, ","); + return fmt::merge(combo_strings, ","); } u8 cfg_pad::get_motor_speed(VibrateMotor& motor, f32 multiplier) const diff --git a/rpcs3/Emu/Io/pad_config.h b/rpcs3/Emu/Io/pad_config.h index 07de4a7299..24fe3e50b4 100644 --- a/rpcs3/Emu/Io/pad_config.h +++ b/rpcs3/Emu/Io/pad_config.h @@ -5,10 +5,42 @@ #include "Utilities/Config.h" #include +#include namespace pad { constexpr static std::string_view keyboard_device_name = "Keyboard"; + + struct combo + { + public: + combo() = default; + combo(std::set buttons) : m_buttons(std::move(buttons)) {} + + const std::set& buttons() const + { + return m_buttons; + } + + void add_button(const std::string& button) + { + if (button.empty()) return; + m_buttons.insert(button); + } + + bool operator==(const combo& other) const + { + return m_buttons == other.m_buttons; + } + + bool operator<(const combo& other) const + { + return m_buttons < other.m_buttons; + } + + private: + std::set m_buttons; + }; } struct cfg_sensor final : cfg::node @@ -25,8 +57,8 @@ struct cfg_pad final : cfg::node cfg_pad() {}; cfg_pad(node* owner, const std::string& name) : cfg::node(owner, name) {} - static std::vector> get_buttons(std::string_view str); - static std::string get_buttons(std::vector> vec); + static std::vector get_combos(std::string_view button_string); + static std::string get_button_string(std::vector& combos); u8 get_motor_speed(VibrateMotor& motor, f32 multiplier) const; u8 get_large_motor_speed(std::array& motors) const; diff --git a/rpcs3/Input/evdev_joystick_handler.cpp b/rpcs3/Input/evdev_joystick_handler.cpp index 5b5e2731ae..104d8d8c41 100644 --- a/rpcs3/Input/evdev_joystick_handler.cpp +++ b/rpcs3/Input/evdev_joystick_handler.cpp @@ -373,7 +373,7 @@ PadHandlerBase::connection evdev_joystick_handler::get_next_button_press(const s const auto find_value = [&, this](const std::string& str) { - const std::vector> combos = cfg_pad::get_buttons(str); + const std::vector combos = cfg_pad::get_combos(str); u16 value{}; @@ -385,19 +385,19 @@ PadHandlerBase::connection evdev_joystick_handler::get_next_button_press(const s } }; - for (const std::vector& names : combos) + for (const pad::combo& combo : combos) { - for (const u32 code : find_key_codes(rev_axis_list, names)) + for (const u32 code : find_key_codes(rev_axis_list, combo)) { set_value(code, true); } - for (const u32 code : find_key_codes(axis_list, names)) + for (const u32 code : find_key_codes(axis_list, combo)) { set_value(code, false); } - for (const u32 code : find_key_codes(button_list, names)) + for (const u32 code : find_key_codes(button_list, combo)) { set_value(code, false); } @@ -1379,37 +1379,37 @@ bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr pad) const auto find_buttons = [&](const cfg::string& name) -> std::vector> { - const std::vector> str_combos = cfg_pad::get_buttons(name.to_string()); + const std::vector combos = cfg_pad::get_combos(name.to_string()); // In evdev we store indices to an EvdevButton vector in our pad objects instead of the usual key codes. - std::vector> combos; + std::vector> index_combos; - for (const std::vector& names : str_combos) + for (const pad::combo& combo : combos) { std::set indices; - for (const u32 code : find_key_codes(axis_list, names)) + for (const u32 code : find_key_codes(axis_list, combo)) { indices.insert(register_evdevbutton(code, true, false)); } - for (const u32 code : find_key_codes(rev_axis_list, names)) + for (const u32 code : find_key_codes(rev_axis_list, combo)) { indices.insert(register_evdevbutton(code, true, true)); } - for (const u32 code : find_key_codes(button_list, names)) + for (const u32 code : find_key_codes(button_list, combo)) { indices.insert(register_evdevbutton(code, false, false)); } if (!indices.empty()) { - combos.push_back(std::move(indices)); + index_combos.push_back(std::move(indices)); } } - return combos; + return index_combos; }; const auto find_motion_button = [&](const cfg_sensor& sensor) -> evdev_sensor diff --git a/rpcs3/Input/keyboard_pad_handler.cpp b/rpcs3/Input/keyboard_pad_handler.cpp index e7435b09ba..662b03c0a3 100644 --- a/rpcs3/Input/keyboard_pad_handler.cpp +++ b/rpcs3/Input/keyboard_pad_handler.cpp @@ -895,13 +895,13 @@ std::vector> keyboard_pad_handler::GetKeyCombos(const cfg::string& { std::vector> res; - for (const std::vector& combo : cfg_pad::get_buttons(cfg_string.to_string())) + for (const pad::combo& combo : cfg_pad::get_combos(cfg_string.to_string())) { std::set key_codes; - for (const std::string& key_name : combo) + for (const std::string& button : combo.buttons()) { - if (u32 code = GetKeyCode(QString::fromStdString(key_name)); code != Qt::NoButton) + if (u32 code = GetKeyCode(QString::fromStdString(button)); code != Qt::NoButton) { key_codes.insert(code); } @@ -1051,7 +1051,7 @@ bool keyboard_pad_handler::bindPadToDevice(std::shared_ptr pad) const auto find_combos = [this](const cfg::string& name) { - std::vector> combos = find_key_combos(mouse_list, name, false); + std::vector> combos = find_key_combos(mouse_list, name); for (const std::set& combo : GetKeyCombos(name)) combos.push_back(combo); if (!combos.empty()) diff --git a/rpcs3/Input/mm_joystick_handler.cpp b/rpcs3/Input/mm_joystick_handler.cpp index 12ca000d1d..0769d9db4d 100644 --- a/rpcs3/Input/mm_joystick_handler.cpp +++ b/rpcs3/Input/mm_joystick_handler.cpp @@ -232,7 +232,7 @@ pad_preview_values mm_joystick_handler::get_preview_values(const std::unordered_ u16 value{}; // The DS3 Button is considered pressed if any configured button combination is pressed - for (const std::set& codes : find_key_combos(button_list, str, std::string())) + for (const std::set& codes : find_key_combos(button_list, str)) { bool combo_pressed = !codes.empty(); u16 combo_val = 0; diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index 5305b8606a..049579a351 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -51,30 +51,30 @@ inline bool CreateConfigFile(const QString& dir, const QString& name) return true; } -void pad_settings_dialog::pad_button::insert_key(const std::string& key, binding_mode mode) +void pad_settings_dialog::pad_button::insert_button(const std::string& button, binding_mode mode) { - std::vector> combos; + std::vector combos; if (mode != binding_mode::single) { - combos = cfg_pad::get_buttons(m_keys); + combos = cfg_pad::get_combos(m_button_string); } if (combos.empty() || mode != binding_mode::combo) { - combos.push_back({key}); + combos.push_back(pad::combo({button})); } else if (mode == binding_mode::combo) { - combos.back().push_back(key); + combos.back().add_button(button); } - update(cfg_pad::get_buttons(combos)); + update(cfg_pad::get_button_string(combos)); } -void pad_settings_dialog::pad_button::update(const std::string& keys) +void pad_settings_dialog::pad_button::update(const std::string& button_string) { - m_keys = keys; - QString new_text = QString::fromStdString(keys); + m_button_string = button_string; + QString new_text = QString::fromStdString(button_string); m_text = new_text.replace(",", ", ").replace("&", " + "); } @@ -622,7 +622,7 @@ void pad_settings_dialog::InitButtons() { if (value == 0) continue; - m_cfg_entries[m_button_id].insert_key(key, mode); + m_cfg_entries[m_button_id].insert_button(key, mode); // Switch to combo mode for all further keys mode = binding_mode::combo; @@ -632,7 +632,7 @@ void pad_settings_dialog::InitButtons() { if (value == 0) continue; - m_cfg_entries[m_button_id].insert_key(key, mode); + m_cfg_entries[m_button_id].insert_button(key, mode); // Switch to combo mode for all further keys mode = binding_mode::combo; @@ -674,16 +674,16 @@ void pad_settings_dialog::InitButtons() const std::vector buttons = { - m_cfg_entries[button_ids::id_pad_l2].keys(), - m_cfg_entries[button_ids::id_pad_r2].keys(), - m_cfg_entries[button_ids::id_pad_lstick_left].keys(), - m_cfg_entries[button_ids::id_pad_lstick_right].keys(), - m_cfg_entries[button_ids::id_pad_lstick_down].keys(), - m_cfg_entries[button_ids::id_pad_lstick_up].keys(), - m_cfg_entries[button_ids::id_pad_rstick_left].keys(), - m_cfg_entries[button_ids::id_pad_rstick_right].keys(), - m_cfg_entries[button_ids::id_pad_rstick_down].keys(), - m_cfg_entries[button_ids::id_pad_rstick_up].keys() + m_cfg_entries[button_ids::id_pad_l2].button_string(), + m_cfg_entries[button_ids::id_pad_r2].button_string(), + m_cfg_entries[button_ids::id_pad_lstick_left].button_string(), + m_cfg_entries[button_ids::id_pad_lstick_right].button_string(), + m_cfg_entries[button_ids::id_pad_lstick_down].button_string(), + m_cfg_entries[button_ids::id_pad_lstick_up].button_string(), + m_cfg_entries[button_ids::id_pad_rstick_left].button_string(), + m_cfg_entries[button_ids::id_pad_rstick_right].button_string(), + m_cfg_entries[button_ids::id_pad_rstick_down].button_string(), + m_cfg_entries[button_ids::id_pad_rstick_up].button_string() }; // Check if this is the first call during a remap @@ -1023,7 +1023,7 @@ void pad_settings_dialog::keyPressEvent(QKeyEvent* keyEvent) } else { - m_cfg_entries[m_button_id].insert_key(keyboard_pad_handler::GetKeyName(keyEvent, false), m_binding_mode); + m_cfg_entries[m_button_id].insert_button(keyboard_pad_handler::GetKeyName(keyEvent, false), m_binding_mode); } ReactivateButtons(); @@ -1050,7 +1050,7 @@ void pad_settings_dialog::mouseReleaseEvent(QMouseEvent* event) } else { - m_cfg_entries[m_button_id].insert_key((static_cast(m_handler.get()))->GetMouseName(event), m_binding_mode); + m_cfg_entries[m_button_id].insert_button((static_cast(m_handler.get()))->GetMouseName(event), m_binding_mode); } ReactivateButtons(); @@ -1112,7 +1112,7 @@ void pad_settings_dialog::wheelEvent(QWheelEvent* event) } } - m_cfg_entries[m_button_id].insert_key((static_cast(m_handler.get()))->GetMouseName(key), m_binding_mode); + m_cfg_entries[m_button_id].insert_button((static_cast(m_handler.get()))->GetMouseName(key), m_binding_mode); ReactivateButtons(); } @@ -1163,7 +1163,7 @@ void pad_settings_dialog::mouseMoveEvent(QMouseEvent* event) if (key != 0) { - m_cfg_entries[m_button_id].insert_key((static_cast(m_handler.get()))->GetMouseName(key), m_binding_mode); + m_cfg_entries[m_button_id].insert_button((static_cast(m_handler.get()))->GetMouseName(key), m_binding_mode); ReactivateButtons(); } } @@ -2110,7 +2110,7 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id) // Check for duplicate button choices if (m_handler->m_type != pad_handler::null) { - std::set unique_keys; + std::set unique_button_strings; for (const auto& [id, button] : m_cfg_entries) { // Let's ignore special keys, unless we're using a keyboard @@ -2120,13 +2120,13 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id) continue; } - for (const std::vector& combo : cfg_pad::get_buttons(button.keys())) + for (const pad::combo& combo : cfg_pad::get_combos(button.button_string())) { - for (const std::string& key : combo) + for (const std::string& button_string : combo.buttons()) { - if (const auto& [it, ok] = unique_keys.insert(key); !ok) + if (const auto& [it, ok] = unique_button_strings.insert(button_string); !ok) { - m_duplicate_buttons[m_last_player_id] = key; + m_duplicate_buttons[m_last_player_id] = button_string; break; } } @@ -2137,7 +2137,7 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id) // Apply buttons for (const auto& entry : m_cfg_entries) { - entry.second.cfg_text()->from_string(entry.second.keys()); + entry.second.cfg_text()->from_string(entry.second.button_string()); } // Apply rest of config diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.h b/rpcs3/rpcs3qt/pad_settings_dialog.h index 5fded0edcd..4a6c253689 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.h +++ b/rpcs3/rpcs3qt/pad_settings_dialog.h @@ -93,16 +93,16 @@ class pad_settings_dialog : public QDialog update(*cfg_text); } - void insert_key(const std::string& key, binding_mode mode); - void update(const std::string& keys); + void insert_button(const std::string& button, binding_mode mode); + void update(const std::string& button_string); cfg::string* cfg_text() const { return m_cfg_text; } - const std::string& keys() const { return m_keys; } + const std::string& button_string() const { return m_button_string; } const QString& text() const { return m_text; } private: cfg::string* m_cfg_text = nullptr; - std::string m_keys; + std::string m_button_string; QString m_text; }; From 5578edf9e49e358183578933249a8e6f3aded739 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 18 Mar 2026 09:09:18 +0100 Subject: [PATCH 002/108] Input: Map PS button to start+select by default --- rpcs3/Emu/Io/pad_config.cpp | 21 ++++++++++++ rpcs3/Emu/Io/pad_config.h | 1 + rpcs3/Input/ds3_pad_handler.cpp | 44 +++++++++++++------------- rpcs3/Input/ds4_pad_handler.cpp | 2 +- rpcs3/Input/dualsense_pad_handler.cpp | 2 +- rpcs3/Input/evdev_joystick_handler.cpp | 2 +- rpcs3/Input/ps_move_handler.cpp | 2 +- rpcs3/Input/sdl_pad_handler.cpp | 2 +- rpcs3/Input/skateboard_pad_handler.cpp | 2 +- rpcs3/Input/xinput_pad_handler.cpp | 2 +- 10 files changed, 51 insertions(+), 29 deletions(-) diff --git a/rpcs3/Emu/Io/pad_config.cpp b/rpcs3/Emu/Io/pad_config.cpp index 5b89ca709d..68c1f9fabf 100644 --- a/rpcs3/Emu/Io/pad_config.cpp +++ b/rpcs3/Emu/Io/pad_config.cpp @@ -83,6 +83,27 @@ std::string cfg_pad::get_button_string(std::vector& combos) return fmt::merge(combo_strings, ","); } +std::string cfg_pad::make_button_string(const std::unordered_map& button_list, const std::vector>& button_combos) +{ + std::vector combos; + + for (const std::set& button_combo : button_combos) + { + if (button_combo.empty()) continue; + + pad::combo combo {}; + + for (u32 button : button_combo) + { + combo.add_button(::at32(button_list, button)); + } + + combos.push_back(std::move(combo)); + } + + return get_button_string(combos); +} + u8 cfg_pad::get_motor_speed(VibrateMotor& motor, f32 multiplier) const { // If motor is small, use either 0 or 255. diff --git a/rpcs3/Emu/Io/pad_config.h b/rpcs3/Emu/Io/pad_config.h index 24fe3e50b4..2d789805e2 100644 --- a/rpcs3/Emu/Io/pad_config.h +++ b/rpcs3/Emu/Io/pad_config.h @@ -59,6 +59,7 @@ struct cfg_pad final : cfg::node static std::vector get_combos(std::string_view button_string); static std::string get_button_string(std::vector& combos); + static std::string make_button_string(const std::unordered_map& button_list, const std::vector>& button_combos); u8 get_motor_speed(VibrateMotor& motor, f32 multiplier) const; u8 get_large_motor_speed(std::array& motors) const; diff --git a/rpcs3/Input/ds3_pad_handler.cpp b/rpcs3/Input/ds3_pad_handler.cpp index 77c924e60e..d809cd9d86 100644 --- a/rpcs3/Input/ds3_pad_handler.cpp +++ b/rpcs3/Input/ds3_pad_handler.cpp @@ -170,31 +170,31 @@ void ds3_pad_handler::init_config(cfg_pad* cfg) if (!cfg) return; // Set default button mapping - cfg->ls_left.def = ::at32(button_list, DS3KeyCodes::LSXNeg); - cfg->ls_down.def = ::at32(button_list, DS3KeyCodes::LSYNeg); + cfg->ls_left.def = ::at32(button_list, DS3KeyCodes::LSXNeg); + cfg->ls_down.def = ::at32(button_list, DS3KeyCodes::LSYNeg); cfg->ls_right.def = ::at32(button_list, DS3KeyCodes::LSXPos); - cfg->ls_up.def = ::at32(button_list, DS3KeyCodes::LSYPos); - cfg->rs_left.def = ::at32(button_list, DS3KeyCodes::RSXNeg); - cfg->rs_down.def = ::at32(button_list, DS3KeyCodes::RSYNeg); + cfg->ls_up.def = ::at32(button_list, DS3KeyCodes::LSYPos); + cfg->rs_left.def = ::at32(button_list, DS3KeyCodes::RSXNeg); + cfg->rs_down.def = ::at32(button_list, DS3KeyCodes::RSYNeg); cfg->rs_right.def = ::at32(button_list, DS3KeyCodes::RSXPos); - cfg->rs_up.def = ::at32(button_list, DS3KeyCodes::RSYPos); - cfg->start.def = ::at32(button_list, DS3KeyCodes::Start); - cfg->select.def = ::at32(button_list, DS3KeyCodes::Select); - cfg->ps.def = ::at32(button_list, DS3KeyCodes::PSButton); - cfg->square.def = ::at32(button_list, DS3KeyCodes::Square); - cfg->cross.def = ::at32(button_list, DS3KeyCodes::Cross); - cfg->circle.def = ::at32(button_list, DS3KeyCodes::Circle); + cfg->rs_up.def = ::at32(button_list, DS3KeyCodes::RSYPos); + cfg->start.def = ::at32(button_list, DS3KeyCodes::Start); + cfg->select.def = ::at32(button_list, DS3KeyCodes::Select); + cfg->ps.def = cfg_pad::make_button_string(button_list, {{DS3KeyCodes::PSButton}, {DS3KeyCodes::Start, DS3KeyCodes::Select}}); + cfg->square.def = ::at32(button_list, DS3KeyCodes::Square); + cfg->cross.def = ::at32(button_list, DS3KeyCodes::Cross); + cfg->circle.def = ::at32(button_list, DS3KeyCodes::Circle); cfg->triangle.def = ::at32(button_list, DS3KeyCodes::Triangle); - cfg->left.def = ::at32(button_list, DS3KeyCodes::Left); - cfg->down.def = ::at32(button_list, DS3KeyCodes::Down); - cfg->right.def = ::at32(button_list, DS3KeyCodes::Right); - cfg->up.def = ::at32(button_list, DS3KeyCodes::Up); - cfg->r1.def = ::at32(button_list, DS3KeyCodes::R1); - cfg->r2.def = ::at32(button_list, DS3KeyCodes::R2); - cfg->r3.def = ::at32(button_list, DS3KeyCodes::R3); - cfg->l1.def = ::at32(button_list, DS3KeyCodes::L1); - cfg->l2.def = ::at32(button_list, DS3KeyCodes::L2); - cfg->l3.def = ::at32(button_list, DS3KeyCodes::L3); + cfg->left.def = ::at32(button_list, DS3KeyCodes::Left); + cfg->down.def = ::at32(button_list, DS3KeyCodes::Down); + cfg->right.def = ::at32(button_list, DS3KeyCodes::Right); + cfg->up.def = ::at32(button_list, DS3KeyCodes::Up); + cfg->r1.def = ::at32(button_list, DS3KeyCodes::R1); + cfg->r2.def = ::at32(button_list, DS3KeyCodes::R2); + cfg->r3.def = ::at32(button_list, DS3KeyCodes::R3); + cfg->l1.def = ::at32(button_list, DS3KeyCodes::L1); + cfg->l2.def = ::at32(button_list, DS3KeyCodes::L2); + cfg->l3.def = ::at32(button_list, DS3KeyCodes::L3); cfg->pressure_intensity_button.def = ::at32(button_list, DS3KeyCodes::None); cfg->analog_limiter_button.def = ::at32(button_list, DS3KeyCodes::None); diff --git a/rpcs3/Input/ds4_pad_handler.cpp b/rpcs3/Input/ds4_pad_handler.cpp index 962dd65969..706c14ae7f 100644 --- a/rpcs3/Input/ds4_pad_handler.cpp +++ b/rpcs3/Input/ds4_pad_handler.cpp @@ -162,7 +162,7 @@ void ds4_pad_handler::init_config(cfg_pad* cfg) cfg->rs_up.def = ::at32(button_list, DS4KeyCodes::RSYPos); cfg->start.def = ::at32(button_list, DS4KeyCodes::Options); cfg->select.def = ::at32(button_list, DS4KeyCodes::Share); - cfg->ps.def = ::at32(button_list, DS4KeyCodes::PSButton); + cfg->ps.def = cfg_pad::make_button_string(button_list, {{DS4KeyCodes::PSButton}, {DS4KeyCodes::Options, DS4KeyCodes::Share}}); cfg->square.def = ::at32(button_list, DS4KeyCodes::Square); cfg->cross.def = ::at32(button_list, DS4KeyCodes::Cross); cfg->circle.def = ::at32(button_list, DS4KeyCodes::Circle); diff --git a/rpcs3/Input/dualsense_pad_handler.cpp b/rpcs3/Input/dualsense_pad_handler.cpp index 4058eb97d1..3936d0c5a7 100644 --- a/rpcs3/Input/dualsense_pad_handler.cpp +++ b/rpcs3/Input/dualsense_pad_handler.cpp @@ -235,7 +235,7 @@ void dualsense_pad_handler::init_config(cfg_pad* cfg) cfg->rs_up.def = ::at32(button_list, DualSenseKeyCodes::RSYPos); cfg->start.def = ::at32(button_list, DualSenseKeyCodes::Options); cfg->select.def = ::at32(button_list, DualSenseKeyCodes::Share); - cfg->ps.def = ::at32(button_list, DualSenseKeyCodes::PSButton); + cfg->ps.def = cfg_pad::make_button_string(button_list, {{DualSenseKeyCodes::PSButton}, {DualSenseKeyCodes::Options, DualSenseKeyCodes::Share}}); cfg->square.def = ::at32(button_list, DualSenseKeyCodes::Square); cfg->cross.def = ::at32(button_list, DualSenseKeyCodes::Cross); cfg->circle.def = ::at32(button_list, DualSenseKeyCodes::Circle); diff --git a/rpcs3/Input/evdev_joystick_handler.cpp b/rpcs3/Input/evdev_joystick_handler.cpp index 104d8d8c41..a8a957502a 100644 --- a/rpcs3/Input/evdev_joystick_handler.cpp +++ b/rpcs3/Input/evdev_joystick_handler.cpp @@ -88,7 +88,7 @@ void evdev_joystick_handler::init_config(cfg_pad* cfg) cfg->rs_up.def = ::at32(rev_axis_list, ABS_RY); cfg->start.def = ::at32(button_list, BTN_START); cfg->select.def = ::at32(button_list, BTN_SELECT); - cfg->ps.def = ::at32(button_list, BTN_MODE); + cfg->ps.def = cfg_pad::make_button_string(button_list, {{BTN_MODE}, {BTN_START, BTN_SELECT}}); cfg->square.def = ::at32(button_list, BTN_X); cfg->cross.def = ::at32(button_list, BTN_A); cfg->circle.def = ::at32(button_list, BTN_B); diff --git a/rpcs3/Input/ps_move_handler.cpp b/rpcs3/Input/ps_move_handler.cpp index fa3e5d542a..cf11479fc4 100644 --- a/rpcs3/Input/ps_move_handler.cpp +++ b/rpcs3/Input/ps_move_handler.cpp @@ -152,7 +152,7 @@ void ps_move_handler::init_config(cfg_pad* cfg) cfg->rs_up.def = ::at32(button_list, ps_move_key_codes::none); cfg->start.def = ::at32(button_list, ps_move_key_codes::start); cfg->select.def = ::at32(button_list, ps_move_key_codes::select); - cfg->ps.def = ::at32(button_list, ps_move_key_codes::ps); + cfg->ps.def = cfg_pad::make_button_string(button_list, {{ps_move_key_codes::ps}, {ps_move_key_codes::start, ps_move_key_codes::select}}); cfg->square.def = ::at32(button_list, ps_move_key_codes::square); cfg->cross.def = ::at32(button_list, ps_move_key_codes::cross); cfg->circle.def = ::at32(button_list, ps_move_key_codes::circle); diff --git a/rpcs3/Input/sdl_pad_handler.cpp b/rpcs3/Input/sdl_pad_handler.cpp index 9fd6c79dd5..099de9000e 100644 --- a/rpcs3/Input/sdl_pad_handler.cpp +++ b/rpcs3/Input/sdl_pad_handler.cpp @@ -121,7 +121,7 @@ void sdl_pad_handler::init_config(cfg_pad* cfg) cfg->rs_up.def = ::at32(button_list, SDLKeyCodes::RSYPos); cfg->start.def = ::at32(button_list, SDLKeyCodes::Start); cfg->select.def = ::at32(button_list, SDLKeyCodes::Back); - cfg->ps.def = ::at32(button_list, SDLKeyCodes::Guide); + cfg->ps.def = cfg_pad::make_button_string(button_list, {{SDLKeyCodes::Guide}, {SDLKeyCodes::Start, SDLKeyCodes::Back}}); cfg->square.def = ::at32(button_list, SDLKeyCodes::West); cfg->cross.def = ::at32(button_list, SDLKeyCodes::South); cfg->circle.def = ::at32(button_list, SDLKeyCodes::East); diff --git a/rpcs3/Input/skateboard_pad_handler.cpp b/rpcs3/Input/skateboard_pad_handler.cpp index 614d3e5976..732a22808a 100644 --- a/rpcs3/Input/skateboard_pad_handler.cpp +++ b/rpcs3/Input/skateboard_pad_handler.cpp @@ -112,7 +112,7 @@ void skateboard_pad_handler::init_config(cfg_pad* cfg) cfg->rs_up.def = ::at32(button_list, skateboard_key_codes::none); cfg->start.def = ::at32(button_list, skateboard_key_codes::start); cfg->select.def = ::at32(button_list, skateboard_key_codes::select); - cfg->ps.def = ::at32(button_list, skateboard_key_codes::ps); + cfg->ps.def = cfg_pad::make_button_string(button_list, {{skateboard_key_codes::ps}, {skateboard_key_codes::start, skateboard_key_codes::select}}); cfg->square.def = ::at32(button_list, skateboard_key_codes::square); cfg->cross.def = ::at32(button_list, skateboard_key_codes::cross); cfg->circle.def = ::at32(button_list, skateboard_key_codes::circle); diff --git a/rpcs3/Input/xinput_pad_handler.cpp b/rpcs3/Input/xinput_pad_handler.cpp index f7cfd9eb92..349d36c722 100644 --- a/rpcs3/Input/xinput_pad_handler.cpp +++ b/rpcs3/Input/xinput_pad_handler.cpp @@ -101,7 +101,7 @@ void xinput_pad_handler::init_config(cfg_pad* cfg) cfg->rs_up.def = ::at32(button_list, XInputKeyCodes::RSYPos); cfg->start.def = ::at32(button_list, XInputKeyCodes::Start); cfg->select.def = ::at32(button_list, XInputKeyCodes::Back); - cfg->ps.def = ::at32(button_list, XInputKeyCodes::Guide); + cfg->ps.def = cfg_pad::make_button_string(button_list, {{XInputKeyCodes::Guide}, {XInputKeyCodes::Start, XInputKeyCodes::Back}}); cfg->square.def = ::at32(button_list, XInputKeyCodes::X); cfg->cross.def = ::at32(button_list, XInputKeyCodes::A); cfg->circle.def = ::at32(button_list, XInputKeyCodes::B); From 7b58340a60060cb75151a00bf7d2a8881e5f7b52 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 18 Mar 2026 09:49:17 +0100 Subject: [PATCH 003/108] Input: optimize keyboard button handling on release --- rpcs3/Input/keyboard_pad_handler.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/rpcs3/Input/keyboard_pad_handler.cpp b/rpcs3/Input/keyboard_pad_handler.cpp index 662b03c0a3..21c6b3c7e8 100644 --- a/rpcs3/Input/keyboard_pad_handler.cpp +++ b/rpcs3/Input/keyboard_pad_handler.cpp @@ -86,8 +86,6 @@ void keyboard_pad_handler::Key(const u32 code, bool pressed, u16 value) { const auto register_new_button_value = [code, pressed, value](Button& btn) -> u16 { - u16 actual_value = 0; - // Make sure we keep this button pressed until all related keys are released. if (pressed) { @@ -96,8 +94,16 @@ void keyboard_pad_handler::Key(const u32 code, bool pressed, u16 value) else { btn.m_pressed_keys.erase(code); + + // Optimization: just skip the whole combo parsing if there are no keys pressed + if (btn.m_pressed_keys.empty()) + { + return 0; + } } + u16 actual_value = 0; + // Get the max value of all pressed keys for this DS3 button for (const std::set& key_codes : btn.m_key_combos) { @@ -262,7 +268,7 @@ void keyboard_pad_handler::Key(const u32 code, bool pressed, u16 value) { const u16 actual_value = pressed ? MultipliedInput(value, is_left_stick ? l_stick_multiplier : r_stick_multiplier) : value; - const auto register_new_stick_value = [&](bool is_max) + const auto register_new_stick_value = [&](bool is_max) -> std::pair { const std::vector>& key_combos = is_max ? stick.m_key_combos_max : stick.m_key_combos_min; std::map& pressed_keys = is_max ? stick.m_pressed_keys_max : stick.m_pressed_keys_min; From f076d1e1e65e88c798862fc4156df4a374e6514c Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 18 Mar 2026 10:18:24 +0100 Subject: [PATCH 004/108] Input: Fix keyboard stick handling --- rpcs3/Input/keyboard_pad_handler.cpp | 29 ++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/rpcs3/Input/keyboard_pad_handler.cpp b/rpcs3/Input/keyboard_pad_handler.cpp index 21c6b3c7e8..e17598f41c 100644 --- a/rpcs3/Input/keyboard_pad_handler.cpp +++ b/rpcs3/Input/keyboard_pad_handler.cpp @@ -6,6 +6,7 @@ #include "Input/product_info.h" #include "rpcs3qt/gs_frame.h" +#include #include bool keyboard_pad_handler::Init() @@ -284,10 +285,10 @@ void keyboard_pad_handler::Key(const u32 code, bool pressed, u16 value) pressed_keys.erase(code); } + // Get the value of all the combos for this stick direction bool any_combo_pressed = false; u16 new_val = 0; - // Get the min/max value of all pressed keys for this stick for (const std::set& key_codes : key_combos) { if (key_codes.empty()) continue; @@ -315,25 +316,29 @@ void keyboard_pad_handler::Key(const u32 code, bool pressed, u16 value) } } + // Make sure we keep this combo pressed until all related keys are released. if (any_combo_pressed) { - if (pressed_combos.contains(code)) - { - u16& pressed_val = pressed_combos[code]; - pressed_val = is_max ? std::max(new_val, pressed_val) : std::min(new_val, pressed_val); - new_val = pressed_val; - } - else - { - pressed_combos[code] = new_val; - } + new_val = std::ceil(new_val / 2.0f); + + pressed_combos[code] = new_val; } else { pressed_combos.erase(code); + + if (pressed_combos.empty()) + { + return std::pair(false, 0); + } } - return std::pair(any_combo_pressed, static_cast(std::ceil(new_val / 2.0f))); + // Get the min/max value of all pressed combos for this stick + const auto min_max_it = is_max + ? std::max_element(pressed_combos.cbegin(), pressed_combos.cend(), [](const auto& a, const auto& b) { return a.second < b.second; }) + : std::min_element(pressed_combos.cbegin(), pressed_combos.cend(), [](const auto& a, const auto& b) { return a.second < b.second; }); + + return std::pair(!pressed_combos.empty(), min_max_it->second); }; if (is_max) From 4c106960c575b2ac41cbd4e2cda5e6e647221d17 Mon Sep 17 00:00:00 2001 From: FlexBy420 <68403300+FlexBy420@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:50:04 +0100 Subject: [PATCH 005/108] Update current firmware to 4.93 --- rpcs3/rpcs3qt/main_window.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 1b77f8f2b1..0c3a9b162c 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -1525,7 +1525,7 @@ void main_window::HandlePupInstallation(const QString& file_path, const QString& return; } - static constexpr std::string_view cur_version = "4.92"; + static constexpr std::string_view cur_version = "4.93"; std::string version_string; From 0c27b63ab489903ce9b2d0e7428a474a674b09dd Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 18 Mar 2026 16:27:28 +0100 Subject: [PATCH 006/108] Qt: fix duplicate button or combo warning --- rpcs3/Emu/Io/pad_config.cpp | 2 +- rpcs3/Emu/Io/pad_config.h | 5 +++++ rpcs3/rpcs3qt/pad_settings_dialog.cpp | 23 +++++++++++------------ rpcs3/rpcs3qt/pad_settings_dialog.h | 2 +- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/rpcs3/Emu/Io/pad_config.cpp b/rpcs3/Emu/Io/pad_config.cpp index 68c1f9fabf..7d042bddbc 100644 --- a/rpcs3/Emu/Io/pad_config.cpp +++ b/rpcs3/Emu/Io/pad_config.cpp @@ -76,7 +76,7 @@ std::string cfg_pad::get_button_string(std::vector& combos) for (const pad::combo& combo : combos) { // Merge all keys for this combo (seperated by '&') - combo_strings.push_back(fmt::merge(combo.buttons(), "&")); + combo_strings.push_back(combo.to_string()); } // Merge combos (seperated by ',') diff --git a/rpcs3/Emu/Io/pad_config.h b/rpcs3/Emu/Io/pad_config.h index 2d789805e2..86779c0859 100644 --- a/rpcs3/Emu/Io/pad_config.h +++ b/rpcs3/Emu/Io/pad_config.h @@ -28,6 +28,11 @@ namespace pad m_buttons.insert(button); } + std::string to_string() const + { + return fmt::merge(m_buttons, "&"); + } + bool operator==(const combo& other) const { return m_buttons == other.m_buttons; diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index 049579a351..9d9d9d31e2 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -2102,7 +2102,7 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id) return; } - m_duplicate_buttons[m_last_player_id].clear(); + m_duplicate_combos[m_last_player_id].clear(); auto& player = g_cfg_input.player[m_last_player_id]; m_last_player_id = new_player_id; @@ -2110,7 +2110,7 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id) // Check for duplicate button choices if (m_handler->m_type != pad_handler::null) { - std::set unique_button_strings; + std::set unique_combo_strings; for (const auto& [id, button] : m_cfg_entries) { // Let's ignore special keys, unless we're using a keyboard @@ -2122,13 +2122,12 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id) for (const pad::combo& combo : cfg_pad::get_combos(button.button_string())) { - for (const std::string& button_string : combo.buttons()) + std::string combo_string = combo.to_string(); + + if (const auto& [it, ok] = unique_combo_strings.insert(combo_string); !ok) { - if (const auto& [it, ok] = unique_button_strings.insert(button_string); !ok) - { - m_duplicate_buttons[m_last_player_id] = button_string; - break; - } + m_duplicate_combos[m_last_player_id] = std::move(combo_string); + break; } } } @@ -2212,16 +2211,16 @@ void pad_settings_dialog::save(bool check_duplicates) if (check_duplicates) { - for (const auto& [player_id, key] : m_duplicate_buttons) + for (const auto& [player_id, combo] : m_duplicate_combos) { - if (!key.empty()) + if (!combo.empty()) { int result = QMessageBox::Yes; m_gui_settings->ShowConfirmationBox( tr("Warning!"), - tr("The %0 button %1 of Player %2 was assigned at least twice.
Please consider adjusting the configuration.

Continue anyway?
") + tr("The %0 button or combo %1 of Player %2 was assigned at least twice.
Please consider adjusting the configuration.

Continue anyway?
") .arg(QString::fromStdString(g_cfg_input.player[player_id]->handler.to_string())) - .arg(QString::fromStdString(key)) + .arg(QString::fromStdString(combo)) .arg(player_id + 1), gui::ib_same_buttons, &result, this); diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.h b/rpcs3/rpcs3qt/pad_settings_dialog.h index 4a6c253689..56e7cc09be 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.h +++ b/rpcs3/rpcs3qt/pad_settings_dialog.h @@ -152,7 +152,7 @@ private: QButtonGroup* m_pad_buttons = nullptr; atomic_t m_button_id = button_ids::id_pad_begin; std::map m_cfg_entries; - std::map m_duplicate_buttons; + std::map m_duplicate_combos; // Real time stick values int m_lx = 0; From 1f2a9354580e001938cb5ba7edb5cec047baa286 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 18 Mar 2026 16:31:53 +0100 Subject: [PATCH 007/108] Qt: Do not close pad settings dialog if user said no in duplicate button dialog --- rpcs3/rpcs3qt/pad_settings_dialog.cpp | 15 ++++++++++----- rpcs3/rpcs3qt/pad_settings_dialog.h | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index 9d9d9d31e2..d0334d3ae0 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -2205,7 +2205,7 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id) cfg.product_id.set(info.product_id); } -void pad_settings_dialog::save(bool check_duplicates) +bool pad_settings_dialog::save(bool check_duplicates) { ApplyCurrentPlayerConfig(m_last_player_id); @@ -2225,7 +2225,9 @@ void pad_settings_dialog::save(bool check_duplicates) gui::ib_same_buttons, &result, this); if (result == QMessageBox::No) - return; + { + return false; + } break; } @@ -2238,13 +2240,16 @@ void pad_settings_dialog::save(bool check_duplicates) g_cfg_input_configs.save(); g_cfg_input.save(m_title_id, m_config_file); + + return true; } void pad_settings_dialog::SaveExit() { - save(true); - - QDialog::accept(); + if (save(true)) + { + QDialog::accept(); + } } void pad_settings_dialog::CancelExit() diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.h b/rpcs3/rpcs3qt/pad_settings_dialog.h index 56e7cc09be..4f20d2d0db 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.h +++ b/rpcs3/rpcs3qt/pad_settings_dialog.h @@ -216,7 +216,7 @@ private: std::pair get_config_files(); - void save(bool check_duplicates); + bool save(bool check_duplicates); void SaveExit(); void CancelExit(); From 253d4565f1483b19fb8a53f14d4f3a937389ca16 Mon Sep 17 00:00:00 2001 From: Ani Date: Wed, 18 Mar 2026 19:24:01 +0100 Subject: [PATCH 008/108] config: Enable start in fullscreen mode by default --- rpcs3/Emu/system_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 3d7dba8798..11c350215d 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -347,7 +347,7 @@ struct cfg_root : cfg::node cfg::_bool autostart{ this, "Automatically start games after boot", true, true }; cfg::_bool autoexit{ this, "Exit RPCS3 when process finishes", false, true }; cfg::_bool autopause{ this, "Pause emulation on RPCS3 focus loss", false, true }; - cfg::_bool start_fullscreen{ this, "Start games in fullscreen mode", false, true }; + cfg::_bool start_fullscreen{ this, "Start games in fullscreen mode", true, true }; cfg::_bool prevent_display_sleep{ this, "Prevent display sleep while running games", true, true }; cfg::_bool show_trophy_popups{ this, "Show trophy popups", true, true }; cfg::_bool show_rpcn_popups{ this, "Show RPCN popups", true, true }; From e690e7e45865ace0008f4f8f8afe2c3a49c02911 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 18 Mar 2026 21:46:49 +0300 Subject: [PATCH 009/108] gl/vk: Implement tri-state Vsync setting --- rpcs3/Emu/RSX/GL/GLGSRender.cpp | 15 ++++++++++- rpcs3/Emu/RSX/GSRender.cpp | 2 ++ rpcs3/Emu/RSX/GSRender.h | 2 ++ rpcs3/Emu/RSX/VK/VKPresent.cpp | 6 +++++ rpcs3/Emu/RSX/VK/vkutils/swapchain.cpp | 19 ++++++++----- rpcs3/Emu/system_config.h | 3 +-- rpcs3/Emu/system_config_types.cpp | 16 +++++++++++ rpcs3/Emu/system_config_types.h | 7 +++++ rpcs3/rpcs3qt/emu_settings.cpp | 7 +++++ rpcs3/rpcs3qt/emu_settings_type.h | 2 +- rpcs3/rpcs3qt/settings_dialog.cpp | 6 ++--- rpcs3/rpcs3qt/settings_dialog.ui | 37 +++++++++++++++++++++----- 12 files changed, 101 insertions(+), 21 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index 4ad9619d7f..973123fc82 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -139,7 +139,20 @@ void GLGSRender::on_init_thread() gl::set_command_context(gl_state); // Enable adaptive vsync if vsync is requested - gl::set_swapinterval(g_cfg.video.vsync ? -1 : 0); + int swap_interval = 0; + switch (g_cfg.video.vsync) + { + default: + case vsync_mode::off: + break; + case vsync_mode::adaptive: + swap_interval = -1; + break; + case vsync_mode::full: + swap_interval = 1; + break; + } + gl::set_swapinterval(swap_interval); if (g_cfg.video.debug_output) gl::enable_debugging(); diff --git a/rpcs3/Emu/RSX/GSRender.cpp b/rpcs3/Emu/RSX/GSRender.cpp index e9e859952e..f09f4704da 100644 --- a/rpcs3/Emu/RSX/GSRender.cpp +++ b/rpcs3/Emu/RSX/GSRender.cpp @@ -13,6 +13,8 @@ GSRender::GSRender(utils::serial* ar) noexcept : rsx::thread(ar) { m_frame = nullptr; } + + m_vsync_mode = g_cfg.video.vsync; } GSRender::~GSRender() diff --git a/rpcs3/Emu/RSX/GSRender.h b/rpcs3/Emu/RSX/GSRender.h index d2a6fd9c5f..f597b5562b 100644 --- a/rpcs3/Emu/RSX/GSRender.h +++ b/rpcs3/Emu/RSX/GSRender.h @@ -23,6 +23,8 @@ protected: draw_context_t m_context = nullptr; bool m_continuous_mode = false; + vsync_mode m_vsync_mode{}; + public: ~GSRender() override; diff --git a/rpcs3/Emu/RSX/VK/VKPresent.cpp b/rpcs3/Emu/RSX/VK/VKPresent.cpp index 5761a99120..f8345726ef 100644 --- a/rpcs3/Emu/RSX/VK/VKPresent.cpp +++ b/rpcs3/Emu/RSX/VK/VKPresent.cpp @@ -135,6 +135,7 @@ bool VKGSRender::reinitialize_swapchain() swapchain_unavailable = false; should_reinitialize_swapchain = false; + m_vsync_mode = g_cfg.video.vsync; return true; } @@ -425,6 +426,11 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info) } } + if (m_vsync_mode != g_cfg.video.vsync) + { + swapchain_unavailable = true; + } + if (swapchain_unavailable || should_reinitialize_swapchain) { // Reinitializing the swapchain is a failable operation. However, not all failures are fatal (e.g minimized window). diff --git a/rpcs3/Emu/RSX/VK/vkutils/swapchain.cpp b/rpcs3/Emu/RSX/VK/vkutils/swapchain.cpp index a296e393e2..fe4464cb03 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/swapchain.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/swapchain.cpp @@ -234,14 +234,19 @@ namespace vk VkPresentModeKHR swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR; std::vector preferred_modes; - if (!g_cfg.video.vk.force_fifo) + switch (g_cfg.video.vsync) { - // List of preferred modes in decreasing desirability - // NOTE: Always picks "triple-buffered vsync" types if possible - if (!g_cfg.video.vsync) - { - preferred_modes = { VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR }; - } + case vsync_mode::off: + preferred_modes = { VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR }; + break; + case vsync_mode::adaptive: + preferred_modes = { VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR }; + break; + case vsync_mode::full: + default: + // FIFO is guaranteed to be supported, no need to go through a preference chain + preferred_modes = {}; + break; } bool mode_found = false; diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 11c350215d..058eda2f12 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -127,6 +127,7 @@ struct cfg_root : cfg::node cfg::_enum antialiasing_level{ this, "MSAA", msaa_level::_auto }; cfg::_enum shadermode{ this, "Shader Mode", shader_mode::async_recompiler }; cfg::_enum shader_precision{ this, "Shader Precision", gpu_preset_level::high }; + cfg::_enum vsync{ this, "VSync Mode", vsync_mode::off, true }; cfg::_bool write_color_buffers{ this, "Write Color Buffers" }; cfg::_bool write_depth_buffer{ this, "Write Depth Buffer" }; @@ -134,7 +135,6 @@ struct cfg_root : cfg::node cfg::_bool read_depth_buffer{ this, "Read Depth Buffer" }; cfg::_bool handle_tiled_memory{ this, "Handle RSX Memory Tiling", false, true }; cfg::_bool log_programs{ this, "Log shader programs" }; - cfg::_bool vsync{ this, "VSync" }; cfg::_bool debug_output{ this, "Debug output" }; cfg::_bool debug_overlay{ this, "Debug overlay", false, true }; cfg::_bool renderdoc_compatiblity{ this, "Renderdoc Compatibility Mode" }; @@ -184,7 +184,6 @@ struct cfg_root : cfg::node node_vk(cfg::node* _this) : cfg::node(_this, "Vulkan") {} cfg::string adapter{ this, "Adapter" }; - cfg::_bool force_fifo{ this, "Force FIFO present mode" }; cfg::_bool force_primitive_restart{ this, "Force primitive restart flag" }; cfg::_enum exclusive_fullscreen_mode{ this, "Exclusive Fullscreen Mode", vk_exclusive_fs_mode::unspecified}; cfg::_bool asynchronous_texture_streaming{ this, "Asynchronous Texture Streaming", false }; diff --git a/rpcs3/Emu/system_config_types.cpp b/rpcs3/Emu/system_config_types.cpp index beb13db4d5..daefd49a42 100644 --- a/rpcs3/Emu/system_config_types.cpp +++ b/rpcs3/Emu/system_config_types.cpp @@ -734,3 +734,19 @@ void fmt_class_string::format(std::string& out, u64 arg) return unknown; }); } + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](vsync_mode value) + { + switch (value) + { + case vsync_mode::off: return "Disabled"; + case vsync_mode::adaptive: return "Adaptive"; + case vsync_mode::full: return "Full"; + } + + return unknown; + }); +} diff --git a/rpcs3/Emu/system_config_types.h b/rpcs3/Emu/system_config_types.h index d58e8664fa..f11e9caa3f 100644 --- a/rpcs3/Emu/system_config_types.h +++ b/rpcs3/Emu/system_config_types.h @@ -363,3 +363,10 @@ enum class xfloat_accuracy relaxed, // Approximate accuracy for only the "FCGT", "FNMS", "FREST" AND "FRSQEST" instructions inaccurate }; + +enum class vsync_mode +{ + off, + adaptive, + full, +}; diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 22cf9b4032..30860e10f5 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -1499,6 +1499,13 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ case xfloat_accuracy::inaccurate: return tr("Inaccurate XFloat"); } break; + case emu_settings_type::VSync: + switch (static_cast(index)) + { + case vsync_mode::off: return tr("Disabled", "VSync Mode"); + case vsync_mode::adaptive: return tr("Adaptive", "VSync Mode"); + case vsync_mode::full: return tr("Full", "VSync Mode"); + } default: break; } diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index a3e47fe733..fccc13a0dd 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -278,7 +278,7 @@ inline static const std::map settings_location { emu_settings_type::ReadColorBuffers, { "Video", "Read Color Buffers"}}, { emu_settings_type::ReadDepthBuffer, { "Video", "Read Depth Buffer"}}, { emu_settings_type::HandleRSXTiledMemory, { "Video", "Handle RSX Memory Tiling"}}, - { emu_settings_type::VSync, { "Video", "VSync"}}, + { emu_settings_type::VSync, { "Video", "VSync Mode"}}, { emu_settings_type::DebugOutput, { "Video", "Debug output"}}, { emu_settings_type::DebugOverlay, { "Video", "Debug overlay"}}, { emu_settings_type::RenderdocCompatibility, { "Video", "Renderdoc Compatibility Mode"}}, diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index d25d3ad9fb..19fee331fb 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -502,6 +502,9 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std } } + m_emu_settings->EnhanceComboBox(ui->vsyncMode, emu_settings_type::VSync); + SubscribeTooltip(ui->vsyncMode, tooltips.settings.vsync); + m_emu_settings->EnhanceComboBox(ui->antiAliasing, emu_settings_type::MSAA); SubscribeTooltip(ui->gb_antiAliasing, tooltips.settings.anti_aliasing); @@ -604,9 +607,6 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceCheckBox(ui->dumpColor, emu_settings_type::WriteColorBuffers); SubscribeTooltip(ui->dumpColor, tooltips.settings.dump_color); - m_emu_settings->EnhanceCheckBox(ui->vsync, emu_settings_type::VSync); - SubscribeTooltip(ui->vsync, tooltips.settings.vsync); - m_emu_settings->EnhanceCheckBox(ui->stretchToDisplayArea, emu_settings_type::StretchToDisplayArea); SubscribeTooltip(ui->stretchToDisplayArea, tooltips.settings.stretch_to_display_area); diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index 30100b7935..c5a5378f4c 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -511,6 +511,36 @@ + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Vsync + + + + + + + + + + + @@ -908,13 +938,6 @@ - - - - VSync - - - From 7a7e25f901255e65b379c4f7a19ce5a1f41a7263 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 18 Mar 2026 22:33:08 +0300 Subject: [PATCH 010/108] overlays: Add Vsync configuration to overlays --- rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp | 2 ++ rpcs3/Emu/localized_string_id.h | 1 + rpcs3/rpcs3qt/localized_emu.h | 1 + 3 files changed, 4 insertions(+) diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp index 71b78fe1d8..c732790fb7 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp @@ -152,6 +152,8 @@ namespace rsx home_menu_settings_video::home_menu_settings_video(s16 x, s16 y, u16 width, u16 height, bool use_separators, home_menu_page* parent) : home_menu_settings_page(x, y, width, height, use_separators, parent, get_localized_string(localized_string_id::HOME_MENU_SETTINGS_VIDEO)) { + add_dropdown(&g_cfg.video.vsync, localized_string_id::HOME_MENU_SETTINGS_VIDEO_VSYNC); + add_dropdown(&g_cfg.video.frame_limit, localized_string_id::HOME_MENU_SETTINGS_VIDEO_FRAME_LIMIT); add_unsigned_slider(&g_cfg.video.anisotropic_level_override, localized_string_id::HOME_MENU_SETTINGS_VIDEO_ANISOTROPIC_OVERRIDE, "x", 2, {{0, "Auto"}}, {14}); diff --git a/rpcs3/Emu/localized_string_id.h b/rpcs3/Emu/localized_string_id.h index b4ef2516fe..16aa47fc78 100644 --- a/rpcs3/Emu/localized_string_id.h +++ b/rpcs3/Emu/localized_string_id.h @@ -213,6 +213,7 @@ enum class localized_string_id HOME_MENU_SETTINGS_AUDIO_TIME_STRETCHING, HOME_MENU_SETTINGS_AUDIO_TIME_STRETCHING_THRESHOLD, HOME_MENU_SETTINGS_VIDEO, + HOME_MENU_SETTINGS_VIDEO_VSYNC, HOME_MENU_SETTINGS_VIDEO_FRAME_LIMIT, HOME_MENU_SETTINGS_VIDEO_ANISOTROPIC_OVERRIDE, HOME_MENU_SETTINGS_VIDEO_OUTPUT_SCALING, diff --git a/rpcs3/rpcs3qt/localized_emu.h b/rpcs3/rpcs3qt/localized_emu.h index 920f9d9d14..00563e04a8 100644 --- a/rpcs3/rpcs3qt/localized_emu.h +++ b/rpcs3/rpcs3qt/localized_emu.h @@ -234,6 +234,7 @@ private: case localized_string_id::HOME_MENU_SETTINGS_AUDIO_TIME_STRETCHING: return tr("Enable Time Stretching", "Audio"); case localized_string_id::HOME_MENU_SETTINGS_AUDIO_TIME_STRETCHING_THRESHOLD: return tr("Time Stretching Threshold", "Audio"); case localized_string_id::HOME_MENU_SETTINGS_VIDEO: return tr("Video"); + case localized_string_id::HOME_MENU_SETTINGS_VIDEO_VSYNC: return tr("VSync", "Video"); case localized_string_id::HOME_MENU_SETTINGS_VIDEO_FRAME_LIMIT: return tr("Frame Limit", "Video"); case localized_string_id::HOME_MENU_SETTINGS_VIDEO_ANISOTROPIC_OVERRIDE: return tr("Anisotropic Filter Override", "Video"); case localized_string_id::HOME_MENU_SETTINGS_VIDEO_OUTPUT_SCALING: return tr("Output Scaling", "Video"); From cb501e32ca1c1c55feef6fcf01016297aceb6d74 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 18 Mar 2026 22:37:14 +0300 Subject: [PATCH 011/108] gl: Make vsync setting dynamic --- rpcs3/Emu/RSX/GL/GLGSRender.cpp | 43 +++++++++++++++++++++------------ rpcs3/Emu/RSX/GL/GLGSRender.h | 2 ++ rpcs3/Emu/RSX/GL/GLPresent.cpp | 1 + 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index 973123fc82..20553eeb00 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -138,21 +138,7 @@ void GLGSRender::on_init_thread() gl::init(); gl::set_command_context(gl_state); - // Enable adaptive vsync if vsync is requested - int swap_interval = 0; - switch (g_cfg.video.vsync) - { - default: - case vsync_mode::off: - break; - case vsync_mode::adaptive: - swap_interval = -1; - break; - case vsync_mode::full: - swap_interval = 1; - break; - } - gl::set_swapinterval(swap_interval); + update_swap_interval(); if (g_cfg.video.debug_output) gl::enable_debugging(); @@ -593,6 +579,33 @@ void GLGSRender::on_exit() gl::set_primary_context_thread(false); } +void GLGSRender::update_swap_interval() +{ + const vsync_mode current_mode = g_cfg.video.vsync; + if (current_mode == m_vsync_mode) + { + return; + } + + // Enable adaptive vsync if vsync is requested + int swap_interval = 0; + switch (current_mode) + { + default: + case vsync_mode::off: + break; + case vsync_mode::adaptive: + swap_interval = -1; + break; + case vsync_mode::full: + swap_interval = 1; + break; + } + + gl::set_swapinterval(swap_interval); + m_vsync_mode = current_mode; +} + void GLGSRender::clear_surface(u32 arg) { if (skip_current_frame) return; diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.h b/rpcs3/Emu/RSX/GL/GLGSRender.h index a05eb0bf3d..779519fee7 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.h +++ b/rpcs3/Emu/RSX/GL/GLGSRender.h @@ -186,6 +186,8 @@ private: gl::texture* get_present_source(gl::present_surface_info* info, const rsx::avconf& avconfig); + void update_swap_interval(); + public: void set_viewport(); void set_scissor(bool clip_viewport); diff --git a/rpcs3/Emu/RSX/GL/GLPresent.cpp b/rpcs3/Emu/RSX/GL/GLPresent.cpp index 4c241d7d33..68a570e359 100644 --- a/rpcs3/Emu/RSX/GL/GLPresent.cpp +++ b/rpcs3/Emu/RSX/GL/GLPresent.cpp @@ -252,6 +252,7 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info) if (info.emu_flip) { evaluate_cpu_usage_reduction_limits(); + update_swap_interval(); } // Get window state From 43799762c74c44962f2df6ed6587357f6eb300ab Mon Sep 17 00:00:00 2001 From: kd-11 Date: Thu, 19 Mar 2026 00:42:46 +0300 Subject: [PATCH 012/108] qt: Update tooltips with new vsync behavior --- rpcs3/rpcs3qt/tooltips.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 6f07c531a2..3199d5cb4a 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -190,7 +190,7 @@ public: const QString resolution_scale = tr("Scales the game's resolution by the given percentage.\nThe base resolution is always 1280x720.\nSet this value to 100% if you want to use the normal Resolution options.\nValues below 100% will usually not improve performance."); const QString minimum_scalable_dimension = tr("Only framebuffers greater than this size will be upscaled.\nIncreasing this value might fix problems with missing graphics when upscaling, especially when Write Color Buffers is enabled.\nIf unsure, don't change this option."); const QString dump_color = tr("Enable this option if you get missing graphics or broken lighting ingame.\nMight degrade performance and introduce stuttering in some cases.\nRequired for Demon's Souls."); - const QString vsync = tr("By having this off you might obtain a higher framerate at the cost of tearing artifacts in the game."); + const QString vsync = tr("Enables vertical synchronization to eliminate tearing.\nAdaptive Mode - Prefers keeping up performance. It may skip frames or even tear to avoid reducing the game's framerate.\nFull Mode - No tearing allowed even if performance is reduced. This mode will by default limit your framerate to the display's refresh rate unless overriden in the driver control panel."); const QString strict_rendering_mode = tr("Enforces strict compliance to the API specification.\nMight result in degraded performance in some games.\nCan resolve rare cases of missing graphics and flickering.\nIf unsure, don't use this option."); const QString stretch_to_display_area = tr("Overrides the aspect ratio and stretches the image to the full display area."); const QString multithreaded_rsx = tr("Offloads some RSX operations to a secondary thread.\nImproves performance for high-core processors.\nMay cause slowdown in weaker CPUs due to the extra worker thread load."); From 2f3b66985e22ff89d21bc603bb430970879073b4 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Thu, 19 Mar 2026 11:41:27 +0100 Subject: [PATCH 013/108] Clean up code using mutable + const when possible --- rpcs3/Emu/Io/LogitechG27.cpp | 18 +++++++------- rpcs3/Emu/Io/LogitechG27.h | 12 +++++----- rpcs3/Emu/NP/np_cache.cpp | 28 ++++++++++++---------- rpcs3/Emu/NP/np_cache.h | 16 ++++++------- rpcs3/Emu/NP/np_contexts.cpp | 2 +- rpcs3/Emu/NP/np_contexts.h | 4 ++-- rpcs3/Emu/NP/np_gui_cache.cpp | 2 +- rpcs3/Emu/NP/np_gui_cache.h | 4 ++-- rpcs3/Emu/NP/np_handler.cpp | 4 ++-- rpcs3/Emu/NP/np_handler.h | 6 ++--- rpcs3/Emu/NP/rpcn_client.cpp | 12 +++++----- rpcs3/Emu/NP/rpcn_client.h | 16 ++++++------- rpcs3/Emu/NP/signaling_handler.cpp | 6 ++--- rpcs3/Emu/NP/signaling_handler.h | 8 +++---- rpcs3/Emu/RSX/Overlays/overlay_manager.cpp | 2 +- rpcs3/Emu/RSX/Overlays/overlay_manager.h | 6 ++--- 16 files changed, 74 insertions(+), 72 deletions(-) diff --git a/rpcs3/Emu/Io/LogitechG27.cpp b/rpcs3/Emu/Io/LogitechG27.cpp index 7ae996c864..3f07e92560 100644 --- a/rpcs3/Emu/Io/LogitechG27.cpp +++ b/rpcs3/Emu/Io/LogitechG27.cpp @@ -884,7 +884,7 @@ static s16 fetch_sdl_as_axis(SDL_Joystick* joystick, const sdl_mapping& mapping) return 0; } -static s16 fetch_sdl_axis_avg(std::map>& joysticks, const sdl_mapping& mapping) +static s16 fetch_sdl_axis_avg(const std::map>& joysticks, const sdl_mapping& mapping) { constexpr s16 MAX = 0x7FFF; constexpr s16 MIN = -0x8000; @@ -910,7 +910,7 @@ static s16 fetch_sdl_axis_avg(std::map>& joystic return std::clamp(sdl_joysticks_total_value / static_cast(joysticks_of_type->second.size()), MIN, MAX); } -static bool sdl_to_logitech_g27_button(std::map>& joysticks, const sdl_mapping& mapping) +static bool sdl_to_logitech_g27_button(const std::map>& joysticks, const sdl_mapping& mapping) { auto joysticks_of_type = joysticks.find(mapping.device_type_id); if (joysticks_of_type == joysticks.end()) @@ -931,21 +931,21 @@ static bool sdl_to_logitech_g27_button(std::map> return pressed; } -static u16 sdl_to_logitech_g27_steering(std::map>& joysticks, const sdl_mapping& mapping) +static u16 sdl_to_logitech_g27_steering(const std::map>& joysticks, const sdl_mapping& mapping) { const s16 avg = fetch_sdl_axis_avg(joysticks, mapping); const u16 unsigned_avg = avg + 0x8000; return unsigned_avg * (0xFFFF >> 2) / 0xFFFF; } -static u8 sdl_to_logitech_g27_pedal(std::map>& joysticks, const sdl_mapping& mapping) +static u8 sdl_to_logitech_g27_pedal(const std::map>& joysticks, const sdl_mapping& mapping) { const s16 avg = fetch_sdl_axis_avg(joysticks, mapping); const u16 unsigned_avg = avg + 0x8000; return unsigned_avg * 0xFF / 0xFFFF; } -void usb_device_logitech_g27::transfer_dfex(u32 buf_size, u8* buf, UsbTransfer* transfer) +void usb_device_logitech_g27::transfer_dfex(u32 buf_size, u8* buf, UsbTransfer* transfer) const { DFEX_data data{}; ensure(buf_size >= sizeof(data)); @@ -979,7 +979,7 @@ void usb_device_logitech_g27::transfer_dfex(u32 buf_size, u8* buf, UsbTransfer* std::memcpy(buf, &data, sizeof(data)); } -void usb_device_logitech_g27::transfer_dfp(u32 buf_size, u8* buf, UsbTransfer* transfer) +void usb_device_logitech_g27::transfer_dfp(u32 buf_size, u8* buf, UsbTransfer* transfer) const { DFP_data data{}; ensure(buf_size >= sizeof(data)); @@ -1015,7 +1015,7 @@ void usb_device_logitech_g27::transfer_dfp(u32 buf_size, u8* buf, UsbTransfer* t std::memcpy(buf, &data, sizeof(data)); } -void usb_device_logitech_g27::transfer_dfgt(u32 buf_size, u8* buf, UsbTransfer* transfer) +void usb_device_logitech_g27::transfer_dfgt(u32 buf_size, u8* buf, UsbTransfer* transfer) const { DFGT_data data{}; ensure(buf_size >= sizeof(data)); @@ -1057,7 +1057,7 @@ void usb_device_logitech_g27::transfer_dfgt(u32 buf_size, u8* buf, UsbTransfer* std::memcpy(buf, &data, sizeof(data)); } -void usb_device_logitech_g27::transfer_g25(u32 buf_size, u8* buf, UsbTransfer* transfer) +void usb_device_logitech_g27::transfer_g25(u32 buf_size, u8* buf, UsbTransfer* transfer) const { G25_data data{}; ensure(buf_size >= sizeof(data)); @@ -1105,7 +1105,7 @@ void usb_device_logitech_g27::transfer_g25(u32 buf_size, u8* buf, UsbTransfer* t std::memcpy(buf, &data, sizeof(data)); } -void usb_device_logitech_g27::transfer_g27(u32 buf_size, u8* buf, UsbTransfer* transfer) +void usb_device_logitech_g27::transfer_g27(u32 buf_size, u8* buf, UsbTransfer* transfer) const { G27_data data{}; ensure(buf_size >= sizeof(data)); diff --git a/rpcs3/Emu/Io/LogitechG27.h b/rpcs3/Emu/Io/LogitechG27.h index dc68db68b9..ccb2f58908 100644 --- a/rpcs3/Emu/Io/LogitechG27.h +++ b/rpcs3/Emu/Io/LogitechG27.h @@ -121,11 +121,11 @@ public: private: void sdl_refresh(); void set_personality(logitech_personality personality, bool reconnect = false); - void transfer_dfex(u32 buf_size, u8* buf, UsbTransfer* transfer); - void transfer_dfp(u32 buf_size, u8* buf, UsbTransfer* transfer); - void transfer_dfgt(u32 buf_size, u8* buf, UsbTransfer* transfer); - void transfer_g25(u32 buf_size, u8* buf, UsbTransfer* transfer); - void transfer_g27(u32 buf_size, u8* buf, UsbTransfer* transfer); + void transfer_dfex(u32 buf_size, u8* buf, UsbTransfer* transfer) const; + void transfer_dfp(u32 buf_size, u8* buf, UsbTransfer* transfer) const; + void transfer_dfgt(u32 buf_size, u8* buf, UsbTransfer* transfer) const; + void transfer_g25(u32 buf_size, u8* buf, UsbTransfer* transfer) const; + void transfer_g27(u32 buf_size, u8* buf, UsbTransfer* transfer) const; u32 m_controller_index = 0; @@ -134,7 +134,7 @@ private: logitech_g27_sdl_mapping m_mapping {}; bool m_reverse_effects = false; - std::mutex m_sdl_handles_mutex; + mutable std::mutex m_sdl_handles_mutex; SDL_Joystick* m_led_joystick_handle = nullptr; SDL_Haptic* m_haptic_handle = nullptr; std::map> m_joysticks; diff --git a/rpcs3/Emu/NP/np_cache.cpp b/rpcs3/Emu/NP/np_cache.cpp index 4731e0cede..4d70cacaa0 100644 --- a/rpcs3/Emu/NP/np_cache.cpp +++ b/rpcs3/Emu/NP/np_cache.cpp @@ -125,7 +125,7 @@ namespace np rooms[room_id].opt_param = *sce_opt_param; } - std::pair> cache_manager::get_slots(SceNpMatching2RoomId room_id) + std::pair> cache_manager::get_slots(SceNpMatching2RoomId room_id) const { std::lock_guard lock(mutex); @@ -134,7 +134,7 @@ namespace np return {SCE_NP_MATCHING2_ERROR_ROOM_NOT_FOUND, {}}; } - const auto& room = rooms[room_id]; + const auto& room = ::at32(rooms, room_id); SceNpMatching2RoomSlotInfo slots{}; @@ -166,7 +166,7 @@ namespace np return {CELL_OK, slots}; } - std::pair> cache_manager::get_memberids(u64 room_id, s32 sort_method) + std::pair> cache_manager::get_memberids(u64 room_id, s32 sort_method) const { std::lock_guard lock(mutex); @@ -175,7 +175,7 @@ namespace np return {SCE_NP_MATCHING2_ERROR_ROOM_NOT_FOUND, {}}; } - const auto& room = rooms[room_id]; + const auto& room = ::at32(rooms, room_id); std::vector vec_memberids; @@ -211,7 +211,7 @@ namespace np return {CELL_OK, vec_memberids}; } - std::pair> cache_manager::get_password(SceNpMatching2RoomId room_id) + std::pair> cache_manager::get_password(SceNpMatching2RoomId room_id) const { std::lock_guard lock(mutex); @@ -220,15 +220,17 @@ namespace np return {SCE_NP_MATCHING2_ERROR_ROOM_NOT_FOUND, {}}; } - if (!rooms[room_id].owner) + const auto& room = ::at32(rooms, room_id); + + if (!room.owner) { return {SCE_NP_MATCHING2_ERROR_NOT_ALLOWED, {}}; } - return {CELL_OK, rooms[room_id].password}; + return {CELL_OK, room.password}; } - std::pair> cache_manager::get_opt_param(SceNpMatching2RoomId room_id) + std::pair> cache_manager::get_opt_param(SceNpMatching2RoomId room_id) const { std::lock_guard lock(mutex); @@ -237,10 +239,10 @@ namespace np return {SCE_NP_MATCHING2_ERROR_ROOM_NOT_FOUND, {}}; } - return {CELL_OK, rooms[room_id].opt_param}; + return {CELL_OK, ::at32(rooms, room_id).opt_param}; } - error_code cache_manager::get_member_and_attrs(SceNpMatching2RoomId room_id, SceNpMatching2RoomMemberId member_id, const std::vector& binattrs_list, SceNpMatching2RoomMemberDataInternal* ptr_member, u32 addr_data, u32 size_data, bool include_onlinename, bool include_avatarurl) + error_code cache_manager::get_member_and_attrs(SceNpMatching2RoomId room_id, SceNpMatching2RoomMemberId member_id, const std::vector& binattrs_list, SceNpMatching2RoomMemberDataInternal* ptr_member, u32 addr_data, u32 size_data, bool include_onlinename, bool include_avatarurl) const { std::lock_guard lock(mutex); @@ -249,7 +251,7 @@ namespace np return SCE_NP_MATCHING2_ERROR_ROOM_NOT_FOUND; } - if (!rooms[room_id].members.contains(member_id)) + if (!::at32(rooms, room_id).members.contains(member_id)) { return SCE_NP_MATCHING2_ERROR_ROOM_MEMBER_NOT_FOUND; } @@ -352,7 +354,7 @@ namespace np return not_an_error(needed_data_size); } - std::pair> cache_manager::get_npid(u64 room_id, u16 member_id) + std::pair> cache_manager::get_npid(u64 room_id, u16 member_id) const { std::lock_guard lock(mutex); @@ -371,7 +373,7 @@ namespace np return {CELL_OK, ::at32(::at32(rooms, room_id).members, member_id).userInfo.npId}; } - std::optional cache_manager::get_memberid(u64 room_id, const SceNpId& npid) + std::optional cache_manager::get_memberid(u64 room_id, const SceNpId& npid) const { std::lock_guard lock(mutex); diff --git a/rpcs3/Emu/NP/np_cache.h b/rpcs3/Emu/NP/np_cache.h index 8870f169a8..bd0bd8a736 100644 --- a/rpcs3/Emu/NP/np_cache.h +++ b/rpcs3/Emu/NP/np_cache.h @@ -74,16 +74,16 @@ namespace np void update_password(SceNpMatching2RoomId room_id, const std::optional& password); void update_opt_param(SceNpMatching2RoomId room_id, const SceNpMatching2SignalingOptParam* sce_opt_param); - std::pair> get_slots(SceNpMatching2RoomId room_id); - std::pair> get_memberids(u64 room_id, s32 sort_method); - std::pair> get_password(SceNpMatching2RoomId room_id); - std::pair> get_opt_param(SceNpMatching2RoomId room_id); - error_code get_member_and_attrs(SceNpMatching2RoomId room_id, SceNpMatching2RoomMemberId member_id, const std::vector& binattrs_list, SceNpMatching2RoomMemberDataInternal* ptr_member, u32 addr_data, u32 size_data, bool include_onlinename, bool include_avatarurl); - std::pair> get_npid(u64 room_id, u16 member_id); - std::optional get_memberid(u64 room_id, const SceNpId& npid); + std::pair> get_slots(SceNpMatching2RoomId room_id) const; + std::pair> get_memberids(u64 room_id, s32 sort_method) const; + std::pair> get_password(SceNpMatching2RoomId room_id) const; + std::pair> get_opt_param(SceNpMatching2RoomId room_id) const; + error_code get_member_and_attrs(SceNpMatching2RoomId room_id, SceNpMatching2RoomMemberId member_id, const std::vector& binattrs_list, SceNpMatching2RoomMemberDataInternal* ptr_member, u32 addr_data, u32 size_data, bool include_onlinename, bool include_avatarurl) const; + std::pair> get_npid(u64 room_id, u16 member_id) const; + std::optional get_memberid(u64 room_id, const SceNpId& npid) const; private: - shared_mutex mutex; + mutable shared_mutex mutex; std::map rooms; }; } // namespace np diff --git a/rpcs3/Emu/NP/np_contexts.cpp b/rpcs3/Emu/NP/np_contexts.cpp index f43d02c362..739a46368b 100644 --- a/rpcs3/Emu/NP/np_contexts.cpp +++ b/rpcs3/Emu/NP/np_contexts.cpp @@ -25,7 +25,7 @@ generic_async_transaction_context::~generic_async_transaction_context() } } -std::optional generic_async_transaction_context::get_transaction_status() +std::optional generic_async_transaction_context::get_transaction_status() const { std::lock_guard lock(mutex); return result; diff --git a/rpcs3/Emu/NP/np_contexts.h b/rpcs3/Emu/NP/np_contexts.h index 20e123730b..98ed36fa0a 100644 --- a/rpcs3/Emu/NP/np_contexts.h +++ b/rpcs3/Emu/NP/np_contexts.h @@ -20,12 +20,12 @@ struct generic_async_transaction_context generic_async_transaction_context(const SceNpCommunicationId& communicationId, const SceNpCommunicationPassphrase& passphrase, u64 timeout); - std::optional get_transaction_status(); + std::optional get_transaction_status() const; void abort_transaction(); error_code wait_for_completion(); void set_result_and_wake(error_code err); - shared_mutex mutex; + mutable shared_mutex mutex; std::condition_variable_any wake_cond, completion_cond; std::optional result; SceNpCommunicationId communicationId; diff --git a/rpcs3/Emu/NP/np_gui_cache.cpp b/rpcs3/Emu/NP/np_gui_cache.cpp index fee08356d5..1b85275d24 100644 --- a/rpcs3/Emu/NP/np_gui_cache.cpp +++ b/rpcs3/Emu/NP/np_gui_cache.cpp @@ -63,7 +63,7 @@ namespace np np_gui_cache.error("Cache mismatch: tried to remove a member but it wasn't in the room"); } - error_code gui_cache_manager::get_room_member_list(const SceNpRoomId& room_id, u32 buf_len, vm::ptr data) + error_code gui_cache_manager::get_room_member_list(const SceNpRoomId& room_id, u32 buf_len, vm::ptr data) const { std::lock_guard lock(mutex); diff --git a/rpcs3/Emu/NP/np_gui_cache.h b/rpcs3/Emu/NP/np_gui_cache.h index d742cc5749..431259df6b 100644 --- a/rpcs3/Emu/NP/np_gui_cache.h +++ b/rpcs3/Emu/NP/np_gui_cache.h @@ -45,10 +45,10 @@ namespace np void add_member(const SceNpRoomId& room_id, const SceNpMatchingRoomMember* user_info, bool new_member); void del_member(const SceNpRoomId& room_id, const SceNpMatchingRoomMember* user_info); - error_code get_room_member_list(const SceNpRoomId& room_id, u32 buf_len, vm::ptr data); + error_code get_room_member_list(const SceNpRoomId& room_id, u32 buf_len, vm::ptr data) const; private: - shared_mutex mutex; + mutable shared_mutex mutex; std::map rooms; }; } // namespace np diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index f1fda0752c..1686327fb3 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -1441,7 +1441,7 @@ namespace np return req_id; } - u32 np_handler::get_players_history_count(u32 options) + u32 np_handler::get_players_history_count(u32 options) const { const bool all_history = (options == SCE_NP_BASIC_PLAYERS_HISTORY_OPTIONS_ALL); @@ -1459,7 +1459,7 @@ namespace np })); } - bool np_handler::get_player_history_entry(u32 options, u32 index, SceNpId* npid) + bool np_handler::get_player_history_entry(u32 options, u32 index, SceNpId* npid) const { const bool all_history = (options == SCE_NP_BASIC_PLAYERS_HISTORY_OPTIONS_ALL); diff --git a/rpcs3/Emu/NP/np_handler.h b/rpcs3/Emu/NP/np_handler.h index 62b69a73ac..7e43bbf3cd 100644 --- a/rpcs3/Emu/NP/np_handler.h +++ b/rpcs3/Emu/NP/np_handler.h @@ -261,8 +261,8 @@ namespace np ticket get_clan_ticket() const; void add_player_to_history(const SceNpId* npid, const char* description); u32 add_players_to_history(const SceNpId* npids, const char* description, u32 count); - u32 get_players_history_count(u32 options); - bool get_player_history_entry(u32 options, u32 index, SceNpId* npid); + u32 get_players_history_count(u32 options) const; + bool get_player_history_entry(u32 options, u32 index, SceNpId* npid) const; SceNpMatching2MemoryInfo get_memory_info() const; error_code abort_request(u32 req_id); @@ -518,7 +518,7 @@ namespace np player_history& get_player_and_set_timestamp(const SceNpId& npid, u64 timestamp); void save_players_history(); - shared_mutex mutex_history; + mutable shared_mutex mutex_history; std::map players_history; // npid / history struct diff --git a/rpcs3/Emu/NP/rpcn_client.cpp b/rpcs3/Emu/NP/rpcn_client.cpp index 56cfabd855..96ab505abf 100644 --- a/rpcs3/Emu/NP/rpcn_client.cpp +++ b/rpcs3/Emu/NP/rpcn_client.cpp @@ -3180,7 +3180,7 @@ namespace rpcn } } - std::optional>> rpcn_client::get_message(u64 id) + std::optional>> rpcn_client::get_message(u64 id) const { { std::lock_guard lock(mutex_messages); @@ -3238,21 +3238,21 @@ namespace rpcn active_messages.erase(id); } - u32 rpcn_client::get_num_friends() + u32 rpcn_client::get_num_friends() const { std::lock_guard lock(mutex_friends); return ::size32(friend_infos.friends); } - u32 rpcn_client::get_num_blocks() + u32 rpcn_client::get_num_blocks() const { std::lock_guard lock(mutex_friends); return ::size32(friend_infos.blocked); } - std::optional rpcn_client::get_friend_by_index(u32 index) + std::optional rpcn_client::get_friend_by_index(u32 index) const { std::lock_guard lock(mutex_friends); @@ -3270,7 +3270,7 @@ namespace rpcn return it->first; } - std::optional> rpcn_client::get_friend_presence_by_index(u32 index) + std::optional> rpcn_client::get_friend_presence_by_index(u32 index) const { std::lock_guard lock(mutex_friends); @@ -3284,7 +3284,7 @@ namespace rpcn return std::optional(*it); } - std::optional> rpcn_client::get_friend_presence_by_npid(const std::string& npid) + std::optional> rpcn_client::get_friend_presence_by_npid(const std::string& npid) const { std::lock_guard lock(mutex_friends); const auto it = friend_infos.friends.find(npid); diff --git a/rpcs3/Emu/NP/rpcn_client.h b/rpcs3/Emu/NP/rpcn_client.h index 377cd9c898..daa60d90e7 100644 --- a/rpcs3/Emu/NP/rpcn_client.h +++ b/rpcs3/Emu/NP/rpcn_client.h @@ -242,7 +242,7 @@ namespace rpcn std::mutex mutex_packets_to_send; // Friends related - shared_mutex mutex_friends; + mutable shared_mutex mutex_friends; std::set> friend_cbs; friend_data friend_infos; @@ -304,11 +304,11 @@ namespace rpcn std::optional add_friend(const std::string& friend_username); bool remove_friend(const std::string& friend_username); - u32 get_num_friends(); - u32 get_num_blocks(); - std::optional get_friend_by_index(u32 index); - std::optional> get_friend_presence_by_index(u32 index); - std::optional> get_friend_presence_by_npid(const std::string& npid); + u32 get_num_friends() const; + u32 get_num_blocks() const; + std::optional get_friend_by_index(u32 index) const; + std::optional> get_friend_presence_by_index(u32 index) const; + std::optional> get_friend_presence_by_npid(const std::string& npid) const; std::vector>> get_notifications(); std::map>> get_replies(); @@ -316,7 +316,7 @@ namespace rpcn std::map get_presence_states(); std::vector get_new_messages(); - std::optional>> get_message(u64 id); + std::optional>> get_message(u64 id) const; std::vector>>> get_messages_and_register_cb(SceNpBasicMessageMainType type, bool include_bootable, message_cb_func cb_func, void* cb_param); void remove_message_cb(message_cb_func cb_func, void* cb_param); void mark_message_used(u64 id); @@ -445,7 +445,7 @@ namespace rpcn return (void_cb_func < void_other_cb_func) || ((!(void_other_cb_func < void_cb_func)) && (cb_param < other.cb_param)); } }; - shared_mutex mutex_messages; + mutable shared_mutex mutex_messages; std::set message_cbs; std::unordered_map>> messages; // msg id / (sender / message) std::set active_messages; // msg id of messages that have not been discarded diff --git a/rpcs3/Emu/NP/signaling_handler.cpp b/rpcs3/Emu/NP/signaling_handler.cpp index 85c9321e28..a4c59763cb 100644 --- a/rpcs3/Emu/NP/signaling_handler.cpp +++ b/rpcs3/Emu/NP/signaling_handler.cpp @@ -832,7 +832,7 @@ u32 signaling_handler::init_sig2(const SceNpId& npid, u64 room_id, u16 member_id return conn_id; } -std::optional signaling_handler::get_conn_id_from_npid(const SceNpId& npid) +std::optional signaling_handler::get_conn_id_from_npid(const SceNpId& npid) const { std::lock_guard lock(data_mutex); @@ -843,7 +843,7 @@ std::optional signaling_handler::get_conn_id_from_npid(const SceNpId& npid) return std::nullopt; } -std::optional signaling_handler::get_sig_infos(u32 conn_id) +std::optional signaling_handler::get_sig_infos(u32 conn_id) const { std::lock_guard lock(data_mutex); if (sig_peers.contains(conn_id)) @@ -852,7 +852,7 @@ std::optional signaling_handler::get_sig_infos(u32 conn_id) return std::nullopt; } -std::optional signaling_handler::get_conn_id_from_addr(u32 addr, u16 port) +std::optional signaling_handler::get_conn_id_from_addr(u32 addr, u16 port) const { std::lock_guard lock(data_mutex); diff --git a/rpcs3/Emu/NP/signaling_handler.h b/rpcs3/Emu/NP/signaling_handler.h index a3eb027fe1..c97c049d36 100644 --- a/rpcs3/Emu/NP/signaling_handler.h +++ b/rpcs3/Emu/NP/signaling_handler.h @@ -63,9 +63,9 @@ public: u32 init_sig1(const SceNpId& npid); u32 init_sig2(const SceNpId& npid, u64 room_id, u16 member_id); - std::optional get_sig_infos(u32 conn_id); - std::optional get_conn_id_from_npid(const SceNpId& npid); - std::optional get_conn_id_from_addr(u32 addr, u16 port); + std::optional get_sig_infos(u32 conn_id) const; + std::optional get_conn_id_from_npid(const SceNpId& npid) const; + std::optional get_conn_id_from_addr(u32 addr, u16 port) const; void add_sig_ctx(u32 ctx_id); void remove_sig_ctx(u32 ctx_id); @@ -128,7 +128,7 @@ private: void retire_all_packets(std::shared_ptr& si); void stop_sig_nl(u32 conn_id, bool forceful); - shared_mutex data_mutex; + mutable shared_mutex data_mutex; atomic_t wakey = 0; signaling_packet sig_packet{}; diff --git a/rpcs3/Emu/RSX/Overlays/overlay_manager.cpp b/rpcs3/Emu/RSX/Overlays/overlay_manager.cpp index 9ffa9b14e3..70f933fbaa 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_manager.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_manager.cpp @@ -68,7 +68,7 @@ namespace rsx m_list_mutex.unlock_shared(); } - std::shared_ptr display_manager::get(u32 uid) + std::shared_ptr display_manager::get(u32 uid) const { reader_lock lock(m_list_mutex); diff --git a/rpcs3/Emu/RSX/Overlays/overlay_manager.h b/rpcs3/Emu/RSX/Overlays/overlay_manager.h index e42f3721b3..be244b1997 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_manager.h +++ b/rpcs3/Emu/RSX/Overlays/overlay_manager.h @@ -21,7 +21,7 @@ namespace rsx std::vector> m_iface_list; std::vector> m_dirty_list; - shared_mutex m_list_mutex; + mutable shared_mutex m_list_mutex; lf_queue m_uids_to_remove; lf_queue m_type_ids_to_remove; atomic_t m_pending_removals_count = 0; @@ -130,11 +130,11 @@ namespace rsx void dispose(const std::vector& uids); // Returns pointer to the object matching the given uid - std::shared_ptr get(u32 uid); + std::shared_ptr get(u32 uid) const; // Returns pointer to the first object matching the given type template - std::shared_ptr get() + std::shared_ptr get() const { reader_lock lock(m_list_mutex); From d54e54b66ddc4675d2eed9cad6bac408fc333ef0 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Thu, 19 Mar 2026 01:35:05 +0300 Subject: [PATCH 014/108] rsx/cfg: Handle IF-ELSE aliasing statement as inverted condition - If there is no IF block but ELSE block exists, treat the branch as being inverted and the ELSE block as the main IF body - Rare but seen in some games --- rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp | 8 ++++++++ rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h | 3 +++ rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp | 10 +++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp index c195b4a75b..d00a104e8f 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp @@ -459,4 +459,12 @@ namespace rsx::assembler::FP return result; } + + // Invert execution mask on an instruction + void invert_conditional_execution_mask(Instruction* instruction) + { + // We want to invert src0.exec_if_gt|lt|eq which should be at bit offset 18-20 + constexpr u32 inv_mask = (0b111 << 18u); + instruction->bytecode[1] = instruction->bytecode[1] ^ inv_mask; + } } diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h index a29b22c842..636ac06082 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h +++ b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h @@ -131,5 +131,8 @@ namespace rsx::assembler // Compile a register file annotated blob to register references std::vector compile_register_file(const std::array& file); + + // Invert execution mask on an instruction + void invert_conditional_execution_mask(Instruction* instruction); } } diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp index 99e7c56f6e..954954b377 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "CFG.h" +#include "FPOpcodes.h" #include "Emu/RSX/Common/simple_array.hpp" #include "Emu/RSX/Program/RSXFragmentProgram.h" @@ -211,7 +212,14 @@ namespace rsx::assembler auto parent = bb; bb = safe_insert_block(parent, pc + 1u, EdgeType::IF); - if (end_addr != else_addr) + + if (else_addr == pc + 1u) + { + // Empty IF block. We co-opt the ELSE block as the IF and invert the condition. + auto& inst = parent->instructions.back(); + FP::invert_conditional_execution_mask(&inst); + } + else if (end_addr != else_addr) { else_blocks.push_back(safe_insert_block(parent, else_addr, EdgeType::ELSE)); } From 3c68c36fa0ba2ca4397cc5e3f11bfb149afc0dea Mon Sep 17 00:00:00 2001 From: kd-11 Date: Thu, 19 Mar 2026 03:06:19 +0300 Subject: [PATCH 015/108] rsx/cfg: Log a warning when encountering empty IF blocks with ELSE aliasing - Hardware tests confirm this is correct behavior, the condition just inverts --- rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp index 954954b377..c6d092c92a 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp @@ -218,6 +218,7 @@ namespace rsx::assembler // Empty IF block. We co-opt the ELSE block as the IF and invert the condition. auto& inst = parent->instructions.back(); FP::invert_conditional_execution_mask(&inst); + rsx_log.warning("CFG: Condition at L%u was inverted to cover empty IF block."); } else if (end_addr != else_addr) { From ddd226f0ea1d365d5919c647216b0596c1c7fea4 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Thu, 19 Mar 2026 03:26:58 +0300 Subject: [PATCH 016/108] rsx/fp/cfg: Add unit test for special cond inversion --- rpcs3/tests/test_rsx_cfg.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/rpcs3/tests/test_rsx_cfg.cpp b/rpcs3/tests/test_rsx_cfg.cpp index ded749fd24..a20b3a7d0a 100644 --- a/rpcs3/tests/test_rsx_cfg.cpp +++ b/rpcs3/tests/test_rsx_cfg.cpp @@ -228,4 +228,27 @@ namespace rsx::assembler ASSERT_EQ(graph.blocks.size(), 1); EXPECT_EQ(graph.blocks.front().instructions.size(), 1); } + + TEST(CFG, FpToCFG_EmptyIFWithELSE) + { + auto ir = FPIR::from_source( + "IF.LT;" // Empty branch + "ELSE;" // With real ELSE + " MOV R1, R2;" // Content. Should execute if branch cond fails (IF.GE) + "ENDIF;" + "MOV R0, R1;" // False merge block. + ); + + RSXFragmentProgram program{}; + auto bytecode = ir.compile(); + program.data = bytecode.data(); + + FlowGraph graph = deconstruct_fragment_program(program); + + ASSERT_EQ(graph.blocks.size(), 3); + ASSERT_EQ(graph.blocks.front().instructions.size(), 1); + EXPECT_EQ(SRC0{ .HEX = graph.blocks.front().instructions[0].bytecode[1] }.exec_if_lt, 0); + EXPECT_EQ(SRC0{ .HEX = graph.blocks.front().instructions[0].bytecode[1] }.exec_if_gr, 1); + EXPECT_EQ(SRC0{ .HEX = graph.blocks.front().instructions[0].bytecode[1] }.exec_if_eq, 1); + } } From b73c45f1fb7183297a772a79ac26556784abb481 Mon Sep 17 00:00:00 2001 From: Ani Date: Wed, 18 Mar 2026 23:41:50 +0100 Subject: [PATCH 017/108] overlay: Add 8px left margin on the sidebar --- rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp index 9d239355c9..8f27e4172d 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp @@ -194,6 +194,7 @@ namespace rsx label_widget->set_size(m_sidebar->w, 60); label_widget->set_font("Arial", 16); label_widget->back_color.a = 0.f; + label_widget->set_margin(8, 0); label_widget->set_padding(16, 4, 16, 4); label_widget->auto_resize(); label_widget->set_size(label_widget->w, 60); @@ -213,6 +214,7 @@ namespace rsx auto icon_view = std::make_unique(); icon_view->set_raw_image(icon_info); icon_view->set_size(42, 60); + icon_view->set_margin(8, 0); icon_view->set_padding(18, 0, 18, 18); const u16 packed_width = icon_view->padding_left + icon_view->w + label_widget->w + 18; // rpad From 84277f41a117c5564715ff859710b4662c2fc88c Mon Sep 17 00:00:00 2001 From: schm1dtmac Date: Thu, 19 Mar 2026 18:38:55 +0000 Subject: [PATCH 018/108] Fix mac builds (& add some failsafes) --- .ci/build-mac.sh | 7 +++---- .ci/deploy-mac.sh | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.ci/build-mac.sh b/.ci/build-mac.sh index f386866264..8e33a09d72 100755 --- a/.ci/build-mac.sh +++ b/.ci/build-mac.sh @@ -16,12 +16,12 @@ export HOMEBREW_NO_INSTALL_CLEANUP=1 brew install -f --overwrite --quiet ccache "llvm@$LLVM_COMPILER_VER" brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" if [ "$AARCH64" -eq 1 ]; then - brew install -f --overwrite --quiet googletest opencv@4 sdl3 vulkan-headers vulkan-loader molten-vk - brew unlink --quiet ffmpeg fmt qtbase qtsvg qtdeclarative + brew install -f --overwrite --quiet googletest opencv@4 sdl3 vulkan-headers vulkan-loader molten-vk + brew unlink --quiet ffmpeg fmt qtbase qtsvg qtdeclarative protobuf || true else arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet python@3.14 opencv@4 "llvm@$LLVM_COMPILER_VER" sdl3 vulkan-headers vulkan-loader molten-vk - arch -x86_64 /usr/local/bin/brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative + arch -x86_64 /usr/local/bin/brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative protobuf || true fi export CXX=clang++ @@ -122,7 +122,6 @@ cmake .. \ -DUSE_SYSTEM_MVK=ON \ -DUSE_SYSTEM_SDL=ON \ -DUSE_SYSTEM_OPENCV=ON \ - -DUSE_SYSTEM_PROTOBUF=ON \ -G Ninja fi diff --git a/.ci/deploy-mac.sh b/.ci/deploy-mac.sh index 70db614b59..819e0725bc 100755 --- a/.ci/deploy-mac.sh +++ b/.ci/deploy-mac.sh @@ -21,7 +21,7 @@ rm -rf "rpcs3.app/Contents/Frameworks/QtPdf.framework" \ "rpcs3.app/Contents/Frameworks/QtVirtualKeyboard.framework" \ "rpcs3.app/Contents/Plugins/platforminputcontexts" \ "rpcs3.app/Contents/Plugins/virtualkeyboard" \ -"rpcs3.app/Contents/Resources/git" +"rpcs3.app/Contents/Resources/git" || true ../../.ci/optimize-mac.sh rpcs3.app @@ -49,7 +49,7 @@ QT_TRANS="$WORKDIR/qt-downloader/$QT_VER/clang_64/translations" cp $QT_TRANS/qt_*.qm rpcs3.app/Contents/translations cp $QT_TRANS/qtbase_*.qm rpcs3.app/Contents/translations cp $QT_TRANS/qtmultimedia_*.qm rpcs3.app/Contents/translations -rm -f rpcs3.app/Contents/translations/qt_help_*.qm +rm -f rpcs3.app/Contents/translations/qt_help_*.qm || true # Need to do this rename hack due to case insensitive filesystem mv rpcs3.app RPCS3_.app From 6590f1b55d8681d87665e9a8165ea3c7af002237 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Thu, 19 Mar 2026 23:03:25 +0300 Subject: [PATCH 019/108] overlays: Add RSX memory tiling to settings page --- rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp | 1 + rpcs3/Emu/localized_string_id.h | 1 + rpcs3/rpcs3qt/localized_emu.h | 1 + 3 files changed, 3 insertions(+) diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp index c732790fb7..3d32aaf844 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp @@ -185,6 +185,7 @@ namespace rsx add_unsigned_slider(&g_cfg.video.driver_wakeup_delay, localized_string_id::HOME_MENU_SETTINGS_ADVANCED_DRIVER_WAKE_UP_DELAY, " µs", 20, {}, {}, g_cfg.video.driver_wakeup_delay.min, 800); add_signed_slider(&g_cfg.video.vblank_rate, localized_string_id::HOME_MENU_SETTINGS_ADVANCED_VBLANK_FREQUENCY, " Hz", 30, {}, 30); add_checkbox(&g_cfg.video.vblank_ntsc, localized_string_id::HOME_MENU_SETTINGS_ADVANCED_VBLANK_NTSC); + add_checkbox(&g_cfg.video.handle_tiled_memory, localized_string_id::HOME_MENU_SETTINGS_ADVANCED_RSX_MEMORY_TILING); apply_layout(); } diff --git a/rpcs3/Emu/localized_string_id.h b/rpcs3/Emu/localized_string_id.h index 16aa47fc78..9eae28c832 100644 --- a/rpcs3/Emu/localized_string_id.h +++ b/rpcs3/Emu/localized_string_id.h @@ -234,6 +234,7 @@ enum class localized_string_id HOME_MENU_SETTINGS_ADVANCED_MAX_CPU_PREEMPTIONS, HOME_MENU_SETTINGS_ADVANCED_ACCURATE_RSX_RESERVATION_ACCESS, HOME_MENU_SETTINGS_ADVANCED_SLEEP_TIMERS_ACCURACY, + HOME_MENU_SETTINGS_ADVANCED_RSX_MEMORY_TILING, HOME_MENU_SETTINGS_ADVANCED_MAX_SPURS_THREADS, HOME_MENU_SETTINGS_ADVANCED_DRIVER_WAKE_UP_DELAY, HOME_MENU_SETTINGS_ADVANCED_VBLANK_FREQUENCY, diff --git a/rpcs3/rpcs3qt/localized_emu.h b/rpcs3/rpcs3qt/localized_emu.h index 00563e04a8..11a404d5db 100644 --- a/rpcs3/rpcs3qt/localized_emu.h +++ b/rpcs3/rpcs3qt/localized_emu.h @@ -255,6 +255,7 @@ private: case localized_string_id::HOME_MENU_SETTINGS_ADVANCED_MAX_CPU_PREEMPTIONS: return tr("Max Power Saving CPU-Preemptions", "Advanced"); case localized_string_id::HOME_MENU_SETTINGS_ADVANCED_ACCURATE_RSX_RESERVATION_ACCESS: return tr("Accurate RSX reservation access", "Advanced"); case localized_string_id::HOME_MENU_SETTINGS_ADVANCED_SLEEP_TIMERS_ACCURACY: return tr("Sleep Timers Accuracy", "Advanced"); + case localized_string_id::HOME_MENU_SETTINGS_ADVANCED_RSX_MEMORY_TILING: return tr("Handle RSX Memory Tiling", "Advanced"); case localized_string_id::HOME_MENU_SETTINGS_ADVANCED_MAX_SPURS_THREADS: return tr("Max SPURS Threads", "Advanced"); case localized_string_id::HOME_MENU_SETTINGS_ADVANCED_DRIVER_WAKE_UP_DELAY: return tr("Driver Wake-Up Delay", "Advanced"); case localized_string_id::HOME_MENU_SETTINGS_ADVANCED_VBLANK_FREQUENCY: return tr("VBlank Frequency", "Advanced"); From 852317a071cdb2f5fa6e0b58d84d99ef0f8d673f Mon Sep 17 00:00:00 2001 From: kd-11 Date: Thu, 19 Mar 2026 23:24:20 +0300 Subject: [PATCH 020/108] overlays: Shrink the shader notification popup --- rpcs3/Emu/RSX/Overlays/overlay_compile_notification.cpp | 2 +- rpcs3/Emu/RSX/Overlays/overlay_controls.cpp | 6 ++++++ rpcs3/Emu/RSX/Overlays/overlay_loading_icon.hpp | 4 ++-- rpcs3/Emu/RSX/Overlays/overlay_message.cpp | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/rpcs3/Emu/RSX/Overlays/overlay_compile_notification.cpp b/rpcs3/Emu/RSX/Overlays/overlay_compile_notification.cpp index 434226bfa5..cef92b9908 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_compile_notification.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_compile_notification.cpp @@ -18,7 +18,7 @@ namespace rsx } queue_message( - localized_string_id::RSX_OVERLAYS_COMPILING_SHADERS, + localized_string_id::INVALID, 5'000'000, {}, message_pin_location::bottom_left, diff --git a/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp b/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp index 02b0ff5a68..1c85b0cf6c 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp @@ -407,6 +407,12 @@ namespace rsx void overlay_element::set_text(localized_string_id id) { + if (id == localized_string_id::INVALID) + { + set_text(""); + return; + } + set_unicode_text(get_localized_u32string(id)); } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_loading_icon.hpp b/rpcs3/Emu/RSX/Overlays/overlay_loading_icon.hpp index 28db156e02..926e5457da 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_loading_icon.hpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_loading_icon.hpp @@ -27,8 +27,8 @@ namespace rsx m_frame_width = m_frame_height = 24; m_spacing_x = m_spacing_y = 6; - set_size(24, 30); - set_padding(4, 0, 2, 8); + set_size(24, 24); + set_padding(4, 0, 4, 0); } }; } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_message.cpp b/rpcs3/Emu/RSX/Overlays/overlay_message.cpp index f4de82949f..304e54de1e 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_message.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_message.cpp @@ -31,7 +31,7 @@ namespace rsx m_visible_duration = expiration; m_refs = std::move(refs); - m_text.set_font("Arial", 14); + m_text.set_font("Arial", 12); m_text.set_text(msg_id); m_text.set_padding(4, 8, 4, 8); m_text.auto_resize(); From 410660627db074127b895be1e6720b6cf0958a25 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Fri, 20 Mar 2026 14:16:52 +0300 Subject: [PATCH 021/108] overlays: Add a special message id for spinners with empty text --- rpcs3/Emu/RSX/Overlays/overlay_compile_notification.cpp | 2 +- rpcs3/Emu/RSX/Overlays/overlay_controls.cpp | 6 ------ rpcs3/Emu/localized_string_id.h | 1 + rpcs3/rpcs3qt/localized_emu.h | 1 + 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/rpcs3/Emu/RSX/Overlays/overlay_compile_notification.cpp b/rpcs3/Emu/RSX/Overlays/overlay_compile_notification.cpp index cef92b9908..709e554edf 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_compile_notification.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_compile_notification.cpp @@ -18,7 +18,7 @@ namespace rsx } queue_message( - localized_string_id::INVALID, + localized_string_id::RSX_OVERLAYS_SPINNER_NO_TEXT, 5'000'000, {}, message_pin_location::bottom_left, diff --git a/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp b/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp index 1c85b0cf6c..02b0ff5a68 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp @@ -407,12 +407,6 @@ namespace rsx void overlay_element::set_text(localized_string_id id) { - if (id == localized_string_id::INVALID) - { - set_text(""); - return; - } - set_unicode_text(get_localized_u32string(id)); } diff --git a/rpcs3/Emu/localized_string_id.h b/rpcs3/Emu/localized_string_id.h index 9eae28c832..714990708c 100644 --- a/rpcs3/Emu/localized_string_id.h +++ b/rpcs3/Emu/localized_string_id.h @@ -4,6 +4,7 @@ enum class localized_string_id { INVALID, + RSX_OVERLAYS_SPINNER_NO_TEXT, RSX_OVERLAYS_TROPHY_BRONZE, RSX_OVERLAYS_TROPHY_SILVER, RSX_OVERLAYS_TROPHY_GOLD, diff --git a/rpcs3/rpcs3qt/localized_emu.h b/rpcs3/rpcs3qt/localized_emu.h index 11a404d5db..aabdd8c017 100644 --- a/rpcs3/rpcs3qt/localized_emu.h +++ b/rpcs3/rpcs3qt/localized_emu.h @@ -38,6 +38,7 @@ private: { switch (id) { + case localized_string_id::RSX_OVERLAYS_SPINNER_NO_TEXT: return ""; case localized_string_id::RSX_OVERLAYS_TROPHY_BRONZE: return tr("You have earned a bronze trophy.\n%0", "Trophy text").arg(std::forward(args)...); case localized_string_id::RSX_OVERLAYS_TROPHY_SILVER: return tr("You have earned a silver trophy.\n%0", "Trophy text").arg(std::forward(args)...); case localized_string_id::RSX_OVERLAYS_TROPHY_GOLD: return tr("You have earned a gold trophy.\n%0", "Trophy text").arg(std::forward(args)...); From c6407b37a541052f46dde437079f2571223a3e02 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 19 Mar 2026 19:51:24 +0100 Subject: [PATCH 022/108] Batch create/remove steam shortcuts --- rpcs3/rpcs3qt/game_list_actions.cpp | 19 ++--- rpcs3/rpcs3qt/shortcut_utils.cpp | 124 ++++++++++++++++++++-------- rpcs3/rpcs3qt/shortcut_utils.h | 9 +- rpcs3/rpcs3qt/steam_utils.cpp | 11 ++- 4 files changed, 113 insertions(+), 50 deletions(-) diff --git a/rpcs3/rpcs3qt/game_list_actions.cpp b/rpcs3/rpcs3qt/game_list_actions.cpp index ad453dc079..5ff938e990 100644 --- a/rpcs3/rpcs3qt/game_list_actions.cpp +++ b/rpcs3/rpcs3qt/game_list_actions.cpp @@ -846,16 +846,19 @@ bool game_list_actions::RemoveContentList(const std::string& serial, bool is_int RemoveContentBySerial(rpcs3::utils::get_icons_dir(), serial, "icons"); } - // Remove shortcuts in "games/shortcuts" folder and from desktop / start menu (if any) + // Remove shortcuts in "games/shortcuts" folder and from desktop / start menu / Steam (if any) if (content_types & SHORTCUTS) { if (const auto it = m_content_info.name_list.find(serial); it != m_content_info.name_list.cend()) { + std::vector> games; for (const std::string& name : it->second) { - // Remove all shortcuts - gui::utils::remove_shortcuts(name, serial); + games.push_back(std::pair(name, serial)); } + + // Batch remove all shortcuts + gui::utils::batch_remove_shortcuts(games); } } @@ -1452,15 +1455,7 @@ void game_list_actions::CreateShortcuts(const std::vector& games, con return; } - bool success = true; - - for (const game_info& gameinfo : games) - { - if (!gui::utils::create_shortcuts(gameinfo, locations)) - { - success = false; - } - } + const bool success = gui::utils::batch_create_shortcuts(games, locations); #ifdef _WIN32 if (locations.size() == 1 && locations.contains(gui::utils::shortcut_location::rpcs3_shortcuts)) diff --git a/rpcs3/rpcs3qt/shortcut_utils.cpp b/rpcs3/rpcs3qt/shortcut_utils.cpp index 46ceaa003e..d7b5586e75 100644 --- a/rpcs3/rpcs3qt/shortcut_utils.cpp +++ b/rpcs3/rpcs3qt/shortcut_utils.cpp @@ -81,7 +81,7 @@ namespace gui::utils return false; } - if (!gui::utils::create_square_pixmap(icon, size)) + if (!create_square_pixmap(icon, size)) { sys_log.error("Failed to create shortcut. Icon empty."); return false; @@ -109,6 +109,12 @@ namespace gui::utils return true; } + static std::string make_simple_name(const std::string& name) + { + const std::string simple_name = QString::fromStdString(vfs::escape(name, true)).simplified().toStdString(); + return simple_name; + } + bool create_shortcut(const std::string& name, const std::string& path, [[maybe_unused]] const std::string& serial, @@ -118,6 +124,7 @@ namespace gui::utils [[maybe_unused]] const std::string& target_icon_dir, const std::string& src_banner_path, shortcut_location location, + std::shared_ptr steam_sc, std::shared_ptr archive) { if (name.empty()) @@ -127,7 +134,7 @@ namespace gui::utils } // Remove illegal characters from filename - const std::string simple_name = QString::fromStdString(vfs::escape(name, true)).simplified().toStdString(); + const std::string simple_name = make_simple_name(name); if (simple_name.empty() || simple_name == "." || simple_name == "..") { sys_log.error("Failed to create shortcut: Cleaned file name empty or not allowed"); @@ -194,9 +201,8 @@ namespace gui::utils if (location == shortcut_location::steam) { sys_log.notice("Creating %s shortcut with arguments '%s'", location, target_cli_args); - steam_shortcut steam_sc{}; - steam_sc.add_shortcut(simple_name, rpcs3_path, working_dir, target_cli_args, target_icon_path, src_icon_path, src_banner_path, archive); - return steam_sc.write_file(); + ensure(steam_sc)->add_shortcut(simple_name, rpcs3_path, working_dir, target_cli_args, target_icon_path, src_icon_path, src_banner_path, archive); + return true; } sys_log.notice("Creating %s shortcut '%s' with arguments '%s' and .ico dir '%s'", location, link_path, target_cli_args, target_icon_dir); @@ -381,9 +387,8 @@ namespace gui::utils if (location == shortcut_location::steam) { - steam_shortcut steam_sc{}; - steam_sc.add_shortcut(simple_name, link_path, macos_dir, ""/*target_cli_args are already in the launcher*/, "", src_icon_path, src_banner_path, archive); - return steam_sc.write_file(); + ensure(steam_sc)->add_shortcut(simple_name, link_path, macos_dir, ""/*target_cli_args are already in the launcher*/, "", src_icon_path, src_banner_path, archive); + return true; } return true; @@ -410,9 +415,8 @@ namespace gui::utils { sys_log.notice("Creating %s shortcut with arguments '%s'", location, target_cli_args); const std::string working_dir{fs::get_executable_dir()}; - steam_shortcut steam_sc{}; - steam_sc.add_shortcut(simple_name, exe_path, working_dir, target_cli_args, target_icon_path, src_icon_path, src_banner_path, archive); - return steam_sc.write_file(); + ensure(steam_sc)->add_shortcut(simple_name, exe_path, working_dir, target_cli_args, target_icon_path, src_icon_path, src_banner_path, archive); + return true; } fmt::append(link_path, "/%s.desktop", simple_name); @@ -466,7 +470,8 @@ namespace gui::utils } bool create_shortcuts(const std::shared_ptr& game, - const std::set& locations) + const std::set& locations, + std::shared_ptr steam_sc) { if (!game || locations.empty()) return false; @@ -530,11 +535,11 @@ namespace gui::utils const std::string cli_arg_token = is_vsh ? "RPCS3_VFS" : "RPCS3_GAMEID"; const std::string cli_arg_value = is_vsh ? ("dev_flash/" + game->info.path.substr(dev_flash.size())) : gameid_token_value; - for (gui::utils::shortcut_location location : locations) + for (shortcut_location location : locations) { std::string banner_path; - if (location == gui::utils::shortcut_location::steam) + if (location == shortcut_location::steam) { // Try to find a nice banner for steam const std::string sfo_dir = is_iso ? "PS3_GAME" : rpcs3::utils::get_sfo_dir_from_game_path(game->info.path); @@ -550,19 +555,27 @@ namespace gui::utils } #ifdef __linux__ - const std::string percent = location == gui::utils::shortcut_location::steam ? "%" : "%%"; + const std::string percent = location == shortcut_location::steam ? "%" : "%%"; #else const std::string percent = "%"; #endif const std::string target_cli_args = fmt::format("--no-gui \"%s%s%s:%s\"", percent, cli_arg_token, percent, cli_arg_value); - if (!gameid_token_value.empty() && create_shortcut(game->info.name, game->icon_in_archive ? game->info.path : "", game->info.serial, target_cli_args, game->info.name, game->info.icon_path, target_icon_dir, banner_path, location, archive)) + if (!gameid_token_value.empty() && create_shortcut(game->info.name, game->icon_in_archive ? game->info.path : "", game->info.serial, target_cli_args, game->info.name, game->info.icon_path, target_icon_dir, banner_path, location, steam_sc, archive)) { - sys_log.success("Created %s shortcut for %s", location, QString::fromStdString(game->info.name).simplified()); + if (location == shortcut_location::steam) + { + // Creation is done in caller + sys_log.notice("Prepared %s shortcut for '%s'", location, QString::fromStdString(game->info.name).simplified()); + } + else + { + sys_log.success("Created %s shortcut for '%s'", location, QString::fromStdString(game->info.name).simplified()); + } } else { - sys_log.error("Failed to create %s shortcut for %s", location, QString::fromStdString(game->info.name).simplified()); + sys_log.error("Failed to create %s shortcut for '%s'", location, QString::fromStdString(game->info.name).simplified()); success = false; } } @@ -570,9 +583,36 @@ namespace gui::utils return success; } - void remove_shortcuts(const std::string& name, [[maybe_unused]] const std::string& serial) + bool batch_create_shortcuts(const std::vector>& games, + const std::set& locations) + { + if (games.empty() || locations.empty()) return false; + + std::shared_ptr steam_sc; + + if (locations.contains(shortcut_location::steam)) + { + // Batch steam shortcut creation + steam_sc = std::make_shared(); + } + + bool result = true; + + for (const auto& game : games) + { + result &= create_shortcuts(game, locations, steam_sc); + } + + if (steam_sc) + { + result &= steam_sc->write_file(); + } + + return result; + } + + static void remove_shortcuts(const std::string& simple_name, [[maybe_unused]] const std::string& serial) { - const std::string simple_name = QString::fromStdString(vfs::escape(name, true)).simplified().toStdString(); if (simple_name.empty() || simple_name == "." || simple_name == "..") { sys_log.error("Failed to remove shortcuts: Cleaned file name empty or not allowed"); @@ -611,7 +651,7 @@ namespace gui::utils std::vector locations = { shortcut_location::desktop, shortcut_location::applications, - shortcut_location::steam, + //shortcut_location::steam, // Handled separately }; #ifdef _WIN32 locations.push_back(shortcut_location::rpcs3_shortcuts); @@ -631,18 +671,8 @@ namespace gui::utils link_path += "/RPCS3"; break; case shortcut_location::steam: - { - const std::string exe_path = fs::get_executable_path(); - const std::string working_dir = fs::get_executable_dir(); - steam_shortcut steam_sc{}; - steam_sc.remove_shortcut(simple_name, exe_path, working_dir); - if (!steam_sc.write_file()) - { - sys_log.error("Failed to remove steam shortcut for '%s'", simple_name); - } - - continue; - } + fmt::throw_exception("Steam shortcuts should be removed in batch"); + break; #ifdef _WIN32 case shortcut_location::rpcs3_shortcuts: link_path = rpcs3::utils::get_games_shortcuts_dir(); @@ -665,4 +695,32 @@ namespace gui::utils const std::string icon_path = fmt::format("%sIcons/game_icons/%s/shortcut.%s", fs::get_config_dir(), serial, icon_extension); remove_path(icon_path, true); } + + void batch_remove_shortcuts(const std::vector>& games) + { + if (games.empty()) return; + + // Batch steam shortcut removal + const std::string exe_path = fs::get_executable_path(); + const std::string working_dir = fs::get_executable_dir(); + const bool is_steam_installed = steam_shortcut::steam_installed(); + steam_shortcut steam_sc{}; + + for (const auto& [name, serial] : games) + { + const std::string simple_name = make_simple_name(name); + + remove_shortcuts(simple_name, serial); + + if (is_steam_installed) + { + steam_sc.remove_shortcut(simple_name, exe_path, working_dir); + } + } + + if (is_steam_installed && !steam_sc.write_file()) + { + sys_log.error("Failed to remove steam shortcuts"); + } + } } diff --git a/rpcs3/rpcs3qt/shortcut_utils.h b/rpcs3/rpcs3qt/shortcut_utils.h index aec921dadd..d1ed3962e2 100644 --- a/rpcs3/rpcs3qt/shortcut_utils.h +++ b/rpcs3/rpcs3qt/shortcut_utils.h @@ -5,6 +5,8 @@ class iso_archive; namespace gui::utils { + class steam_shortcut; + enum class shortcut_location { desktop, @@ -24,10 +26,11 @@ namespace gui::utils const std::string& target_icon_dir, const std::string& src_banner_path, shortcut_location shortcut_location, + std::shared_ptr steam_sc = nullptr, std::shared_ptr archive = nullptr); - bool create_shortcuts(const std::shared_ptr& game, - const std::set& locations); + bool batch_create_shortcuts(const std::vector>& games, + const std::set& locations); - void remove_shortcuts(const std::string& name, const std::string& serial); + void batch_remove_shortcuts(const std::vector>& games); } diff --git a/rpcs3/rpcs3qt/steam_utils.cpp b/rpcs3/rpcs3qt/steam_utils.cpp index 915cf69915..2642e25b37 100644 --- a/rpcs3/rpcs3qt/steam_utils.cpp +++ b/rpcs3/rpcs3qt/steam_utils.cpp @@ -346,7 +346,9 @@ namespace gui::utils const auto launch_options = entry.value("LaunchOptions"); const auto icon = entry.value("icon"); - if (appid.has_value() && appid.value() == it->appid && + const bool appid_matches = appid.has_value() && appid.value() == it->appid; + + if (appid_matches && exe.has_value() && exe.value() == it->exe && start_dir.has_value() && start_dir.value() == it->start_dir && launch_options.has_value() && launch_options.value() == it->launch_options && @@ -357,6 +359,11 @@ namespace gui::utils } else { + if (appid_matches) + { + sys_log.notice("Entry '%s' already exists in steam shortcut file '%s' but with other parameters. Creating an additional one...", it->app_name, file_path); + } + it++; } } @@ -369,7 +376,7 @@ namespace gui::utils if (m_entries_to_add.empty() && removed_entries.empty()) { - sys_log.notice("No matching entries found in steam shortcut file '%s'.", file_path); + sys_log.notice("Nothing to add or remove in steam shortcut file '%s'.", file_path); return true; } From 2ba7756c0e2cbba363844104142515c02749cd58 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Fri, 20 Mar 2026 15:40:09 +0100 Subject: [PATCH 023/108] Add overlay notification for received messages --- rpcs3/Emu/NP/np_handler.cpp | 22 ++++++++++++++-------- rpcs3/Emu/localized_string_id.h | 3 +++ rpcs3/rpcs3qt/localized_emu.h | 2 ++ 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index 1686327fb3..3eb2bef0ed 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -1228,16 +1228,22 @@ namespace np } auto messages = rpcn->get_new_messages(); - if (basic_handler_registered) + + for (const auto msg_id : messages) { - for (const auto msg_id : messages) + const auto opt_msg = rpcn->get_message(msg_id); + + if (!opt_msg) + { + continue; + } + + const auto& msg = opt_msg.value(); + const localized_string_id loc_id = (msg->second.mainType == SCE_NP_BASIC_MESSAGE_MAIN_TYPE_INVITE) ? localized_string_id::CELL_NP_MESSAGE_INVITE_RECEIVED : localized_string_id::CELL_NP_MESSAGE_OTHER_RECEIVED; + rsx::overlays::queue_message(get_localized_string(loc_id, msg->first.c_str()), 6'000'000); + + if (basic_handler_registered) { - const auto opt_msg = rpcn->get_message(msg_id); - if (!opt_msg) - { - continue; - } - const auto& msg = opt_msg.value(); if (strncmp(msg->second.commId.data, basic_handler.context.data, sizeof(basic_handler.context.data) - 1) == 0) { u32 event; diff --git a/rpcs3/Emu/localized_string_id.h b/rpcs3/Emu/localized_string_id.h index 714990708c..9b91abab99 100644 --- a/rpcs3/Emu/localized_string_id.h +++ b/rpcs3/Emu/localized_string_id.h @@ -158,6 +158,9 @@ enum class localized_string_id CELL_NP_SENDMESSAGE_DIALOG_CONFIRMATION_INVITE, CELL_NP_SENDMESSAGE_DIALOG_CONFIRMATION_ADD_FRIEND, + CELL_NP_MESSAGE_INVITE_RECEIVED, + CELL_NP_MESSAGE_OTHER_RECEIVED, + RECORDING_ABORTED, RPCN_NO_ERROR, diff --git a/rpcs3/rpcs3qt/localized_emu.h b/rpcs3/rpcs3qt/localized_emu.h index aabdd8c017..8ee62d1603 100644 --- a/rpcs3/rpcs3qt/localized_emu.h +++ b/rpcs3/rpcs3qt/localized_emu.h @@ -181,6 +181,8 @@ private: case localized_string_id::CELL_NP_SENDMESSAGE_DIALOG_CONFIRMATION: return tr("Send message to %0 ?\n\nSubject:", "SENDMESSAGE_DIALOG").arg(std::forward(args)...); case localized_string_id::CELL_NP_SENDMESSAGE_DIALOG_CONFIRMATION_INVITE: return tr("Send invite to %0 ?\n\nSubject:", "SENDMESSAGE_DIALOG").arg(std::forward(args)...); case localized_string_id::CELL_NP_SENDMESSAGE_DIALOG_CONFIRMATION_ADD_FRIEND: return tr("Send friend request to %0 ?\n\nSubject:", "SENDMESSAGE_DIALOG").arg(std::forward(args)...); + case localized_string_id::CELL_NP_MESSAGE_INVITE_RECEIVED: return tr("Received an invite from %0").arg(std::forward(args)...); + case localized_string_id::CELL_NP_MESSAGE_OTHER_RECEIVED: return tr("Received a message from %0").arg(std::forward(args)...); case localized_string_id::RECORDING_ABORTED: return tr("Recording aborted!"); case localized_string_id::RPCN_NO_ERROR: return tr("RPCN: No Error"); case localized_string_id::RPCN_ERROR_INVALID_INPUT: return tr("RPCN: Invalid Input (Wrong Host/Port)"); From 67f69bb4b3243a3e89c7fb0a89e5da8dc1c41d4c Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 21 Mar 2026 01:06:42 +0300 Subject: [PATCH 024/108] rsx/cfg: Fix handling of instructions with literal input - The hardware is pretty dumb about it, it just unconditionally skips the next instruction --- rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp | 8 +++++++ rpcs3/Emu/RSX/Program/ProgramStateCache.cpp | 3 +-- rpcs3/tests/test_rsx_cfg.cpp | 25 +++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp index c6d092c92a..0178ba5efb 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp @@ -165,6 +165,14 @@ namespace rsx::assembler if (opcode == RSX_FP_OPCODE_NOP) { + if (includes_literal_constant()) + { + // Verified behavior on real hardware + // If any input on a non-flow-control instruction is of literal type the next instruction is assumed to be data + // You can actually use this behavior to mask off instructions completely + pc++; + } + pc++; continue; } diff --git a/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp b/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp index 528b5c6316..f1c2d49097 100644 --- a/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp +++ b/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp @@ -679,8 +679,7 @@ fragment_program_utils::fragment_program_metadata fragment_program_utils::analys break; } - if (rsx::assembler::FP::get_operand_count(opcode) > 0 && - is_any_src_constant(inst)) + if (is_any_src_constant(inst)) { // Instruction references constant, skip one slot occupied by data index++; diff --git a/rpcs3/tests/test_rsx_cfg.cpp b/rpcs3/tests/test_rsx_cfg.cpp index a20b3a7d0a..81027e61db 100644 --- a/rpcs3/tests/test_rsx_cfg.cpp +++ b/rpcs3/tests/test_rsx_cfg.cpp @@ -251,4 +251,29 @@ namespace rsx::assembler EXPECT_EQ(SRC0{ .HEX = graph.blocks.front().instructions[0].bytecode[1] }.exec_if_gr, 1); EXPECT_EQ(SRC0{ .HEX = graph.blocks.front().instructions[0].bytecode[1] }.exec_if_eq, 1); } + + TEST(CFG, FpToCFG_SkipOverImmediateOperand) + { + auto ir = FPIR::from_source( + "MOV R0, #{ 0.25 };" // NOP with real dst and one literal input + "MOV R0, R1;" // False merge block. + ); + + RSXFragmentProgram program{}; + auto bytecode = ir.compile(); + program.data = bytecode.data(); + + ASSERT_EQ(bytecode.size(), 12); + + // Patch the first instruction to be a NOP with a literal as input + const u32 decoded_d0 = ((bytecode[0] & 0xFF00FF00u) >> 16u) | ((bytecode[0] & 0x00FF00FFu) << 16u); + OPDEST d0{ .HEX = decoded_d0 }; + d0.opcode = RSX_FP_OPCODE_NOP; + bytecode[0] = ((d0.HEX & 0xFF00FF00u) >> 16u) | ((d0.HEX & 0x00FF00FFu) << 16u); + + FlowGraph graph = deconstruct_fragment_program(program); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 1); + } } From cd1d6282b4d8ddaf46de3af506eb5da69723c0f1 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Fri, 20 Mar 2026 22:08:06 +0100 Subject: [PATCH 025/108] Qt: Remove some unused code --- bin/GuiConfigs/Classic (Bright).qss | 4 -- bin/GuiConfigs/Darker Style by TheMitoSan.qss | 5 --- bin/GuiConfigs/Envy.qss | 5 --- bin/GuiConfigs/Kuroi (Dark) by Ani.qss | 5 --- .../ModernBlue Theme by TheMitoSan.qss | 5 --- bin/GuiConfigs/Nekotekina by GooseWing.qss | 6 --- bin/GuiConfigs/Skyline (Nightfall).qss | 5 --- bin/GuiConfigs/Skyline.qss | 5 --- bin/GuiConfigs/YoRHa by Ani.qss | 5 --- rpcs3/rpcs3qt/main_window.cpp | 16 +------- rpcs3/rpcs3qt/main_window.h | 1 - rpcs3/rpcs3qt/qt_utils.cpp | 39 ------------------- rpcs3/rpcs3qt/qt_utils.h | 3 -- rpcs3/rpcs3qt/stylesheets.h | 3 -- 14 files changed, 2 insertions(+), 105 deletions(-) diff --git a/bin/GuiConfigs/Classic (Bright).qss b/bin/GuiConfigs/Classic (Bright).qss index 143e42df94..f961e63e00 100644 --- a/bin/GuiConfigs/Classic (Bright).qss +++ b/bin/GuiConfigs/Classic (Bright).qss @@ -37,10 +37,6 @@ QSlider#sizeSlider::handle:horizontal { QLabel#toolbar_icon_color { color: rgba(64,64,64,255); } -/* thumbnail icon color stylesheet */ -QLabel#thumbnail_icon_color { - color: rgba(0,100,231,255); -} /* gamelist icon color stylesheet */ QLabel#gamelist_icon_background_color { color: rgba(209,209,209,255); diff --git a/bin/GuiConfigs/Darker Style by TheMitoSan.qss b/bin/GuiConfigs/Darker Style by TheMitoSan.qss index 47270a211e..8797ffd3c9 100644 --- a/bin/GuiConfigs/Darker Style by TheMitoSan.qss +++ b/bin/GuiConfigs/Darker Style by TheMitoSan.qss @@ -237,11 +237,6 @@ QLabel#gamelist_icon_background_color { color: transparent; } -/* Set Windows Taskbar Thumbnail colors */ -QLabel#thumbnail_icon_color { - color: #262626; -} - /* Set Log colors */ QPlainTextEdit#log_frame { background-color: #000; /* Black */ diff --git a/bin/GuiConfigs/Envy.qss b/bin/GuiConfigs/Envy.qss index a78ba04f58..3e2e1ddc35 100644 --- a/bin/GuiConfigs/Envy.qss +++ b/bin/GuiConfigs/Envy.qss @@ -573,11 +573,6 @@ QLabel#gamelist_icon_background_color { color: transparent; } -/* Set Windows Taskbar Thumbnail colors */ -QLabel#thumbnail_icon_color { - color: #23262d; -} - /* Log colors */ QPlainTextEdit#log_frame { background-color: #23262d; diff --git a/bin/GuiConfigs/Kuroi (Dark) by Ani.qss b/bin/GuiConfigs/Kuroi (Dark) by Ani.qss index 54c667213b..2cd81267f1 100644 --- a/bin/GuiConfigs/Kuroi (Dark) by Ani.qss +++ b/bin/GuiConfigs/Kuroi (Dark) by Ani.qss @@ -265,11 +265,6 @@ QLabel#gamelist_icon_background_color { color: transparent; } -/* Set Taskbar Thumbnail colors */ -QLabel#thumbnail_icon_color { - color: #444444; -} - /* Memory Viewer */ QLabel#memory_viewer_address_panel { color: #00cbff; /* Font Color: Blue */ diff --git a/bin/GuiConfigs/ModernBlue Theme by TheMitoSan.qss b/bin/GuiConfigs/ModernBlue Theme by TheMitoSan.qss index 410db682f7..96b84a9196 100644 --- a/bin/GuiConfigs/ModernBlue Theme by TheMitoSan.qss +++ b/bin/GuiConfigs/ModernBlue Theme by TheMitoSan.qss @@ -244,11 +244,6 @@ QLabel#gamelist_icon_background_color { color: transparent; } -/* Set Windows Taskbar Thumbnail colors */ -QLabel#thumbnail_icon_color { - color: #262626; -} - /* Set Log colors */ QPlainTextEdit#log_frame { background-color: #181d24; /* Black */ diff --git a/bin/GuiConfigs/Nekotekina by GooseWing.qss b/bin/GuiConfigs/Nekotekina by GooseWing.qss index 435d550755..93fadcdd68 100755 --- a/bin/GuiConfigs/Nekotekina by GooseWing.qss +++ b/bin/GuiConfigs/Nekotekina by GooseWing.qss @@ -397,12 +397,6 @@ QLabel#gamelist_icon_background_color { } -/* Set Windows Taskbar Thumbnail colors */ -QLabel#thumbnail_icon_color { - color: #ffd785; -} - - QLabel#log_level_always { color: #00ffff; /* Cyan */ } diff --git a/bin/GuiConfigs/Skyline (Nightfall).qss b/bin/GuiConfigs/Skyline (Nightfall).qss index 625a6a28b6..4729a59173 100644 --- a/bin/GuiConfigs/Skyline (Nightfall).qss +++ b/bin/GuiConfigs/Skyline (Nightfall).qss @@ -656,11 +656,6 @@ QLabel#color_button { background: transparent; } -/* Set Windows Taskbar Thumbnail colors */ -QLabel#thumbnail_icon_color { - color: #370048; -} - /* Debugger colors */ QLabel#debugger_frame_breakpoint { color: #000; /* Font Color: Black */ diff --git a/bin/GuiConfigs/Skyline.qss b/bin/GuiConfigs/Skyline.qss index ef3c7c6857..1004dc0016 100644 --- a/bin/GuiConfigs/Skyline.qss +++ b/bin/GuiConfigs/Skyline.qss @@ -664,11 +664,6 @@ QLabel#color_button { background: transparent; } -/* Set Windows Taskbar Thumbnail colors */ -QLabel#thumbnail_icon_color { - color: #8500ae; -} - /* Debugger colors */ QLabel#debugger_frame_breakpoint { color: #000; /* Font Color: Black */ diff --git a/bin/GuiConfigs/YoRHa by Ani.qss b/bin/GuiConfigs/YoRHa by Ani.qss index c772f25196..51f09897b1 100644 --- a/bin/GuiConfigs/YoRHa by Ani.qss +++ b/bin/GuiConfigs/YoRHa by Ani.qss @@ -379,11 +379,6 @@ QLabel#gamelist_icon_background_color { color: transparent; } -/* Set Windows Taskbar Thumbnail colors */ -QLabel#thumbnail_icon_color { - color: #4d4940; -} - QLabel#log_level_always { color: #00ffff; /* Cyan */ } diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 0c3a9b162c..d586ac55e4 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -213,8 +213,6 @@ bool main_window::Init([[maybe_unused]] bool with_cli_boot) m_shortcut_handler = new shortcut_handler(gui::shortcuts::shortcut_handler_id::main_window, this, m_gui_settings); connect(m_shortcut_handler, &shortcut_handler::shortcut_activated, this, &main_window::handle_shortcut); - show(); // needs to be done before creating the thumbnail toolbar - // enable play options if a recent game exists const bool enable_play_last = !m_recent_game.actions.isEmpty() && m_recent_game.actions.first(); @@ -279,6 +277,8 @@ bool main_window::Init([[maybe_unused]] bool with_cli_boot) update_gui_pad_thread(); + show(); + return true; } @@ -1795,17 +1795,6 @@ void main_window::SaveWindowState() const } } -void main_window::RepaintThumbnailIcons() -{ - const QColor color = gui::utils::get_foreground_color(); - [[maybe_unused]] const QColor new_color = gui::utils::get_label_color("thumbnail_icon_color", color, color); - - [[maybe_unused]] const auto icon = [&new_color](const QString& path) - { - return gui::utils::get_colorized_icon(QPixmap::fromImage(gui::utils::get_opaque_image_area(path)), Qt::black, new_color); - }; -} - void main_window::RepaintToolBarIcons() { const QColor color = gui::utils::get_foreground_color(); @@ -2311,7 +2300,6 @@ void main_window::RepaintGui() } RepaintToolBarIcons(); - RepaintThumbnailIcons(); Q_EMIT RequestDialogRepaint(); } diff --git a/rpcs3/rpcs3qt/main_window.h b/rpcs3/rpcs3qt/main_window.h index 7f0bb8e2ea..e9206070cf 100644 --- a/rpcs3/rpcs3qt/main_window.h +++ b/rpcs3/rpcs3qt/main_window.h @@ -134,7 +134,6 @@ protected: private: void ConfigureGuiFromSettings(); void RepaintToolBarIcons(); - void RepaintThumbnailIcons(); void CreateActions(); void CreateConnects(); void CreateDockWindows(); diff --git a/rpcs3/rpcs3qt/qt_utils.cpp b/rpcs3/rpcs3qt/qt_utils.cpp index ca5d677bf1..7c0760f3a8 100644 --- a/rpcs3/rpcs3qt/qt_utils.cpp +++ b/rpcs3/rpcs3qt/qt_utils.cpp @@ -312,45 +312,6 @@ namespace gui return get_aligned_pixmap(QPixmap(path), icon_size, device_pixel_ratio, mode, h_alignment, v_alignment); } - QImage get_opaque_image_area(const QString& path) - { - QImage image = QImage(path); - - int w_min = 0; - int w_max = image.width(); - int h_min = 0; - int h_max = image.height(); - - for (int y = 0; y < image.height(); ++y) - { - const QRgb* row = reinterpret_cast(image.constScanLine(y)); - bool row_filled = false; - - for (int x = 0; x < image.width(); ++x) - { - if (qAlpha(row[x])) - { - row_filled = true; - w_min = std::max(w_min, x); - - if (w_max > x) - { - w_max = x; - x = w_min; - } - } - } - - if (row_filled) - { - h_max = std::min(h_max, y); - h_min = y; - } - } - - return image.copy(QRect(QPoint(w_max, h_max), QPoint(w_min, h_min))); - } - // taken from https://stackoverflow.com/a/30818424/8353754 // because size policies won't work as expected (see similar bugs in Qt bugtracker) void resize_combo_box_view(QComboBox* combo) diff --git a/rpcs3/rpcs3qt/qt_utils.h b/rpcs3/rpcs3qt/qt_utils.h index 6da1029541..22e94109ef 100644 --- a/rpcs3/rpcs3qt/qt_utils.h +++ b/rpcs3/rpcs3qt/qt_utils.h @@ -132,9 +132,6 @@ namespace gui // Returns a scaled, aligned QPixmap QPixmap get_aligned_pixmap(const QString& path, const QSize& icon_size, qreal device_pixel_ratio, Qt::TransformationMode mode, align_h h_alignment, align_v v_alignment); - // Returns the part of the image loaded from path that is inside the bounding box of its opaque areas - QImage get_opaque_image_area(const QString& path); - // Workaround: resize the dropdown combobox items void resize_combo_box_view(QComboBox* combo); diff --git a/rpcs3/rpcs3qt/stylesheets.h b/rpcs3/rpcs3qt/stylesheets.h index 52367de7db..7396cec605 100644 --- a/rpcs3/rpcs3qt/stylesheets.h +++ b/rpcs3/rpcs3qt/stylesheets.h @@ -24,9 +24,6 @@ namespace gui // main window toolbar icon color "QLabel#toolbar_icon_color { color: #5b5b5b; }" - // thumbnail icon color - "QLabel#thumbnail_icon_color { color: rgba(0, 100, 231, 255); }" - // game list icon color "QLabel#gamelist_icon_background_color { color: rgba(240, 240, 240, 255); }" From 62b2ffd7cadc3e850e308fb8a5366e432e55a115 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Fri, 20 Mar 2026 00:33:45 +0100 Subject: [PATCH 026/108] Qt/overlays: Add secondary audio output for emulation --- rpcs3/rpcs3qt/qt_video_source.cpp | 93 ++++++++++++++++++------------- rpcs3/rpcs3qt/qt_video_source.h | 5 +- 2 files changed, 58 insertions(+), 40 deletions(-) diff --git a/rpcs3/rpcs3qt/qt_video_source.cpp b/rpcs3/rpcs3qt/qt_video_source.cpp index 8877e00c0c..b14db8a019 100644 --- a/rpcs3/rpcs3qt/qt_video_source.cpp +++ b/rpcs3/rpcs3qt/qt_video_source.cpp @@ -8,14 +8,23 @@ #include #include -static video_source* s_audio_source = nullptr; -static std::unique_ptr s_audio_player = nullptr; -static std::unique_ptr s_audio_output = nullptr; -static std::unique_ptr s_audio_buffer = nullptr; -static std::unique_ptr s_audio_data = nullptr; +struct qt_audio_instance +{ + static constexpr u32 gui_index = 0; + static constexpr u32 emu_index = 1; -qt_video_source::qt_video_source() + video_source* source = nullptr; + std::unique_ptr player; + std::unique_ptr output; + std::unique_ptr buffer; + std::unique_ptr data; +}; + +static std::array s_audio_instance = {}; + +qt_video_source::qt_video_source(bool is_emulation) : video_source() + , m_audio_instance_index(is_emulation ? qt_audio_instance::emu_index : qt_audio_instance::gui_index) { } @@ -216,18 +225,21 @@ void qt_video_source::stop_movie() void qt_video_source::start_audio() { - if (m_audio_path.isEmpty() || s_audio_source == this) return; + if (m_audio_path.isEmpty()) return; - if (!s_audio_player) + qt_audio_instance& audio = ::at32(s_audio_instance, m_audio_instance_index); + if (audio.source == this) return; + + if (!audio.player) { - s_audio_output = std::make_unique(); - s_audio_player = std::make_unique(); - s_audio_player->setAudioOutput(s_audio_output.get()); + audio.output = std::make_unique(); + audio.player = std::make_unique(); + audio.player->setAudioOutput(audio.output.get()); } if (m_iso_path.empty()) { - s_audio_player->setSource(QUrl::fromLocalFile(m_audio_path)); + audio.player->setSource(QUrl::fromLocalFile(m_audio_path)); } else { @@ -236,18 +248,18 @@ void qt_video_source::start_audio() const auto audio_size = audio_file.size(); if (audio_size == 0) return; - std::unique_ptr old_audio_data = std::move(s_audio_data); - s_audio_data = std::make_unique(audio_size, 0); - audio_file.read(s_audio_data->data(), audio_size); + std::unique_ptr old_audio_data = std::move(audio.data); + audio.data = std::make_unique(audio_size, 0); + audio_file.read(audio.data->data(), audio_size); - if (!s_audio_buffer) + if (!audio.buffer) { - s_audio_buffer = std::make_unique(); + audio.buffer = std::make_unique(); } - s_audio_buffer->setBuffer(s_audio_data.get()); - s_audio_buffer->open(QIODevice::ReadOnly); - s_audio_player->setSourceDevice(s_audio_buffer.get()); + audio.buffer->setBuffer(audio.data.get()); + audio.buffer->open(QIODevice::ReadOnly); + audio.player->setSourceDevice(audio.buffer.get()); if (old_audio_data) { @@ -255,26 +267,27 @@ void qt_video_source::start_audio() } } - s_audio_output->setVolume(g_cfg.audio.volume.get() / 100.0f); - s_audio_player->play(); - s_audio_source = this; + audio.output->setVolume(g_cfg.audio.volume.get() / 100.0f); + audio.player->play(); + audio.source = this; } void qt_video_source::stop_audio() { - if (s_audio_source != this) return; + qt_audio_instance& audio = ::at32(s_audio_instance, m_audio_instance_index); + if (audio.source != this) return; - s_audio_source = nullptr; + audio.source = nullptr; - if (s_audio_player) + if (audio.player) { - s_audio_player->stop(); - s_audio_player.reset(); + audio.player->stop(); + audio.player.reset(); } - s_audio_output.reset(); - s_audio_buffer.reset(); - s_audio_data.reset(); + audio.output.reset(); + audio.buffer.reset(); + audio.data.reset(); } QPixmap qt_video_source::get_movie_image(const QVideoFrame& frame) const @@ -331,14 +344,19 @@ qt_video_source_wrapper::~qt_video_source_wrapper() }); } +void qt_video_source_wrapper::init_video_source() +{ + if (!m_qt_video_source) + { + m_qt_video_source = std::make_unique(true); + } +} + void qt_video_source_wrapper::set_video_path(const std::string& video_path) { Emu.CallFromMainThread([this, path = video_path]() { - if (!m_qt_video_source) - { - m_qt_video_source = std::make_unique(); - } + init_video_source(); m_qt_video_source->m_image_change_callback = [this](const QVideoFrame& frame) { @@ -375,10 +393,7 @@ void qt_video_source_wrapper::set_audio_path(const std::string& audio_path) { Emu.CallFromMainThread([this, path = audio_path]() { - if (!m_qt_video_source) - { - m_qt_video_source = std::make_unique(); - } + init_video_source(); m_qt_video_source->set_audio_path(path); }); diff --git a/rpcs3/rpcs3qt/qt_video_source.h b/rpcs3/rpcs3qt/qt_video_source.h index cda92671a2..8afc46ceb3 100644 --- a/rpcs3/rpcs3qt/qt_video_source.h +++ b/rpcs3/rpcs3qt/qt_video_source.h @@ -14,7 +14,7 @@ class qt_video_source : public video_source { public: - qt_video_source(); + qt_video_source(bool is_emulation = false); virtual ~qt_video_source(); void set_iso_path(const std::string& iso_path); @@ -50,6 +50,7 @@ protected: QString m_video_path; QString m_audio_path; + u32 m_audio_instance_index = 0; std::string m_iso_path; // path of the source archive QByteArray m_video_data{}; QImage m_image{}; @@ -80,5 +81,7 @@ public: void get_image(std::vector& data, int& w, int& h, int& ch, int& bpp) override; private: + void init_video_source(); + std::unique_ptr m_qt_video_source; }; From c80e08a6421b8fe493427aaf87302b6294b9e0c8 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Fri, 20 Mar 2026 00:45:56 +0100 Subject: [PATCH 027/108] Qt: Don't check for movies if play_hover_movies is disabled Apparently this was forgotten. The variable was already captured --- rpcs3/rpcs3qt/game_list_frame.cpp | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 5ba31d9d77..592bb88673 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -621,20 +621,23 @@ void game_list_frame::OnParsingFinished() game.icon_in_archive = archive && archive->exists(game.info.icon_path); } - if (std::string movie_path = game_icon_path + game.info.serial + "/hover.gif"; file_exists(movie_path)) + if (play_hover_movies) { - game.info.movie_path = std::move(movie_path); - game.has_hover_gif = true; - } - else if (std::string movie_path = sfo_dir + "/" + localized_movie; file_exists(movie_path)) - { - game.info.movie_path = std::move(movie_path); - game.has_hover_pam = true; - } - else if (std::string movie_path = sfo_dir + "/ICON1.PAM"; file_exists(movie_path)) - { - game.info.movie_path = std::move(movie_path); - game.has_hover_pam = true; + if (std::string movie_path = game_icon_path + game.info.serial + "/hover.gif"; file_exists(movie_path)) + { + game.info.movie_path = std::move(movie_path); + game.has_hover_gif = true; + } + else if (std::string movie_path = sfo_dir + "/" + localized_movie; file_exists(movie_path)) + { + game.info.movie_path = std::move(movie_path); + game.has_hover_pam = true; + } + else if (std::string movie_path = sfo_dir + "/ICON1.PAM"; file_exists(movie_path)) + { + game.info.movie_path = std::move(movie_path); + game.has_hover_pam = true; + } } if (std::string audio_path = sfo_dir + "/SND0.AT3"; file_exists(audio_path)) From 33f8deffbe004046d12b61e96831376aaa1a55a6 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Fri, 20 Mar 2026 01:01:40 +0100 Subject: [PATCH 028/108] Qt: add setting for disabling hover music --- rpcs3/rpcs3qt/game_list_base.h | 3 ++- rpcs3/rpcs3qt/game_list_frame.cpp | 29 ++++++++++++++++++++------ rpcs3/rpcs3qt/game_list_frame.h | 2 ++ rpcs3/rpcs3qt/game_list_grid.cpp | 34 +++++++++++++++---------------- rpcs3/rpcs3qt/game_list_grid.h | 3 ++- rpcs3/rpcs3qt/game_list_table.cpp | 34 +++++++++++++++---------------- rpcs3/rpcs3qt/game_list_table.h | 3 ++- rpcs3/rpcs3qt/gui_settings.h | 1 + rpcs3/rpcs3qt/main_window.cpp | 2 ++ rpcs3/rpcs3qt/main_window.ui | 12 +++++++++++ 10 files changed, 78 insertions(+), 45 deletions(-) diff --git a/rpcs3/rpcs3qt/game_list_base.h b/rpcs3/rpcs3qt/game_list_base.h index 2af79b0b01..6997594070 100644 --- a/rpcs3/rpcs3qt/game_list_base.h +++ b/rpcs3/rpcs3qt/game_list_base.h @@ -16,7 +16,8 @@ public: [[maybe_unused]] const std::map& notes_map, [[maybe_unused]] const std::map& title_map, [[maybe_unused]] const std::set& selected_item_ids, - [[maybe_unused]] bool play_hover_movies){}; + [[maybe_unused]] bool play_hover_movies, + [[maybe_unused]] bool play_hover_music){}; void set_icon_size(QSize size) { m_icon_size = std::move(size); } void set_icon_color(QColor color) { m_icon_color = std::move(color); } diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 592bb88673..7ef27ea0f9 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -215,6 +215,7 @@ void game_list_frame::LoadSettings() m_prefer_game_data_icons = m_gui_settings->GetValue(gui::gl_pref_gd_icon).toBool(); m_show_custom_icons = m_gui_settings->GetValue(gui::gl_custom_icon).toBool(); m_play_hover_movies = m_gui_settings->GetValue(gui::gl_hover_gifs).toBool(); + m_play_hover_music = m_gui_settings->GetValue(gui::gl_hover_music).toBool(); m_game_list->sync_header_actions(m_column_acts, [this](int col) { return m_gui_settings->GetGamelistColVisibility(static_cast(col)); }); } @@ -482,7 +483,7 @@ void game_list_frame::Refresh(const bool from_drive, const std::vectorclear_list(); const int scroll_position = m_game_list->verticalScrollBar()->value(); - m_game_list->populate(matching_apps, m_notes, m_titles, selected_items, m_play_hover_movies); + m_game_list->populate(matching_apps, m_notes, m_titles, selected_items, m_play_hover_movies, m_play_hover_music); m_game_list->sort(m_game_data.size(), m_sort_column, m_col_sort_order); RepaintIcons(); @@ -498,7 +499,7 @@ void game_list_frame::Refresh(const bool from_drive, const std::vectorclear_list(); - m_game_grid->populate(matching_apps, m_notes, m_titles, selected_items, m_play_hover_movies); + m_game_grid->populate(matching_apps, m_notes, m_titles, selected_items, m_play_hover_movies, m_play_hover_music); RepaintIcons(); } } @@ -521,7 +522,10 @@ void game_list_frame::OnParsingFinished() const std::string localized_icon = fmt::format("ICON0_%02d.PNG", language_index); const std::string localized_movie = fmt::format("ICON1_%02d.PAM", language_index); - const auto add_game = [this, localized_title, localized_icon, localized_movie, dev_flash, cat_unknown_localized = localized.category.unknown.toStdString(), cat_unknown = cat::cat_unknown.toStdString(), game_icon_path, _hdd, play_hover_movies = m_play_hover_movies, show_custom_icons = m_show_custom_icons](const std::string& dir_or_elf) + const auto add_game = [this, localized_title, localized_icon, localized_movie, dev_flash, game_icon_path, _hdd, + cat_unknown_localized = localized.category.unknown.toStdString(), cat_unknown = cat::cat_unknown.toStdString(), + play_hover_movies = m_play_hover_movies, play_hover_music = m_play_hover_music, show_custom_icons = m_show_custom_icons] + (const std::string& dir_or_elf) { std::unique_ptr archive; if (is_file_iso(dir_or_elf)) @@ -640,10 +644,13 @@ void game_list_frame::OnParsingFinished() } } - if (std::string audio_path = sfo_dir + "/SND0.AT3"; file_exists(audio_path)) + if (play_hover_music) { - game.info.audio_path = std::move(audio_path); - game.has_audio_file = true; + if (std::string audio_path = sfo_dir + "/SND0.AT3"; file_exists(audio_path)) + { + game.info.audio_path = std::move(audio_path); + game.has_audio_file = true; + } } const QString serial = QString::fromStdString(game.info.serial); @@ -1394,6 +1401,16 @@ void game_list_frame::SetPlayHoverGifs(bool play) } } +void game_list_frame::SetPlayHoverMusic(bool play) +{ + if (m_play_hover_music != play) + { + m_play_hover_music = play; + m_gui_settings->SetValue(gui::gl_hover_music, play); + Refresh(true); + } +} + void game_list_frame::WaitAndAbortRepaintThreads() { for (const game_info& game : m_game_data) diff --git a/rpcs3/rpcs3qt/game_list_frame.h b/rpcs3/rpcs3qt/game_list_frame.h index b8148630f9..637229bf60 100644 --- a/rpcs3/rpcs3qt/game_list_frame.h +++ b/rpcs3/rpcs3qt/game_list_frame.h @@ -88,6 +88,7 @@ public Q_SLOTS: void SetPreferGameDataIcons(bool enabled); void SetShowCustomIcons(bool show); void SetPlayHoverGifs(bool play); + void SetPlayHoverMusic(bool play); void FocusAndSelectFirstEntryIfNoneIs(); private Q_SLOTS: @@ -204,5 +205,6 @@ private: bool m_prefer_game_data_icons = false; bool m_show_custom_icons = true; bool m_play_hover_movies = true; + bool m_play_hover_music = true; std::optional> m_refresh_funcs_manage_type{std::in_place}; }; diff --git a/rpcs3/rpcs3qt/game_list_grid.cpp b/rpcs3/rpcs3qt/game_list_grid.cpp index 4daf241f5e..94fa571f7e 100644 --- a/rpcs3/rpcs3qt/game_list_grid.cpp +++ b/rpcs3/rpcs3qt/game_list_grid.cpp @@ -45,7 +45,8 @@ void game_list_grid::populate( const std::map& notes_map, const std::map& title_map, const std::set& selected_item_ids, - bool play_hover_movies) + bool play_hover_movies, + bool play_hover_music) { clear_list(); @@ -109,26 +110,23 @@ void game_list_grid::populate( } }); - if (play_hover_movies && (game->has_hover_gif || game->has_hover_pam || game->has_audio_file)) + bool check_iso = false; + + if (play_hover_movies && (game->has_hover_gif || game->has_hover_pam)) { - bool check_iso = false; + item->set_video_path(game->info.movie_path); + check_iso |= !fs::exists(game->info.movie_path); + } - if (game->has_hover_gif || game->has_hover_pam) - { - item->set_video_path(game->info.movie_path); - check_iso |= !fs::exists(game->info.movie_path); - } + if (play_hover_music && game->has_audio_file) + { + item->set_audio_path(game->info.audio_path); + check_iso |= !fs::exists(game->info.audio_path); + } - if (game->has_audio_file) - { - item->set_audio_path(game->info.audio_path); - check_iso |= !fs::exists(game->info.audio_path); - } - - if (check_iso && is_file_iso(game->info.path)) - { - item->set_iso_path(game->info.path); - } + if (check_iso && is_file_iso(game->info.path)) + { + item->set_iso_path(game->info.path); } if (selected_item_ids.contains(game->info.path + game->info.icon_path)) diff --git a/rpcs3/rpcs3qt/game_list_grid.h b/rpcs3/rpcs3qt/game_list_grid.h index e9e5890e81..0aebbe0547 100644 --- a/rpcs3/rpcs3qt/game_list_grid.h +++ b/rpcs3/rpcs3qt/game_list_grid.h @@ -19,7 +19,8 @@ public: const std::map& notes_map, const std::map& title_map, const std::set& selected_item_ids, - bool play_hover_movies) override; + bool play_hover_movies, + bool play_hover_music) override; void repaint_icons(std::vector& game_data, const QColor& icon_color, const QSize& icon_size, qreal device_pixel_ratio) override; diff --git a/rpcs3/rpcs3qt/game_list_table.cpp b/rpcs3/rpcs3qt/game_list_table.cpp index 5a5e183cd1..53d0754bce 100644 --- a/rpcs3/rpcs3qt/game_list_table.cpp +++ b/rpcs3/rpcs3qt/game_list_table.cpp @@ -207,7 +207,8 @@ void game_list_table::populate( const std::map& notes_map, const std::map& title_map, const std::set& selected_item_ids, - bool play_hover_movies) + bool play_hover_movies, + bool play_hover_music) { clear_list(); @@ -299,26 +300,23 @@ void game_list_table::populate( } }); - if (play_hover_movies && (game->has_hover_gif || game->has_hover_pam || game->has_audio_file)) + bool check_iso = false; + + if (play_hover_movies && (game->has_hover_gif || game->has_hover_pam)) { - bool check_iso = false; + icon_item->set_video_path(game->info.movie_path); + check_iso |= !fs::exists(game->info.movie_path); + } - if (game->has_hover_gif || game->has_hover_pam) - { - icon_item->set_video_path(game->info.movie_path); - check_iso |= !fs::exists(game->info.movie_path); - } + if (play_hover_music && game->has_audio_file) + { + icon_item->set_audio_path(game->info.audio_path); + check_iso |= !fs::exists(game->info.audio_path); + } - if (game->has_audio_file) - { - icon_item->set_audio_path(game->info.audio_path); - check_iso |= !fs::exists(game->info.audio_path); - } - - if (check_iso && is_file_iso(game->info.path)) - { - icon_item->set_iso_path(game->info.path); - } + if (check_iso && is_file_iso(game->info.path)) + { + icon_item->set_iso_path(game->info.path); } icon_item->setData(Qt::UserRole, index, true); diff --git a/rpcs3/rpcs3qt/game_list_table.h b/rpcs3/rpcs3qt/game_list_table.h index ac9bff64e5..24949e6baa 100644 --- a/rpcs3/rpcs3qt/game_list_table.h +++ b/rpcs3/rpcs3qt/game_list_table.h @@ -29,7 +29,8 @@ public: const std::map& notes_map, const std::map& title_map, const std::set& selected_item_ids, - bool play_hover_movies) override; + bool play_hover_movies, + bool play_hover_music) override; void repaint_icons(std::vector& game_data, const QColor& icon_color, const QSize& icon_size, qreal device_pixel_ratio) override; diff --git a/rpcs3/rpcs3qt/gui_settings.h b/rpcs3/rpcs3qt/gui_settings.h index bdc6401031..561b07a9c4 100644 --- a/rpcs3/rpcs3qt/gui_settings.h +++ b/rpcs3/rpcs3qt/gui_settings.h @@ -217,6 +217,7 @@ namespace gui const gui_save gl_pref_gd_icon = gui_save(game_list, "pref_gd_icon", false); const gui_save gl_custom_icon = gui_save(game_list, "custom_icon", true); const gui_save gl_hover_gifs = gui_save(game_list, "hover_gifs", true); + const gui_save gl_hover_music = gui_save(game_list, "hover_music", true); const gui_save fs_emulator_dir_list = gui_save(fs, "emulator_dir_list", QStringList()); const gui_save fs_dev_hdd0_list = gui_save(fs, "dev_hdd0_list", QStringList()); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index d586ac55e4..b7900ce4d8 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -3412,6 +3412,7 @@ void main_window::CreateConnects() connect(ui->actionPreferGameDataIcons, &QAction::triggered, m_game_list_frame, &game_list_frame::SetPreferGameDataIcons); connect(ui->showCustomIconsAct, &QAction::triggered, m_game_list_frame, &game_list_frame::SetShowCustomIcons); connect(ui->playHoverGifsAct, &QAction::triggered, m_game_list_frame, &game_list_frame::SetPlayHoverGifs); + connect(ui->playHoverMusicAct, &QAction::triggered, m_game_list_frame, &game_list_frame::SetPlayHoverMusic); connect(m_game_list_frame, &game_list_frame::RequestIconSizeChange, this, [this](int val) { @@ -3718,6 +3719,7 @@ void main_window::ConfigureGuiFromSettings() ui->actionPreferGameDataIcons->setChecked(m_gui_settings->GetValue(gui::gl_pref_gd_icon).toBool()); ui->showCustomIconsAct->setChecked(m_gui_settings->GetValue(gui::gl_custom_icon).toBool()); ui->playHoverGifsAct->setChecked(m_gui_settings->GetValue(gui::gl_hover_gifs).toBool()); + ui->playHoverMusicAct->setChecked(m_gui_settings->GetValue(gui::gl_hover_music).toBool()); m_is_list_mode = m_gui_settings->GetValue(gui::gl_listMode).toBool(); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index ba72c86a1e..8fcf4ac610 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -372,6 +372,7 @@ + @@ -1524,6 +1525,17 @@ Boot ISO + + + true + + + true + + + Play Hover Music + + From 40229adb149b20b03c141055e66af614f791d169 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Fri, 20 Mar 2026 01:27:57 +0100 Subject: [PATCH 029/108] Qt: add gui audio volume setting --- rpcs3/rpcs3qt/gui_settings.cpp | 1 + rpcs3/rpcs3qt/gui_settings.h | 3 ++ rpcs3/rpcs3qt/main_window.cpp | 2 ++ rpcs3/rpcs3qt/qt_video_source.cpp | 11 ++++++- rpcs3/rpcs3qt/settings_dialog.cpp | 10 ++++++ rpcs3/rpcs3qt/settings_dialog.ui | 52 ++++++++++++++++++++++++++++++- rpcs3/rpcs3qt/tooltips.h | 1 + 7 files changed, 78 insertions(+), 2 deletions(-) diff --git a/rpcs3/rpcs3qt/gui_settings.cpp b/rpcs3/rpcs3qt/gui_settings.cpp index 8f42a14dd7..fdbab7345e 100644 --- a/rpcs3/rpcs3qt/gui_settings.cpp +++ b/rpcs3/rpcs3qt/gui_settings.cpp @@ -17,6 +17,7 @@ namespace gui { QString stylesheet; bool custom_stylesheet_active = false; + f32 volume = 1.0f; QString get_savestate_list_column_name(savestate_list_columns col) { diff --git a/rpcs3/rpcs3qt/gui_settings.h b/rpcs3/rpcs3qt/gui_settings.h index 561b07a9c4..c15b4166bf 100644 --- a/rpcs3/rpcs3qt/gui_settings.h +++ b/rpcs3/rpcs3qt/gui_settings.h @@ -13,6 +13,7 @@ namespace gui { extern QString stylesheet; extern bool custom_stylesheet_active; + extern f32 volume; enum custom_roles { @@ -139,6 +140,8 @@ namespace gui const QColor gl_icon_color = QColor(240, 240, 240, 255); + const gui_save gui_volume = gui_save(main_window, "guiVolume", 1.0f); + const gui_save rg_freeze = gui_save(main_window, "recentGamesFrozen", false); const gui_save rg_entries = gui_save(main_window, "recentGamesNames", QVariant::fromValue(q_pair_list())); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index b7900ce4d8..8f9324d73e 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -3663,6 +3663,8 @@ void main_window::ConfigureGuiFromSettings() m_recent_game.entries = gui_settings::Var2List(m_gui_settings->GetValue(gui::rg_entries)); m_recent_save.entries = gui_settings::Var2List(m_gui_settings->GetValue(gui::rs_entries)); + gui::volume = std::clamp(m_gui_settings->GetValue(gui::gui_volume).toFloat() * 100.0f, 0.0f, 100.0f); + const auto update_recent_games_menu = [this](bool is_savestate) { recent_game_wrapper& rgw = is_savestate ? m_recent_save : m_recent_game; diff --git a/rpcs3/rpcs3qt/qt_video_source.cpp b/rpcs3/rpcs3qt/qt_video_source.cpp index b14db8a019..da0f284e0a 100644 --- a/rpcs3/rpcs3qt/qt_video_source.cpp +++ b/rpcs3/rpcs3qt/qt_video_source.cpp @@ -1,7 +1,9 @@ #include "stdafx.h" #include "Emu/System.h" #include "Emu/system_config.h" +#include "Emu/Audio/audio_utils.h" #include "qt_video_source.h" +#include "gui_settings.h" #include "Loader/ISO.h" @@ -267,7 +269,14 @@ void qt_video_source::start_audio() } } - audio.output->setVolume(g_cfg.audio.volume.get() / 100.0f); + f32 volume = gui::volume; + + if (m_audio_instance_index == qt_audio_instance::emu_index) + { + volume = audio::get_volume(); + } + + audio.output->setVolume(std::clamp(volume, 0.0f, 1.0f)); audio.player->play(); audio.source = this; } diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 19fee331fb..b78eaf973a 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -2186,6 +2186,16 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_gui_settings->SetValue(gui::nav_global, checked); }); + // Audio + SubscribeTooltip(ui->gb_gui_volume, tooltips.settings.gui_volume); + connect(ui->guiVolume, &QSlider::valueChanged, [this](int value) + { + ui->guiVolumeLabel->setText(tr("User Interface: %0 %", "GUI volume").arg(value)); + gui::volume = std::clamp(value / 100.0f, 0.0f, 1.0f); + m_gui_settings->SetValue(gui::gui_volume, gui::volume); + }); + ui->guiVolume->setValue(std::clamp(m_gui_settings->GetValue(gui::gui_volume).toFloat() * 100.0f, 0.0f, 100.0f)); + // Discord: SubscribeTooltip(ui->useRichPresence, tooltips.settings.use_rich_presence); SubscribeTooltip(ui->discordState, tooltips.settings.discord_state); diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index c5a5378f4c..0d28134da8 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -513,7 +513,7 @@ - + 0 @@ -3980,6 +3980,56 @@ + + + + Volume + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + GUI: 0% + + + + + + + 100 + + + Qt::Orientation::Horizontal + + + QSlider::TickPosition::TicksBelow + + + 50 + + + + + + + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 3199d5cb4a..c5a5e5be31 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -228,6 +228,7 @@ public: const QString uuid = tr("This is the ID used for hardware statistics.\nIt should only be reset if you change your hardware configuration or if you copied RPCS3 to another PC."); const QString pad_navigation = tr("Use the game pad that is configured for player 1 to navigate in the GUI."); const QString global_navigation = tr("Keep control over pad navigation if RPCS3 is not the active window."); + const QString gui_volume = tr("Set the audio volume of the user interface. This does not affect the ingame audio."); // input From 51514f9dc8f91dcf1fad6927333b5df7dbdbc6ae Mon Sep 17 00:00:00 2001 From: Megamouse Date: Fri, 20 Mar 2026 01:53:40 +0100 Subject: [PATCH 030/108] Qt: start hover audio/video after a timeout --- rpcs3/rpcs3qt/qt_video_source.cpp | 25 ++++++++++++++++++++++++- rpcs3/rpcs3qt/qt_video_source.h | 4 ++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/rpcs3/rpcs3qt/qt_video_source.cpp b/rpcs3/rpcs3qt/qt_video_source.cpp index da0f284e0a..e05b997565 100644 --- a/rpcs3/rpcs3qt/qt_video_source.cpp +++ b/rpcs3/rpcs3qt/qt_video_source.cpp @@ -27,6 +27,7 @@ static std::array s_audio_instance = {}; qt_video_source::qt_video_source(bool is_emulation) : video_source() , m_audio_instance_index(is_emulation ? qt_audio_instance::emu_index : qt_audio_instance::gui_index) + , m_video_timer_timeout_ms(is_emulation ? 0 : 1000) { } @@ -56,7 +57,7 @@ void qt_video_source::set_active(bool active) if (active) { - start_movie(); + start_movie_timer(); } else { @@ -187,6 +188,27 @@ void qt_video_source::init_movie() } } +void qt_video_source::start_movie_timer() +{ + if (m_video_timer_timeout_ms == 0) + { + start_movie(); + return; + } + + if (!m_video_timer) + { + m_video_timer = std::make_unique(); + QObject::connect(m_video_timer.get(), &QTimer::timeout, m_video_timer.get(), [this]() + { + if (!m_active) return; + start_movie(); + }); + } + + m_video_timer->start(m_video_timer_timeout_ms); +} + void qt_video_source::start_movie() { init_movie(); @@ -210,6 +232,7 @@ void qt_video_source::start_movie() void qt_video_source::stop_movie() { m_active = false; + m_video_timer.reset(); if (m_movie) { diff --git a/rpcs3/rpcs3qt/qt_video_source.h b/rpcs3/rpcs3qt/qt_video_source.h index 8afc46ceb3..22f1acb5bd 100644 --- a/rpcs3/rpcs3qt/qt_video_source.h +++ b/rpcs3/rpcs3qt/qt_video_source.h @@ -10,6 +10,7 @@ #include #include #include +#include class qt_video_source : public video_source { @@ -29,6 +30,7 @@ public: void set_active(bool active) override; bool get_active() const override { return m_active; } + void start_movie_timer(); void start_movie(); void stop_movie(); @@ -51,11 +53,13 @@ protected: QString m_video_path; QString m_audio_path; u32 m_audio_instance_index = 0; + u32 m_video_timer_timeout_ms = 0; std::string m_iso_path; // path of the source archive QByteArray m_video_data{}; QImage m_image{}; std::vector m_image_path; + std::unique_ptr m_video_timer; std::unique_ptr m_video_buffer; std::unique_ptr m_media_player; std::unique_ptr m_video_sink; From 54999d15077b986bd993a02fb19a82525ffbd224 Mon Sep 17 00:00:00 2001 From: BehroozRezvani Date: Sun, 22 Mar 2026 04:37:20 +0000 Subject: [PATCH 031/108] Add Zed config files to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 4688d5fa52..a3911be3a0 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,9 @@ CMakeSettings.json *PVS-Studio* PVS/* +# Zed Editor files +.zed/* + # Ignore other system generated files x64/* rpcs3/x64/* From e5840ab8680f896084d766a92f6e20d6c4b12c6a Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 22 Mar 2026 13:29:58 +0100 Subject: [PATCH 032/108] Qt: fix audio timer loop --- rpcs3/rpcs3qt/game_list_grid_item.cpp | 1 + rpcs3/rpcs3qt/qt_video_source.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/rpcs3/rpcs3qt/game_list_grid_item.cpp b/rpcs3/rpcs3qt/game_list_grid_item.cpp index 928a5fa264..a3952a264a 100644 --- a/rpcs3/rpcs3qt/game_list_grid_item.cpp +++ b/rpcs3/rpcs3qt/game_list_grid_item.cpp @@ -77,6 +77,7 @@ bool game_list_grid_item::event(QEvent* event) set_active(true); break; case QEvent::HoverLeave: + case QEvent::FocusOut: set_active(false); break; default: diff --git a/rpcs3/rpcs3qt/qt_video_source.cpp b/rpcs3/rpcs3qt/qt_video_source.cpp index e05b997565..1f60800752 100644 --- a/rpcs3/rpcs3qt/qt_video_source.cpp +++ b/rpcs3/rpcs3qt/qt_video_source.cpp @@ -199,6 +199,7 @@ void qt_video_source::start_movie_timer() if (!m_video_timer) { m_video_timer = std::make_unique(); + m_video_timer->setSingleShot(true); QObject::connect(m_video_timer.get(), &QTimer::timeout, m_video_timer.get(), [this]() { if (!m_active) return; From 1eb72e4b717af63b599fbca00aeada0b81f18ae7 Mon Sep 17 00:00:00 2001 From: Ani Date: Sun, 22 Mar 2026 14:58:21 +0100 Subject: [PATCH 033/108] rpcs3: Add missing #include , fix gcc-16 compilation --- rpcs3/Emu/Cell/Modules/cellDmuxPamf.cpp | 1 + rpcs3/util/atomic.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/rpcs3/Emu/Cell/Modules/cellDmuxPamf.cpp b/rpcs3/Emu/Cell/Modules/cellDmuxPamf.cpp index 91ee7e2426..b8d9ba75b0 100644 --- a/rpcs3/Emu/Cell/Modules/cellDmuxPamf.cpp +++ b/rpcs3/Emu/Cell/Modules/cellDmuxPamf.cpp @@ -10,6 +10,7 @@ #include "cellDmuxPamf.h" #include +#include vm::gvar g_cell_dmux_core_ops_pamf; vm::gvar g_cell_dmux_core_ops_raw_es; diff --git a/rpcs3/util/atomic.cpp b/rpcs3/util/atomic.cpp index 595162cd04..59f0eebe49 100644 --- a/rpcs3/util/atomic.cpp +++ b/rpcs3/util/atomic.cpp @@ -49,6 +49,7 @@ static bool has_waitv() #include #include #include +#include #include "asm.hpp" #include "endian.hpp" From b607993b7b5ba79d6e253aa566766167e19eb154 Mon Sep 17 00:00:00 2001 From: Ani Date: Sun, 22 Mar 2026 15:11:30 +0100 Subject: [PATCH 034/108] overlay: Remove redundant redeclaration Fixes one gcc compilation warning --- rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.h b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.h index 13f47eb41e..ba8b730d44 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.h +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.h @@ -10,8 +10,6 @@ namespace rsx { namespace overlays { - void play_sound(sound_effect sound, std::optional volume); - struct home_menu_settings : public home_menu_page { public: From a0c91bf96a235e84de4ae834e6db659077e42a6e Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 21 Mar 2026 23:11:41 +0300 Subject: [PATCH 035/108] overlays: Use SDF equations to represent curved shapes --- rpcs3/Emu/RSX/Overlays/overlay_controls.cpp | 113 +++++++----------- rpcs3/Emu/RSX/Overlays/overlay_controls.h | 36 +++++- .../Program/GLSLSnippets/OverlayRenderFS.glsl | 74 ++++++++++++ rpcs3/Emu/RSX/Program/RSXOverlay.h | 17 ++- 4 files changed, 167 insertions(+), 73 deletions(-) diff --git a/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp b/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp index 02b0ff5a68..0a1af3c6c5 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp @@ -585,6 +585,18 @@ namespace rsx return result; } + void overlay_element::configure_sdf(compiled_resource::command_config& config, sdf_function func) + { + config.sdf_config.func = func; + config.sdf_config.cx = margin_left + x + (w / 2.f); + config.sdf_config.cy = margin_top + y + (h / 2.f); + config.sdf_config.hx = w / 2.f; + config.sdf_config.hy = h / 2.f; + config.sdf_config.br = 0.f; + config.sdf_config.bw = border_size; + config.sdf_config.border_color = border_color; + } + compiled_resource& overlay_element::get_compiled() { if (is_compiled()) @@ -609,6 +621,14 @@ namespace rsx config.pulse_sinus_offset = pulse_sinus_offset; config.pulse_speed_modifier = pulse_speed_modifier; + if (border_size != 0 && + border_color.a > 0.f && + w > border_size && + h > border_size) + { + configure_sdf(config, sdf_function::box); + } + auto& verts = compiled_resources_temp.draw_commands.front().verts; verts.resize(4); @@ -1095,82 +1115,33 @@ namespace rsx return compiled_resources; } -#ifdef __APPLE__ - if (true) -#else - if (radius == 0 || radius > (w / 2)) -#endif + overlay_element::get_compiled(); + auto& config = compiled_resources.draw_commands.front().config; + configure_sdf(config, sdf_function::rounded_box); + config.sdf_config.br = radius; + + m_is_compiled = true; + return compiled_resources; + } + + compiled_resource& ellipse::get_compiled() + { + if (is_compiled()) + { + return compiled_resources; + } + + compiled_resources.clear(); + + if (!is_visible()) { - // Invalid radius - compiled_resources = overlay_element::get_compiled(); m_is_compiled = true; return compiled_resources; } - compiled_resource compiled_resources_temp = {}; - compiled_resources_temp.append({}); // Bg horizontal mid - compiled_resources_temp.append({}); // Bg horizontal top - compiled_resources_temp.append({}); // Bg horizontal bottom - compiled_resources_temp.append({}); // Bg upper-left - compiled_resources_temp.append({}); // Bg lower-left - compiled_resources_temp.append({}); // Bg upper-right - compiled_resources_temp.append({}); // Bg lower-right - - for (auto& draw_cmd : compiled_resources_temp.draw_commands) - { - auto& config = draw_cmd.config; - config.color = back_color; - config.disable_vertex_snap = true; - config.pulse_glow = pulse_effect_enabled; - config.pulse_sinus_offset = pulse_sinus_offset; - config.pulse_speed_modifier = pulse_speed_modifier; - } - - auto& bg0 = compiled_resources_temp.draw_commands[0]; - auto& bg1 = compiled_resources_temp.draw_commands[1]; - auto& bg2 = compiled_resources_temp.draw_commands[2]; - - bg0.verts.emplace_back(f32(x), f32(y + radius), 0.f, 0.f); - bg0.verts.emplace_back(f32(x + w), f32(y + radius), 0.f, 0.f); - bg0.verts.emplace_back(f32(x), f32(y + h) - radius, 0.f, 0.f); - bg0.verts.emplace_back(f32(x + w), f32(y + h) - radius, 0.f, 0.f); - - bg1.verts.emplace_back(f32(x + radius), f32(y), 0.f, 0.f); - bg1.verts.emplace_back(f32(x + w) - radius, f32(y), 0.f, 0.f); - bg1.verts.emplace_back(f32(x + radius), f32(y + radius), 0.f, 0.f); - bg1.verts.emplace_back(f32(x + w) - radius, f32(y + radius), 0.f, 0.f); - - bg2.verts.emplace_back(f32(x + radius), f32(y + h) - radius, 0.f, 0.f); - bg2.verts.emplace_back(f32(x + w) - radius, f32(y + h) - radius, 0.f, 0.f); - bg2.verts.emplace_back(f32(x + radius), f32(y + h), 0.f, 0.f); - bg2.verts.emplace_back(f32(x + w) - radius, f32(y + h), 0.f, 0.f); - - // Generate the quadrants - const f32 corners[4][2] = - { - { f32(x + radius), f32(y + radius) }, - { f32(x + radius), f32(y + h) - radius }, - { f32(x + w) - radius, f32(y + radius) }, - { f32(x + w) - radius, f32(y + h) - radius } - }; - - const f32 radius_f = static_cast(radius); - const f32 scale[4][2] = - { - { -radius_f, -radius_f }, - { -radius_f, +radius_f }, - { +radius_f, -radius_f }, - { +radius_f, +radius_f } - }; - - for (int i = 0; i < 4; ++i) - { - auto& command = compiled_resources_temp.draw_commands[i + 3]; - command.config.primitives = rsx::overlays::primitive_type::triangle_fan; - command.verts = generate_unit_quadrant(num_control_points, corners[i], scale[i]); - } - - compiled_resources.add(std::move(compiled_resources_temp), margin_left, margin_top); + rounded_rect::get_compiled(); + auto& config = compiled_resources.draw_commands.front().config; + configure_sdf(config, sdf_function::ellipse); m_is_compiled = true; return compiled_resources; diff --git a/rpcs3/Emu/RSX/Overlays/overlay_controls.h b/rpcs3/Emu/RSX/Overlays/overlay_controls.h index dcfe33b199..0932eaa9ed 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_controls.h +++ b/rpcs3/Emu/RSX/Overlays/overlay_controls.h @@ -31,6 +31,14 @@ namespace rsx triangle_fan = 4 }; + enum class sdf_function : u8 + { + none = 0, + ellipse, + box, + rounded_box, + }; + struct image_info_base { int w = 0, h = 0, channels = 0; @@ -95,6 +103,20 @@ namespace rsx struct compiled_resource { + struct sdf_config_t + { + sdf_function func = sdf_function::none; + + f32 cx; // Center x + f32 cy; // Center y + f32 hx; // Half-size in X + f32 hy; // Half-size in Y + f32 br; // Border radius + f32 bw; // Border width + + color4f border_color; + }; + struct command_config { primitive_type primitives = primitive_type::quad_list; @@ -105,6 +127,8 @@ namespace rsx f32 pulse_sinus_offset = 0.0f; // The current pulse offset f32 pulse_speed_modifier = 0.005f; + sdf_config_t sdf_config; + areaf clip_rect = {}; bool clip_region = false; @@ -171,6 +195,9 @@ namespace rsx f32 pulse_sinus_offset = 0.0f; // The current pulse offset f32 pulse_speed_modifier = 0.005f; + u8 border_size = 0; + color4f border_color = { 0.f, 0.f, 0.f, 1.f }; + // Analog to command_config::get_sinus_value // Apply modifier for sinus pulse. Resets the pulse. For example: // 0 -> reset to 0.5 rising @@ -237,6 +264,8 @@ namespace rsx protected: bool m_is_compiled = false; // Only use m_is_compiled as a getter in is_compiled() if possible + + void configure_sdf(compiled_resource::command_config& config, sdf_function func); }; struct layout_container : public overlay_element @@ -317,12 +346,17 @@ namespace rsx struct rounded_rect : public overlay_element { u8 radius = 5; - u8 num_control_points = 8; // Smoothness control using overlay_element::overlay_element; compiled_resource& get_compiled() override; }; + struct ellipse : public rounded_rect + { + using rounded_rect::rounded_rect; + compiled_resource& get_compiled() override; + }; + struct image_view : public overlay_element { protected: diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/OverlayRenderFS.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/OverlayRenderFS.glsl index 84fdfdb8b7..6bf8b07bee 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/OverlayRenderFS.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/OverlayRenderFS.glsl @@ -13,6 +13,11 @@ R"( #define SAMPLER_MODE_FONT3D 2 #define SAMPLER_MODE_TEXTURE2D 3 +#define SDF_DISABLED 0 +#define SDF_ELLIPSE 1 +#define SDF_BOX 2 +#define SDF_ROUND_BOX 3 + #ifdef VULKAN layout(set=0, binding=0) uniform sampler2D fs0; layout(set=0, binding=1) uniform sampler2DArray fs1; @@ -34,11 +39,17 @@ layout(%push_block) uniform FragmentConfiguration uint fragment_config; float timestamp; float blur_intensity; + vec4 sdf_params; + vec4 sdf_origin; + vec4 sdf_border_color; }; #else uniform uint fragment_config; uniform float timestamp; uniform float blur_intensity; + uniform vec4 sdf_params; + uniform vec2 sdf_origin; + uniform vec4 sdf_border_color; #endif struct config_t @@ -46,6 +57,7 @@ struct config_t bool clip_fragments; bool use_pulse_glow; uint sampler_mode; + uint sdf; }; config_t unpack_fragment_options() @@ -54,9 +66,64 @@ config_t unpack_fragment_options() result.clip_fragments = bitfieldExtract(fragment_config, 0, 1) != 0; result.use_pulse_glow = bitfieldExtract(fragment_config, 1, 1) != 0; result.sampler_mode = bitfieldExtract(fragment_config, 2, 2); + result.sdf = bitfieldExtract(fragment_config, 4, 2); return result; } +vec4 SDF_blend( + const in float sd, + const in float border_width, + const in vec4 inner_color, + const in vec4 border_color, + const in vec4 outer_color) +{ + // Crucially, we need to get the derivative without subracting the border width. + // Subtracting the border width makes the function non-continuous and makes the jaggies hard to get rid of. + float fw = fwidth(sd); + + // Compute the two transition points. The inner edge is of course biased by the border amount as the clamping point + // Treat smoothstep as fancy clamp where e0 < x < e1 + float a = smoothstep(-border_width + fw, -border_width - fw, sd); // inner edge transition + float b = smoothstep(fw, -fw, sd); // outer edge transition + + // Mix the 3 colors with the transition values. + vec4 color = mix(outer_color, border_color, b); + color = mix(color, inner_color, a); + return color; +} + +float SDF_fn(const in uint sdf) +{ + const vec2 p = floor(gl_FragCoord.xy) - sdf_origin.xy; // Screen-spac distance + const vec2 hs = sdf_params.xy; // Half size + const float r = sdf_params.z; // Radius (for round box, ellipses use half size instead) + vec2 v; // Scratch + float d; // Distance calculated + + switch (sdf) + { + case SDF_ELLIPSE: + // Slightly inaccurate hack, but good enough for classification and allows oval shapes + d = length(p / hs) - 1.f; + // Now we need to correct for the border because the circle was scaled down to a unit + return d * length(hs); + case SDF_BOX: + // Insanity, reduced junction of 3 functions + // If for each axis the axis-aligned distance = D then you can select/clamp each axis separately by doing a max(D, 0) on all dimensions + // Length then does the squareroot transformation. + // The second term is to add back the inner distance which is useful for rendering borders + v = abs(p) - hs; + return length(max(v, 0.f)) + min(max(v.x, v.y), 0.0); + case SDF_ROUND_BOX: + // Modified BOX SDF. + // The half box size is shrunk by R in it's diagonal, but we add radius back into the output to bias the output again + v = abs(p) - (hs - r); + return length(max(v, 0.f)) + min(max(v.x, v.y), 0.0) - r; + default: + return -1.f; + } +} + vec4 blur_sample(sampler2D tex, vec2 coord, vec2 tex_offset) { vec2 coords[9]; @@ -125,6 +192,13 @@ void main() diff_color.a *= (sin(timestamp) + 1.f) * 0.5f; } + if (config.sdf != SDF_DISABLED) + { + const float border_w = sdf_params.w; // Border width + const float d = SDF_fn(config.sdf); + diff_color = SDF_blend(d, border_w, diff_color, sdf_border_color, vec4(0.)); + } + switch (config.sampler_mode) { default: diff --git a/rpcs3/Emu/RSX/Program/RSXOverlay.h b/rpcs3/Emu/RSX/Program/RSXOverlay.h index ebb2071a8b..42ee92823d 100644 --- a/rpcs3/Emu/RSX/Program/RSXOverlay.h +++ b/rpcs3/Emu/RSX/Program/RSXOverlay.h @@ -23,7 +23,8 @@ namespace rsx { fragment_clip_bit = 0, pulse_glow_bit = 1, - sampling_mode_bit = 2 + sampling_mode_bit = 2, + sdf_func_offset_bit = 4 }; public: @@ -51,6 +52,13 @@ namespace rsx return *this; } + fragment_options& set_sdf(sdf_function func) + { + value &= ~(0x3 << e_offsets::sdf_func_offset_bit); + value |= (static_cast(func) << e_offsets::sdf_func_offset_bit); + return *this; + } + u32 get() const { return value; @@ -74,6 +82,13 @@ namespace rsx } } + void set_bits(u32 offset, u32 count, u32 set) + { + const u32 mask = (0xffffffffu >> (32 - count)) << offset; + value &= ~mask; + value |= set; + } + public: vertex_options& disable_vertex_snap(bool enable) { From 45bae0046a3e792ae20c572c863a5347d8ac594b Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 21 Mar 2026 23:14:34 +0300 Subject: [PATCH 036/108] vk: Add support for basic SDF rendering - Does not support scaled coordinates yet --- rpcs3/Emu/RSX/VK/VKOverlays.cpp | 21 +++++++++++++++++++-- rpcs3/Emu/RSX/VK/VKOverlays.h | 2 ++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKOverlays.cpp b/rpcs3/Emu/RSX/VK/VKOverlays.cpp index 55daab4a90..c555e7a341 100644 --- a/rpcs3/Emu/RSX/VK/VKOverlays.cpp +++ b/rpcs3/Emu/RSX/VK/VKOverlays.cpp @@ -510,7 +510,7 @@ namespace vk glsl::input_type_push_constant, 0, 0, - glsl::push_constant_ref {.offset = 68, .size = 12 } + glsl::push_constant_ref {.offset = 68, .size = 60 } ) ); return result; @@ -527,6 +527,10 @@ namespace vk // 68: uint fragment_config; // 72: float timestamp; // 76: float blur_intensity; + // 80: vec4 sdf_params; + // 96: vec2 sdf_origin; + // 104: vec2 reserved; + // 112: vec4 sdf_border_color; f32 push_buf[32]; // 1. Vertex config (00 - 63) @@ -557,13 +561,24 @@ namespace vk .texture_mode(m_texture_type) .clip_fragments(m_clip_enabled) .pulse_glow(m_pulse_glow) + .set_sdf(m_sdf_config.func) .get(); push_buf[0] = std::bit_cast(frag_config); push_buf[1] = m_time; push_buf[2] = m_blur_strength; + push_buf[3] = m_sdf_config.hx; + push_buf[4] = m_sdf_config.hy; + push_buf[5] = m_sdf_config.br; + push_buf[6] = m_sdf_config.bw; + push_buf[7] = m_sdf_config.cx; + push_buf[8] = m_sdf_config.cy; + push_buf[9] = 0.f; + push_buf[10] = 0.f; - vkCmdPushConstants(cmd, program->layout(), VK_SHADER_STAGE_FRAGMENT_BIT, 68, 12, push_buf); + std::memcpy(push_buf + 11, m_sdf_config.border_color.rgba, 16); + + vkCmdPushConstants(cmd, program->layout(), VK_SHADER_STAGE_FRAGMENT_BIT, 68, 60, push_buf); } void ui_overlay_renderer::set_primitive_type(rsx::overlays::primitive_type type) @@ -644,6 +659,8 @@ namespace vk m_clip_region = command.config.clip_rect; m_disable_vertex_snap = command.config.disable_vertex_snap; + m_sdf_config = command.config.sdf_config; + vk::image_view* src = nullptr; switch (command.config.texture_ref) { diff --git a/rpcs3/Emu/RSX/VK/VKOverlays.h b/rpcs3/Emu/RSX/VK/VKOverlays.h index 414c7c4945..de2c218ebe 100644 --- a/rpcs3/Emu/RSX/VK/VKOverlays.h +++ b/rpcs3/Emu/RSX/VK/VKOverlays.h @@ -132,6 +132,8 @@ namespace vk areaf m_clip_region; coordf m_viewport; + rsx::overlays::compiled_resource::sdf_config_t m_sdf_config{}; + std::vector> resources; std::unordered_map> font_cache; std::unordered_map> view_cache; From 57e37862f44292b73540e8d8939a71ac9d953d81 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 21 Mar 2026 23:18:36 +0300 Subject: [PATCH 037/108] overlays: Use ellipse SDF for circles --- rpcs3/Emu/RSX/Overlays/overlay_checkbox.cpp | 2 +- rpcs3/Emu/RSX/Overlays/overlay_slider.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/Overlays/overlay_checkbox.cpp b/rpcs3/Emu/RSX/Overlays/overlay_checkbox.cpp index f7cb19237e..d3199f0866 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_checkbox.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_checkbox.cpp @@ -99,7 +99,7 @@ namespace rsx::overlays } auto ellipse_part = std::make_unique(); - auto circle_part = std::make_unique(); + auto circle_part = std::make_unique(); ellipse_part->set_size(dim * 2, dim / 2); ellipse_part->set_pos(0, dim / 4); diff --git a/rpcs3/Emu/RSX/Overlays/overlay_slider.cpp b/rpcs3/Emu/RSX/Overlays/overlay_slider.cpp index bf00563ef5..0040d5b20d 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_slider.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_slider.cpp @@ -29,7 +29,7 @@ namespace rsx::overlays // Base components auto background = std::make_unique(); auto foreground = std::make_unique(); - auto indicator = std::make_unique(); + auto indicator = std::make_unique(); auto value_label = std::make_unique - + + + 1 + + + 0.5 + + @@ -3545,10 +3552,24 @@ - + + + 1 + + + 0.5 + + + + + + Use Window Space + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index c5a5e5be31..0276ac0f82 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -169,10 +169,11 @@ public: const QString perf_overlay_update_interval = tr("Sets the time interval in which the performance overlay is being updated (measured in milliseconds).\nSetting this to 16 milliseconds will refresh the performance overlay at roughly 60Hz.\nThe performance overlay refresh rate does not affect the frame graph statistics and can only be as fast as the current game allows."); const QString perf_overlay_font_size = tr("Sets the font size of the performance overlay (measured in pixels)."); const QString perf_overlay_opacity = tr("Sets the opacity of the performance overlay (measured in %)."); - const QString perf_overlay_margin_x = tr("Sets the horizontal distance to the screen border relative to the screen quadrant (measured in pixels)."); - const QString perf_overlay_margin_y = tr("Sets the vertical distance to the screen border relative to the screen quadrant (measured in pixels)."); + const QString perf_overlay_margin_x = tr("Sets the horizontal distance to the screen border relative to the screen quadrant (measured in %)."); + const QString perf_overlay_margin_y = tr("Sets the vertical distance to the screen border relative to the screen quadrant (measured in %)."); const QString perf_overlay_center_x = tr("Centers the performance overlay horizontally and overrides the horizontal margin."); const QString perf_overlay_center_y = tr("Centers the performance overlay vertically and overrides the vertical margin."); + const QString perf_overlay_use_window_space = tr("Position overlay relative to the full window surface, enabling placement outside game's render area."); const QString shader_load_bg_enabled = tr("Shows a background image during the native shader loading dialog/loading screen.\nBy default the used image will be /PS3_GAME/PIC1.PNG."); const QString shader_load_bg_darkening = tr("Changes the background image darkening effect strength of the native shader loading dialog.\nThis may be used to improve readability and/or aesthetics."); From a4523651c7aa188c1b72ea6ec9f1d807a68c2178 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sat, 24 Jan 2026 22:39:51 +0200 Subject: [PATCH 075/108] RPCS3: Notify RAM shortage, Log current and peak RAM usage --- Utilities/Thread.cpp | 10 ++++++ rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 2 +- rpcs3/Emu/Cell/lv2/sys_fs.cpp | 46 +++++++------------------- rpcs3/Emu/perf_monitor.cpp | 35 +++++++++++++++----- rpcs3/rpcs3.cpp | 4 +++ rpcs3/util/sysinfo.cpp | 16 ++++++++- rpcs3/util/sysinfo.hpp | 2 ++ 7 files changed, 70 insertions(+), 45 deletions(-) diff --git a/Utilities/Thread.cpp b/Utilities/Thread.cpp index 9514ed9cdf..b22c1aeb52 100644 --- a/Utilities/Thread.cpp +++ b/Utilities/Thread.cpp @@ -2879,6 +2879,16 @@ void thread_base::exec() } } + if (auto [total, current] = utils::get_memory_usage(); total - current <= 256 * 1024 * 1024) + { + if (reason_buf.empty()) + { + reason_buf = std::string{reason}; + } + + fmt::append(reason_buf, " (Possible RAM deficiency: free RAM: %dMB)", (total - current) / (1024 * 1024)); + } + if (!reason_buf.empty()) { reason = reason_buf; diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index 96d4afa95e..b10845889b 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -8615,7 +8615,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // Blocks starting from 0x0 or invalid instruction won't be compiled, may need special interpreter fallback } - if (!m_patterns.empty()) + if (!m_patterns.empty() && g_cfg.core.spu_debug) { std::string out_dump; dump(result, out_dump); diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index ffd31227a8..beec0fc4e4 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -900,10 +900,8 @@ lv2_file::open_raw_result_t lv2_file::open_raw(const std::string& local_path, s3 switch (auto error = fs::g_tls_error) { case fs::error::noent: return {CELL_ENOENT}; - default: sys_fs.error("lv2_file::open(): unknown error %s", error); break; + default: fmt::throw_exception("unknown error %s", error); } - - return {CELL_EIO}; } if (flags & CELL_FS_O_MSELF && !verify_mself(file)) @@ -1374,8 +1372,7 @@ error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr path, vm::ptr fd) } default: { - sys_fs.error("sys_fs_opendir(): unknown error %s", error); - return {CELL_EIO, path}; + fmt::throw_exception("unknown error %s", error); } } } @@ -1597,8 +1594,7 @@ error_code sys_fs_stat(ppu_thread& ppu, vm::cptr path, vm::ptr } default: { - sys_fs.error("sys_fs_stat(): unknown error %s", error); - return {CELL_EIO, path}; + fmt::throw_exception("unknown error %s", error); } } } @@ -1732,10 +1728,8 @@ error_code sys_fs_mkdir(ppu_thread& ppu, vm::cptr path, s32 mode) { return {sys_fs.warning, CELL_EEXIST, path}; } - default: sys_fs.error("sys_fs_mkdir(): unknown error %s", error); + default: fmt::throw_exception("unknown error %s", error); } - - return {CELL_EIO, path}; // ??? } sys_fs.notice("sys_fs_mkdir(): directory %s created", path); @@ -1797,10 +1791,8 @@ error_code sys_fs_rename(ppu_thread& ppu, vm::cptr from, vm::cptr to { case fs::error::noent: return {CELL_ENOENT, from}; case fs::error::exist: return {CELL_EEXIST, to}; - default: sys_fs.error("sys_fs_rename(): unknown error %s", error); + default: fmt::throw_exception("unknown error %s", error); } - - return {CELL_EIO, from}; // ??? } sys_fs.notice("sys_fs_rename(): %s renamed to %s", from, to); @@ -1852,10 +1844,8 @@ error_code sys_fs_rmdir(ppu_thread& ppu, vm::cptr path) { case fs::error::noent: return {CELL_ENOENT, path}; case fs::error::notempty: return {CELL_ENOTEMPTY, path}; - default: sys_fs.error("sys_fs_rmdir(): unknown error %s", error); + default: fmt::throw_exception("unknown error %s", error); } - - return {CELL_EIO, path}; // ??? } sys_fs.notice("sys_fs_rmdir(): directory %s removed", path); @@ -1910,10 +1900,8 @@ error_code sys_fs_unlink(ppu_thread& ppu, vm::cptr path) { return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, CELL_ENOENT, path}; } - default: sys_fs.error("sys_fs_unlink(): unknown error %s", error); + default: fmt::throw_exception("unknown error %s", error); } - - return {CELL_EIO, path}; // ??? } sys_fs.notice("sys_fs_unlink(): file %s deleted", path); @@ -2632,10 +2620,8 @@ error_code sys_fs_lseek(ppu_thread& ppu, u32 fd, s64 offset, s32 whence, vm::ptr switch (auto error = fs::g_tls_error) { case fs::error::inval: return {CELL_EINVAL, "fd=%u, offset=0x%x, whence=%d", fd, offset, whence}; - default: sys_fs.error("sys_fs_lseek(): unknown error %s", error); + default: fmt::throw_exception("unknown error %s", error); } - - return CELL_EIO; // ??? } lock.unlock(); @@ -2751,10 +2737,8 @@ error_code sys_fs_get_block_size(ppu_thread& ppu, vm::cptr path, vm::ptr(ppu.test_stopped()); @@ -2809,10 +2793,8 @@ error_code sys_fs_truncate(ppu_thread& ppu, vm::cptr path, u64 size) { return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, CELL_ENOENT, path}; } - default: sys_fs.error("sys_fs_truncate(): unknown error %s", error); + default: fmt::throw_exception("unknown error %s", error); } - - return {CELL_EIO, path}; // ??? } return CELL_OK; @@ -2858,10 +2840,8 @@ error_code sys_fs_ftruncate(ppu_thread& ppu, u32 fd, u64 size) switch (auto error = fs::g_tls_error) { case fs::error::ok: - default: sys_fs.error("sys_fs_ftruncate(): unknown error %s", error); + default: fmt::throw_exception("unknown error %s", error); } - - return CELL_EIO; // ??? } return CELL_OK; @@ -3057,10 +3037,8 @@ error_code sys_fs_utime(ppu_thread& ppu, vm::cptr path, vm::cptr per_core_usage; std::string msg; - for (u64 sleep_until = get_system_time(); thread_ctrl::state() != thread_state::aborting;) + for (u64 sleep_until = get_system_time();;) { thread_ctrl::wait_until(&sleep_until, update_interval_us); elapsed_us += update_interval_us; - if (thread_ctrl::state() == thread_state::aborting) - { - break; - } - double total_usage = 0.0; stats.get_per_core_usage(per_core_usage, total_usage); - if (elapsed_us >= log_interval_us) + const u64 current_mem_use = utils::get_memory_usage().second; + const u64 mem_use_increase = current_mem_use >= max_memory_usage ? current_mem_use - max_memory_usage : 0; + + const u64 log_interval = (mem_use_increase >= log_mem_increase ? log_interval_us_min : log_interval_us_max); + + if (elapsed_us >= log_interval || thread_ctrl::state() == thread_state::aborting) { + max_memory_usage = std::max(current_mem_use, max_memory_usage); elapsed_us = 0; const bool is_paused = Emu.IsPaused(); @@ -76,7 +82,18 @@ void perf_monitor::operator()() fmt::append(msg, "%s %.1f%%", i > 0 ? "," : "", per_core_usage[i]); } + if (max_memory_usage) + { + fmt::append(msg, ", RAM Usage: %dMB (Peak: %dMB)", current_mem_use / (1024 * 1024), max_memory_usage / (1024 * 1024)); + } + perf_log.notice("%s", msg); + + if (thread_ctrl::state() == thread_state::aborting) + { + // Log once before terminating + break; + } } } } diff --git a/rpcs3/rpcs3.cpp b/rpcs3/rpcs3.cpp index cb2058874e..86e3c47171 100644 --- a/rpcs3/rpcs3.cpp +++ b/rpcs3/rpcs3.cpp @@ -197,6 +197,10 @@ std::set get_one_drive_paths() fmt::append(buf, "\nBuild: \"%s\"", rpcs3::get_verbose_version()); fmt::append(buf, "\nDate: \"%s\"", std::chrono::system_clock::now()); + + const auto [total, current] = utils::get_memory_usage(); + + fmt::append(buf, "\nRAM Usage: %dMB/%dMB (%dMB free)", current / (1024 * 1024), total / (1024 * 1024), (total - current) / (1024 * 1024)); } std::string_view text = s_is_error_launch ? _text : buf; diff --git a/rpcs3/util/sysinfo.cpp b/rpcs3/util/sysinfo.cpp index 8abe584a94..81c0b3d31a 100755 --- a/rpcs3/util/sysinfo.cpp +++ b/rpcs3/util/sysinfo.cpp @@ -736,6 +736,20 @@ std::string utils::get_firmware_version() return {}; } +std::pair utils::get_memory_usage() +{ +#ifdef _WIN32 + ::MEMORYSTATUSEX status{}; + status.dwLength = sizeof(status); + ::GlobalMemoryStatusEx(&status); + return { status.ullTotalPhys, status.ullTotalPhys - status.ullAvailPhys }; + +#else + // TODO + return { get_total_memory(), 0 }; +#endif +} + utils::OS_version utils::get_OS_version() { OS_version res {}; @@ -1087,7 +1101,7 @@ static const bool s_tsc_freq_evaluated = []() -> bool u64 utils::get_total_memory() { #ifdef _WIN32 - ::MEMORYSTATUSEX memInfo; + ::MEMORYSTATUSEX memInfo{}; memInfo.dwLength = sizeof(memInfo); ::GlobalMemoryStatusEx(&memInfo); return memInfo.ullTotalPhys; diff --git a/rpcs3/util/sysinfo.hpp b/rpcs3/util/sysinfo.hpp index d9bd0c6660..491c54b088 100755 --- a/rpcs3/util/sysinfo.hpp +++ b/rpcs3/util/sysinfo.hpp @@ -71,6 +71,8 @@ namespace utils std::string get_firmware_version(); + std::pair get_memory_usage(); + struct OS_version { std::string type; From 976cd1ce6640daf6d19a9618c40b100f89fe9434 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:39:33 +0300 Subject: [PATCH 076/108] SaveStates: Fix restart after saving --- .../RSX/Overlays/HomeMenu/overlay_home_menu_savestate.cpp | 2 +- rpcs3/Emu/System.cpp | 6 +++--- rpcs3/Emu/System.h | 2 +- rpcs3/rpcs3qt/gs_frame.cpp | 2 +- rpcs3/rpcs3qt/main_window.cpp | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_savestate.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_savestate.cpp index b359fb562c..4d0681193f 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_savestate.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_savestate.cpp @@ -26,7 +26,7 @@ namespace rsx { if (!suspend_mode) { - Emu.after_kill_callback = []() { Emu.Restart(); }; + Emu.after_kill_callback = []() { Emu.Restart(true, false); }; // Make sure we keep the game window opened Emu.SetContinuousMode(true); diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 7818bd20d5..4fdb5e47f3 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -3996,7 +3996,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s })); } -game_boot_result Emulator::Restart(bool graceful) +game_boot_result Emulator::Restart(bool graceful, bool reset_path) { if (m_state == system_state::stopping) { @@ -4004,7 +4004,7 @@ game_boot_result Emulator::Restart(bool graceful) return game_boot_result::still_running; } - Emu.after_kill_callback = [this] + Emu.after_kill_callback = [this, reset_path] { // Reset boot path in case of ISO if (m_path.starts_with(iso_device::virtual_device_name)) @@ -4016,7 +4016,7 @@ game_boot_result Emulator::Restart(bool graceful) } // If continuous mode changed the path, restart from the original executable - if (!m_path_original.empty() && m_path_original != m_path) + if (reset_path && !m_path_original.empty() && m_path_original != m_path) { sys_log.notice("Restart: Resetting boot path from '%s' to original '%s'", m_path, m_path_original); m_path = m_path_original; diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 3bf33bcd52..ac2243389c 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -442,7 +442,7 @@ public: void Resume(); void GracefulShutdown(bool allow_autoexit = true, bool async_op = false, bool savestate = false, bool continuous_mode = false); void Kill(bool allow_autoexit = true, bool savestate = false, savestate_stage* stage = nullptr); - game_boot_result Restart(bool graceful = true); + game_boot_result Restart(bool graceful = true, bool reset_path = true); bool Quit(bool force_quit); static void CleanUp(); diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index c73db8a687..2dab09d87f 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -362,7 +362,7 @@ void gs_frame::handle_shortcut(gui::shortcuts::shortcut shortcut_key, const QKey { Emu.after_kill_callback = []() { - Emu.Restart(); + Emu.Restart(true, false); }; // Make sure we keep the game window opened diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 94157571e8..a82fe5b091 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -2538,7 +2538,7 @@ void main_window::CreateConnects() { Emu.after_kill_callback = []() { - Emu.Restart(); + Emu.Restart(true, false); }; // Make sure we keep the game window opened From e2dff6bbf8fce033f1c92ff7afb4d3ae91b3cb47 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 28 Mar 2026 03:05:46 +0300 Subject: [PATCH 077/108] rsx: Implement resolution scale change handling in surface cache --- rpcs3/Emu/RSX/Common/surface_store.h | 95 +++++++++++++++++++++++++++- rpcs3/Emu/RSX/Common/surface_utils.h | 14 ++-- rpcs3/Emu/RSX/GL/GLPresent.cpp | 13 ++++ rpcs3/Emu/RSX/GL/GLRenderTargets.h | 17 +++-- rpcs3/Emu/RSX/VK/VKPresent.cpp | 13 ++++ rpcs3/Emu/RSX/VK/VKRenderTargets.h | 17 +++-- rpcs3/Emu/RSX/rsx_utils.h | 6 ++ 7 files changed, 155 insertions(+), 20 deletions(-) diff --git a/rpcs3/Emu/RSX/Common/surface_store.h b/rpcs3/Emu/RSX/Common/surface_store.h index 51152ea32a..0cfbbe849c 100644 --- a/rpcs3/Emu/RSX/Common/surface_store.h +++ b/rpcs3/Emu/RSX/Common/surface_store.h @@ -132,7 +132,7 @@ namespace rsx free_rsx_memory(Traits::get(sink)); } - Traits::clone_surface(cmd, sink, region.source, new_address, region); + Traits::clone_surface(cmd, sink, region.source, new_address, region, region.source->resolution_scaling_config); allocate_rsx_memory(Traits::get(sink)); if (invalidated) [[unlikely]] @@ -449,7 +449,7 @@ namespace rsx } } - if (Traits::surface_matches_properties(surface, format, width, height, antialias)) + if (Traits::surface_matches_properties(surface, format, width, height, antialias, scaling_config)) { if (!pitch_compatible) { @@ -496,7 +496,7 @@ namespace rsx for (auto It = invalidated_resources.begin(); It != invalidated_resources.end(); It++) { auto &surface = *It; - if (Traits::surface_matches_properties(surface, format, width, height, antialias, true)) + if (Traits::surface_matches_properties(surface, format, width, height, antialias, scaling_config, true)) { new_surface_storage = std::move(surface); Traits::notify_surface_reused(new_surface_storage); @@ -1469,5 +1469,94 @@ namespace rsx } } } + + void sync_scaling_config(command_list_type cmd, const rsx::surface_scaling_config_t& active_config) + { + auto process_list_function = [&](surface_ranged_map& data, const utils::address_range32& range) + { + std::vector surfaces_to_clone; + + for (auto It = data.begin_range(range); It != data.end();) + { + auto surface = Traits::get(It->second); + if (surface->get_resolution_scaling_config() == active_config) + { + ++It; + continue; + } + + surfaces_to_clone.push_back(surface); + + // Invalidate the previous surface + invalidate(It->second); + It = data.erase(It); + } + + for (auto& surface : surfaces_to_clone) + { + // Enqueue the memory transfer + surface_storage_type sink{}; + deferred_clipped_region copy{}; + copy.width = surface->template get_surface_width<>(); + copy.height = surface->template get_surface_height<>(); + copy.transfer_scale_x = 1.f; + copy.transfer_scale_y = 1.f; + copy.target = nullptr; + copy.source = surface; + + Traits::clone_surface(cmd, sink, surface, surface->base_addr, copy, active_config); + allocate_rsx_memory(Traits::get(sink)); + + // Replace with the new one + ensure(copy.target == Traits::get(sink)); + data.emplace(surface->get_memory_range(), std::move(sink)); + } + }; + + const auto rtt_bind_backup = m_bound_render_targets; + const auto dsv_bind_backup = m_bound_depth_stencil; + + // Unbind everything. We'll restore it later + for (auto& rtt_bind : m_bound_render_targets) + { + rtt_bind = {}; + } + + m_bound_depth_stencil = {}; + + process_list_function(m_render_targets_storage, m_render_targets_memory_range); + process_list_function(m_depth_stencil_storage, m_depth_stencil_memory_range); + + // Restore bindings. + for (int i = 0; i < 4; ++i) + { + const auto address = rtt_bind_backup[i].first; + if (!address) + { + continue; + } + + auto rtt = m_render_targets_storage.find(address); + ensure(rtt != m_render_targets_storage.end()); + + m_bound_render_targets[i] = + { + address, + Traits::get(rtt->second) + }; + } + + if (const auto ds_address = dsv_bind_backup.first) + { + auto ds = m_depth_stencil_storage.find(ds_address); + ensure(ds != m_depth_stencil_storage.end()); + + m_bound_depth_stencil = + { + ds_address, + Traits::get(ds->second) + }; + } + } }; } diff --git a/rpcs3/Emu/RSX/Common/surface_utils.h b/rpcs3/Emu/RSX/Common/surface_utils.h index ffd71e6003..e740bf34a7 100644 --- a/rpcs3/Emu/RSX/Common/surface_utils.h +++ b/rpcs3/Emu/RSX/Common/surface_utils.h @@ -554,10 +554,14 @@ namespace rsx } // Apply resolution scale if needed - if (resolution_scaling_config.scale_percent != 100) + if (resolution_scaling_config.scale_percent != 100 || + region.source->resolution_scaling_config.scale_percent != 100) { - auto [src_width, src_height] = rsx::apply_resolution_scale(resolution_scaling_config, slice.width, slice.height, slice.source->width(), slice.source->height()); - auto [dst_width, dst_height] = rsx::apply_resolution_scale(resolution_scaling_config, slice.width, slice.height, slice.target->width(), slice.target->height()); + const auto& src_res_scale = region.source->resolution_scaling_config; + const auto& dst_res_scale = resolution_scaling_config; + + auto [src_width, src_height] = rsx::apply_resolution_scale(src_res_scale, slice.width, slice.height, slice.source->width(), slice.source->height()); + auto [dst_width, dst_height] = rsx::apply_resolution_scale(dst_res_scale, slice.width, slice.height, slice.target->width(), slice.target->height()); slice.transfer_scale_x *= f32(dst_width) / src_width; slice.transfer_scale_y *= f32(dst_height) / src_height; @@ -565,8 +569,8 @@ namespace rsx slice.width = src_width; slice.height = src_height; - std::tie(slice.src_x, slice.src_y) = rsx::apply_resolution_scale(resolution_scaling_config, slice.src_x, slice.src_y, slice.source->width(), slice.source->height()); - std::tie(slice.dst_x, slice.dst_y) = rsx::apply_resolution_scale(resolution_scaling_config, slice.dst_x, slice.dst_y, slice.target->width(), slice.target->height()); + std::tie(slice.src_x, slice.src_y) = rsx::apply_resolution_scale(src_res_scale, slice.src_x, slice.src_y, slice.source->width(), slice.source->height()); + std::tie(slice.dst_x, slice.dst_y) = rsx::apply_resolution_scale(dst_res_scale, slice.dst_x, slice.dst_y, slice.target->width(), slice.target->height()); } } diff --git a/rpcs3/Emu/RSX/GL/GLPresent.cpp b/rpcs3/Emu/RSX/GL/GLPresent.cpp index 2e23ca33b6..2aa11868ee 100644 --- a/rpcs3/Emu/RSX/GL/GLPresent.cpp +++ b/rpcs3/Emu/RSX/GL/GLPresent.cpp @@ -517,6 +517,19 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info) m_frame->flip(m_context); rsx::thread::flip(info); + // Data sync + const rsx::surface_scaling_config_t active_res_scaling_config = + { + .scale_percent = static_cast(g_cfg.video.resolution_scale_percent), + .min_scalable_dimension = static_cast(g_cfg.video.min_scalable_dimension), + }; + + if (active_res_scaling_config != this->resolution_scaling_config) + { + m_rtts.sync_scaling_config(cmd, active_res_scaling_config); + this->resolution_scaling_config = active_res_scaling_config; + } + // Cleanup m_gl_texture_cache.on_frame_end(); m_vertex_cache->purge(); diff --git a/rpcs3/Emu/RSX/GL/GLRenderTargets.h b/rpcs3/Emu/RSX/GL/GLRenderTargets.h index ecf7e161da..f5e2252b92 100644 --- a/rpcs3/Emu/RSX/GL/GLRenderTargets.h +++ b/rpcs3/Emu/RSX/GL/GLRenderTargets.h @@ -229,13 +229,14 @@ struct gl_render_target_traits void clone_surface( gl::command_context& cmd, std::unique_ptr& sink, gl::render_target* ref, - u32 address, barrier_descriptor_t& prev) + u32 address, barrier_descriptor_t& prev, + const rsx::surface_scaling_config_t& scaling_config) { if (!sink) { auto internal_format = static_cast(ref->get_internal_format()); const auto [new_w, new_h] = rsx::apply_resolution_scale( - ref->resolution_scaling_config, + scaling_config, prev.width, prev.height, ref->get_surface_width(), ref->get_surface_height()); @@ -248,7 +249,7 @@ struct gl_render_target_traits sink->format_info = ref->format_info; sink->sample_layout = ref->sample_layout; - sink->resolution_scaling_config = ref->resolution_scaling_config; + sink->resolution_scaling_config = scaling_config; sink->set_name(fmt::format("SINK_%u@0x%x", sink->id(), address)); sink->set_spp(ref->get_spp()); @@ -385,6 +386,7 @@ struct gl_render_target_traits gl::texture::internal_format format, usz width, usz height, rsx::surface_antialiasing antialias, + const rsx::surface_scaling_config_t& scaling_config, bool check_refs = false) { if (check_refs && surface->has_refs()) @@ -392,7 +394,8 @@ struct gl_render_target_traits return surface->get_internal_format() == format && surface->get_spp() == get_format_sample_count(antialias) && - surface->matches_dimensions(static_cast(width), static_cast(height)); + surface->matches_dimensions(static_cast(width), static_cast(height)) && + surface->resolution_scaling_config == scaling_config; } static @@ -401,10 +404,11 @@ struct gl_render_target_traits rsx::surface_color_format format, usz width, usz height, rsx::surface_antialiasing antialias, + const rsx::surface_scaling_config_t& scaling_config, bool check_refs=false) { const auto internal_fmt = rsx::internals::surface_color_format_to_gl(format).internal_format; - return int_surface_matches_properties(surface, internal_fmt, width, height, antialias, check_refs); + return int_surface_matches_properties(surface, internal_fmt, width, height, antialias, scaling_config, check_refs); } static @@ -413,10 +417,11 @@ struct gl_render_target_traits rsx::surface_depth_format2 format, usz width, usz height, rsx::surface_antialiasing antialias, + const rsx::surface_scaling_config_t& scaling_config, bool check_refs = false) { const auto internal_fmt = rsx::internals::surface_depth_format_to_gl(format).internal_format; - return int_surface_matches_properties(surface, internal_fmt, width, height, antialias, check_refs); + return int_surface_matches_properties(surface, internal_fmt, width, height, antialias, scaling_config, check_refs); } static diff --git a/rpcs3/Emu/RSX/VK/VKPresent.cpp b/rpcs3/Emu/RSX/VK/VKPresent.cpp index 16adbadc41..47f59381bb 100644 --- a/rpcs3/Emu/RSX/VK/VKPresent.cpp +++ b/rpcs3/Emu/RSX/VK/VKPresent.cpp @@ -963,4 +963,17 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info) m_frame->flip(m_context); rsx::thread::flip(info); + + // Data sync + const rsx::surface_scaling_config_t active_res_scaling_config = + { + .scale_percent = static_cast(g_cfg.video.resolution_scale_percent), + .min_scalable_dimension = static_cast(g_cfg.video.min_scalable_dimension), + }; + + if (active_res_scaling_config != this->resolution_scaling_config) + { + m_rtts.sync_scaling_config(*m_current_command_buffer, active_res_scaling_config); + this->resolution_scaling_config = active_res_scaling_config; + } } diff --git a/rpcs3/Emu/RSX/VK/VKRenderTargets.h b/rpcs3/Emu/RSX/VK/VKRenderTargets.h index 7d4f515b4e..c040e9bca0 100644 --- a/rpcs3/Emu/RSX/VK/VKRenderTargets.h +++ b/rpcs3/Emu/RSX/VK/VKRenderTargets.h @@ -337,12 +337,13 @@ namespace vk static void clone_surface( vk::command_buffer& cmd, std::unique_ptr& sink, vk::render_target* ref, - u32 address, barrier_descriptor_t& prev) + u32 address, barrier_descriptor_t& prev, + const rsx::surface_scaling_config_t& scaling_config) { if (!sink) { const auto [new_w, new_h] = rsx::apply_resolution_scale( - ref->resolution_scaling_config, + scaling_config, prev.width, prev.height, ref->get_surface_width(), ref->get_surface_height()); @@ -363,7 +364,7 @@ namespace vk sink->add_ref(); sink->sample_layout = ref->sample_layout; - sink->resolution_scaling_config = ref->resolution_scaling_config; + sink->resolution_scaling_config = scaling_config; sink->set_spp(ref->get_spp()); sink->format_info = ref->format_info; @@ -521,6 +522,7 @@ namespace vk VkFormat format, usz width, usz height, rsx::surface_antialiasing antialias, + const rsx::surface_scaling_config_t& scaling_config, bool check_refs) { if (check_refs && surface->has_refs()) @@ -531,7 +533,8 @@ namespace vk return (surface->info.format == format && surface->get_spp() == get_format_sample_count(antialias) && - surface->matches_dimensions(static_cast(width), static_cast(height))); + surface->matches_dimensions(static_cast(width), static_cast(height))) && + surface->resolution_scaling_config == scaling_config; } static bool surface_matches_properties( @@ -539,10 +542,11 @@ namespace vk rsx::surface_color_format format, usz width, usz height, rsx::surface_antialiasing antialias, + const rsx::surface_scaling_config_t& scaling_config, bool check_refs = false) { VkFormat vk_format = vk::get_compatible_surface_format(format).first; - return int_surface_matches_properties(surface, vk_format, width, height, antialias, check_refs); + return int_surface_matches_properties(surface, vk_format, width, height, antialias, scaling_config, check_refs); } static bool surface_matches_properties( @@ -550,11 +554,12 @@ namespace vk rsx::surface_depth_format2 format, usz width, usz height, rsx::surface_antialiasing antialias, + const rsx::surface_scaling_config_t& scaling_config, bool check_refs = false) { auto device = vk::get_current_renderer(); VkFormat vk_format = vk::get_compatible_depth_surface_format(device->get_formats_support(), format); - return int_surface_matches_properties(surface, vk_format, width, height, antialias, check_refs); + return int_surface_matches_properties(surface, vk_format, width, height, antialias, scaling_config, check_refs); } static void spill_buffer(std::unique_ptr& /*bo*/) diff --git a/rpcs3/Emu/RSX/rsx_utils.h b/rpcs3/Emu/RSX/rsx_utils.h index 310af56dab..79aef602c6 100644 --- a/rpcs3/Emu/RSX/rsx_utils.h +++ b/rpcs3/Emu/RSX/rsx_utils.h @@ -220,6 +220,12 @@ namespace rsx u16 min_scalable_dimension = 0; f32 scale_factor() const { return scale_percent * 0.01f; } + + bool operator == (const surface_scaling_config_t& that) const + { + return this->scale_percent == that.scale_percent && + this->min_scalable_dimension == that.min_scalable_dimension; + } }; template From fe3cbde1d3a0b18edca4c978fdb36779a1e8cd24 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 28 Mar 2026 03:10:31 +0300 Subject: [PATCH 078/108] rsx: Add optimization to skip cloning surfaces whose dimensions did not change - If only the threshold changed or the surface is below threshold anyway, nothing will change - In those cases, just update the internal data and continue --- rpcs3/Emu/RSX/Common/surface_store.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/rpcs3/Emu/RSX/Common/surface_store.h b/rpcs3/Emu/RSX/Common/surface_store.h index 0cfbbe849c..70007178fa 100644 --- a/rpcs3/Emu/RSX/Common/surface_store.h +++ b/rpcs3/Emu/RSX/Common/surface_store.h @@ -1485,6 +1485,21 @@ namespace rsx continue; } + // Perform a test scaling and check if anything is different after scaling + // There are many cases where this will avoid creating new surfaces + const auto [new_w, new_h] = rsx::apply_inverse_resolution_scale( + active_config, + surface->template get_surface_width<>(), + surface->template get_surface_height<>()); + + if (new_w == surface->width() && new_h == surface->height()) + { + // Not affected by resolution scale. Just update the details and move on. + surface->resolution_scaling_config = active_config; + ++It; + continue; + } + surfaces_to_clone.push_back(surface); // Invalidate the previous surface From c00fa2cc8c6fd49fcff1a208cd1507bd73dc5fdc Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 28 Mar 2026 03:39:30 +0300 Subject: [PATCH 079/108] config: Make resolution scaling a dynamic option --- rpcs3/Emu/system_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 75d824938f..6403754dd8 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -161,7 +161,7 @@ struct cfg_root : cfg::node cfg::_bool precise_zpass_count{ this, "Accurate ZCULL stats", true }; cfg::_int<1, 8> consecutive_frames_to_draw{ this, "Consecutive Frames To Draw", 1, true}; cfg::_int<1, 8> consecutive_frames_to_skip{ this, "Consecutive Frames To Skip", 1, true}; - cfg::_int<25, 800> resolution_scale_percent{ this, "Resolution Scale", 100 }; + cfg::_int<25, 800> resolution_scale_percent{ this, "Resolution Scale", 100, true }; cfg::uint<0, 16> anisotropic_level_override{ this, "Anisotropic Filter Override", 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 }; From 91c136b6d0db5de188bfcac5f8ae1962aa4c73de Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 28 Mar 2026 03:54:23 +0300 Subject: [PATCH 080/108] rsx/overlay: Add resolution scale slider to home menu --- .../RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp | 7 +++---- rpcs3/Emu/localized_string_id.h | 1 + rpcs3/Emu/system_config.h | 2 +- rpcs3/rpcs3qt/localized_emu.h | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp index 1a2f99dca3..3d9439c112 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp @@ -152,16 +152,15 @@ namespace rsx home_menu_settings_video::home_menu_settings_video(s16 x, s16 y, u16 width, u16 height, bool use_separators, home_menu_page* parent) : home_menu_settings_page(x, y, width, height, use_separators, parent, get_localized_string(localized_string_id::HOME_MENU_SETTINGS_VIDEO)) { + add_unsigned_slider(&g_cfg.video.resolution_scale_percent, localized_string_id::HOME_MENU_SETTINGS_VIDEO_RESOLUTION_SCALE_PERCENT, "%", 25); + add_dropdown(&g_cfg.video.vsync, localized_string_id::HOME_MENU_SETTINGS_VIDEO_VSYNC); add_dropdown(&g_cfg.video.frame_limit, localized_string_id::HOME_MENU_SETTINGS_VIDEO_FRAME_LIMIT); add_unsigned_slider(&g_cfg.video.anisotropic_level_override, localized_string_id::HOME_MENU_SETTINGS_VIDEO_ANISOTROPIC_OVERRIDE, "x", 2, {{0, "Auto"}}, {14}); add_dropdown(&g_cfg.video.output_scaling, localized_string_id::HOME_MENU_SETTINGS_VIDEO_OUTPUT_SCALING); - if (g_cfg.video.renderer == video_renderer::vulkan && g_cfg.video.output_scaling == output_scaling_mode::fsr) - { - add_unsigned_slider(&g_cfg.video.rcas_sharpening_intensity, localized_string_id::HOME_MENU_SETTINGS_VIDEO_RCAS_SHARPENING, " %", 1); - } + add_unsigned_slider(&g_cfg.video.rcas_sharpening_intensity, localized_string_id::HOME_MENU_SETTINGS_VIDEO_RCAS_SHARPENING, " %", 1); add_checkbox(&g_cfg.video.stretch_to_display_area, localized_string_id::HOME_MENU_SETTINGS_VIDEO_STRETCH_TO_DISPLAY); diff --git a/rpcs3/Emu/localized_string_id.h b/rpcs3/Emu/localized_string_id.h index 9387b7f6e7..08fb639bcf 100644 --- a/rpcs3/Emu/localized_string_id.h +++ b/rpcs3/Emu/localized_string_id.h @@ -222,6 +222,7 @@ enum class localized_string_id HOME_MENU_SETTINGS_VIDEO_ANISOTROPIC_OVERRIDE, HOME_MENU_SETTINGS_VIDEO_OUTPUT_SCALING, HOME_MENU_SETTINGS_VIDEO_RCAS_SHARPENING, + HOME_MENU_SETTINGS_VIDEO_RESOLUTION_SCALE_PERCENT, HOME_MENU_SETTINGS_VIDEO_STRETCH_TO_DISPLAY, HOME_MENU_SETTINGS_VIDEO_STEREO_MODE, HOME_MENU_SETTINGS_INPUT, diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 6403754dd8..38dbea98f8 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -161,7 +161,7 @@ struct cfg_root : cfg::node cfg::_bool precise_zpass_count{ this, "Accurate ZCULL stats", true }; cfg::_int<1, 8> consecutive_frames_to_draw{ this, "Consecutive Frames To Draw", 1, true}; cfg::_int<1, 8> consecutive_frames_to_skip{ this, "Consecutive Frames To Skip", 1, true}; - cfg::_int<25, 800> resolution_scale_percent{ this, "Resolution Scale", 100, true }; + cfg::uint<25, 800> resolution_scale_percent{ this, "Resolution Scale", 100, true }; cfg::uint<0, 16> anisotropic_level_override{ this, "Anisotropic Filter Override", 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 }; diff --git a/rpcs3/rpcs3qt/localized_emu.h b/rpcs3/rpcs3qt/localized_emu.h index 6f2acb31c7..c0a0780860 100644 --- a/rpcs3/rpcs3qt/localized_emu.h +++ b/rpcs3/rpcs3qt/localized_emu.h @@ -242,6 +242,7 @@ private: case localized_string_id::HOME_MENU_SETTINGS_VIDEO_ANISOTROPIC_OVERRIDE: return tr("Anisotropic Filter Override", "Video"); case localized_string_id::HOME_MENU_SETTINGS_VIDEO_OUTPUT_SCALING: return tr("Output Scaling", "Video"); case localized_string_id::HOME_MENU_SETTINGS_VIDEO_RCAS_SHARPENING: return tr("FidelityFX CAS Sharpening Intensity", "Video"); + case localized_string_id::HOME_MENU_SETTINGS_VIDEO_RESOLUTION_SCALE_PERCENT: return tr("Resolution Scale", "Video"); case localized_string_id::HOME_MENU_SETTINGS_VIDEO_STRETCH_TO_DISPLAY: return tr("Stretch To Display Area", "Video"); case localized_string_id::HOME_MENU_SETTINGS_VIDEO_STEREO_MODE: return tr("Stereo Mode", "Video"); case localized_string_id::HOME_MENU_SETTINGS_INPUT: return tr("Input"); From b57390d3cd8d6d232f52755216970deaffcf2c45 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 28 Mar 2026 04:03:54 +0300 Subject: [PATCH 081/108] rsx/overlays: Add resolution scale threshold to home menu --- rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp | 1 + rpcs3/Emu/localized_string_id.h | 1 + rpcs3/Emu/system_config.h | 2 +- rpcs3/rpcs3qt/localized_emu.h | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp index 3d9439c112..65a3b4263d 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp @@ -153,6 +153,7 @@ namespace rsx : home_menu_settings_page(x, y, width, height, use_separators, parent, get_localized_string(localized_string_id::HOME_MENU_SETTINGS_VIDEO)) { add_unsigned_slider(&g_cfg.video.resolution_scale_percent, localized_string_id::HOME_MENU_SETTINGS_VIDEO_RESOLUTION_SCALE_PERCENT, "%", 25); + add_unsigned_slider(&g_cfg.video.min_scalable_dimension, localized_string_id::HOME_MENU_SETTINGS_VIDEO_RESOLUTION_SCALE_THRESHOLD, "px", 1); add_dropdown(&g_cfg.video.vsync, localized_string_id::HOME_MENU_SETTINGS_VIDEO_VSYNC); diff --git a/rpcs3/Emu/localized_string_id.h b/rpcs3/Emu/localized_string_id.h index 08fb639bcf..0bcf1caf8b 100644 --- a/rpcs3/Emu/localized_string_id.h +++ b/rpcs3/Emu/localized_string_id.h @@ -223,6 +223,7 @@ enum class localized_string_id HOME_MENU_SETTINGS_VIDEO_OUTPUT_SCALING, HOME_MENU_SETTINGS_VIDEO_RCAS_SHARPENING, HOME_MENU_SETTINGS_VIDEO_RESOLUTION_SCALE_PERCENT, + HOME_MENU_SETTINGS_VIDEO_RESOLUTION_SCALE_THRESHOLD, HOME_MENU_SETTINGS_VIDEO_STRETCH_TO_DISPLAY, HOME_MENU_SETTINGS_VIDEO_STEREO_MODE, HOME_MENU_SETTINGS_INPUT, diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 38dbea98f8..099e0243a1 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -164,7 +164,7 @@ struct cfg_root : cfg::node cfg::uint<25, 800> resolution_scale_percent{ this, "Resolution Scale", 100, true }; cfg::uint<0, 16> anisotropic_level_override{ this, "Anisotropic Filter Override", 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::uint<1, 1024> min_scalable_dimension{ this, "Minimum Scalable Dimension", 16, true }; 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 }; cfg::uint<0, 16667> driver_wakeup_delay{ this, "Driver Wake-Up Delay", 0, true }; diff --git a/rpcs3/rpcs3qt/localized_emu.h b/rpcs3/rpcs3qt/localized_emu.h index c0a0780860..fe0451b44c 100644 --- a/rpcs3/rpcs3qt/localized_emu.h +++ b/rpcs3/rpcs3qt/localized_emu.h @@ -243,6 +243,7 @@ private: case localized_string_id::HOME_MENU_SETTINGS_VIDEO_OUTPUT_SCALING: return tr("Output Scaling", "Video"); case localized_string_id::HOME_MENU_SETTINGS_VIDEO_RCAS_SHARPENING: return tr("FidelityFX CAS Sharpening Intensity", "Video"); case localized_string_id::HOME_MENU_SETTINGS_VIDEO_RESOLUTION_SCALE_PERCENT: return tr("Resolution Scale", "Video"); + case localized_string_id::HOME_MENU_SETTINGS_VIDEO_RESOLUTION_SCALE_THRESHOLD: return tr("Resolution Scale Threshold", "Video"); case localized_string_id::HOME_MENU_SETTINGS_VIDEO_STRETCH_TO_DISPLAY: return tr("Stretch To Display Area", "Video"); case localized_string_id::HOME_MENU_SETTINGS_VIDEO_STEREO_MODE: return tr("Stereo Mode", "Video"); case localized_string_id::HOME_MENU_SETTINGS_INPUT: return tr("Input"); From 64c24959cb519f623990d1b54f9337fffe53fee9 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 28 Mar 2026 04:13:33 +0300 Subject: [PATCH 082/108] rsx: Fix clang build --- rpcs3/Emu/RSX/Common/surface_store.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/Common/surface_store.h b/rpcs3/Emu/RSX/Common/surface_store.h index 70007178fa..d3e9db45b2 100644 --- a/rpcs3/Emu/RSX/Common/surface_store.h +++ b/rpcs3/Emu/RSX/Common/surface_store.h @@ -1474,7 +1474,7 @@ namespace rsx { auto process_list_function = [&](surface_ranged_map& data, const utils::address_range32& range) { - std::vector surfaces_to_clone; + std::vector surfaces_to_clone; for (auto It = data.begin_range(range); It != data.end();) { From 0052108e876a384b637259468d8e7627d99d66ef Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 28 Mar 2026 13:12:23 +0300 Subject: [PATCH 083/108] rsx/rtt: Fix transfer region calculation when source and destination scaling does not match --- rpcs3/Emu/RSX/Common/surface_utils.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/rpcs3/Emu/RSX/Common/surface_utils.h b/rpcs3/Emu/RSX/Common/surface_utils.h index e740bf34a7..8ec97600bc 100644 --- a/rpcs3/Emu/RSX/Common/surface_utils.h +++ b/rpcs3/Emu/RSX/Common/surface_utils.h @@ -559,9 +559,11 @@ namespace rsx { const auto& src_res_scale = region.source->resolution_scaling_config; const auto& dst_res_scale = resolution_scaling_config; + const auto src_surface = ensure(dynamic_cast(slice.source)); + const auto dst_surface = ensure(dynamic_cast(slice.target)); - auto [src_width, src_height] = rsx::apply_resolution_scale(src_res_scale, slice.width, slice.height, slice.source->width(), slice.source->height()); - auto [dst_width, dst_height] = rsx::apply_resolution_scale(dst_res_scale, slice.width, slice.height, slice.target->width(), slice.target->height()); + auto [src_width, src_height] = rsx::apply_resolution_scale(src_res_scale, slice.width, slice.height, src_surface->get_surface_width(), src_surface->get_surface_height()); + auto [dst_width, dst_height] = rsx::apply_resolution_scale(dst_res_scale, slice.width, slice.height, dst_surface->get_surface_width(), dst_surface->get_surface_height()); slice.transfer_scale_x *= f32(dst_width) / src_width; slice.transfer_scale_y *= f32(dst_height) / src_height; @@ -569,8 +571,8 @@ namespace rsx slice.width = src_width; slice.height = src_height; - std::tie(slice.src_x, slice.src_y) = rsx::apply_resolution_scale(src_res_scale, slice.src_x, slice.src_y, slice.source->width(), slice.source->height()); - std::tie(slice.dst_x, slice.dst_y) = rsx::apply_resolution_scale(dst_res_scale, slice.dst_x, slice.dst_y, slice.target->width(), slice.target->height()); + std::tie(slice.src_x, slice.src_y) = rsx::apply_resolution_scale(src_res_scale, slice.src_x, slice.src_y, src_surface->get_surface_width(), src_surface->get_surface_height()); + std::tie(slice.dst_x, slice.dst_y) = rsx::apply_resolution_scale(dst_res_scale, slice.dst_x, slice.dst_y, dst_surface->get_surface_width(), dst_surface->get_surface_height()); } } From 069821a2e79a14dd5035c19b7f34990cc7887e28 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 28 Mar 2026 13:13:36 +0300 Subject: [PATCH 084/108] rsx/rtts: Fix broken optimization check - Typo. Applying inverse res scale is really not what we want --- rpcs3/Emu/RSX/Common/surface_store.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/Common/surface_store.h b/rpcs3/Emu/RSX/Common/surface_store.h index d3e9db45b2..88268de22c 100644 --- a/rpcs3/Emu/RSX/Common/surface_store.h +++ b/rpcs3/Emu/RSX/Common/surface_store.h @@ -1487,7 +1487,7 @@ namespace rsx // Perform a test scaling and check if anything is different after scaling // There are many cases where this will avoid creating new surfaces - const auto [new_w, new_h] = rsx::apply_inverse_resolution_scale( + const auto [new_w, new_h] = rsx::apply_resolution_scale( active_config, surface->template get_surface_width<>(), surface->template get_surface_height<>()); From 51ea735cb5fab5f3ab0dda756f19a753aaa07451 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 28 Mar 2026 19:33:35 +0300 Subject: [PATCH 085/108] rsx/vk: Try to reclaim memory aggressively when changing resolution scale --- rpcs3/Emu/RSX/Common/surface_store.h | 6 +++++- rpcs3/Emu/RSX/VK/VKPresent.cpp | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/Common/surface_store.h b/rpcs3/Emu/RSX/Common/surface_store.h index 88268de22c..4b3aaa0605 100644 --- a/rpcs3/Emu/RSX/Common/surface_store.h +++ b/rpcs3/Emu/RSX/Common/surface_store.h @@ -1523,8 +1523,12 @@ namespace rsx allocate_rsx_memory(Traits::get(sink)); // Replace with the new one - ensure(copy.target == Traits::get(sink)); + auto new_surface = Traits::get(sink); + ensure(copy.target == new_surface); data.emplace(surface->get_memory_range(), std::move(sink)); + + // Force barrier to reduce VRAM pressure + new_surface->memory_barrier(cmd, rsx::surface_access::memory_read); } }; diff --git a/rpcs3/Emu/RSX/VK/VKPresent.cpp b/rpcs3/Emu/RSX/VK/VKPresent.cpp index 47f59381bb..432e8c7e87 100644 --- a/rpcs3/Emu/RSX/VK/VKPresent.cpp +++ b/rpcs3/Emu/RSX/VK/VKPresent.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "VKGSRender.h" #include "vkutils/buffer_object.h" +#include "vkutils/memory.h" #include "Emu/RSX/Overlays/overlay_manager.h" #include "Emu/RSX/Overlays/overlay_debug_overlay.h" #include "Emu/Cell/Modules/cellVideoOut.h" @@ -973,7 +974,22 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info) if (active_res_scaling_config != this->resolution_scaling_config) { + // First, try to reclaim any memory since the res scale upgrade is so memory intensive + if (const auto severity = vk::vmm_determine_memory_load_severity(); + severity > rsx::problem_severity::low && m_rtts.handle_memory_pressure(*m_current_command_buffer, severity)) + { + flush_command_queue(true); + } + + // Then apply the change m_rtts.sync_scaling_config(*m_current_command_buffer, active_res_scaling_config); this->resolution_scaling_config = active_res_scaling_config; + + // Finally reclaim any unused resources + if (const auto severity = vk::vmm_determine_memory_load_severity(); + severity > rsx::problem_severity::low && m_rtts.handle_memory_pressure(*m_current_command_buffer, severity)) + { + flush_command_queue(true); + } } } From f0f9ef6f3057dfb201dbdbab519207afc9f2bfe4 Mon Sep 17 00:00:00 2001 From: oltolm Date: Fri, 13 Mar 2026 13:55:51 +0100 Subject: [PATCH 086/108] cmake: fix build with latest Curl --- buildfiles/cmake/FindWolfSSL.cmake | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/buildfiles/cmake/FindWolfSSL.cmake b/buildfiles/cmake/FindWolfSSL.cmake index d2e30be60b..35f316837c 100644 --- a/buildfiles/cmake/FindWolfSSL.cmake +++ b/buildfiles/cmake/FindWolfSSL.cmake @@ -1,4 +1,3 @@ -set(WOLFSSL_LIBRARY ON) -set(WOLFSSL_INCLUDE_DIR ON) -set(WOLFSSL_LIBRARIES wolfssl) +set(WOLFSSL_LIBRARY wolfssl) +set(WOLFSSL_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/3rdparty/wolfssl) set(WOLFSSL_FOUND TRUE) From e6204d3c1d1ef048a220025bf75851f53f6751c3 Mon Sep 17 00:00:00 2001 From: oltolm Date: Fri, 27 Mar 2026 17:20:42 +0100 Subject: [PATCH 087/108] CMake: fix the case USE_SYSTEM_ZLIB=OFF and USE_SYSTEM_CURL=OFF --- buildfiles/cmake/FindZLIB.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildfiles/cmake/FindZLIB.cmake b/buildfiles/cmake/FindZLIB.cmake index 0a29abafa9..ff5869a5f7 100644 --- a/buildfiles/cmake/FindZLIB.cmake +++ b/buildfiles/cmake/FindZLIB.cmake @@ -3,9 +3,9 @@ if(USE_SYSTEM_ZLIB) find_package(ZLIB) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) else() - add_library(ZLIB::ZLIB INTERFACE IMPORTED) + add_library(ZLIB::ZLIB STATIC IMPORTED) set_target_properties(ZLIB::ZLIB PROPERTIES - INTERFACE_LINK_LIBRARIES zlibstatic + IMPORTED_LOCATION "${CMAKE_BINARY_DIR}/3rdparty/zlib/zlib/libzlibstatic.a" INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/3rdparty/zlib/zlib;${CMAKE_BINARY_DIR}/3rdparty/zlib/zlib") set(ZLIB_FOUND TRUE) endif() From 1791876eda617a9b3b0af44977996debed0e40ff Mon Sep 17 00:00:00 2001 From: oltolm Date: Fri, 27 Mar 2026 17:21:41 +0100 Subject: [PATCH 088/108] remove unused lambda captures --- rpcs3/headless_application.cpp | 2 +- rpcs3/rpcs3qt/auto_pause_settings_dialog.cpp | 2 +- rpcs3/rpcs3qt/clans_settings_dialog.cpp | 2 +- rpcs3/rpcs3qt/emu_settings.cpp | 2 +- rpcs3/rpcs3qt/game_list_actions.cpp | 2 +- rpcs3/rpcs3qt/game_list_grid.cpp | 2 +- rpcs3/rpcs3qt/game_list_table.cpp | 2 +- rpcs3/rpcs3qt/gs_frame.cpp | 2 +- rpcs3/rpcs3qt/gui_application.cpp | 8 ++++---- rpcs3/rpcs3qt/kernel_explorer.cpp | 4 ++-- rpcs3/rpcs3qt/main_window.cpp | 8 ++++---- rpcs3/rpcs3qt/savestate_manager_dialog.cpp | 2 +- rpcs3/rpcs3qt/settings_dialog.cpp | 4 ++-- rpcs3/rpcs3qt/sound_effect_manager_dialog.cpp | 2 +- rpcs3/rpcs3qt/trophy_manager_dialog.cpp | 12 ++++++------ rpcs3/rpcs3qt/vfs_dialog_path_widget.cpp | 2 +- 16 files changed, 29 insertions(+), 29 deletions(-) diff --git a/rpcs3/headless_application.cpp b/rpcs3/headless_application.cpp index 135a61c491..5d9240254d 100644 --- a/rpcs3/headless_application.cpp +++ b/rpcs3/headless_application.cpp @@ -49,7 +49,7 @@ void headless_application::InitializeCallbacks() { EmuCallbacks callbacks = CreateCallbacks(); - callbacks.try_to_quit = [this](bool force_quit, std::function on_exit) -> bool + callbacks.try_to_quit = [](bool force_quit, std::function on_exit) -> bool { if (force_quit) { diff --git a/rpcs3/rpcs3qt/auto_pause_settings_dialog.cpp b/rpcs3/rpcs3qt/auto_pause_settings_dialog.cpp index 26cbfc96b7..4eaadb9e7e 100644 --- a/rpcs3/rpcs3qt/auto_pause_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/auto_pause_settings_dialog.cpp @@ -181,7 +181,7 @@ void auto_pause_settings_dialog::ShowContextMenu(const QPoint &pos) OnEntryConfig(idx, true); }); connect(remove, &QAction::triggered, this, &auto_pause_settings_dialog::OnRemove); - connect(config, &QAction::triggered, this, [=, this]() {OnEntryConfig(row, false); }); + connect(config, &QAction::triggered, this, [=]() {OnEntryConfig(row, false); }); myMenu.exec(m_pause_list->viewport()->mapToGlobal(pos)); } diff --git a/rpcs3/rpcs3qt/clans_settings_dialog.cpp b/rpcs3/rpcs3qt/clans_settings_dialog.cpp index 9745d11294..0b26b791ee 100644 --- a/rpcs3/rpcs3qt/clans_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/clans_settings_dialog.cpp @@ -66,7 +66,7 @@ clans_settings_dialog::clans_settings_dialog(QWidget* parent) g_cfg_clans.save(); }); - connect(m_cbx_protocol, &QComboBox::currentIndexChanged, this, [this](int index) + connect(m_cbx_protocol, &QComboBox::currentIndexChanged, this, [](int index) { if (index < 0) return; diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 30860e10f5..0ec017a02c 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -207,7 +207,7 @@ bool emu_settings::ValidateSettings(bool cleanup) bool is_clean = true; std::function&, cfg::_base*)> search_level; - search_level = [&search_level, &is_clean, &cleanup, this](int level, YAML::Node& yml_node, std::vector& keys, cfg::_base* cfg_base) + search_level = [&search_level, &is_clean, &cleanup](int level, YAML::Node& yml_node, std::vector& keys, cfg::_base* cfg_base) { if (!yml_node || !yml_node.IsMap()) { diff --git a/rpcs3/rpcs3qt/game_list_actions.cpp b/rpcs3/rpcs3qt/game_list_actions.cpp index 5ff938e990..25b9f06b96 100644 --- a/rpcs3/rpcs3qt/game_list_actions.cpp +++ b/rpcs3/rpcs3qt/game_list_actions.cpp @@ -908,7 +908,7 @@ void game_list_actions::BatchActionBySerials(progress_dialog* pdlg, const std::s const int serials_size = ::narrow(serials.size()); - *iterate_over_serial = [=, this, index_ptr = index](int index) + *iterate_over_serial = [=, index_ptr = index](int index) { if (index == serials_size) { diff --git a/rpcs3/rpcs3qt/game_list_grid.cpp b/rpcs3/rpcs3qt/game_list_grid.cpp index 94fa571f7e..903fdf016a 100644 --- a/rpcs3/rpcs3qt/game_list_grid.cpp +++ b/rpcs3/rpcs3qt/game_list_grid.cpp @@ -21,7 +21,7 @@ game_list_grid::game_list_grid() Q_EMIT IconReady(game, item); }; - connect(this, &game_list_grid::IconReady, this, [this](const game_info& game, const movie_item_base* item) + connect(this, &game_list_grid::IconReady, this, [](const game_info& game, const movie_item_base* item) { if (game && item && game->item == item) item->image_change_callback(); }, Qt::QueuedConnection); // The default 'AutoConnection' doesn't seem to work in this specific case... diff --git a/rpcs3/rpcs3qt/game_list_table.cpp b/rpcs3/rpcs3qt/game_list_table.cpp index 53d0754bce..3d40467bb1 100644 --- a/rpcs3/rpcs3qt/game_list_table.cpp +++ b/rpcs3/rpcs3qt/game_list_table.cpp @@ -54,7 +54,7 @@ game_list_table::game_list_table(game_list_frame* frame, std::shared_ptritem == item) item->image_change_callback(); }); diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index 2dab09d87f..bcc148d9a6 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -793,7 +793,7 @@ f64 gs_frame::client_display_rate() { f64 rate = 20.; // Minimum is 20 - Emu.BlockingCallFromMainThread([this, &rate]() + Emu.BlockingCallFromMainThread([&rate]() { const QList screens = QGuiApplication::screens(); diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index 606a081fff..8379e2f5e0 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -737,8 +737,8 @@ void gui_application::InitializeCallbacks() callbacks.get_msg_dialog = [this]() -> std::shared_ptr { return m_show_gui ? std::make_shared() : nullptr; }; callbacks.get_osk_dialog = [this]() -> std::shared_ptr { return m_show_gui ? std::make_shared() : nullptr; }; callbacks.get_save_dialog = []() -> std::unique_ptr { return std::make_unique(); }; - callbacks.get_sendmessage_dialog = [this]() -> std::shared_ptr { return std::make_shared(); }; - callbacks.get_recvmessage_dialog = [this]() -> std::shared_ptr { return std::make_shared(); }; + callbacks.get_sendmessage_dialog = []() -> std::shared_ptr { return std::make_shared(); }; + callbacks.get_recvmessage_dialog = []() -> std::shared_ptr { return std::make_shared(); }; callbacks.get_trophy_notification_dialog = [this]() -> std::unique_ptr { return std::make_unique(m_game_window); }; callbacks.on_run = [this](bool start_playtime) { OnEmulatorRun(start_playtime); }; @@ -839,7 +839,7 @@ void gui_application::InitializeCallbacks() }; } - callbacks.on_emulation_stop_no_response = [this](std::shared_ptr> closed_successfully, int seconds_waiting_already) + callbacks.on_emulation_stop_no_response = [](std::shared_ptr> closed_successfully, int seconds_waiting_already) { const std::string terminate_message = tr("Stopping emulator took too long." "\nSome thread has probably deadlocked. Aborting.").toStdString(); @@ -849,7 +849,7 @@ void gui_application::InitializeCallbacks() report_fatal_error(terminate_message); } - Emu.CallFromMainThread([this, closed_successfully, seconds_waiting_already, terminate_message] + Emu.CallFromMainThread([closed_successfully, seconds_waiting_already, terminate_message] { const auto seconds = std::make_shared(seconds_waiting_already); diff --git a/rpcs3/rpcs3qt/kernel_explorer.cpp b/rpcs3/rpcs3qt/kernel_explorer.cpp index 68c6f45e2b..926949fb58 100644 --- a/rpcs3/rpcs3qt/kernel_explorer.cpp +++ b/rpcs3/rpcs3qt/kernel_explorer.cpp @@ -316,7 +316,7 @@ void kernel_explorer::update() add_solid_node(find_node(root, additional_nodes::process_info), QString::fromStdString(fmt::format("Process Info, Sdk Version: 0x%08x, PPC SEG: %#x, SFO Category: %s (Fake: %s)", g_ps3_process_info.sdk_ver, g_ps3_process_info.ppc_seg, Emu.GetCat(), Emu.GetFakeCat()))); - auto display_program_segments = [this](QTreeWidgetItem* tree, const ppu_module& m) + auto display_program_segments = [](QTreeWidgetItem* tree, const ppu_module& m) { for (usz i = 0; i < m.segs.size(); i++) { @@ -661,7 +661,7 @@ void kernel_explorer::update() const s32 prio = ppu.prio.load().prio; std::string prio_text = fmt::format("%4d", prio); prio_text = fmt::replace_all(prio_text, " ", " "); - + ppu_threads.emplace_back(prio, fmt::format(u8"PPU 0x%07x: PRIO: %s, “%s”Joiner: %s, Status: %s, State: %s, %s func: “%s”%s", id, prio_text, *ppu.ppu_tname.load(), ppu.joiner.load(), status, ppu.state.load() , ppu.ack_suspend ? "After" : (ppu.current_function ? "In" : "Last"), func ? func : "", get_wait_time_str(ppu.start_time))); }, idm::unlocked); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index a82fe5b091..0d62ef5b72 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -1734,7 +1734,7 @@ void main_window::DecryptSPRXLibraries() dlg->set_button_enabled(QDialogButtonBox::StandardButton::Ok, text.size() - (text.indexOf('x') + 1) == 32); }); - connect(dlg, &QDialog::accepted, this, [this, iterate, dlg, mod_index, decrypter, repeat_count]() + connect(dlg, &QDialog::accepted, this, [iterate, dlg, mod_index, decrypter, repeat_count]() { std::string text = dlg->get_input_text().toStdString(); @@ -3118,13 +3118,13 @@ void main_window::CreateConnects() m_game_list_frame->Refresh(true); // New user may have different games unlocked. }); - connect(ui->actionManage_Screenshots, &QAction::triggered, this, [this] + connect(ui->actionManage_Screenshots, &QAction::triggered, this, [] { screenshot_manager_dialog* screenshot_manager = new screenshot_manager_dialog(); screenshot_manager->show(); }); - connect(ui->actionManage_SoundEffects, &QAction::triggered, this, [this] + connect(ui->actionManage_SoundEffects, &QAction::triggered, this, [] { sound_effect_manager_dialog* dlg = new sound_effect_manager_dialog(); dlg->show(); @@ -3376,7 +3376,7 @@ void main_window::CreateConnects() welcome->open(); }); - connect(ui->supportAct, &QAction::triggered, this, [this] + connect(ui->supportAct, &QAction::triggered, this, [] { QDesktopServices::openUrl(QUrl("https://rpcs3.net/patreon")); }); diff --git a/rpcs3/rpcs3qt/savestate_manager_dialog.cpp b/rpcs3/rpcs3qt/savestate_manager_dialog.cpp index 2898c9b170..dbeca1698f 100644 --- a/rpcs3/rpcs3qt/savestate_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/savestate_manager_dialog.cpp @@ -575,7 +575,7 @@ void savestate_manager_dialog::ShowGameTableContextMenu(const QPoint& pos) if (!name.isEmpty()) { QAction* copy_name = new QAction(tr("&Copy Name"), menu); - connect(copy_name, &QAction::triggered, this, [this, name]() + connect(copy_name, &QAction::triggered, this, [name]() { QApplication::clipboard()->setText(name); }); diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index c95247748a..f4fcee4985 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -858,7 +858,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std ui->vulkansched->setEnabled(is_vulkan); }; - const auto apply_fsr_specific_options = [r_creator, this]() + const auto apply_fsr_specific_options = [this]() { const auto [text, value] = get_data(ui->outputScalingMode, ui->outputScalingMode->currentIndex()); const bool fsr_selected = static_cast(value) == output_scaling_mode::fsr; @@ -2095,7 +2095,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std connect(ui->edit_button_game_window_title_format, &QAbstractButton::clicked, [get_game_window_title, set_game_window_title, this]() { - auto get_game_window_title_label = [get_game_window_title, set_game_window_title, this](const QString& format) + auto get_game_window_title_label = [get_game_window_title](const QString& format) { const QString game_window_title = get_game_window_title(format); diff --git a/rpcs3/rpcs3qt/sound_effect_manager_dialog.cpp b/rpcs3/rpcs3qt/sound_effect_manager_dialog.cpp index cfd93246eb..3f7aeccce0 100644 --- a/rpcs3/rpcs3qt/sound_effect_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/sound_effect_manager_dialog.cpp @@ -41,7 +41,7 @@ sound_effect_manager_dialog::sound_effect_manager_dialog(QWidget* parent) } QPushButton* button = new QPushButton("", this); - connect(button, &QAbstractButton::clicked, this, [this, button, sound, name]() + connect(button, &QAbstractButton::clicked, this, [this, sound, name]() { const std::string path = rsx::overlays::get_sound_filepath(sound); if (fs::is_file(path)) diff --git a/rpcs3/rpcs3qt/trophy_manager_dialog.cpp b/rpcs3/rpcs3qt/trophy_manager_dialog.cpp index 51ef2d2e9f..11046f2a70 100644 --- a/rpcs3/rpcs3qt/trophy_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/trophy_manager_dialog.cpp @@ -392,7 +392,7 @@ trophy_manager_dialog::trophy_manager_dialog(std::shared_ptr gui_s m_trophy_table->create_header_actions(m_trophy_column_acts, [this](int col) { return m_gui_settings->GetTrophylistColVisibility(static_cast(col)); }, [this](int col, bool visible) { m_gui_settings->SetTrophylistColVisibility(static_cast(col), visible); }); - + m_game_table->create_header_actions(m_game_column_acts, [this](int col) { return m_gui_settings->GetTrophyGamelistColVisibility(static_cast(col)); }, [this](int col, bool visible) { m_gui_settings->SetTrophyGamelistColVisibility(static_cast(col), visible); }); @@ -651,7 +651,7 @@ void trophy_manager_dialog::ResizeGameIcons() const int trophy_index = item->data(GameUserRole::GameIndex).toInt(); QString trophy_icon_path = QString::fromStdString(m_trophies_db[trophy_index]->path); - item->set_icon_load_func([this, icon_path = std::move(trophy_icon_path), localized_icon, trophy_index, cancel = item->icon_loading_aborted(), dpr](int index) + item->set_icon_load_func([this, icon_path = std::move(trophy_icon_path), localized_icon, cancel = item->icon_loading_aborted(), dpr](int index) { if (cancel && cancel->load()) { @@ -897,7 +897,7 @@ void trophy_manager_dialog::ShowTrophyTableContextMenu(const QPoint& pos) if (!name.isEmpty() && !desc.isEmpty()) { QAction* copy_both = new QAction(tr("&Copy Name + Description"), copy_menu); - connect(copy_both, &QAction::triggered, this, [this, name, desc]() + connect(copy_both, &QAction::triggered, this, [name, desc]() { QApplication::clipboard()->setText(name % QStringLiteral("\n\n") % desc); }); @@ -907,7 +907,7 @@ void trophy_manager_dialog::ShowTrophyTableContextMenu(const QPoint& pos) if (!name.isEmpty()) { QAction* copy_name = new QAction(tr("&Copy Name"), copy_menu); - connect(copy_name, &QAction::triggered, this, [this, name]() + connect(copy_name, &QAction::triggered, this, [name]() { QApplication::clipboard()->setText(name); }); @@ -917,7 +917,7 @@ void trophy_manager_dialog::ShowTrophyTableContextMenu(const QPoint& pos) if (!desc.isEmpty()) { QAction* copy_desc = new QAction(tr("&Copy Description"), copy_menu); - connect(copy_desc, &QAction::triggered, this, [this, desc]() + connect(copy_desc, &QAction::triggered, this, [desc]() { QApplication::clipboard()->setText(desc); }); @@ -1038,7 +1038,7 @@ void trophy_manager_dialog::ShowGameTableContextMenu(const QPoint& pos) if (!name.isEmpty()) { QAction* copy_name = new QAction(tr("&Copy Name"), menu); - connect(copy_name, &QAction::triggered, this, [this, name]() + connect(copy_name, &QAction::triggered, this, [name]() { QApplication::clipboard()->setText(name); }); diff --git a/rpcs3/rpcs3qt/vfs_dialog_path_widget.cpp b/rpcs3/rpcs3qt/vfs_dialog_path_widget.cpp index f5ceac8aee..44aceff70a 100644 --- a/rpcs3/rpcs3qt/vfs_dialog_path_widget.cpp +++ b/rpcs3/rpcs3qt/vfs_dialog_path_widget.cpp @@ -81,7 +81,7 @@ vfs_dialog_path_widget::vfs_dialog_path_widget(const QString& name, const QStrin item->setCheckState(Qt::CheckState::Checked); }); - connect(m_dir_list, &QListWidget::currentRowChanged, this, [this, button_remove_dir](int row) + connect(m_dir_list, &QListWidget::currentRowChanged, this, [button_remove_dir](int row) { button_remove_dir->setEnabled(row > 0); }); From f8fe64ff7766df55bc9af080257732456173cf93 Mon Sep 17 00:00:00 2001 From: oltolm Date: Tue, 24 Mar 2026 11:56:10 +0100 Subject: [PATCH 089/108] find_dialog: fix compiler warning --- rpcs3/rpcs3qt/find_dialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/rpcs3qt/find_dialog.cpp b/rpcs3/rpcs3qt/find_dialog.cpp index 25d9442f2b..756e355e4f 100644 --- a/rpcs3/rpcs3qt/find_dialog.cpp +++ b/rpcs3/rpcs3qt/find_dialog.cpp @@ -15,7 +15,7 @@ find_dialog::find_dialog(QPlainTextEdit* edit, QWidget *parent, Qt::WindowFlags QCheckBox* cb_case_sensitive = new QCheckBox(tr("Case sensitive")); cb_case_sensitive->setChecked(m_case_sensitive); - connect(cb_case_sensitive, &QCheckBox::toggled, this, [=](bool checked) + connect(cb_case_sensitive, &QCheckBox::toggled, this, [this](bool checked) { m_case_sensitive = checked; }); From aa7cf5ea153ececef87d0dc0982522ea690b281e Mon Sep 17 00:00:00 2001 From: Ani Date: Sun, 29 Mar 2026 18:48:59 +0200 Subject: [PATCH 090/108] sysinfo: Implement RAM Usage stats for Linux (#18473) Reads memory statistics from "/proc/meminfo" --- rpcs3/util/sysinfo.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/rpcs3/util/sysinfo.cpp b/rpcs3/util/sysinfo.cpp index 81c0b3d31a..16b40d7e52 100755 --- a/rpcs3/util/sysinfo.cpp +++ b/rpcs3/util/sysinfo.cpp @@ -743,7 +743,26 @@ std::pair utils::get_memory_usage() status.dwLength = sizeof(status); ::GlobalMemoryStatusEx(&status); return { status.ullTotalPhys, status.ullTotalPhys - status.ullAvailPhys }; +#elif __linux__ + std::ifstream proc("/proc/meminfo"); + std::string line; + uint64_t mem_total = get_total_memory(); + uint64_t mem_available = 0; + while (std::getline(proc, line)) + { + if (line.rfind("MemTotal:", 0) == 0 && line.find("kB") != std::string::npos) + { + mem_total = std::stoull(line.substr(line.find_first_of("0123456789"))) * 1024; + } + else if (line.rfind("MemAvailable:", 0) == 0 && line.find("kB") != std::string::npos) + { + mem_available = std::stoull(line.substr(line.find_first_of("0123456789"))) * 1024; + break; + } + } + + return { mem_total, mem_total - mem_available }; #else // TODO return { get_total_memory(), 0 }; From bd2b2c2747b14d862d9bd68392dafac68c991d72 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 29 Mar 2026 09:31:35 +0200 Subject: [PATCH 091/108] Qt: add recording settings dialog --- Utilities/Config.h | 6 +- rpcs3/Emu/Io/recording_config.h | 12 +- rpcs3/rpcs3.vcxproj | 28 ++ rpcs3/rpcs3.vcxproj.filters | 18 + rpcs3/rpcs3qt/CMakeLists.txt | 2 + rpcs3/rpcs3qt/main_window.cpp | 7 + rpcs3/rpcs3qt/main_window.ui | 6 + rpcs3/rpcs3qt/recording_settings_dialog.cpp | 455 ++++++++++++++++++++ rpcs3/rpcs3qt/recording_settings_dialog.h | 47 ++ rpcs3/rpcs3qt/recording_settings_dialog.ui | 273 ++++++++++++ 10 files changed, 845 insertions(+), 9 deletions(-) create mode 100644 rpcs3/rpcs3qt/recording_settings_dialog.cpp create mode 100644 rpcs3/rpcs3qt/recording_settings_dialog.h create mode 100644 rpcs3/rpcs3qt/recording_settings_dialog.ui diff --git a/Utilities/Config.h b/Utilities/Config.h index ca9af028a5..4c79cbf31c 100644 --- a/Utilities/Config.h +++ b/Utilities/Config.h @@ -393,7 +393,7 @@ namespace cfg void set(const s64& value) { - ensure(value >= Min && value <= Max); + 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(value); } @@ -484,7 +484,7 @@ namespace cfg void set(const f64& value) { - ensure(value >= Min && value <= Max); + 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(value); } @@ -571,7 +571,7 @@ namespace cfg void set(const u64& value) { - ensure(value >= Min && value <= Max); + 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(value); } diff --git a/rpcs3/Emu/Io/recording_config.h b/rpcs3/Emu/Io/recording_config.h index 127d24015f..ef73149f5e 100644 --- a/rpcs3/Emu/Io/recording_config.h +++ b/rpcs3/Emu/Io/recording_config.h @@ -13,13 +13,13 @@ struct cfg_recording final : cfg::node node_video(cfg::node* _this) : cfg::node(_this, "Video") {} cfg::uint<0, 60> framerate{this, "Framerate", 30}; - cfg::uint<0, 7680> width{this, "Width", 1280}; - cfg::uint<0, 4320> height{this, "Height", 720}; + cfg::uint<640, 7680> width{this, "Width", 1280}; + cfg::uint<360, 4320> height{this, "Height", 720}; cfg::uint<0, 192> pixel_format{this, "AVPixelFormat", 0}; // AVPixelFormat::AV_PIX_FMT_YUV420P cfg::uint<0, 0xFFFF> video_codec{this, "AVCodecID", 12}; // AVCodecID::AV_CODEC_ID_MPEG4 - cfg::uint<0, 25000000> video_bps{this, "Video Bitrate", 4000000}; - cfg::uint<0, 5> max_b_frames{this, "Max B-Frames", 2}; - cfg::uint<0, 20> gop_size{this, "Group of Pictures Size", 12}; + cfg::uint<1'000'000, 60'000'000> video_bps{this, "Video Bitrate", 4'000'000}; + cfg::uint<0, 3> max_b_frames{this, "Max B-Frames", 2}; + cfg::uint<1, 120> gop_size{this, "Group of Pictures Size", 30}; } video{ this }; @@ -28,7 +28,7 @@ struct cfg_recording final : cfg::node node_audio(cfg::node* _this) : cfg::node(_this, "Audio") {} cfg::uint<0x10000, 0x17000> audio_codec{this, "AVCodecID", 86018}; // AVCodecID::AV_CODEC_ID_AAC - cfg::uint<0, 25000000> audio_bps{this, "Audio Bitrate", 320000}; + cfg::uint<64'000, 320'000> audio_bps{this, "Audio Bitrate", 192'000}; } audio{ this }; diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 5401efb323..d25ca3905c 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -386,6 +386,9 @@ true + + true + true @@ -689,6 +692,9 @@ true + + true + true @@ -868,6 +874,7 @@ + @@ -1157,6 +1164,7 @@ + @@ -1624,6 +1632,16 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\protobuf\protobuf\src" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\protobuf\protobuf\src" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWIN32_LEAN_AND_MEAN -DHAVE_VULKAN -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -DQT_MULTIMEDIA_LIB -DQT_MULTIMEDIAWIDGETS_LIB -DQT_SVG_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\SoundTouch\soundtouch\include" "-I.\..\3rdparty\cubeb\extra" "-I.\..\3rdparty\cubeb\cubeb\include" "-I.\..\3rdparty\protobuf\protobuf\src" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" "-I$(QTDIR)\include\QtMultimedia" "-I$(QTDIR)\include\QtMultimediaWidgets" "-I$(QTDIR)\include\QtSvg" + @@ -2196,6 +2214,16 @@ .\QTGeneratedFiles\ui_%(Filename).h;%(Outputs) "$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)" + + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\QTGeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\QTGeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) Uic%27ing %(Identity)... diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index d9ebc34ef6..28af783415 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -291,6 +291,12 @@ Generated Files\Release + + Generated Files\Debug + + + Generated Files\Release + Generated Files\Debug @@ -441,6 +447,9 @@ Gui\settings + + Gui\settings + Gui\log @@ -1304,6 +1313,9 @@ Generated Files + + Generated Files + Generated Files @@ -1555,6 +1567,9 @@ Form Files + + Form Files + Form Files @@ -1597,6 +1612,9 @@ Gui\settings + + Gui\settings + Gui\log diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 5c330c7ae4..b0a75d9bfa 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -83,6 +83,7 @@ add_library(rpcs3_ui STATIC qt_video_source.cpp raw_mouse_settings_dialog.cpp register_editor_dialog.cpp + recording_settings_dialog.cpp recvmessage_dialog_frame.cpp render_creator.cpp rpcn_settings_dialog.cpp @@ -135,6 +136,7 @@ add_library(rpcs3_ui STATIC patch_creator_dialog.ui patch_manager_dialog.ui ps_move_tracker_dialog.ui + recording_settings_dialog.ui settings_dialog.ui shortcut_dialog.ui welcome_dialog.ui diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 0d62ef5b72..b4776d2fc1 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -46,6 +46,7 @@ #include "welcome_dialog.h" #include "music_player_dialog.h" #include "sound_effect_manager_dialog.h" +#include "recording_settings_dialog.h" #include #include @@ -3130,6 +3131,12 @@ void main_window::CreateConnects() dlg->show(); }); + connect(ui->actionRecording, &QAction::triggered, this, [this] + { + recording_settings_dialog* dlg = new recording_settings_dialog(this); + dlg->open(); + }); + connect(ui->toolsCgDisasmAct, &QAction::triggered, this, [this] { cg_disasm_window* cgdw = new cg_disasm_window(m_gui_settings); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index 8fcf4ac610..7a7c965ce8 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -290,6 +290,7 @@ + @@ -1536,6 +1537,11 @@ Play Hover Music + + + Recording + + diff --git a/rpcs3/rpcs3qt/recording_settings_dialog.cpp b/rpcs3/rpcs3qt/recording_settings_dialog.cpp new file mode 100644 index 0000000000..befd14713a --- /dev/null +++ b/rpcs3/rpcs3qt/recording_settings_dialog.cpp @@ -0,0 +1,455 @@ +#include "stdafx.h" +#include "recording_settings_dialog.h" +#include "ui_recording_settings_dialog.h" + +#include + +#ifdef _MSC_VER +#pragma warning(push, 0) +#else +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wall" +#pragma GCC diagnostic ignored "-Wextra" +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif +extern "C" { +#include "libavcodec/avcodec.h" +#include "libavformat/avformat.h" +} +#ifdef _MSC_VER +#pragma warning(pop) +#else +#pragma GCC diagnostic pop +#endif + +LOG_CHANNEL(cfg_log, "CFG"); + +static std::vector get_video_codecs(const AVOutputFormat* fmt) +{ + std::vector codecs; + + void* opaque = nullptr; + while (const AVCodec* codec = av_codec_iterate(&opaque)) + { + if (!codec->pix_fmts) + continue; + + if (codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) + continue; + + if (codec->type != AVMediaType::AVMEDIA_TYPE_VIDEO) + continue; + + switch (codec->id) + { + case AV_CODEC_ID_H264: + case AV_CODEC_ID_HEVC: + case AV_CODEC_ID_MPEG4: + case AV_CODEC_ID_AV1: + break; + default: + continue; + } + + if (!av_codec_is_encoder(codec)) + continue; + + if (avformat_query_codec(fmt, codec->id, FF_COMPLIANCE_NORMAL) != 1) + continue; + + codecs.push_back(codec); + } + + return codecs; +} + +static std::vector get_audio_codecs(const AVOutputFormat* fmt) +{ + std::vector codecs; + + void* opaque = nullptr; + while (const AVCodec* codec = av_codec_iterate(&opaque)) + { + if (codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) + continue; + + if (codec->type != AVMediaType::AVMEDIA_TYPE_AUDIO) + continue; + + if (!av_codec_is_encoder(codec)) + continue; + + if (avformat_query_codec(fmt, codec->id, FF_COMPLIANCE_NORMAL) != 1) + continue; + + codecs.push_back(codec); + } + + return codecs; +} + +recording_settings_dialog::recording_settings_dialog(QWidget* parent) + : QDialog(parent), ui(new Ui::recording_settings_dialog) +{ + ui->setupUi(this); + setAttribute(Qt::WA_DeleteOnClose); + + if (!g_cfg_recording.load()) + { + cfg_log.notice("Could not load recording config. Using defaults."); + } + + ui->combo_presets->addItem(tr("720p 30fps"), static_cast(quality_preset::_720p_30)); + ui->combo_presets->addItem(tr("720p 60fps"), static_cast(quality_preset::_720p_60)); + ui->combo_presets->addItem(tr("1080p 30fps"), static_cast(quality_preset::_1080p_30)); + ui->combo_presets->addItem(tr("1080p 60fps"), static_cast(quality_preset::_1080p_60)); + ui->combo_presets->addItem(tr("1440p 30fps"), static_cast(quality_preset::_1440p_30)); + ui->combo_presets->addItem(tr("1440p 60fps"), static_cast(quality_preset::_1440p_60)); + ui->combo_presets->addItem(tr("2160p 30fps"), static_cast(quality_preset::_2160p_30)); + ui->combo_presets->addItem(tr("2160p 60fps"), static_cast(quality_preset::_2160p_60)); + ui->combo_presets->addItem(tr("Custom"), static_cast(quality_preset::custom)); + connect(ui->combo_presets, &QComboBox::currentIndexChanged, this, [this](int index) + { + const QVariant var = ui->combo_presets->itemData(index); + if (var.canConvert()) + { + const quality_preset preset = static_cast(var.toInt()); + select_preset(preset, g_cfg_recording); + update_ui(); + } + }); + + ui->combo_resolution->addItem("360p", QVariant::fromValue(QPair(640, 360))); + ui->combo_resolution->addItem("480p", QVariant::fromValue(QPair(854, 480))); + ui->combo_resolution->addItem("720p", QVariant::fromValue(QPair(1280, 720))); + ui->combo_resolution->addItem("1080p", QVariant::fromValue(QPair(1920, 1080))); + ui->combo_resolution->addItem("1440p", QVariant::fromValue(QPair(2560, 1440))); + ui->combo_resolution->addItem("2160p", QVariant::fromValue(QPair(3840, 2160))); + connect(ui->combo_resolution, &QComboBox::currentIndexChanged, this, [this](int index) + { + const QVariant var = ui->combo_resolution->itemData(index); + if (var.canConvert>()) + { + const QPair size = var.value>(); + g_cfg_recording.video.width.set(size.first); + g_cfg_recording.video.height.set(size.second); + update_preset(); + } + }); + + const AVOutputFormat* fmt = av_guess_format("mp4", nullptr, nullptr); + m_video_codecs = get_video_codecs(fmt); + m_audio_codecs = get_audio_codecs(fmt); + + for (const AVCodec* codec : m_video_codecs) + { + if (!codec) continue; + + const std::string name = codec->long_name ? codec->long_name : avcodec_get_name(codec->id); + ui->combo_video_codec->addItem(QString::fromStdString(name), static_cast(codec->id)); + } + + for (const AVCodec* codec : m_audio_codecs) + { + if (!codec) continue; + + const std::string name = codec->long_name ? codec->long_name : avcodec_get_name(codec->id); + ui->combo_audio_codec->addItem(QString::fromStdString(name), static_cast(codec->id)); + } + + connect(ui->combo_video_codec, &QComboBox::currentIndexChanged, this, [this](int index) + { + const QVariant var = ui->combo_video_codec->itemData(index); + if (var.canConvert()) + { + const int codec_id = var.toInt(); + g_cfg_recording.video.video_codec.set(codec_id); + update_preset(); + } + }); + + connect(ui->combo_audio_codec, &QComboBox::currentIndexChanged, this, [this](int index) + { + const QVariant var = ui->combo_audio_codec->itemData(index); + if (var.canConvert()) + { + const int codec_id = var.toInt(); + g_cfg_recording.audio.audio_codec.set(codec_id); + update_preset(); + } + }); + + ui->combo_framerate->addItem("30", 30); + ui->combo_framerate->addItem("60", 60); + connect(ui->combo_framerate, &QComboBox::currentIndexChanged, this, [this](int index) + { + const QVariant var = ui->combo_framerate->itemData(index); + if (var.canConvert()) + { + const int fps = var.toInt(); + g_cfg_recording.video.framerate.set(fps); + update_preset(); + } + }); + + ui->spinbox_video_bitrate->setSingleStep(1); + ui->spinbox_video_bitrate->setMinimum(g_cfg_recording.video.video_bps.min); + ui->spinbox_video_bitrate->setMaximum(g_cfg_recording.video.video_bps.max); + connect(ui->spinbox_video_bitrate, &QSpinBox::valueChanged, this, [this](int value) + { + g_cfg_recording.video.video_bps.set(value); + update_preset(); + }); + + ui->spinbox_audio_bitrate->setSingleStep(1); + ui->spinbox_audio_bitrate->setMinimum(g_cfg_recording.audio.audio_bps.min); + ui->spinbox_audio_bitrate->setMaximum(g_cfg_recording.audio.audio_bps.max); + connect(ui->spinbox_audio_bitrate, &QSpinBox::valueChanged, this, [this](int value) + { + g_cfg_recording.audio.audio_bps.set(value); + update_preset(); + }); + + ui->spinbox_gop_size->setSingleStep(1); + ui->spinbox_gop_size->setMinimum(g_cfg_recording.video.gop_size.min); + ui->spinbox_gop_size->setMaximum(g_cfg_recording.video.gop_size.max); + connect(ui->spinbox_gop_size, &QSpinBox::valueChanged, this, [this](int value) + { + g_cfg_recording.video.gop_size.set(value); + update_preset(); + }); + + ui->spinbox_max_b_frames->setSingleStep(1); + ui->spinbox_max_b_frames->setMinimum(g_cfg_recording.video.max_b_frames.min); + ui->spinbox_max_b_frames->setMaximum(g_cfg_recording.video.max_b_frames.max); + connect(ui->spinbox_max_b_frames, &QSpinBox::valueChanged, this, [this](int value) + { + g_cfg_recording.video.max_b_frames.set(value); + update_preset(); + }); + + connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) + { + if (button == ui->buttonBox->button(QDialogButtonBox::Save)) + { + g_cfg_recording.save(); + accept(); + } + else if (button == ui->buttonBox->button(QDialogButtonBox::Cancel)) + { + reject(); + } + else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) + { + g_cfg_recording.from_default(); + update_ui(); + update_preset(); + } + }); + + connect(this, &QDialog::rejected, this, []() + { + if (!g_cfg_recording.load()) + { + cfg_log.notice("Could not load recording config. Using defaults."); + } + }); + + update_ui(); + update_preset(); +} + +recording_settings_dialog::~recording_settings_dialog() +{ +} + +void recording_settings_dialog::update_preset() +{ + const quality_preset preset = current_preset(); + ui->combo_presets->setCurrentIndex(ui->combo_presets->findData(static_cast(preset))); +} + +void recording_settings_dialog::update_ui() +{ + ui->combo_resolution->blockSignals(true); + ui->combo_framerate->blockSignals(true); + ui->combo_video_codec->blockSignals(true); + ui->combo_audio_codec->blockSignals(true); + ui->spinbox_video_bitrate->blockSignals(true); + ui->spinbox_audio_bitrate->blockSignals(true); + ui->spinbox_gop_size->blockSignals(true); + ui->spinbox_max_b_frames->blockSignals(true); + + ui->combo_resolution->setCurrentIndex(ui->combo_resolution->findData(QVariant::fromValue(QPair(g_cfg_recording.video.width.get(), g_cfg_recording.video.height.get())))); + ui->combo_framerate->setCurrentIndex(ui->combo_framerate->findData(static_cast(g_cfg_recording.video.framerate.get()))); + ui->combo_video_codec->setCurrentIndex(ui->combo_video_codec->findData(static_cast(g_cfg_recording.video.video_codec.get()))); + ui->combo_audio_codec->setCurrentIndex(ui->combo_audio_codec->findData(static_cast(g_cfg_recording.audio.audio_codec.get()))); + ui->spinbox_video_bitrate->setValue(g_cfg_recording.video.video_bps); + ui->spinbox_audio_bitrate->setValue(g_cfg_recording.audio.audio_bps); + ui->spinbox_gop_size->setValue(g_cfg_recording.video.gop_size); + ui->spinbox_max_b_frames->setValue(g_cfg_recording.video.max_b_frames); + + ui->combo_resolution->blockSignals(false); + ui->combo_framerate->blockSignals(false); + ui->combo_video_codec->blockSignals(false); + ui->combo_audio_codec->blockSignals(false); + ui->spinbox_video_bitrate->blockSignals(false); + ui->spinbox_audio_bitrate->blockSignals(false); + ui->spinbox_gop_size->blockSignals(false); + ui->spinbox_max_b_frames->blockSignals(false); + + const auto get_codec_name = [](const std::vector& codecs, u32 id) + { + for (const AVCodec* codec : codecs) + { + if (codec && codec->id == static_cast(id)) + { + const std::string name = codec->long_name ? codec->long_name : avcodec_get_name(codec->id); + return name; + } + } + return std::string(); + }; + + ui->label_info_keys->setText( + tr("Resolution:") + "\n" + + tr("Framerate:") + "\n" + + tr("Video Codec:") + "\n" + + tr("Video Bitrate:") + "\n" + + tr("Audio Codec:") + "\n" + + tr("Audio Bitrate:") + "\n" + + tr("Gop-Size:") + "\n" + + tr("Max B-Frames:") + ); + + ui->label_info_values->setText(QString::fromStdString( + fmt::format("%d x %d\n%d fps\n%s\n%d\n%s\n%d\n%d\n%d", + g_cfg_recording.video.width.get(), g_cfg_recording.video.height.get(), + g_cfg_recording.video.framerate.get(), + get_codec_name(m_video_codecs, g_cfg_recording.video.video_codec.get()), + g_cfg_recording.video.video_bps.get(), + get_codec_name(m_audio_codecs, g_cfg_recording.audio.audio_codec.get()), + g_cfg_recording.audio.audio_bps.get(), + g_cfg_recording.video.gop_size.get(), + g_cfg_recording.video.max_b_frames.get() + ) + )); +} + +void recording_settings_dialog::select_preset(quality_preset preset, cfg_recording& cfg) +{ + if (preset == quality_preset::custom) + { + return; + } + + cfg.audio.audio_codec.set(static_cast(AVCodecID::AV_CODEC_ID_AAC)); + cfg.audio.audio_bps.set(192'000); // 192 kbps + + cfg.video.video_codec.set(static_cast(AVCodecID::AV_CODEC_ID_MPEG4)); + cfg.video.pixel_format.set(static_cast(::AV_PIX_FMT_YUV420P)); + + switch (preset) + { + case quality_preset::_720p_30: + case quality_preset::_720p_60: + cfg.video.width.set(1280); + cfg.video.height.set(720); + break; + case quality_preset::_1080p_30: + case quality_preset::_1080p_60: + cfg.video.width.set(1920); + cfg.video.height.set(1080); + break; + case quality_preset::_1440p_30: + case quality_preset::_1440p_60: + cfg.video.width.set(2560); + cfg.video.height.set(1440); + break; + case quality_preset::_2160p_30: + case quality_preset::_2160p_60: + cfg.video.width.set(3840); + cfg.video.height.set(2160); + break; + case quality_preset::custom: + break; + } + + switch (preset) + { + case quality_preset::_720p_30: + case quality_preset::_1080p_30: + case quality_preset::_1440p_30: + case quality_preset::_2160p_30: + cfg.video.framerate.set(30); + break; + case quality_preset::_720p_60: + case quality_preset::_1080p_60: + case quality_preset::_1440p_60: + case quality_preset::_2160p_60: + cfg.video.framerate.set(60); + break; + case quality_preset::custom: + break; + } + + switch (preset) + { + case quality_preset::_720p_30: + cfg.video.video_bps.set(4'000'000); + break; + case quality_preset::_720p_60: + cfg.video.video_bps.set(6'000'000); + break; + case quality_preset::_1080p_30: + cfg.video.video_bps.set(8'000'000); + break; + case quality_preset::_1080p_60: + cfg.video.video_bps.set(12'000'000); + break; + case quality_preset::_1440p_30: + cfg.video.video_bps.set(16'000'000); + break; + case quality_preset::_1440p_60: + cfg.video.video_bps.set(24'000'000); + break; + case quality_preset::_2160p_30: + cfg.video.video_bps.set(40'000'000); + break; + case quality_preset::_2160p_60: + cfg.video.video_bps.set(60'000'000); + break; + case quality_preset::custom: + break; + } + + cfg.video.gop_size.set(cfg.video.framerate.get()); + cfg.video.max_b_frames.set(2); +} + +recording_settings_dialog::quality_preset recording_settings_dialog::current_preset() +{ + for (u32 i = 0; i < static_cast(quality_preset::custom); i++) + { + const quality_preset preset = static_cast(i); + + cfg_recording cfg; + select_preset(preset, cfg); + + if (g_cfg_recording.video.framerate.get() == cfg.video.framerate.get() && + g_cfg_recording.video.width.get() == cfg.video.width.get() && + g_cfg_recording.video.height.get() == cfg.video.height.get() && + g_cfg_recording.video.pixel_format.get() == cfg.video.pixel_format.get() && + g_cfg_recording.video.video_codec.get() == cfg.video.video_codec.get() && + g_cfg_recording.video.video_bps.get() == cfg.video.video_bps.get() && + g_cfg_recording.video.max_b_frames.get() == cfg.video.max_b_frames.get() && + g_cfg_recording.video.gop_size.get() == cfg.video.gop_size.get() && + g_cfg_recording.audio.audio_codec.get() == cfg.audio.audio_codec.get() && + g_cfg_recording.audio.audio_bps.get() == cfg.audio.audio_bps.get()) + { + return preset; + } + } + + return quality_preset::custom; +} diff --git a/rpcs3/rpcs3qt/recording_settings_dialog.h b/rpcs3/rpcs3qt/recording_settings_dialog.h new file mode 100644 index 0000000000..3a17d7d8a8 --- /dev/null +++ b/rpcs3/rpcs3qt/recording_settings_dialog.h @@ -0,0 +1,47 @@ +#pragma once + +#include "util/types.hpp" +#include "Emu/Io/recording_config.h" + +#include + +namespace Ui +{ + class recording_settings_dialog; +} + +struct AVCodec; + +class recording_settings_dialog : public QDialog +{ + Q_OBJECT + +public: + recording_settings_dialog(QWidget* parent = nullptr); + virtual ~recording_settings_dialog(); + +private: + enum class quality_preset + { + _720p_30, + _720p_60, + _1080p_30, + _1080p_60, + _1440p_30, + _1440p_60, + _2160p_30, + _2160p_60, + custom + }; + + void update_preset(); + void update_ui(); + + static void select_preset(quality_preset preset, cfg_recording& cfg); + static quality_preset current_preset(); + + Ui::recording_settings_dialog* ui; + + std::vector m_video_codecs; + std::vector m_audio_codecs; +}; diff --git a/rpcs3/rpcs3qt/recording_settings_dialog.ui b/rpcs3/rpcs3qt/recording_settings_dialog.ui new file mode 100644 index 0000000000..9d11e1299a --- /dev/null +++ b/rpcs3/rpcs3qt/recording_settings_dialog.ui @@ -0,0 +1,273 @@ + + + recording_settings_dialog + + + + 0 + 0 + 692 + 734 + + + + Recording Settings + + + + + + 0 + + + + Presets + + + + + + Preset + + + + + + + + + + + + Info + + + + + + Keys + + + + + + + Values + + + + + + + Qt::Orientation::Horizontal + + + + 0 + 0 + + + + + + + + + + + Qt::Orientation::Vertical + + + + 0 + 0 + + + + + + + + + Advanced + + + + + + Video + + + + + + Codec + + + + + + + + + + + + Resolution + + + + + + + + + + + + Framerate + + + + + + + + + + + + Bitrate + + + + + + + + + + + + Group of Pictures Size + + + + + + + + + + + + Max. B-Frames + + + + + + + + + + + + + + + Audio + + + + + + Codec + + + + + + + + + + + + Bitrate + + + + + + + + + + + + + + + Qt::Orientation::Vertical + + + + 0 + 0 + + + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::RestoreDefaults|QDialogButtonBox::StandardButton::Save + + + + + + + + + buttonBox + accepted() + recording_settings_dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + recording_settings_dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + From ab03d76ed6ce7910ded0e67db756c36a098822f6 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 29 Mar 2026 18:32:44 +0200 Subject: [PATCH 092/108] overlays: show latching message for fatal errors --- rpcs3/Emu/RSX/Overlays/overlay_controls.cpp | 8 ++++---- rpcs3/Emu/RSX/Overlays/overlay_controls.h | 4 ++-- rpcs3/Emu/RSX/Overlays/overlay_edit_text.cpp | 4 ++-- rpcs3/Emu/RSX/Overlays/overlay_edit_text.hpp | 4 ++-- rpcs3/Emu/RSX/Overlays/overlay_message.cpp | 2 +- rpcs3/Emu/RSX/Overlays/overlay_message.h | 2 +- rpcs3/Emu/RSX/Overlays/overlay_progress_bar.cpp | 2 +- rpcs3/Emu/RSX/Overlays/overlay_progress_bar.hpp | 2 +- rpcs3/Emu/RSX/Overlays/overlay_utils.cpp | 14 +++++++------- rpcs3/Emu/RSX/Overlays/overlay_utils.h | 12 ++++++------ rpcs3/Emu/System.cpp | 2 +- rpcs3/rpcs3.cpp | 8 +++++++- 12 files changed, 35 insertions(+), 29 deletions(-) diff --git a/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp b/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp index 93148d655c..5208efd747 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp @@ -431,25 +431,25 @@ namespace rsx m_is_compiled = false; } - void overlay_element::set_text(const std::string& text) + void overlay_element::set_text(std::string_view text) { std::u32string new_text = utf8_to_u32string(text); const bool is_dirty = this->text != new_text; - this->text = std::move(new_text); if (is_dirty) { + this->text = std::move(new_text); m_is_compiled = false; } } - void overlay_element::set_unicode_text(const std::u32string& text) + void overlay_element::set_unicode_text(std::u32string_view text) { const bool is_dirty = this->text != text; - this->text = text; if (is_dirty) { + this->text = text; m_is_compiled = false; } } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_controls.h b/rpcs3/Emu/RSX/Overlays/overlay_controls.h index 04b6820287..8fa835f595 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_controls.h +++ b/rpcs3/Emu/RSX/Overlays/overlay_controls.h @@ -242,8 +242,8 @@ namespace rsx // NOTE: Functions as a simple position offset. Top left corner is the anchor. virtual void set_margin(u16 left, u16 top); virtual void set_margin(u16 margin); - virtual void set_text(const std::string& text); - virtual void set_unicode_text(const std::u32string& text); + virtual void set_text(std::string_view text); + virtual void set_unicode_text(std::u32string_view text); void set_text(localized_string_id id); void set_text(const localized_string& container); virtual void set_font(const char* font_name, u16 font_size); diff --git a/rpcs3/Emu/RSX/Overlays/overlay_edit_text.cpp b/rpcs3/Emu/RSX/Overlays/overlay_edit_text.cpp index ba3d138f96..4fad1f65e9 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_edit_text.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_edit_text.cpp @@ -95,12 +95,12 @@ namespace rsx } } - void edit_text::set_text(const std::string& text) + void edit_text::set_text(std::string_view text) { set_unicode_text(utf8_to_u32string(text)); } - void edit_text::set_unicode_text(const std::u32string& text) + void edit_text::set_unicode_text(std::u32string_view text) { value = text; diff --git a/rpcs3/Emu/RSX/Overlays/overlay_edit_text.hpp b/rpcs3/Emu/RSX/Overlays/overlay_edit_text.hpp index 624580e4f4..7d4f6d93b5 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_edit_text.hpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_edit_text.hpp @@ -26,8 +26,8 @@ namespace rsx using label::label; - void set_text(const std::string& text) override; - void set_unicode_text(const std::u32string& text) override; + void set_text(std::string_view text) override; + void set_unicode_text(std::u32string_view text) override; void set_placeholder(const std::u32string& placeholder_text); diff --git a/rpcs3/Emu/RSX/Overlays/overlay_message.cpp b/rpcs3/Emu/RSX/Overlays/overlay_message.cpp index 304e54de1e..1c49401d02 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_message.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_message.cpp @@ -90,7 +90,7 @@ namespace rsx return m_loc_id == id; } - bool message_item::text_matches(const std::u32string& text) const + bool message_item::text_matches(std::u32string_view text) const { return m_text.text == text; } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_message.h b/rpcs3/Emu/RSX/Overlays/overlay_message.h index 219103e843..e8cb1a3285 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_message.h +++ b/rpcs3/Emu/RSX/Overlays/overlay_message.h @@ -31,7 +31,7 @@ namespace rsx compiled_resource& get_compiled() override; bool id_matches(localized_string_id id) const; - bool text_matches(const std::u32string& text) const; + bool text_matches(std::u32string_view text) const; void set_label_text(const std::string& text); diff --git a/rpcs3/Emu/RSX/Overlays/overlay_progress_bar.cpp b/rpcs3/Emu/RSX/Overlays/overlay_progress_bar.cpp index 860f54544a..b9140f2a69 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_progress_bar.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_progress_bar.cpp @@ -58,7 +58,7 @@ namespace rsx set_pos(x + dx, y + dy); } - void progress_bar::set_text(const std::string& str) + void progress_bar::set_text(std::string_view str) { text_view.set_text(str); text_view.align_text(text_align::center); diff --git a/rpcs3/Emu/RSX/Overlays/overlay_progress_bar.hpp b/rpcs3/Emu/RSX/Overlays/overlay_progress_bar.hpp index 6ed6b73c77..d622796dae 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_progress_bar.hpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_progress_bar.hpp @@ -24,7 +24,7 @@ namespace rsx void set_pos(s16 _x, s16 _y) override; void set_size(u16 _w, u16 _h) override; void translate(s16 dx, s16 dy) override; - void set_text(const std::string& str) override; + void set_text(std::string_view str) override; compiled_resource& get_compiled() override; }; diff --git a/rpcs3/Emu/RSX/Overlays/overlay_utils.cpp b/rpcs3/Emu/RSX/Overlays/overlay_utils.cpp index 18eac492e9..047ca6fff3 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_utils.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_utils.cpp @@ -60,7 +60,7 @@ static auto s_ascii_lowering_map = []() }(); template -void process_multibyte(const std::string& s, F&& func) +void process_multibyte(std::string_view s, F&& func) { const usz end = s.length(); for (usz index = 0; index < end; ++index) @@ -110,7 +110,7 @@ void process_multibyte(const std::string& s, F&& func) } } -std::string utf8_to_ascii8(const std::string& utf8_string) +std::string utf8_to_ascii8(std::string_view utf8_string) { std::string out; out.reserve(utf8_string.length()); @@ -135,7 +135,7 @@ std::string utf8_to_ascii8(const std::string& utf8_string) return out; } -std::string utf16_to_ascii8(const std::u16string& utf16_string) +std::string utf16_to_ascii8(std::u16string_view utf16_string) { // Strip extended codes, map to '#' instead (placeholder) std::string out; @@ -152,7 +152,7 @@ std::string utf16_to_ascii8(const std::u16string& utf16_string) return out; } -std::u16string ascii8_to_utf16(const std::string& ascii_string) +std::u16string ascii8_to_utf16(std::string_view ascii_string) { std::u16string out; out.reserve(ascii_string.length()); @@ -168,7 +168,7 @@ std::u16string ascii8_to_utf16(const std::string& ascii_string) return out; } -std::u32string utf8_to_u32string(const std::string& utf8_string) +std::u32string utf8_to_u32string(std::string_view utf8_string) { std::u32string result; result.reserve(utf8_string.size()); @@ -181,7 +181,7 @@ std::u32string utf8_to_u32string(const std::string& utf8_string) return result; } -std::u16string u32string_to_utf16(const std::u32string& utf32_string) +std::u16string u32string_to_utf16(std::u32string_view utf32_string) { std::u16string result; result.reserve(utf32_string.size()); @@ -194,7 +194,7 @@ std::u16string u32string_to_utf16(const std::u32string& utf32_string) return result; } -std::u32string utf16_to_u32string(const std::u16string& utf16_string) +std::u32string utf16_to_u32string(std::u16string_view utf16_string) { std::u32string result; result.reserve(utf16_string.size()); diff --git a/rpcs3/Emu/RSX/Overlays/overlay_utils.h b/rpcs3/Emu/RSX/Overlays/overlay_utils.h index aaa70f09a3..ab3ec0894d 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_utils.h +++ b/rpcs3/Emu/RSX/Overlays/overlay_utils.h @@ -218,9 +218,9 @@ void operator < (const vector3_base& lhs, T rhs) using vector3i = vector3_base; using vector3f = vector3_base; -std::string utf8_to_ascii8(const std::string& utf8_string); -std::string utf16_to_ascii8(const std::u16string& utf16_string); -std::u16string ascii8_to_utf16(const std::string& ascii_string); -std::u32string utf8_to_u32string(const std::string& utf8_string); -std::u16string u32string_to_utf16(const std::u32string& utf32_string); -std::u32string utf16_to_u32string(const std::u16string& utf16_string); +std::string utf8_to_ascii8(std::string_view utf8_string); +std::string utf16_to_ascii8(std::u16string_view utf16_string); +std::u16string ascii8_to_utf16(std::string_view ascii_string); +std::u32string utf8_to_u32string(std::string_view utf8_string); +std::u16string u32string_to_utf16(std::u32string_view utf32_string); +std::u32string utf16_to_u32string(std::u16string_view utf16_string); diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 4fdb5e47f3..8736ae70a2 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -2837,7 +2837,7 @@ bool Emulator::Pause(bool freeze_emulation, bool show_resume_message) auto msg_ref = std::make_shared>(1); // No timeout - rsx::overlays::queue_message(status == system_state::paused ? localized_string_id::EMULATION_PAUSED_RESUME_WITH_START : localized_string_id::EMULATION_FROZEN, -1, msg_ref); + rsx::overlays::queue_message(status == system_state::paused ? localized_string_id::EMULATION_PAUSED_RESUME_WITH_START : localized_string_id::EMULATION_FROZEN, umax, msg_ref); m_pause_msgs_refs.emplace_back(msg_ref); auto refresh_l = [this, msg_ref, status]() diff --git a/rpcs3/rpcs3.cpp b/rpcs3/rpcs3.cpp index 86e3c47171..921394e311 100644 --- a/rpcs3/rpcs3.cpp +++ b/rpcs3/rpcs3.cpp @@ -69,6 +69,7 @@ DYNAMIC_IMPORT("ntdll.dll", NtSetTimerResolution, NTSTATUS(ULONG DesiredResoluti #include "rpcs3_version.h" #include "Emu/System.h" #include "Emu/system_utils.hpp" +#include "Emu/RSX/Overlays/overlay_message.h" #include #include @@ -312,7 +313,8 @@ public: { if (msg == logs::level::fatal || (msg == logs::level::always && m_log_always)) { - std::string _msg = "RPCS3: "; + static const std::string rpcs3_prefix = "RPCS3: "; + std::string _msg = rpcs3_prefix; if (!prefix.empty()) { @@ -351,7 +353,11 @@ public: #endif if (msg == logs::level::fatal) { + std::string overlay_msg = "Fatal error: " + _msg.substr(rpcs3_prefix.size()); + fmt::trim_back(overlay_msg, " \t\n"); + // Pause emulation if fatal error encountered + rsx::overlays::queue_message(overlay_msg, umax); Emu.Pause(true); } } From 44dc29e52b5342c8c03f1b480c9313f4b3a72298 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 24 Mar 2026 19:35:46 +0100 Subject: [PATCH 093/108] Qt: Add some some more thread names --- rpcs3/Emu/System.cpp | 6 +++++- rpcs3/rpcs3qt/downloader.cpp | 3 +++ rpcs3/rpcs3qt/memory_viewer_panel.cpp | 2 ++ rpcs3/rpcs3qt/movie_item_base.cpp | 5 +++++ rpcs3/rpcs3qt/ps_move_tracker_dialog.cpp | 2 ++ rpcs3/rpcs3qt/screenshot_item.cpp | 3 +++ rpcs3/rpcs3qt/update_manager.cpp | 1 - 7 files changed, 20 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 8736ae70a2..da7251e291 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -3160,7 +3160,11 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta if (async_op) { - std::thread{perform_kill}.detach(); + std::thread{[perform_kill]() + { + thread_base::set_name("Perform Kill"); + perform_kill(); + }}.detach(); } else { diff --git a/rpcs3/rpcs3qt/downloader.cpp b/rpcs3/rpcs3qt/downloader.cpp index 852449c0af..9a5038fcc1 100644 --- a/rpcs3/rpcs3qt/downloader.cpp +++ b/rpcs3/rpcs3qt/downloader.cpp @@ -8,6 +8,7 @@ #include "progress_dialog.h" #include "util/logs.hpp" +#include "Utilities/Thread.h" #include @@ -82,6 +83,8 @@ void downloader::start(const std::string& url, bool follow_location, bool show_p m_thread = QThread::create([this] { + thread_base::set_name("Downloader"); + // Reset error buffer before we call curl_easy_perform m_curl->reset_error_buffer(); diff --git a/rpcs3/rpcs3qt/memory_viewer_panel.cpp b/rpcs3/rpcs3qt/memory_viewer_panel.cpp index bcaa6537ef..0c779a9558 100644 --- a/rpcs3/rpcs3qt/memory_viewer_panel.cpp +++ b/rpcs3/rpcs3qt/memory_viewer_panel.cpp @@ -504,6 +504,8 @@ memory_viewer_panel::memory_viewer_panel(QWidget* parent, std::shared_ptr @@ -10,6 +11,8 @@ screenshot_item::screenshot_item(QWidget* parent) { m_thread.reset(QThread::create([this]() { + thread_base::set_name("Screenshot item"); + const QPixmap pixmap = gui::utils::get_aligned_pixmap(icon_path, icon_size, 1.0, Qt::SmoothTransformation, gui::utils::align_h::center, gui::utils::align_v::center); Q_EMIT signal_icon_update(pixmap); })); diff --git a/rpcs3/rpcs3qt/update_manager.cpp b/rpcs3/rpcs3qt/update_manager.cpp index d32a1810b9..a34292ce0e 100644 --- a/rpcs3/rpcs3qt/update_manager.cpp +++ b/rpcs3/rpcs3qt/update_manager.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #if defined(_WIN32) || defined(__APPLE__) #include <7z.h> From b94e6d486412bc098b998f00159d0e7c50a50196 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 29 Mar 2026 09:43:35 +0200 Subject: [PATCH 094/108] Fix some warnings --- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 6 +++--- rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 7511b2ae7b..948c6e8b14 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -2323,7 +2323,7 @@ public: llvm::Value* loop_dictator_after_adjustment{}; spu_opcode_t reg_target{}; - reg_target.rt = reduced_loop_info->cond_val_register_idx; + reg_target.rt = static_cast(reduced_loop_info->cond_val_register_idx); if (reg_target.rt != reduced_loop_info->cond_val_register_idx) { @@ -2375,7 +2375,7 @@ public: else { spu_opcode_t reg_incr{}; - reg_incr.rt = reduced_loop_info->cond_val_incr; + reg_incr.rt = static_cast(reduced_loop_info->cond_val_incr); if (reg_incr.rt != reduced_loop_info->cond_val_incr) { @@ -2425,7 +2425,7 @@ public: else { spu_opcode_t reg_target2{}; - reg_target2.rt = reduced_loop_info->cond_val_register_argument_idx; + reg_target2.rt = static_cast(reduced_loop_info->cond_val_register_argument_idx); if (reg_target2.rt != reduced_loop_info->cond_val_register_argument_idx) { diff --git a/rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.cpp b/rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.cpp index e62097c71c..635833c9d8 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_perf_metrics.cpp @@ -948,7 +948,7 @@ namespace rsx perf_overlay->set_update_interval(perf_settings.update_interval); perf_overlay->set_font(perf_settings.font); perf_overlay->set_font_size(perf_settings.font_size); - perf_overlay->set_margins(perf_settings.margin_x, perf_settings.margin_y, perf_settings.center_x.get(), perf_settings.center_y.get()); + perf_overlay->set_margins(static_cast(perf_settings.margin_x.get()), static_cast(perf_settings.margin_y.get()), perf_settings.center_x.get(), perf_settings.center_y.get()); perf_overlay->use_window_space = perf_settings.perf_overlay_use_window_space.get(); perf_overlay->set_opacity(perf_settings.opacity / 100.f); perf_overlay->set_body_colors(perf_settings.color_body, perf_settings.background_body); From 433110b4d84a5601f3bf80d35eb50e9f085f5884 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 24 Mar 2026 19:03:27 +0100 Subject: [PATCH 095/108] Update wolfssl to 5.9.0 --- 3rdparty/wolfssl/wolfssl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/wolfssl/wolfssl b/3rdparty/wolfssl/wolfssl index b077c81eb6..922d04b356 160000 --- a/3rdparty/wolfssl/wolfssl +++ b/3rdparty/wolfssl/wolfssl @@ -1 +1 @@ -Subproject commit b077c81eb635392e694ccedbab8b644297ec0285 +Subproject commit 922d04b3568c6428a9fb905ddee3ef5a68db3108 From a10390e52ae51ff1d4c8adfc57d9f72bd19239aa Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 29 Mar 2026 09:45:48 +0200 Subject: [PATCH 096/108] Update libpng to 1.6.56 --- 3rdparty/libpng/libpng | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/libpng/libpng b/3rdparty/libpng/libpng index c3e304954a..d5515b5b8b 160000 --- a/3rdparty/libpng/libpng +++ b/3rdparty/libpng/libpng @@ -1 +1 @@ -Subproject commit c3e304954a9cfd154bc0dfbfea2b01cd61d6546d +Subproject commit d5515b5b8be3901aac04e5bd8bd5c89f287bcd33 From 6dc06b3ff5126469a15fb45a8cd86b3e8e922b3e Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 24 Mar 2026 18:57:06 +0100 Subject: [PATCH 097/108] Update Qt to 6.11.0 --- .ci/setup-windows.sh | 2 +- .github/workflows/rpcs3.yml | 14 +++++++------- BUILDING.md | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.ci/setup-windows.sh b/.ci/setup-windows.sh index cee6d624de..d8016d8c13 100755 --- a/.ci/setup-windows.sh +++ b/.ci/setup-windows.sh @@ -6,7 +6,7 @@ QT_HOST="http://qt.mirror.constant.com/" QT_URL_VER=$(echo "$QT_VER" | sed "s/\.//g") QT_VER_MSVC_UP=$(echo "${QT_VER_MSVC}" | tr '[:lower:]' '[:upper:]') -QT_PREFIX="online/qtsdkrepository/windows_x86/desktop/qt${QT_VER_MAIN}_${QT_URL_VER}/qt${QT_VER_MAIN}_${QT_URL_VER}/qt.qt${QT_VER_MAIN}.${QT_URL_VER}." +QT_PREFIX="online/qtsdkrepository/windows_x86/desktop/qt${QT_VER_MAIN}_${QT_URL_VER}/qt${QT_VER_MAIN}_${QT_URL_VER}_${QT_VER_MSVC}_64/qt.qt${QT_VER_MAIN}.${QT_URL_VER}." QT_PREFIX_2="win64_${QT_VER_MSVC}_64/${QT_VER}-0-${QT_DATE}" QT_SUFFIX="-Windows-Windows_11_24H2-${QT_VER_MSVC_UP}-Windows-Windows_11_24H2-X86_64.7z" QT_BASE_URL="${QT_HOST}${QT_PREFIX}${QT_PREFIX_2}qtbase${QT_SUFFIX}" diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index 740e844d04..f474643e09 100644 --- a/.github/workflows/rpcs3.yml +++ b/.github/workflows/rpcs3.yml @@ -30,23 +30,23 @@ jobs: matrix: include: - os: ubuntu-24.04 - docker_img: "rpcs3/rpcs3-ci-jammy:1.9" + docker_img: "rpcs3/rpcs3-ci-jammy:1.10" build_sh: "/rpcs3/.ci/build-linux.sh" compiler: clang UPLOAD_COMMIT_HASH: d812f1254a1157c80fd402f94446310560f54e5f UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux" - os: ubuntu-24.04 - docker_img: "rpcs3/rpcs3-ci-jammy:1.9" + docker_img: "rpcs3/rpcs3-ci-jammy:1.10" build_sh: "/rpcs3/.ci/build-linux.sh" compiler: gcc - os: ubuntu-24.04-arm - docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.9" + docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.10" build_sh: "/rpcs3/.ci/build-linux-aarch64.sh" compiler: clang UPLOAD_COMMIT_HASH: a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1 UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux-arm64" - os: ubuntu-24.04-arm - docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.9" + docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.10" build_sh: "/rpcs3/.ci/build-linux-aarch64.sh" compiler: gcc name: RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} @@ -134,7 +134,7 @@ jobs: runs-on: macos-14 env: CCACHE_DIR: /tmp/ccache_dir - QT_VER: '6.10.2' + QT_VER: '6.11.0' QT_VER_MAIN: '6' LLVM_COMPILER_VER: '21' RELEASE_MESSAGE: ../GitHubReleaseMessage.txt @@ -213,9 +213,9 @@ jobs: env: COMPILER: msvc QT_VER_MAIN: '6' - QT_VER: '6.10.2' + QT_VER: '6.11.0' QT_VER_MSVC: 'msvc2022' - QT_DATE: '202601261212' + QT_DATE: '202603180535' LLVM_VER: '19.1.7' VULKAN_VER: '1.3.268.0' VULKAN_SDK_SHA: '8459ef49bd06b697115ddd3d97c9aec729e849cd775f5be70897718a9b3b9db5' diff --git a/BUILDING.md b/BUILDING.md index 597621e810..c1774908fd 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -20,26 +20,26 @@ The following tools are required to build RPCS3 on Windows 10 or later: with standalone **CMake** tool. - [Python 3.6+](https://www.python.org/downloads/) (add to PATH) -- [Qt 6.10.2](https://www.qt.io/download-qt-installer) In case you can't download from the official installer, you can use [Another Qt installer](https://github.com/miurahr/aqtinstall) (In that case you will need to manually add the "qtmultimedia" module when installing Qt) +- [Qt 6.11.0](https://www.qt.io/download-qt-installer) In case you can't download from the official installer, you can use [Another Qt installer](https://github.com/miurahr/aqtinstall) (In that case you will need to manually add the "qtmultimedia" module when installing Qt) - [Vulkan SDK 1.3.268.0](https://vulkan.lunarg.com/sdk/home) (see "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/windows/getting_started.html)) for now future SDKs don't work. You need precisely 1.3.268.0. The `sln` solution available only on **Visual Studio** is the preferred building solution. It easily allows to build the **RPCS3** application in `Release` and `Debug` mode. In order to build **RPCS3** with the `sln` solution (with **Visual Studio**), **Qt** libs need to be detected. To detect the libs: -- add and set the `QTDIR` environment variable, e.g. `\6.10.2\msvc2022_64\` +- add and set the `QTDIR` environment variable, e.g. `\6.11.0\msvc2022_64\` - or use the [Visual Studio Qt Plugin](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.QtVisualStudioTools2022) **NOTE:** If you have issues with the **Visual Studio Qt Plugin**, you may want to uninstall it and install the [Legacy Qt Plugin](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.LEGACYQtVisualStudioTools2022) instead. In order to build **RPCS3** with the `CMake` solution (with both **Visual Studio** and standalone **CMake** tool): -- add and set the `Qt6_ROOT` environment variable to the **Qt** libs path, e.g. `\6.10.2\msvc2022_64\` +- add and set the `Qt6_ROOT` environment variable to the **Qt** libs path, e.g. `\6.11.0\msvc2022_64\` ### Linux These are the essentials tools to build RPCS3 on Linux. Some of them can be installed through your favorite package manager: - Clang 17+ or GCC 13+ - [CMake 3.28.0+](https://www.cmake.org/download/) -- [Qt 6.10.2](https://www.qt.io/download-qt-installer) +- [Qt 6.11.0](https://www.qt.io/download-qt-installer) - [Vulkan SDK 1.3.268.0](https://vulkan.lunarg.com/sdk/home) (See "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/linux/getting_started.html)) for now future SDKs don't work. You need precisely 1.3.268.0. - [SDL3](https://github.com/libsdl-org/SDL/releases) (for the FAudio backend) @@ -123,7 +123,7 @@ Start **Visual Studio**, click on `Open a project or solution` and select the `r ##### Configuring the Qt Plugin (if used) 1) go to `Extensions->Qt VS Tools->Qt Versions` -2) add the path to your Qt installation with compiler e.g. `\6.10.2\msvc2022_64`, version will fill in automatically +2) add the path to your Qt installation with compiler e.g. `\6.11.0\msvc2022_64`, version will fill in automatically 3) go to `Extensions->Qt VS Tools->Options->Legacy Project Format`. (Only available in the **Legacy Qt Plugin**) 4) set `Build: Run pre-build setup` to `true`. (Only available in the **Legacy Qt Plugin**) From 3e60bd2aa600c328eebccb7b905f9c5e6d394aef Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 18 Mar 2026 18:56:46 +0100 Subject: [PATCH 098/108] steam: disable steam input for shortcuts --- rpcs3/rpcs3qt/steam_utils.cpp | 188 ++++++++++++++++++++++++++++++++-- rpcs3/rpcs3qt/steam_utils.h | 4 +- 2 files changed, 184 insertions(+), 8 deletions(-) diff --git a/rpcs3/rpcs3qt/steam_utils.cpp b/rpcs3/rpcs3qt/steam_utils.cpp index 2642e25b37..e5aedf9e20 100644 --- a/rpcs3/rpcs3qt/steam_utils.cpp +++ b/rpcs3/rpcs3qt/steam_utils.cpp @@ -71,6 +71,8 @@ namespace gui::utils } const std::vector data = vdf.to_vector(); + vdf.close(); + usz last_pos = 0; usz pos = 0; @@ -507,9 +509,17 @@ namespace gui::utils sys_log.success("Removed steam shortcut(s) for '%s'", entry.app_name); } + update_steam_input_config(user_dir); + return true; } + bool steam_shortcut::steam_installed() + { + const std::string path = get_steam_path(); + return !path.empty() && fs::is_dir(path); + } + u32 steam_shortcut::crc32(const std::string& data) { u32 crc = 0xFFFFFFFF; @@ -527,12 +537,6 @@ namespace gui::utils return ~crc; } - bool steam_shortcut::steam_installed() - { - const std::string path = get_steam_path(); - return !path.empty() && fs::is_dir(path); - } - u32 steam_shortcut::steam_appid(const std::string& exe, const std::string& name) { return crc32(exe + name) | 0x80000000; @@ -667,8 +671,177 @@ namespace gui::utils return str; } + void steam_shortcut::update_steam_input_config(const std::string& user_dir) + { + if (m_entries_to_add.empty() && m_entries_to_remove.empty()) + { + return; + } + + const std::string vdf_path = user_dir + "localconfig.vdf"; + const std::string backup_path = fs::get_config_dir() + "/localconfig.vdf.backup"; + + if (fs::is_file(vdf_path) && !fs::copy_file(vdf_path, backup_path, true)) + { + sys_log.error("Failed to backup steam localconfig file '%s'", vdf_path); + return; + } + + fs::file vdf(vdf_path); + if (!vdf) + { + sys_log.error("update_steam_input_config: Failed to open steam localconfig file '%s': %s", vdf_path, fs::g_tls_error); + return; + } + + std::string content = vdf.to_string(); + vdf.close(); + + static const std::string app_section_start = "\n\t\"apps\"\n\t{"; + static const std::string app_section_end = "\n\t}\n"; + static const std::string entry_section_end = "\n\t\t}"; + + bool nothing_to_remove = m_entries_to_remove.empty(); + + usz app_pos = content.rfind(app_section_start); + if (app_pos == umax) + { + if (!nothing_to_remove) + { + // We don't have to remove anything because this section did not exist + sys_log.notice("update_steam_input_config: Could not find \"apps\" section. No need to remove anything."); + nothing_to_remove = true; + } + + if (m_entries_to_add.empty()) + { + return; // Nothing to do anyway + } + + const usz insert_pos = content.rfind("\n}"); + if (insert_pos == umax) + { + sys_log.error("update_steam_input_config: Could not find main section end"); + return; + } + + sys_log.notice("update_steam_input_config: Inserting missing \"apps\" section"); + content.insert(insert_pos, fmt::format("%s\n\t}", app_section_start)); + + app_pos = content.rfind(app_section_start); + ensure(app_pos != umax); + } + + const usz search_start = app_pos + app_section_start.size(); + + const usz insert_pos = content.find(app_section_end, search_start); + if (insert_pos == umax) + { + sys_log.error("update_steam_input_config: Could not find apps section end"); + return; + } + + const auto appid_string = [](s32 signed_appid) + { + return fmt::format("\n\t\t\"%d\"\n", signed_appid); + }; + + const auto find_entry = [&content, &appid_string, search_start, insert_pos](s32 signed_appid) -> usz + { + const usz pos = content.find(appid_string(signed_appid), search_start); + if (pos >= insert_pos) return umax; + return pos; + }; + + bool dirty = false; + + for (const shortcut_entry& entry : m_entries_to_remove) + { + constexpr bool removal_diabled = true; // Disabled for now. Steam doesn't seem to remove the entries either + if constexpr (removal_diabled) + { + break; + } + + if (nothing_to_remove) + { + break; + } + + const s32 signed_appid = static_cast(entry.appid); + const usz pos = find_entry(signed_appid); + + if (pos == umax) + { + // does not exist, do nothing + sys_log.notice("update_steam_input_config: Entry for '%s' with appid '%d' does not exist. Skipping removal", entry.app_name, signed_appid); + continue; + } + + // Find the opening brace of this entry + const usz pos_brace_open = content.find('{', pos); + if (pos_brace_open == umax || pos_brace_open >= insert_pos) + { + sys_log.error("update_steam_input_config: Can't find opening brace for entry for '%s' with appid '%d'.", entry.app_name, signed_appid); + continue; + } + + // Find the closing brace + const usz pos_brace_close = content.find(entry_section_end, pos_brace_open); + if (pos_brace_close == umax || pos_brace_close >= insert_pos) + { + sys_log.error("update_steam_input_config: Can't find closing brace for entry for '%s' with appid '%d'.", entry.app_name, signed_appid); + continue; + } + + // Include the closing brace line + const usz erase_end = pos_brace_close + entry_section_end.size(); + + // Erase the whole block + content.erase(pos, erase_end - pos); + + sys_log.notice("update_steam_input_config: Removed '%s' with appid '%d'", entry.app_name, signed_appid); + + dirty = true; + } + + for (const shortcut_entry& entry : m_entries_to_add) + { + const s32 signed_appid = static_cast(entry.appid); + const usz pos = find_entry(signed_appid); + + if (pos != umax) + { + // already exists, do nothing + sys_log.notice("update_steam_input_config: Entry for '%s' with appid '%d' already exists", entry.app_name, signed_appid); + continue; + } + + sys_log.notice("update_steam_input_config: Inserting '%s' with appid '%d'", entry.app_name, signed_appid); + content.insert(insert_pos, fmt::format( + "%s" + "\t\t{\n" + "\t\t\t\"UseSteamControllerConfig\"\t\t\"0\"\n" + //"\t\t\t\"SteamControllerRumble\"\t\t\"-1\"\n" + //"\t\t\t\"SteamControllerRumbleIntensity\"\t\t\"320\"\n" + "\t\t}", appid_string(signed_appid))); + + dirty = true; + } + + if (dirty && !fs::write_file(vdf_path, fs::rewrite, content)) + { + sys_log.error("Failed to update steam localconfig '%s': '%s'", vdf_path, fs::g_tls_error); + + if (!fs::copy_file(backup_path, vdf_path, true)) + { + sys_log.error("Failed to restore steam localconfig backup: '%s'", fs::g_tls_error); + } + } + } + #ifdef _WIN32 - std::string get_registry_string(const wchar_t* key, const wchar_t* name) + static std::string get_registry_string(const wchar_t* key, const wchar_t* name) { HKEY hkey = NULL; LSTATUS status = RegOpenKeyW(HKEY_CURRENT_USER, key, &hkey); @@ -832,6 +1005,7 @@ namespace gui::utils // } const std::string content = vdf.to_string(); + vdf.close(); usz user_count = 0; diff --git a/rpcs3/rpcs3qt/steam_utils.h b/rpcs3/rpcs3qt/steam_utils.h index 369ee084a2..deb7c0dad7 100644 --- a/rpcs3/rpcs3qt/steam_utils.h +++ b/rpcs3/rpcs3qt/steam_utils.h @@ -103,6 +103,8 @@ namespace gui::utils bool parse_file(const std::string& path); + void update_steam_input_config(const std::string& user_dir); + static u32 crc32(const std::string& data); static u32 steam_appid(const std::string& exe, const std::string& name); @@ -115,7 +117,7 @@ namespace gui::utils static std::string steamid64_to_32(const std::string& steam_id); static std::string get_steam_path(); static std::string get_last_active_steam_user(const std::string& steam_path); - + static std::string get_steam_banner_path(steam_banner banner, const std::string& grid_dir, u32 appid); static void create_steam_banner(steam_banner banner, const std::string& src_path, const QPixmap& src_icon, const std::string& grid_dir, const shortcut_entry& entry); From 789bab97be41cb5413cbc832a06729a10e2238eb Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 30 Mar 2026 12:39:46 +0200 Subject: [PATCH 099/108] Qt: add some sorting options to the screenshot manager --- rpcs3/rpcs3qt/main_window.cpp | 4 +- rpcs3/rpcs3qt/qt_utils.cpp | 5 + rpcs3/rpcs3qt/screenshot_item.cpp | 39 +++- rpcs3/rpcs3qt/screenshot_item.h | 17 +- rpcs3/rpcs3qt/screenshot_manager_dialog.cpp | 197 +++++++++++++++----- rpcs3/rpcs3qt/screenshot_manager_dialog.h | 34 +++- 6 files changed, 232 insertions(+), 64 deletions(-) diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index b4776d2fc1..130434f0c2 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -3119,9 +3119,9 @@ void main_window::CreateConnects() m_game_list_frame->Refresh(true); // New user may have different games unlocked. }); - connect(ui->actionManage_Screenshots, &QAction::triggered, this, [] + connect(ui->actionManage_Screenshots, &QAction::triggered, this, [this] { - screenshot_manager_dialog* screenshot_manager = new screenshot_manager_dialog(); + screenshot_manager_dialog* screenshot_manager = new screenshot_manager_dialog(m_game_list_frame ? m_game_list_frame->GetGameInfo() : std::vector{}); screenshot_manager->show(); }); diff --git a/rpcs3/rpcs3qt/qt_utils.cpp b/rpcs3/rpcs3qt/qt_utils.cpp index 7c0760f3a8..ede9f6be9a 100644 --- a/rpcs3/rpcs3qt/qt_utils.cpp +++ b/rpcs3/rpcs3qt/qt_utils.cpp @@ -279,6 +279,11 @@ namespace gui exp_img.setDevicePixelRatio(device_pixel_ratio); exp_img.fill(Qt::transparent); + if (pixmap.isNull()) + { + return exp_img; + } + // Load scaled pixmap pixmap = pixmap.scaled(icon_size, Qt::KeepAspectRatio, mode); diff --git a/rpcs3/rpcs3qt/screenshot_item.cpp b/rpcs3/rpcs3qt/screenshot_item.cpp index 2d24c7cb36..2897cf968c 100644 --- a/rpcs3/rpcs3qt/screenshot_item.cpp +++ b/rpcs3/rpcs3qt/screenshot_item.cpp @@ -4,26 +4,37 @@ #include -screenshot_item::screenshot_item(QWidget* parent) +screenshot_item::screenshot_item(QWidget* parent, QSize icon_size, const QString& icon_path, const QPixmap& placeholder) : flow_widget_item(parent) + , m_icon_path(icon_path) + , m_icon_size(icon_size) { + setToolTip(icon_path); + cb_on_first_visibility = [this]() { m_thread.reset(QThread::create([this]() { thread_base::set_name("Screenshot item"); - const QPixmap pixmap = gui::utils::get_aligned_pixmap(icon_path, icon_size, 1.0, Qt::SmoothTransformation, gui::utils::align_h::center, gui::utils::align_v::center); + const QPixmap src_icon = QPixmap(m_icon_path); + if (src_icon.isNull()) return; + + const QPixmap pixmap = gui::utils::get_aligned_pixmap(src_icon, m_icon_size, 1.0, Qt::SmoothTransformation, gui::utils::align_h::center, gui::utils::align_v::center); Q_EMIT signal_icon_update(pixmap); })); m_thread->start(); }; - label = new QLabel(this); + m_label = new QLabel(this); + m_label->setPixmap(placeholder); + QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); - layout->addWidget(label); + layout->addWidget(m_label); setLayout(layout); + + connect(this, &screenshot_item::signal_icon_update, this, &screenshot_item::update_icon, Qt::ConnectionType::QueuedConnection); } screenshot_item::~screenshot_item() @@ -33,3 +44,23 @@ screenshot_item::~screenshot_item() m_thread->wait(); } } + +void screenshot_item::update_icon(const QPixmap& pixmap) +{ + if (m_label) + { + m_label->setPixmap(pixmap); + } +} + +void screenshot_item::mouseDoubleClickEvent(QMouseEvent* ev) +{ + flow_widget_item::mouseDoubleClickEvent(ev); + + if (!ev) return; + + if (ev->button() == Qt::LeftButton) + { + Q_EMIT signal_icon_preview(m_icon_path); + } +} diff --git a/rpcs3/rpcs3qt/screenshot_item.h b/rpcs3/rpcs3qt/screenshot_item.h index 36f0232b1f..df223783f3 100644 --- a/rpcs3/rpcs3qt/screenshot_item.h +++ b/rpcs3/rpcs3qt/screenshot_item.h @@ -3,22 +3,29 @@ #include "flow_widget_item.h" #include #include +#include class screenshot_item : public flow_widget_item { Q_OBJECT public: - screenshot_item(QWidget* parent); + screenshot_item(QWidget* parent, QSize icon_size, const QString& icon_path, const QPixmap& placeholder); virtual ~screenshot_item(); - QString icon_path; - QSize icon_size; - QLabel* label{}; - private: + QLabel* m_label{}; + QString m_icon_path; + QSize m_icon_size; std::unique_ptr m_thread; +protected: + void mouseDoubleClickEvent(QMouseEvent* ev) override; + Q_SIGNALS: void signal_icon_update(const QPixmap& pixmap); + void signal_icon_preview(const QString& path); + +public Q_SLOTS: + void update_icon(const QPixmap& pixmap); }; diff --git a/rpcs3/rpcs3qt/screenshot_manager_dialog.cpp b/rpcs3/rpcs3qt/screenshot_manager_dialog.cpp index 3825dfd747..d99ce35605 100644 --- a/rpcs3/rpcs3qt/screenshot_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/screenshot_manager_dialog.cpp @@ -10,13 +10,15 @@ #include #include #include +#include #include #include #include LOG_CHANNEL(gui_log, "GUI"); -screenshot_manager_dialog::screenshot_manager_dialog(QWidget* parent) : QDialog(parent) +screenshot_manager_dialog::screenshot_manager_dialog(const std::vector& games, QWidget* parent) + : QDialog(parent), m_games(games) { setWindowTitle(tr("Screenshots")); setAttribute(Qt::WA_DeleteOnClose); @@ -28,11 +30,50 @@ screenshot_manager_dialog::screenshot_manager_dialog(QWidget* parent) : QDialog( m_placeholder = QPixmap(m_icon_size); m_placeholder.fill(Qt::gray); - connect(this, &screenshot_manager_dialog::signal_icon_preview, this, &screenshot_manager_dialog::show_preview); - connect(this, &screenshot_manager_dialog::signal_entry_parsed, this, &screenshot_manager_dialog::add_entry); + connect(this, &screenshot_manager_dialog::signal_entry_parsed, this, &screenshot_manager_dialog::add_entry, Qt::ConnectionType::QueuedConnection); - QVBoxLayout* layout = new QVBoxLayout; + m_combo_sort_filter = new QComboBox(); + m_combo_sort_filter->setSizeAdjustPolicy(QComboBox::AdjustToContents); + m_combo_sort_filter->addItem(tr("Sort by Game"), static_cast(sort_filter::game)); + m_combo_sort_filter->addItem(tr("Sort by Date"), static_cast(sort_filter::date)); + connect(m_combo_sort_filter, &QComboBox::currentIndexChanged, this, [this](int /*index*/){ reload(); }); + + m_combo_type_filter = new QComboBox(); + m_combo_type_filter->setSizeAdjustPolicy(QComboBox::AdjustToContents); + m_combo_type_filter->addItem(tr("All Screenshots"), static_cast(type_filter::all)); + m_combo_type_filter->addItem(tr("RPCS3 Screenshots"), static_cast(type_filter::rpcs3)); + m_combo_type_filter->addItem(tr("Cell Screenshots"), static_cast(type_filter::cell)); + connect(m_combo_type_filter, &QComboBox::currentIndexChanged, this, [this](int /*index*/){ reload(); }); + + m_combo_game_filter = new QComboBox(); + m_combo_game_filter->setSizeAdjustPolicy(QComboBox::AdjustToContents); + m_combo_game_filter->addItem(tr("All Games"), QString()); + connect(m_combo_game_filter, &QComboBox::currentIndexChanged, this, [this](int /*index*/){ reload(); }); + + QHBoxLayout* sort_layout = new QHBoxLayout(); + sort_layout->addWidget(m_combo_sort_filter); + QGroupBox* gb_sort = new QGroupBox(tr("Sort")); + gb_sort->setLayout(sort_layout); + + QHBoxLayout* type_layout = new QHBoxLayout(); + type_layout->addWidget(m_combo_type_filter); + QGroupBox* gb_type = new QGroupBox(tr("Filter Type")); + gb_type->setLayout(type_layout); + + QHBoxLayout* game_layout = new QHBoxLayout(); + game_layout->addWidget(m_combo_game_filter); + QGroupBox* gb_game = new QGroupBox(tr("Filter Game")); + gb_game->setLayout(game_layout); + + QHBoxLayout* top_layout = new QHBoxLayout(); + top_layout->addWidget(gb_sort); + top_layout->addWidget(gb_type); + top_layout->addWidget(gb_game); + top_layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Minimum)); + + QVBoxLayout* layout = new QVBoxLayout(); layout->setContentsMargins(0, 0, 0, 0); + layout->addLayout(top_layout); layout->addWidget(m_flow_widget); setLayout(layout); @@ -47,14 +88,8 @@ screenshot_manager_dialog::~screenshot_manager_dialog() void screenshot_manager_dialog::add_entry(const QString& path) { - screenshot_item* item = new screenshot_item(m_flow_widget); - ensure(item->label); - item->setToolTip(path); - item->installEventFilter(this); - item->label->setPixmap(m_placeholder); - item->icon_path = path; - item->icon_size = m_icon_size; - connect(item, &screenshot_item::signal_icon_update, this, &screenshot_manager_dialog::update_icon); + screenshot_item* item = new screenshot_item(m_flow_widget, m_icon_size, path, m_placeholder); + connect(item, &screenshot_item::signal_icon_preview, this, &screenshot_manager_dialog::show_preview); m_flow_widget->add_widget(item); } @@ -65,28 +100,74 @@ void screenshot_manager_dialog::show_preview(const QString& path) preview->show(); } -void screenshot_manager_dialog::update_icon(const QPixmap& pixmap) -{ - if (screenshot_item* item = static_cast(QObject::sender())) - { - if (item->label) - { - item->label->setPixmap(pixmap); - } - } -} - void screenshot_manager_dialog::reload() { m_abort_parsing = true; + m_parsing_watcher.disconnect(); gui::utils::stop_future_watcher(m_parsing_watcher, true); - const std::string screenshot_path_qt = fs::get_config_dir() + "screenshots/"; - const std::string screenshot_path_cell = rpcs3::utils::get_hdd0_dir() + "/photo/"; + const type_filter t_filter = static_cast(m_combo_type_filter->currentData().toInt()); + const sort_filter s_filter = static_cast(m_combo_sort_filter->currentData().toInt()); + const QString game_filter = m_combo_game_filter->currentData().toString(); + + const std::string screenshot_path_rpcs3 = fs::get_config_dir() + "screenshots/"; + const std::string screenshot_path_cell = rpcs3::utils::get_hdd0_dir() + "/photo/"; + + std::vector folders; + switch (t_filter) + { + case type_filter::all: + folders.push_back(screenshot_path_rpcs3); + folders.push_back(screenshot_path_cell); + break; + case type_filter::rpcs3: + folders.push_back(screenshot_path_rpcs3); + break; + case type_filter::cell: + folders.push_back(screenshot_path_cell); + break; + } m_flow_widget->clear(); + m_game_folders.clear(); m_abort_parsing = false; - m_parsing_watcher.setFuture(QtConcurrent::map(m_parsing_threads, [this, screenshot_path_qt, screenshot_path_cell](int index) + + connect(&m_parsing_watcher, &QFutureWatcher::finished, this, [this]() + { + std::vector> games; + for (const auto& [dirname, paths] : m_game_folders) + { + const std::string serial = dirname.toStdString(); + std::string text = serial; + for (const auto& game : m_games) + { + if (game && game->info.serial == serial) + { + text = fmt::format("%s (%s)", game->info.name, serial); + break; + } + } + games.push_back(std::pair(dirname, QString::fromStdString(text))); + } + + std::sort(games.begin(), games.end(), [](const std::pair& l, const std::pair& r) + { + return l.second < r.second; + }); + + const QString old_filter = m_combo_game_filter->currentData().toString(); + m_combo_game_filter->blockSignals(true); + m_combo_game_filter->clear(); + m_combo_game_filter->addItem(tr("All Games"), QString()); + for (const auto& [dirname, text] : games) + { + m_combo_game_filter->addItem(text, dirname); + } + m_combo_game_filter->setCurrentIndex(m_combo_game_filter->findData(old_filter)); + m_combo_game_filter->blockSignals(false); + }); + + m_parsing_watcher.setFuture(QtConcurrent::map(m_parsing_threads, [this, folders, game_filter, s_filter](int index) { if (index != 0) { @@ -95,26 +176,68 @@ void screenshot_manager_dialog::reload() const QStringList filter{ QStringLiteral("*.png") }; - for (const std::string& path : { screenshot_path_qt, screenshot_path_cell }) + for (const std::string& folder : folders) { if (m_abort_parsing) { return; } - if (path.empty()) + if (folder.empty()) { gui_log.error("Screenshot manager: Trying to load screenshots from empty path!"); continue; } - QDirIterator dir_iter(QString::fromStdString(path), filter, QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); + QDirIterator dir_iter(QString::fromStdString(folder), filter, QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); while (dir_iter.hasNext() && !m_abort_parsing) { - Q_EMIT signal_entry_parsed(dir_iter.next()); + QFileInfo info(dir_iter.next()); + const QString dirname = info.dir().dirName(); + m_game_folders[dirname].push_back(std::move(info)); } } + + switch (s_filter) + { + case sort_filter::game: + { + for (const auto& [dirname, infos] : m_game_folders) + { + if (game_filter.isEmpty() || game_filter == dirname) + { + for (const QFileInfo& info : infos) + { + Q_EMIT signal_entry_parsed(info.filePath()); + } + } + } + break; + } + case sort_filter::date: + { + std::vector sorted_infos; + for (const auto& [dirname, infos] : m_game_folders) + { + if (game_filter.isEmpty() || game_filter == dirname) + { + sorted_infos.insert(sorted_infos.end(), infos.begin(), infos.end()); + } + } + + std::sort(sorted_infos.begin(), sorted_infos.end(), [](const QFileInfo& a, const QFileInfo& b) + { + return a.lastModified() < b.lastModified(); + }); + + for (const QFileInfo& info : sorted_infos) + { + Q_EMIT signal_entry_parsed(info.filePath()); + } + break; + } + } })); } @@ -123,17 +246,3 @@ void screenshot_manager_dialog::showEvent(QShowEvent* event) QDialog::showEvent(event); reload(); } - -bool screenshot_manager_dialog::eventFilter(QObject* watched, QEvent* event) -{ - if (event && event->type() == QEvent::MouseButtonDblClick && static_cast(event)->button() == Qt::LeftButton) - { - if (screenshot_item* item = static_cast(watched)) - { - Q_EMIT signal_icon_preview(item->icon_path); - return true; - } - } - - return false; -} diff --git a/rpcs3/rpcs3qt/screenshot_manager_dialog.h b/rpcs3/rpcs3qt/screenshot_manager_dialog.h index ab4d7c13a8..a236eb5e0d 100644 --- a/rpcs3/rpcs3qt/screenshot_manager_dialog.h +++ b/rpcs3/rpcs3qt/screenshot_manager_dialog.h @@ -1,30 +1,29 @@ #pragma once #include "flow_widget.h" +#include "gui_game_info.h" +#include #include +#include #include #include #include -#include +#include #include +#include +#include class screenshot_manager_dialog : public QDialog { Q_OBJECT public: - screenshot_manager_dialog(QWidget* parent = nullptr); + screenshot_manager_dialog(const std::vector& games, QWidget* parent = nullptr); ~screenshot_manager_dialog(); - bool eventFilter(QObject* watched, QEvent* event) override; - Q_SIGNALS: void signal_entry_parsed(const QString& path); - void signal_icon_preview(const QString& path); - -public Q_SLOTS: - void update_icon(const QPixmap& pixmap); private Q_SLOTS: void add_entry(const QString& path); @@ -36,11 +35,28 @@ protected: private: void reload(); + enum class type_filter + { + all, + rpcs3, + cell + }; + + enum class sort_filter + { + game, + date + }; + + std::vector m_games; bool m_abort_parsing = false; const std::array m_parsing_threads{0}; QFutureWatcher m_parsing_watcher; flow_widget* m_flow_widget = nullptr; - + QComboBox* m_combo_sort_filter = nullptr; + QComboBox* m_combo_game_filter = nullptr; + QComboBox* m_combo_type_filter = nullptr; QSize m_icon_size; QPixmap m_placeholder; + std::map> m_game_folders; }; From f92677fa7ed2345e225b9bb963894d6daf1722d4 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 30 Mar 2026 12:47:26 +0200 Subject: [PATCH 100/108] rsx: Add missing break --- rpcs3/Emu/Cell/PPUThread.cpp | 2 +- rpcs3/Emu/RSX/RSXThread.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index 0062bc2825..f5d91cc519 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -1286,7 +1286,7 @@ extern bool ppu_patch(u32 addr, u32 value) { if (addr % 4) { - ppu_log.fatal("Patch failed at 0x%x: unanligned memory address.", addr); + ppu_log.fatal("Patch failed at 0x%x: unaligned memory address.", addr); return false; } diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 5158ce9a29..3392b9693b 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -1868,6 +1868,7 @@ namespace rsx } default: rsx_log.fatal("Unhandled framebuffer option changed 0x%x", opt); + break; } } From b0dbac9a0774cc97f9628dca56ef7c69e7d0df95 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 29 Mar 2026 17:16:54 +0200 Subject: [PATCH 101/108] Qt: hide log frame by default --- rpcs3/rpcs3qt/debugger_frame.cpp | 1 + rpcs3/rpcs3qt/game_list_frame.cpp | 2 ++ rpcs3/rpcs3qt/gui_settings.h | 2 +- rpcs3/rpcs3qt/log_frame.cpp | 2 ++ rpcs3/rpcs3qt/main_window.cpp | 3 --- 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/rpcs3/rpcs3qt/debugger_frame.cpp b/rpcs3/rpcs3qt/debugger_frame.cpp index b8ed1d642f..2184974d85 100644 --- a/rpcs3/rpcs3qt/debugger_frame.cpp +++ b/rpcs3/rpcs3qt/debugger_frame.cpp @@ -58,6 +58,7 @@ debugger_frame::debugger_frame(std::shared_ptr gui_settings, QWidg : custom_dock_widget(tr("Debugger [Press F1 for Help]"), parent) , m_gui_settings(std::move(gui_settings)) { + setObjectName("debugger"); setContentsMargins(0, 0, 0, 0); m_update = new QTimer(this); diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 7ef27ea0f9..36e26cf43d 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -40,6 +40,8 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std , m_emu_settings(std::move(emu_settings)) , m_persistent_settings(std::move(persistent_settings)) { + setObjectName("gamelist"); + m_game_list_actions = std::make_shared(this, m_gui_settings); m_icon_size = gui::gl_icon_size_min; // ensure a valid size diff --git a/rpcs3/rpcs3qt/gui_settings.h b/rpcs3/rpcs3qt/gui_settings.h index c15b4166bf..41be2732ab 100644 --- a/rpcs3/rpcs3qt/gui_settings.h +++ b/rpcs3/rpcs3qt/gui_settings.h @@ -173,7 +173,7 @@ namespace gui const gui_save fd_save_log = gui_save(main_window, "lastExplorePathSaveLog", ""); const gui_save mw_debugger = gui_save(main_window, "debuggerVisible", false); - const gui_save mw_logger = gui_save(main_window, "loggerVisible", true); + const gui_save mw_logger = gui_save(main_window, "loggerVisible", false); const gui_save mw_gamelist = gui_save(main_window, "gamelistVisible", true); const gui_save mw_toolBarVisible = gui_save(main_window, "toolBarVisible", true); const gui_save mw_titleBarsVisible = gui_save(main_window, "titleBarsVisible", true); diff --git a/rpcs3/rpcs3qt/log_frame.cpp b/rpcs3/rpcs3qt/log_frame.cpp index cf570eea7a..61ca66a909 100644 --- a/rpcs3/rpcs3qt/log_frame.cpp +++ b/rpcs3/rpcs3qt/log_frame.cpp @@ -113,6 +113,8 @@ static gui_listener s_gui_listener; log_frame::log_frame(std::shared_ptr _gui_settings, QWidget* parent) : custom_dock_widget(tr("Log"), parent), m_gui_settings(std::move(_gui_settings)) { + setObjectName("logger"); + const int max_block_count_log = m_gui_settings->GetValue(gui::l_limit).toInt(); const int max_block_count_tty = m_gui_settings->GetValue(gui::l_limit_tty).toInt(); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 130434f0c2..2a366705ab 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -3512,11 +3512,8 @@ void main_window::CreateDockWindows() m_mw->setContextMenuPolicy(Qt::PreventContextMenu); m_game_list_frame = new game_list_frame(m_gui_settings, m_emu_settings, m_persistent_settings, m_mw); - m_game_list_frame->setObjectName("gamelist"); m_debugger_frame = new debugger_frame(m_gui_settings, m_mw); - m_debugger_frame->setObjectName("debugger"); m_log_frame = new log_frame(m_gui_settings, m_mw); - m_log_frame->setObjectName("logger"); m_mw->addDockWidget(Qt::LeftDockWidgetArea, m_game_list_frame); m_mw->addDockWidget(Qt::LeftDockWidgetArea, m_log_frame); From cb3a83cba743cba540dee72d8448308a488d6f35 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 30 Mar 2026 15:32:37 +0200 Subject: [PATCH 102/108] overlays: add setting for fatal error hint (disabled by default) --- .../Overlays/HomeMenu/overlay_home_menu_settings.cpp | 1 + rpcs3/Emu/localized_string_id.h | 1 + rpcs3/Emu/system_config.h | 1 + rpcs3/rpcs3.cpp | 10 +++++++--- rpcs3/rpcs3qt/emu_settings_type.h | 2 ++ rpcs3/rpcs3qt/localized_emu.h | 1 + rpcs3/rpcs3qt/settings_dialog.cpp | 3 +++ rpcs3/rpcs3qt/settings_dialog.ui | 7 +++++++ rpcs3/rpcs3qt/tooltips.h | 1 + 9 files changed, 24 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp index 65a3b4263d..d9efa0522a 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp @@ -229,6 +229,7 @@ namespace rsx add_checkbox(&g_cfg.misc.show_pressure_intensity_toggle_hint, localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_SHOW_PRESSURE_INTENSITY_TOGGLE_HINT); add_checkbox(&g_cfg.misc.show_analog_limiter_toggle_hint, localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_SHOW_ANALOG_LIMITER_TOGGLE_HINT); add_checkbox(&g_cfg.misc.show_mouse_and_keyboard_toggle_hint, localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_SHOW_MOUSE_AND_KB_TOGGLE_HINT); + add_checkbox(&g_cfg.misc.show_fatal_error_hints, localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_SHOW_FATAL_ERROR_HINTS); add_checkbox(&g_cfg.video.record_with_overlays, localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_RECORD_WITH_OVERLAYS); apply_layout(); diff --git a/rpcs3/Emu/localized_string_id.h b/rpcs3/Emu/localized_string_id.h index 0bcf1caf8b..0a2e654d4e 100644 --- a/rpcs3/Emu/localized_string_id.h +++ b/rpcs3/Emu/localized_string_id.h @@ -254,6 +254,7 @@ enum class localized_string_id HOME_MENU_SETTINGS_OVERLAYS_SHOW_PRESSURE_INTENSITY_TOGGLE_HINT, HOME_MENU_SETTINGS_OVERLAYS_SHOW_ANALOG_LIMITER_TOGGLE_HINT, HOME_MENU_SETTINGS_OVERLAYS_SHOW_MOUSE_AND_KB_TOGGLE_HINT, + HOME_MENU_SETTINGS_OVERLAYS_SHOW_FATAL_ERROR_HINTS, HOME_MENU_SETTINGS_OVERLAYS_RECORD_WITH_OVERLAYS, HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY, HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_ENABLE, diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 099e0243a1..85b84d6b85 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -357,6 +357,7 @@ struct cfg_root : cfg::node cfg::_bool show_pressure_intensity_toggle_hint{ this, "Show pressure intensity toggle hint", true, true }; cfg::_bool show_analog_limiter_toggle_hint{ this, "Show analog limiter toggle hint", true, true }; cfg::_bool show_mouse_and_keyboard_toggle_hint{ this, "Show mouse and keyboard toggle hint", true, true }; + cfg::_bool show_fatal_error_hints{ this, "Show fatal error hints", false, true }; cfg::_bool show_capture_hints{ this, "Show capture hints", true, true }; cfg::_bool use_native_interface{ this, "Use native user interface", true }; cfg::string gdb_server{ this, "GDB Server", "127.0.0.1:2345" }; diff --git a/rpcs3/rpcs3.cpp b/rpcs3/rpcs3.cpp index 921394e311..011dfbe39f 100644 --- a/rpcs3/rpcs3.cpp +++ b/rpcs3/rpcs3.cpp @@ -68,6 +68,7 @@ DYNAMIC_IMPORT("ntdll.dll", NtSetTimerResolution, NTSTATUS(ULONG DesiredResoluti #include "util/media_utils.h" #include "rpcs3_version.h" #include "Emu/System.h" +#include "Emu/system_config.h" #include "Emu/system_utils.hpp" #include "Emu/RSX/Overlays/overlay_message.h" #include @@ -353,11 +354,14 @@ public: #endif if (msg == logs::level::fatal) { - std::string overlay_msg = "Fatal error: " + _msg.substr(rpcs3_prefix.size()); - fmt::trim_back(overlay_msg, " \t\n"); + if (g_cfg.misc.show_fatal_error_hints) + { + std::string overlay_msg = "Fatal error: " + _msg.substr(rpcs3_prefix.size()); + fmt::trim_back(overlay_msg, " \t\n"); + rsx::overlays::queue_message(overlay_msg, umax); + } // Pause emulation if fatal error encountered - rsx::overlays::queue_message(overlay_msg, umax); Emu.Pause(true); } } diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index 9b9f8a9279..3748aae000 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -189,6 +189,7 @@ enum class emu_settings_type ShowPressureIntensityToggleHint, ShowAnalogLimiterToggleHint, ShowMouseAndKeyboardToggleHint, + ShowFatalErrorHints, ShowCaptureHints, WindowTitleFormat, PauseDuringHomeMenu, @@ -405,6 +406,7 @@ inline static const std::map settings_location { emu_settings_type::ShowPressureIntensityToggleHint, { "Miscellaneous", "Show pressure intensity toggle hint"}}, { emu_settings_type::ShowAnalogLimiterToggleHint, { "Miscellaneous", "Show analog limiter toggle hint"}}, { emu_settings_type::ShowMouseAndKeyboardToggleHint, { "Miscellaneous", "Show mouse and keyboard toggle hint"}}, + { emu_settings_type::ShowFatalErrorHints, { "Miscellaneous", "Show fatal error hints"}}, { emu_settings_type::ShowCaptureHints, { "Miscellaneous", "Show capture hints" }}, { emu_settings_type::SilenceAllLogs, { "Miscellaneous", "Silence All Logs" }}, { emu_settings_type::WindowTitleFormat, { "Miscellaneous", "Window Title Format" }}, diff --git a/rpcs3/rpcs3qt/localized_emu.h b/rpcs3/rpcs3qt/localized_emu.h index fe0451b44c..d4de0bf282 100644 --- a/rpcs3/rpcs3qt/localized_emu.h +++ b/rpcs3/rpcs3qt/localized_emu.h @@ -274,6 +274,7 @@ private: case localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_SHOW_PRESSURE_INTENSITY_TOGGLE_HINT: return tr("Show Pressure Intensity Toggle Hint", "Overlays"); case localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_SHOW_ANALOG_LIMITER_TOGGLE_HINT: return tr( "Show Analog Limiter Toggle Hint", "Overlays"); case localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_SHOW_MOUSE_AND_KB_TOGGLE_HINT: return tr("Show Mouse And Keyboard Toggle Hint", "Overlays"); + case localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_SHOW_FATAL_ERROR_HINTS: return tr("Show Fatal Error Hints", "Overlays"); case localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_RECORD_WITH_OVERLAYS: return tr("Record With Overlays", "Overlays"); case localized_string_id::HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY: return tr("Performance Overlay"); case localized_string_id::HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_ENABLE: return tr("Enable Performance Overlay", "Performance Overlay"); diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index f4fcee4985..7bf4f02662 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -1847,6 +1847,9 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceCheckBox(ui->showMouseAndKeyboardToggleHint, emu_settings_type::ShowMouseAndKeyboardToggleHint); SubscribeTooltip(ui->showMouseAndKeyboardToggleHint, tooltips.settings.show_mouse_and_keyboard_toggle_hint); + m_emu_settings->EnhanceCheckBox(ui->showFatalErrorHints, emu_settings_type::ShowFatalErrorHints); + SubscribeTooltip(ui->showFatalErrorHints, tooltips.settings.show_fatal_error_hints); + m_emu_settings->EnhanceCheckBox(ui->showCaptureHints, emu_settings_type::ShowCaptureHints); SubscribeTooltip(ui->showCaptureHints, tooltips.settings.show_capture_hints); diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index 282826fcd6..a5bcc011b7 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -3037,6 +3037,13 @@ + + + + Show fatal error hints + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 0276ac0f82..f09642a826 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -154,6 +154,7 @@ public: const QString show_pressure_intensity_toggle_hint = tr("Shows pressure intensity toggle hint using the native overlay."); const QString show_analog_limiter_toggle_hint = tr("Shows analog limiter toggle hint using the native overlay."); const QString show_mouse_and_keyboard_toggle_hint = tr("Shows mouse and keyboard toggle hint using the native overlay."); + const QString show_fatal_error_hints = tr("Shows fatal error hints using the native overlay."); const QString show_capture_hints = tr("Shows screenshot and recording hints using the native overlay."); const QString use_native_interface = tr("Enables use of native HUD within the game window that can interact with game controllers.\nWhen disabled, regular Qt dialogs are used instead.\nCurrently, the on-screen keyboard only supports the English key layout."); const QString record_with_overlays = tr("Enables recording with overlays.\nThis also affects screenshots."); From 7c9261a4615de0cc45363cb44915ed34ef081a63 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 30 Mar 2026 15:57:17 +0200 Subject: [PATCH 103/108] overlays: add toggle for boot sequence music --- .../RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp | 1 + rpcs3/Emu/RSX/RSXThread.cpp | 7 +++++-- rpcs3/Emu/localized_string_id.h | 1 + rpcs3/Emu/system_config.h | 1 + rpcs3/rpcs3qt/emu_settings_type.h | 2 ++ rpcs3/rpcs3qt/localized_emu.h | 1 + rpcs3/rpcs3qt/settings_dialog.cpp | 3 +++ rpcs3/rpcs3qt/settings_dialog.ui | 7 +++++++ rpcs3/rpcs3qt/tooltips.h | 1 + 9 files changed, 22 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp index d9efa0522a..9147b7e5c5 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_settings.cpp @@ -221,6 +221,7 @@ namespace rsx home_menu_settings_overlays::home_menu_settings_overlays(s16 x, s16 y, u16 width, u16 height, bool use_separators, home_menu_page* parent) : home_menu_settings_page(x, y, width, height, use_separators, parent, get_localized_string(localized_string_id::HOME_MENU_SETTINGS_OVERLAYS)) { + add_checkbox(&g_cfg.misc.play_music_during_boot, localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_PLAY_MUSIC_DURING_BOOT); add_checkbox(&g_cfg.misc.show_trophy_popups, localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_SHOW_TROPHY_POPUPS); add_checkbox(&g_cfg.misc.show_rpcn_popups, localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_SHOW_RPCN_POPUPS); add_checkbox(&g_cfg.misc.show_shader_compilation_hint, localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_SHOW_SHADER_COMPILATION_HINT); diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 3392b9693b..a7338cbf56 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -712,9 +712,12 @@ namespace rsx { m_overlay_manager = g_fxo->init(0); - if (const std::string audio_path = Emu.GetSfoDir(true) + "/SND0.AT3"; fs::is_file(audio_path)) + if (g_cfg.misc.play_music_during_boot) { - m_overlay_manager->start_audio(audio_path); + if (const std::string audio_path = Emu.GetSfoDir(true) + "/SND0.AT3"; fs::is_file(audio_path)) + { + m_overlay_manager->start_audio(audio_path); + } } } diff --git a/rpcs3/Emu/localized_string_id.h b/rpcs3/Emu/localized_string_id.h index 0a2e654d4e..c411f6e480 100644 --- a/rpcs3/Emu/localized_string_id.h +++ b/rpcs3/Emu/localized_string_id.h @@ -256,6 +256,7 @@ enum class localized_string_id HOME_MENU_SETTINGS_OVERLAYS_SHOW_MOUSE_AND_KB_TOGGLE_HINT, HOME_MENU_SETTINGS_OVERLAYS_SHOW_FATAL_ERROR_HINTS, HOME_MENU_SETTINGS_OVERLAYS_RECORD_WITH_OVERLAYS, + HOME_MENU_SETTINGS_OVERLAYS_PLAY_MUSIC_DURING_BOOT, HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY, HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_ENABLE, HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_ENABLE_FRAMERATE_GRAPH, diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 85b84d6b85..9dbaa0c723 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -364,6 +364,7 @@ struct cfg_root : cfg::node cfg::_bool silence_all_logs{ this, "Silence All Logs", false, true }; cfg::string title_format{ this, "Window Title Format", "FPS: %F | %R | %V | %T [%t]", true }; cfg::_bool pause_during_home_menu{this, "Pause Emulation During Home Menu", false, false }; + cfg::_bool play_music_during_boot{this, "Play music during boot sequence", true, true }; cfg::_bool enable_gamemode{ this, "Enable GameMode", false, false }; } misc{ this }; diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index 3748aae000..db3e3556da 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -193,6 +193,7 @@ enum class emu_settings_type ShowCaptureHints, WindowTitleFormat, PauseDuringHomeMenu, + PlayMusicDuringBoot, EnableGamemode, // Network @@ -411,6 +412,7 @@ inline static const std::map settings_location { emu_settings_type::SilenceAllLogs, { "Miscellaneous", "Silence All Logs" }}, { emu_settings_type::WindowTitleFormat, { "Miscellaneous", "Window Title Format" }}, { emu_settings_type::PauseDuringHomeMenu, { "Miscellaneous", "Pause Emulation During Home Menu" }}, + { emu_settings_type::PlayMusicDuringBoot, { "Miscellaneous", "Play music during boot sequence" }}, { emu_settings_type::EnableGamemode, { "Miscellaneous", "Enable GameMode" }}, // Networking diff --git a/rpcs3/rpcs3qt/localized_emu.h b/rpcs3/rpcs3qt/localized_emu.h index d4de0bf282..ba4960aa73 100644 --- a/rpcs3/rpcs3qt/localized_emu.h +++ b/rpcs3/rpcs3qt/localized_emu.h @@ -276,6 +276,7 @@ private: case localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_SHOW_MOUSE_AND_KB_TOGGLE_HINT: return tr("Show Mouse And Keyboard Toggle Hint", "Overlays"); case localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_SHOW_FATAL_ERROR_HINTS: return tr("Show Fatal Error Hints", "Overlays"); case localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_RECORD_WITH_OVERLAYS: return tr("Record With Overlays", "Overlays"); + case localized_string_id::HOME_MENU_SETTINGS_OVERLAYS_PLAY_MUSIC_DURING_BOOT: return tr("Play music during boot sequence.", "Overlays"); case localized_string_id::HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY: return tr("Performance Overlay"); case localized_string_id::HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_ENABLE: return tr("Enable Performance Overlay", "Performance Overlay"); case localized_string_id::HOME_MENU_SETTINGS_PERFORMANCE_OVERLAY_ENABLE_FRAMERATE_GRAPH: return tr("Enable Framerate Graph", "Performance Overlay"); diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 7bf4f02662..7e7f115d22 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -1829,6 +1829,9 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std ui->enableGamemode->setVisible(false); #endif + m_emu_settings->EnhanceCheckBox(ui->playMusicDuringBoot, emu_settings_type::PlayMusicDuringBoot); + SubscribeTooltip(ui->playMusicDuringBoot, tooltips.settings.play_music_during_boot); + m_emu_settings->EnhanceCheckBox(ui->showShaderCompilationHint, emu_settings_type::ShowShaderCompilationHint); SubscribeTooltip(ui->showShaderCompilationHint, tooltips.settings.show_shader_compilation_hint); diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index a5bcc011b7..d3b98a2608 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -3009,6 +3009,13 @@ Overlay Settings + + + + Play music during boot sequence + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index f09642a826..caccc1c47b 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -159,6 +159,7 @@ public: const QString use_native_interface = tr("Enables use of native HUD within the game window that can interact with game controllers.\nWhen disabled, regular Qt dialogs are used instead.\nCurrently, the on-screen keyboard only supports the English key layout."); const QString record_with_overlays = tr("Enables recording with overlays.\nThis also affects screenshots."); const QString pause_during_home_menu = tr("When enabled, opening the home menu will also pause emulation.\nWhile most games pause themselves while the home menu is shown, some do not.\nIn that case it can be helpful to pause the emulation whenever the home menu is open."); + const QString play_music_during_boot = tr("Play music during boot sequence if available."); const QString perf_overlay_enabled = tr("Enables or disables the performance overlay."); const QString perf_overlay_framerate_graph_enabled = tr("Enables or disables the framerate graph."); From 1c37f64a58583b2f80ad9bd12f928b49176b60fd Mon Sep 17 00:00:00 2001 From: kayforbe <125169722+kayforbe@users.noreply.github.com> Date: Mon, 30 Mar 2026 20:47:52 +0300 Subject: [PATCH 104/108] Add missing include for MSVC compatibility (#18486) Added missing #include in shortcut_utils.h to fix MSVC build errors (C2059) --- rpcs3/rpcs3qt/shortcut_utils.h | 1 + 1 file changed, 1 insertion(+) diff --git a/rpcs3/rpcs3qt/shortcut_utils.h b/rpcs3/rpcs3qt/shortcut_utils.h index d1ed3962e2..f41741ecdf 100644 --- a/rpcs3/rpcs3qt/shortcut_utils.h +++ b/rpcs3/rpcs3qt/shortcut_utils.h @@ -1,4 +1,5 @@ #pragma once +#include struct gui_game_info; class iso_archive; From 615f416af3081801a9e7973da84b9229173c41a0 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Mon, 30 Mar 2026 13:25:51 +0300 Subject: [PATCH 105/108] SPU Anlyzer: Verify bitset index --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 34 +++++++++++++------------- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 7 +++++- rpcs3/Emu/Cell/SPURecompiler.h | 17 ++++++++++--- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index b10845889b..6a9ed7e7ee 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -6087,17 +6087,17 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s u32 ra = s_reg_max, rb = s_reg_max, rc = s_reg_max; - if (m_use_ra.test(pos / 4)) + if (::at32(m_use_ra, pos / 4)) { ra = op.ra; } - if (m_use_rb.test(pos / 4)) + if (::at32(m_use_rb, pos / 4)) { rb = op.rb; } - if (m_use_rc.test(pos / 4)) + if (::at32(m_use_rc, pos / 4)) { rc = op.rc; } @@ -6361,11 +6361,11 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s const auto& b = ::at32(m_bbs, reduced_loop->loop_pc); const auto& b2 = ::at32(m_bbs, bpc); - if (!reduced_loop->loop_dicts.test(i)) + if (!::at32(reduced_loop->loop_dicts, i)) { - if (b.reg_use[i] || (!b.reg_mod.test(i) && b2.reg_use[i])) + if (b.reg_use[i] || (!::at32(b.reg_mod, i) && b2.reg_use[i])) { - if ((b.reg_use[i] && b.reg_mod.test(i)) || b2.reg_mod.test(i)) + if ((b.reg_use[i] && ::at32(b.reg_mod, i)) || ::at32(b2.reg_mod, i)) { reduced_loop->is_constant_expression = false; reduced_loop->loop_writes.set(i); @@ -6598,7 +6598,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 i = 0; i < reg->regs.size() && reduced_loop->active; i++) { - if (reg->regs.test(i)) + if (::at32(reg->regs, i)) { if (0) if (i == op_rt || reg->modified == 0) { @@ -6644,11 +6644,11 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s auto reg_org = reduced_loop->find_reg(i); u32 reg_index = i; - if (reg_org && !cond_val_incr_before_cond && reg_org->modified == 0 && reg_org->regs.count() - 1u <= 1u && !reg_org->regs.test(i)) + if (reg_org && !cond_val_incr_before_cond && reg_org->modified == 0 && reg_org->regs.count() - 1u <= 1u && !::at32(reg_org->regs, i)) { for (u32 j = 0; j <= s_reg_127; j++) { - if (reg_org->regs.test(j)) + if (::at32(reg_org->regs, j)) { if (const auto reg_found = reduced_loop->find_reg(j)) { @@ -7040,11 +7040,11 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s const auto& b = ::at32(m_bbs, reduced_loop->loop_pc); const auto& b2 = ::at32(m_bbs, bpc); - if (!reduced_loop->loop_dicts.test(i)) + if (!::at32(reduced_loop->loop_dicts, i)) { - if (b.reg_use[i] || (!b.reg_mod.test(i) && b2.reg_use[i])) + if (b.reg_use[i] || (!::at32(b.reg_mod, i) && b2.reg_use[i])) { - if ((b.reg_use[i] && b.reg_mod.test(i)) || b2.reg_mod.test(i)) + if ((b.reg_use[i] && ::at32(b.reg_mod, i)) || ::at32(b2.reg_mod, i)) { reduced_loop->is_constant_expression = false; reduced_loop->loop_writes.set(i); @@ -8298,17 +8298,17 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s u32 ra = s_reg_max, rb = s_reg_max, rc = s_reg_max; - if (m_use_ra.test(pos / 4)) + if (::at32(m_use_ra, pos / 4)) { ra = op.ra; } - if (m_use_rb.test(pos / 4)) + if (::at32(m_use_rb, pos / 4)) { rb = op.rb; } - if (m_use_rc.test(pos / 4)) + if (::at32(m_use_rc, pos / 4)) { rc = op.rc; } @@ -8575,7 +8575,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 i = 0; i < s_reg_max; i++) { - if (pattern.loop_writes.test(i)) + if (::at32(pattern.loop_writes, i)) { if (regs.size() != 1) { @@ -8585,7 +8585,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s fmt::append(regs, " r%u-w", i); } - if (pattern.loop_args.test(i)) + if (::at32(pattern.loop_args, i)) { if (regs.size() != 1) { diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 948c6e8b14..b13c27e376 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -180,7 +180,7 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator bool is_gpr_not_NaN_hint(u32 i) const noexcept { - return block_wide_reg_store_elimination && bb->reg_maybe_float[i] && bb->reg_use[i] >= 3 && !bb->reg_mod[i]; + return block_wide_reg_store_elimination && ::at32(bb->reg_maybe_float, i) && ::at32(bb->reg_use, i) >= 3 && !::at32(bb->reg_mod, i); } }; @@ -2536,6 +2536,11 @@ public: condition = m_ir->CreateAnd(cond_nans, condition); prev_i = umax; } + + if (is_last) + { + break; + } } } diff --git a/rpcs3/Emu/Cell/SPURecompiler.h b/rpcs3/Emu/Cell/SPURecompiler.h index 1fd295f2f6..54ddcb2f1e 100644 --- a/rpcs3/Emu/Cell/SPURecompiler.h +++ b/rpcs3/Emu/Cell/SPURecompiler.h @@ -11,6 +11,17 @@ #include #include +// std::bitset +template + requires requires(std::remove_cvref_t& x, T&& y) { x.count(); x.test(y); x.flip(y); } +[[nodiscard]] constexpr bool at32(CT&& container, T&& index, std::source_location src_loc = std::source_location::current()) +{ + const usz csv = container.size(); + if (csv <= std::forward(index)) [[unlikely]] + fmt::raw_range_error(src_loc, format_object_simplified(index), csv); + return container[std::forward(index)]; +} + // Helper class class spu_cache { @@ -421,7 +432,7 @@ public: return true; } - return regs.count() == 1 && regs.test(reg_val); + return regs.count() == 1 && ::at32(regs, reg_val); } bool is_loop_dictator(u32 reg_val, bool test_predictable = false, bool should_predictable = true) const @@ -431,7 +442,7 @@ public: return false; } - if (regs.count() >= 1 && regs.test(reg_val)) + if (regs.count() >= 1 && ::at32(regs, reg_val)) { if (!test_predictable) { @@ -490,7 +501,7 @@ public: return false; } - if (regs.count() - (regs.test(reg_val) ? 1 : 0)) + if (regs.count() - (::at32(regs, reg_val) ? 1 : 0)) { return false; } From 80f972cd384a5d6304e29fad33582003f36ac9bd Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Mon, 30 Mar 2026 20:30:35 +0300 Subject: [PATCH 106/108] SPU Analyzer: Acknowledge unknown targets --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index 6a9ed7e7ee..fd0a8c33bf 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -2957,7 +2957,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (g_cfg.core.spu_block_size == spu_block_size_type::safe) { // Stop on special instructions (TODO) - m_targets[pos]; + m_targets[pos].push_back(SPU_LS_SIZE); next_block(); break; } @@ -2978,7 +2978,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s spu_log.error("[0x%x] Invalid interrupt flags (DE)", pos); } - m_targets[pos]; + m_targets[pos].push_back(SPU_LS_SIZE); next_block(); break; } @@ -3278,7 +3278,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { if (type == spu_itype::BI || g_cfg.core.spu_block_size == spu_block_size_type::safe || is_no_return) { - m_targets[pos]; + m_targets[pos].push_back(SPU_LS_SIZE); } else { @@ -3291,6 +3291,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s else { m_targets[pos].push_back(pos + 4); + m_targets[pos].push_back(SPU_LS_SIZE); add_block(pos + 4); } From 83bfbc1399217e35b17ad797d6a119fe56442b82 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 29 Mar 2026 20:38:27 +0300 Subject: [PATCH 107/108] rsx/fp: Fix lane mask computation for varying registers --- rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp index d00a104e8f..2ec3e2e384 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp @@ -343,7 +343,8 @@ namespace rsx::assembler::FP std::unordered_set inputs; SRC_Common src { .HEX = instruction->bytecode[operand + 1] }; - if (src.reg_type != RSX_FP_REGISTER_TYPE_TEMP) + if (src.reg_type != RSX_FP_REGISTER_TYPE_TEMP && + src.reg_type != RSX_FP_REGISTER_TYPE_INPUT) { return 0; } From 122ccca50e3508905aff79200e5ada12308275eb Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 31 Mar 2026 00:13:25 +0300 Subject: [PATCH 108/108] rsx: Propagate texture flags when MSAA sampling is required --- rpcs3/Emu/RSX/Program/GLSLCommon.cpp | 5 +++++ .../Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp index 28cbaa61f0..febb5c93bd 100644 --- a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp +++ b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp @@ -418,6 +418,11 @@ namespace glsl enabled_options.push_back("_ENABLE_DEPTH_FORMAT_RECONSTRUCTION"); } + if (props.require_msaa_ops) + { + enabled_options.push_back("_ENABLE_TEXTURE_MULTISAMPLE"); + } + program_common::define_glsl_switches(OS, enabled_options); enabled_options.clear(); diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl index aaf4dc434a..19f3ff3f9c 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl @@ -46,7 +46,7 @@ R"( _texture_bx2_active = false; \ } while (false) #define TEX_FLAGS(index) ((TEX_PARAM(index).flags & ~(_texture_flag_erase)) | _texture_flag_override) -#elif defined(_ENABLE_TEXTURE_ALPHA_KILL) || defined(_ENABLE_FORMAT_CONVERSION) || defined(_ENABLE_DEPTH_FORMAT_RECONSTRUCTION) +#elif defined(_ENABLE_TEXTURE_ALPHA_KILL) || defined(_ENABLE_FORMAT_CONVERSION) || defined(_ENABLE_DEPTH_FORMAT_RECONSTRUCTION) || defined(_ENABLE_TEXTURE_MULTISAMPLE) #define TEX_FLAGS(index) (TEX_PARAM(index).flags) #else #define TEX_FLAGS(index) 0