From 588cf69dad726a4485af945ba0507193c526ccf7 Mon Sep 17 00:00:00 2001 From: Windsurf7 <70599421+Windsurf7@users.noreply.github.com> Date: Mon, 2 Feb 2026 11:21:02 +0300 Subject: [PATCH 01/24] Add mouse-based gyro emulation (#18113) This change adds a hardcoded mouse-based motion sensor emulation feature, inspired by how Cemu handles mouse-driven gyro input. While the game window is focused, holding the right mouse button enables gyro emulation: - Mouse X movement feeds Motion X - Mouse Y movement feeds Motion Z - Mouse Wheel feeds Motion Y The axis mapping and behavior were tested with the "Spark Runner" minigame in _Sly Cooper: Thieves in Time_ and _Bentley's Hackpack_. In accordance with this minigame, a top-down view motion control scheme relies on the X/Z axes. While the right mouse button is being held, mouse deltas are captured via the Qt native event filter and accumulated in the frontend, then consumed by the pad thread. On right mouse button release, motion values are reset to the neutral center to avoid residual drift. This input path is intentionally independent of pad configuration and works even when a keyboard-only profile is selected. This implementation thus resolves issue #13883 by allowing motion-only gameplay without requiring a physical motion-capable controller. --- rpcs3/Input/mouse_gyro_handler.cpp | 146 ++++++++++++++++++++++++++++ rpcs3/Input/mouse_gyro_handler.h | 33 +++++++ rpcs3/Input/pad_thread.cpp | 7 ++ rpcs3/Input/pad_thread.h | 5 + rpcs3/rpcs3.vcxproj | 2 + rpcs3/rpcs3.vcxproj.filters | 6 ++ rpcs3/rpcs3qt/CMakeLists.txt | 1 + rpcs3/rpcs3qt/gs_frame.cpp | 20 ++++ rpcs3/rpcs3qt/shortcut_settings.cpp | 2 + rpcs3/rpcs3qt/shortcut_settings.h | 1 + 10 files changed, 223 insertions(+) create mode 100644 rpcs3/Input/mouse_gyro_handler.cpp create mode 100644 rpcs3/Input/mouse_gyro_handler.h diff --git a/rpcs3/Input/mouse_gyro_handler.cpp b/rpcs3/Input/mouse_gyro_handler.cpp new file mode 100644 index 0000000000..6f1c7cd637 --- /dev/null +++ b/rpcs3/Input/mouse_gyro_handler.cpp @@ -0,0 +1,146 @@ +#include "mouse_gyro_handler.h" + +#include +#include +#include +#include + +#include + +void mouse_gyro_handler::clear() +{ + active = false; + reset = false; + gyro_x = DEFAULT_MOTION_X; + gyro_y = DEFAULT_MOTION_Y; + gyro_z = DEFAULT_MOTION_Z; +} + +bool mouse_gyro_handler::toggle_enabled() +{ + enabled = !enabled; + clear(); + return enabled; +} + +void mouse_gyro_handler::set_gyro_active() +{ + active = true; +} + +void mouse_gyro_handler::set_gyro_reset() +{ + active = false; + reset = true; +} + +void mouse_gyro_handler::set_gyro_xz(s32 off_x, s32 off_y) +{ + if (!active) + return; + + gyro_x = static_cast(std::clamp(off_x, 0, DEFAULT_MOTION_X * 2 - 1)); + gyro_z = static_cast(std::clamp(off_y, 0, DEFAULT_MOTION_Z * 2 - 1)); +} + +void mouse_gyro_handler::set_gyro_y(s32 steps) +{ + if (!active) + return; + + gyro_y = static_cast(std::clamp(gyro_y + steps, 0, DEFAULT_MOTION_Y * 2 - 1)); +} + +void mouse_gyro_handler::handle_event(QEvent* ev, const QWindow& win) +{ + if (!enabled) + return; + + // Mouse-based motion input. + // Captures mouse events while the game window is focused. + // Updates motion sensor values via mouse position and mouse wheel while RMB is held. + // Intentionally independent of chosen pad configuration. + switch (ev->type()) + { + case QEvent::MouseButtonPress: + { + auto* e = static_cast(ev); + if (e->button() == Qt::RightButton) + { + // Enable mouse-driven gyro emulation while RMB is held. + set_gyro_active(); + } + break; + } + case QEvent::MouseButtonRelease: + { + auto* e = static_cast(ev); + if (e->button() == Qt::RightButton) + { + // Disable gyro emulation and request a one-shot motion reset. + set_gyro_reset(); + } + break; + } + case QEvent::MouseMove: + { + auto* e = static_cast(ev); + + // Track cursor offset from window center. + const QPoint center(win.width() / 2, win.height() / 2); + const QPoint cur = e->position().toPoint(); + + const s32 off_x = cur.x() - center.x() + DEFAULT_MOTION_X; + const s32 off_y = cur.y() - center.y() + DEFAULT_MOTION_Z; + + // Determine motion from relative mouse position while gyro emulation is active. + set_gyro_xz(off_x, off_y); + + break; + } + case QEvent::Wheel: + { + auto* e = static_cast(ev); + + // Track mouse wheel steps. + const s32 steps = e->angleDelta().y() / 120; + + // Accumulate mouse wheel steps while gyro emulation is active. + set_gyro_y(steps); + + break; + } + default: + { + break; + } + } +} + +void mouse_gyro_handler::apply_gyro(const std::shared_ptr& pad) +{ + if (!enabled) + return; + + if (!pad || !pad->is_connected()) + return; + + // Inject mouse-based motion sensor values into pad sensors for gyro emulation. + // The Qt frontend maps cursor offset and wheel input to absolute motion values while RMB is held. + if (reset) + { + // RMB released → reset motion + pad->m_sensors[0].m_value = DEFAULT_MOTION_X; + pad->m_sensors[1].m_value = DEFAULT_MOTION_Y; + pad->m_sensors[2].m_value = DEFAULT_MOTION_Z; + clear(); + } + else + { + // RMB held → accumulate motion + // Axes have been chosen as tested in Sly 4 minigames. Top-down view motion uses X/Z axes. + pad->m_sensors[0].m_value = gyro_x; // Mouse X → Motion X + pad->m_sensors[1].m_value = gyro_y; // Mouse Wheel → Motion Y + pad->m_sensors[2].m_value = gyro_z; // Mouse Y → Motion Z + } +} diff --git a/rpcs3/Input/mouse_gyro_handler.h b/rpcs3/Input/mouse_gyro_handler.h new file mode 100644 index 0000000000..97a745d919 --- /dev/null +++ b/rpcs3/Input/mouse_gyro_handler.h @@ -0,0 +1,33 @@ +#pragma once + +#include "util/types.hpp" +#include "util/atomic.hpp" +#include "Emu/Io/pad_types.h" + +class QEvent; +class QWindow; + +// Mouse-based motion sensor emulation state. +class mouse_gyro_handler +{ +private: + atomic_t enabled = false; // Whether mouse-based gyro emulation mode has been enabled by using the associated hotkey + + atomic_t active = false; // Whether right mouse button is currently held (gyro active) + atomic_t reset = false; // One-shot reset request on right mouse button release + atomic_t gyro_x = DEFAULT_MOTION_X; // Accumulated from mouse X position relative to center + atomic_t gyro_y = DEFAULT_MOTION_Y; // Accumulated from mouse wheel delta + atomic_t gyro_z = DEFAULT_MOTION_Z; // Accumulated from mouse Y position relative to center + + void set_gyro_active(); + void set_gyro_reset(); + void set_gyro_xz(s32 off_x, s32 off_y); + void set_gyro_y(s32 steps); + +public: + void clear(); + bool toggle_enabled(); + + void handle_event(QEvent* ev, const QWindow& win); + void apply_gyro(const std::shared_ptr& pad); +}; diff --git a/rpcs3/Input/pad_thread.cpp b/rpcs3/Input/pad_thread.cpp index afc5a73fce..40887ae5fb 100644 --- a/rpcs3/Input/pad_thread.cpp +++ b/rpcs3/Input/pad_thread.cpp @@ -81,6 +81,9 @@ void pad_thread::Init() { std::lock_guard lock(pad::g_pad_mutex); + // Reset mouse-based gyro state + m_mouse_gyro.clear(); + // Cache old settings if possible std::array pad_settings; for (u32 i = 0; i < CELL_PAD_MAX_PORT_NUM; i++) // max 7 pads @@ -606,6 +609,10 @@ void pad_thread::operator()() if (Emu.IsRunning()) { update_pad_states(); + + // Apply mouse-based gyro emulation. + // Intentionally bound to Player 1 only. + m_mouse_gyro.apply_gyro(m_pads[0]); } m_info.now_connect = connected_devices + num_ldd_pad; diff --git a/rpcs3/Input/pad_thread.h b/rpcs3/Input/pad_thread.h index 7b0e0b79fb..20f53e9034 100644 --- a/rpcs3/Input/pad_thread.h +++ b/rpcs3/Input/pad_thread.h @@ -5,6 +5,7 @@ #include "Emu/Io/pad_types.h" #include "Emu/Io/pad_config.h" #include "Emu/Io/pad_config_types.h" +#include "Input/mouse_gyro_handler.h" #include "Utilities/mutex.h" #include @@ -41,6 +42,8 @@ public: static auto constexpr thread_name = "Pad Thread"sv; + mouse_gyro_handler& get_mouse_gyro() { return m_mouse_gyro; } + protected: void Init(); void InitLddPad(u32 handle, const u32* port_status); @@ -67,6 +70,8 @@ private: bool m_resume_emulation_flag = false; bool m_ps_button_pressed = false; atomic_t m_home_menu_open = false; + + mouse_gyro_handler m_mouse_gyro; }; namespace pad diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 5fbf0491e9..9749f60fcd 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -194,6 +194,7 @@ + @@ -1079,6 +1080,7 @@ $(QTDIR)\bin\moc.exe;%(FullPath) $(QTDIR)\bin\moc.exe;%(FullPath) + diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 100a9b1d8f..a011ddf62e 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -1272,6 +1272,9 @@ Generated Files\Release + + Io + @@ -1511,6 +1514,9 @@ Io\camera + + Io + diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index a8fc4c5886..b59d6f7a11 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -157,6 +157,7 @@ add_library(rpcs3_ui STATIC ../Input/hid_pad_handler.cpp ../Input/keyboard_pad_handler.cpp ../Input/mm_joystick_handler.cpp + ../Input/mouse_gyro_handler.cpp ../Input/pad_thread.cpp ../Input/product_info.cpp ../Input/ps_move_calibration.cpp diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index db66ce68a4..6557168cce 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -19,6 +19,7 @@ #include "Emu/RSX/Overlays/overlay_message.h" #include "Emu/Io/interception.h" #include "Emu/Io/recording_config.h" +#include "Input/pad_thread.h" #include #include @@ -402,6 +403,15 @@ void gs_frame::handle_shortcut(gui::shortcuts::shortcut shortcut_key, const QKey audio::change_volume(-5); break; } + case gui::shortcuts::shortcut::gw_toggle_mouse_gyro: + { + if (auto* pad_thr = pad::get_pad_thread(true)) + { + const bool mouse_gyro_enabled = pad_thr->get_mouse_gyro().toggle_enabled(); + gui_log.notice("Mouse-based gyro emulation %s", mouse_gyro_enabled ? "enabled" : "disabled"); + } + break; + } default: { break; @@ -1216,6 +1226,16 @@ bool gs_frame::event(QEvent* ev) // This will make the cursor visible again if it was hidden by the mouse idle timeout handle_cursor(visibility(), false, false, true); } + + // Handle events for mouse-based gyro emulation. + if (Emu.IsRunning()) + { + if (auto* pad_thr = pad::get_pad_thread(true)) + { + pad_thr->get_mouse_gyro().handle_event(ev, *this); + } + } + return QWindow::event(ev); } diff --git a/rpcs3/rpcs3qt/shortcut_settings.cpp b/rpcs3/rpcs3qt/shortcut_settings.cpp index 039f493dae..64feb94777 100644 --- a/rpcs3/rpcs3qt/shortcut_settings.cpp +++ b/rpcs3/rpcs3qt/shortcut_settings.cpp @@ -37,6 +37,7 @@ void fmt_class_string::format(std::string& out, u64 arg) case shortcut::gw_mute_unmute: return "gw_mute_unmute"; case shortcut::gw_volume_up: return "gw_volume_up"; case shortcut::gw_volume_down: return "gw_volume_down"; + case shortcut::gw_toggle_mouse_gyro: return "gw_toggle_mouse_gyro"; case shortcut::count: return "count"; } @@ -88,6 +89,7 @@ shortcut_settings::shortcut_settings() { shortcut::gw_mute_unmute, shortcut_info{ "gw_mute_unmute", tr("Mute/Unmute Audio"), "Ctrl+Shift+M", shortcut_handler_id::game_window, false } }, { shortcut::gw_volume_up, shortcut_info{ "gw_volume_up", tr("Volume Up"), "Ctrl+Shift++", shortcut_handler_id::game_window, true } }, { shortcut::gw_volume_down, shortcut_info{ "gw_volume_down", tr("Volume Down"), "Ctrl+Shift+-", shortcut_handler_id::game_window, true } }, + { shortcut::gw_toggle_mouse_gyro, shortcut_info{ "gw_toggle_mouse_gyro", tr("Toggle Mouse-based Gyro"), "Ctrl+G", shortcut_handler_id::game_window, false } }, }) { } diff --git a/rpcs3/rpcs3qt/shortcut_settings.h b/rpcs3/rpcs3qt/shortcut_settings.h index db6458accb..be14ee1e30 100644 --- a/rpcs3/rpcs3qt/shortcut_settings.h +++ b/rpcs3/rpcs3qt/shortcut_settings.h @@ -46,6 +46,7 @@ namespace gui gw_mute_unmute, gw_volume_up, gw_volume_down, + gw_toggle_mouse_gyro, count }; From ad90f8b44bdf29ce55c6a9b9ad855bde8ce66752 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 1 Feb 2026 20:20:20 +0300 Subject: [PATCH 02/24] rsx: Conservative MM flush during CPU handling of nv3089_image_in --- rpcs3/Emu/RSX/NV47/HW/nv3089.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/RSX/NV47/HW/nv3089.cpp b/rpcs3/Emu/RSX/NV47/HW/nv3089.cpp index 111611d887..6ac502b462 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv3089.cpp +++ b/rpcs3/Emu/RSX/NV47/HW/nv3089.cpp @@ -4,6 +4,7 @@ #include "Emu/RSX/RSXThread.h" #include "Emu/RSX/Core/RSXReservationLock.hpp" #include "Emu/RSX/Common/tiled_dma_copy.hpp" +#include "Emu/RSX/Host/MM.h" #include "context_accessors.define.h" @@ -581,9 +582,11 @@ namespace rsx const u16 out_h = REGS(ctx)->blit_engine_output_height(); // Lock here. RSX cannot execute any locking operations from this point, including ZCULL read barriers + const u32 read_length = src.pitch * src.height; + const u32 write_length = dst.pitch * dst.clip_height; auto res = ::rsx::reservation_lock( - dst.rsx_address, dst.pitch * dst.clip_height, - src.rsx_address, src.pitch * src.height); + dst.rsx_address, write_length, + src.rsx_address, read_length); if (!g_cfg.video.force_cpu_blit_processing && (dst.dma == CELL_GCM_CONTEXT_DMA_MEMORY_FRAME_BUFFER || src.dma == CELL_GCM_CONTEXT_DMA_MEMORY_FRAME_BUFFER) && @@ -593,6 +596,14 @@ namespace rsx return; } + // Conservative MM flush + rsx::simple_array flush_mm_ranges = + { + utils::address_range64::start_length(reinterpret_cast(dst.pixels), write_length), + utils::address_range64::start_length(reinterpret_cast(src.pixels), read_length) + }; + rsx::mm_flush(flush_mm_ranges); + std::vector mirror_tmp; bool src_is_temp = false; @@ -619,7 +630,7 @@ namespace rsx const bool interpolate = in_inter == blit_engine::transfer_interpolator::foh; auto real_dst = dst.pixels; - const auto tiled_region = RSX(ctx)->get_tiled_memory_region(utils::address_range32::start_length(dst.rsx_address, dst.pitch * dst.clip_height)); + const auto tiled_region = RSX(ctx)->get_tiled_memory_region(utils::address_range32::start_length(dst.rsx_address, write_length)); std::vector tmp; if (tiled_region) From 55aae4dd4067a21adf0ad18c183b15989c12b215 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 2 Feb 2026 20:10:25 +0300 Subject: [PATCH 03/24] vk: Fix NVIDIA crash when resizing the game window quickly --- rpcs3/Emu/RSX/VK/VKGSRender.h | 2 +- rpcs3/Emu/RSX/VK/VKPresent.cpp | 31 +++++++++++++++++++++++++++---- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.h b/rpcs3/Emu/RSX/VK/VKGSRender.h index faae1bb78f..0c93741fcb 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.h +++ b/rpcs3/Emu/RSX/VK/VKGSRender.h @@ -221,7 +221,7 @@ private: void frame_context_cleanup(vk::frame_context_t *ctx); void advance_queued_frames(); void present(vk::frame_context_t *ctx); - void reinitialize_swapchain(); + bool reinitialize_swapchain(); vk::viewable_image* get_present_source(vk::present_surface_info* info, const rsx::avconf& avconfig); diff --git a/rpcs3/Emu/RSX/VK/VKPresent.cpp b/rpcs3/Emu/RSX/VK/VKPresent.cpp index f9fbb114ed..5761a99120 100644 --- a/rpcs3/Emu/RSX/VK/VKPresent.cpp +++ b/rpcs3/Emu/RSX/VK/VKPresent.cpp @@ -33,7 +33,7 @@ namespace } } -void VKGSRender::reinitialize_swapchain() +bool VKGSRender::reinitialize_swapchain() { m_swapchain_dims.width = m_frame->client_width(); m_swapchain_dims.height = m_frame->client_height(); @@ -44,7 +44,7 @@ void VKGSRender::reinitialize_swapchain() if (m_swapchain_dims.width == 0 || m_swapchain_dims.height == 0) { swapchain_unavailable = true; - return; + return false; } // NOTE: This operation will create a hard sync point @@ -97,7 +97,7 @@ void VKGSRender::reinitialize_swapchain() { rsx_log.warning("Swapchain initialization failed. Request ignored [%dx%d]", m_swapchain_dims.width, m_swapchain_dims.height); swapchain_unavailable = true; - return; + return false; } // Re-initialize CPU frame contexts @@ -135,6 +135,7 @@ void VKGSRender::reinitialize_swapchain() swapchain_unavailable = false; should_reinitialize_swapchain = false; + return true; } void VKGSRender::present(vk::frame_context_t *ctx) @@ -426,11 +427,32 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info) if (swapchain_unavailable || should_reinitialize_swapchain) { - reinitialize_swapchain(); + // Reinitializing the swapchain is a failable operation. However, not all failures are fatal (e.g minimized window). + // In the worst case, we can have the driver refuse to create the swapchain while we already deleted the previous one. + // In such scenarios, we have to retry a few times before giving up as we cannot proceed without a swapchain. + for (int i = 0; i < 10; ++i) + { + if (reinitialize_swapchain() || m_current_frame) + { + // If m_current_frame exists, then the initialization failure is non-fatal. Proceed as usual. + break; + } + + if (Emu.IsStopped()) + { + m_frame->flip(m_context); + rsx::thread::flip(info); + return; + } + + std::this_thread::sleep_for(100ms); + } } m_profiler.start(); + ensure(m_current_frame, "Invalid swapchain setup. Resizing the game window failed."); + if (m_current_frame == &m_aux_frame_context) { m_current_frame = &m_frame_context_storage[m_current_queue_index]; @@ -582,6 +604,7 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info) rsx_log.warning("vkAcquireNextImageKHR failed with VK_ERROR_OUT_OF_DATE_KHR. Flip request ignored until surface is recreated."); swapchain_unavailable = true; reinitialize_swapchain(); + ensure(m_current_frame, "Could not reinitialize swapchain after VK_ERROR_OUT_OF_DATE_KHR signal!"); continue; default: vk::die_with_error(status); From 8ba48932d3b809feb1caad17790d07bebf6ae225 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 2 Feb 2026 09:38:44 +0100 Subject: [PATCH 04/24] Qt: try to fix update downloader --- rpcs3/rpcs3qt/downloader.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rpcs3/rpcs3qt/downloader.cpp b/rpcs3/rpcs3qt/downloader.cpp index dd861da73e..ae97dc8f77 100644 --- a/rpcs3/rpcs3qt/downloader.cpp +++ b/rpcs3/rpcs3qt/downloader.cpp @@ -99,7 +99,7 @@ void downloader::start(const std::string& url, bool follow_location, bool show_p // The downloader's signals are expected to be disconnected and customized before start is called. // Therefore we need to (re)connect its signal(s) here and not in the constructor. - connect(this, &downloader::signal_buffer_update, this, &downloader::handle_buffer_update); + connect(this, &downloader::signal_buffer_update, this, &downloader::handle_buffer_update, static_cast(Qt::QueuedConnection | Qt::UniqueConnection)); if (show_progress_dialog) { @@ -169,7 +169,7 @@ usz downloader::update_buffer(char* data, usz size) const auto old_size = m_curl_buf.size(); const auto new_size = old_size + size; m_curl_buf.resize(static_cast(new_size)); - memcpy(m_curl_buf.data() + old_size, data, size); + std::memcpy(m_curl_buf.data() + old_size, data, size); int max = 0; @@ -197,6 +197,5 @@ void downloader::handle_buffer_update(int size, int max) const { m_progress_dialog->SetRange(0, max > 0 ? max : m_progress_dialog->maximum()); m_progress_dialog->SetValue(size); - QApplication::processEvents(); } } From f1e679d9819dc1be5b097d068ef5e62d7f97a2ba Mon Sep 17 00:00:00 2001 From: trautamaki <13891897+trautamaki@users.noreply.github.com> Date: Tue, 3 Feb 2026 09:00:48 +0200 Subject: [PATCH 05/24] Switch from FFmpeg av_opt_set_* to swr_alloc_set_opts2 (#18138) ffmpeg 7 doesn't allow to set in_channel_count and out_channel_count anymore --- rpcs3/util/media_utils.cpp | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/rpcs3/util/media_utils.cpp b/rpcs3/util/media_utils.cpp index 571eaa74fd..284f6eb758 100644 --- a/rpcs3/util/media_utils.cpp +++ b/rpcs3/util/media_utils.cpp @@ -575,34 +575,28 @@ namespace utils return; } - // Prepare resampler - av.swr = swr_alloc(); - if (!av.swr) - { - media_log.error("audio_decoder: Failed to allocate resampler for stream #%u in file '%s'", stream_index, path); - has_error = true; - return; - } - const int dst_channels = 2; const AVChannelLayout dst_channel_layout = AV_CHANNEL_LAYOUT_STEREO; const AVSampleFormat dst_format = AV_SAMPLE_FMT_FLT; - int set_err = 0; - if ((set_err = av_opt_set_int(av.swr, "in_channel_count", stream->codecpar->ch_layout.nb_channels, 0)) || - (set_err = av_opt_set_int(av.swr, "out_channel_count", dst_channels, 0)) || - (set_err = av_opt_set_chlayout(av.swr, "in_channel_layout", &stream->codecpar->ch_layout, 0)) || - (set_err = av_opt_set_chlayout(av.swr, "out_channel_layout", &dst_channel_layout, 0)) || - (set_err = av_opt_set_int(av.swr, "in_sample_rate", stream->codecpar->sample_rate, 0)) || - (set_err = av_opt_set_int(av.swr, "out_sample_rate", sample_rate, 0)) || - (set_err = av_opt_set_sample_fmt(av.swr, "in_sample_fmt", static_cast(stream->codecpar->format), 0)) || - (set_err = av_opt_set_sample_fmt(av.swr, "out_sample_fmt", dst_format, 0))) + const int set_err = swr_alloc_set_opts2(&av.swr, &dst_channel_layout, dst_format, + sample_rate, &stream->codecpar->ch_layout, + static_cast(stream->codecpar->format), + stream->codecpar->sample_rate, 0, nullptr); + if (set_err < 0) { media_log.error("audio_decoder: Failed to set resampler options: Error: %d='%s'", set_err, av_error_to_string(set_err)); has_error = true; return; } + if (!av.swr) + { + media_log.error("audio_decoder: Failed to allocate resampler for stream #%u in file '%s'", stream_index, path); + has_error = true; + return; + } + if (int err = swr_init(av.swr); err < 0 || !swr_is_initialized(av.swr)) { media_log.error("audio_decoder: Resampler has not been properly initialized: %d='%s'", err, av_error_to_string(err)); From 622fe1ce478025adaede0d01fdf019af7af3ab5d Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 3 Feb 2026 07:26:09 +0100 Subject: [PATCH 06/24] Qt: don't filter log if the input dialog was canceled --- rpcs3/rpcs3qt/log_viewer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rpcs3/rpcs3qt/log_viewer.cpp b/rpcs3/rpcs3qt/log_viewer.cpp index 3a566937a7..753f8cc0f5 100644 --- a/rpcs3/rpcs3qt/log_viewer.cpp +++ b/rpcs3/rpcs3qt/log_viewer.cpp @@ -187,7 +187,10 @@ void log_viewer::show_context_menu(const QPoint& pos) connect(filter, &QAction::triggered, this, [this]() { - m_filter_term = QInputDialog::getText(this, tr("Filter log"), tr("Enter text"), QLineEdit::EchoMode::Normal, m_filter_term); + bool ok = false; + QString filter_term = QInputDialog::getText(this, tr("Filter log"), tr("Enter text"), QLineEdit::EchoMode::Normal, m_filter_term, &ok); + if (!ok) return; + m_filter_term = std::move(filter_term); filter_log(); }); From 3eb8fdaef4582d29f02c02e443506b25398063fe Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 3 Feb 2026 07:38:01 +0100 Subject: [PATCH 07/24] Qt: simplify log level filtering in log viewer --- rpcs3/rpcs3qt/log_viewer.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/rpcs3/rpcs3qt/log_viewer.cpp b/rpcs3/rpcs3qt/log_viewer.cpp index 753f8cc0f5..8934ef0a7b 100644 --- a/rpcs3/rpcs3qt/log_viewer.cpp +++ b/rpcs3/rpcs3qt/log_viewer.cpp @@ -325,22 +325,17 @@ void log_viewer::filter_log() const auto add_line = [this, &result, &excluded_log_levels, ×tamp_regexp, &thread_regexp](QString& line) { - bool exclude_line = false; - - for (const QString& log_level_prefix : excluded_log_levels) + if (!line.isEmpty()) { - if (line.startsWith(log_level_prefix)) + for (QStringView log_level_prefix : excluded_log_levels) { - exclude_line = true; - break; + if (line.startsWith(log_level_prefix)) + { + return; + } } } - if (exclude_line) - { - return; - } - if (m_filter_term.isEmpty() || line.contains(m_filter_term)) { if (line.isEmpty()) From 294050986f58baa71fdc15a05559120a40b3a22a Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 3 Feb 2026 08:24:42 +0100 Subject: [PATCH 08/24] Qt: allow to exclude strings in log viewer --- rpcs3/rpcs3qt/log_viewer.cpp | 96 ++++++++++++++++++++++++++---------- rpcs3/rpcs3qt/log_viewer.h | 3 ++ 2 files changed, 72 insertions(+), 27 deletions(-) diff --git a/rpcs3/rpcs3qt/log_viewer.cpp b/rpcs3/rpcs3qt/log_viewer.cpp index 8934ef0a7b..b69c07901e 100644 --- a/rpcs3/rpcs3qt/log_viewer.cpp +++ b/rpcs3/rpcs3qt/log_viewer.cpp @@ -28,7 +28,8 @@ LOG_CHANNEL(gui_log, "GUI"); log_viewer::log_viewer(std::shared_ptr gui_settings) : m_gui_settings(std::move(gui_settings)) { - setWindowTitle(tr("Log Viewer")); + update_title(); + setObjectName("log_viewer"); setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_StyledBackground); @@ -59,15 +60,33 @@ log_viewer::log_viewer(std::shared_ptr gui_settings) connect(m_log_text, &QWidget::customContextMenuRequested, this, &log_viewer::show_context_menu); } +void log_viewer::update_title() +{ + QString suffix; + + if (!m_filter_term.isEmpty()) + { + suffix = tr(" | Filter '%0'").arg(m_filter_term); + } + + if (!m_exclude_term.isEmpty()) + { + suffix += tr(" | Exclude '%0'").arg(m_exclude_term); + } + + setWindowTitle(tr("Log Viewer%0").arg(suffix)); +} + void log_viewer::show_context_menu(const QPoint& pos) { QMenu menu; - QAction* clear = new QAction(tr("&Clear")); - QAction* copy = new QAction(tr("&Copy")); - QAction* open = new QAction(tr("&Open log file")); - QAction* save = new QAction(tr("&Save filtered log")); - QAction* filter = new QAction(tr("&Filter log")); - QAction* config = new QAction(tr("&Check config")); + QAction* clear = new QAction(tr("&Clear")); + QAction* copy = new QAction(tr("&Copy")); + QAction* open = new QAction(tr("&Open log file")); + QAction* save = new QAction(tr("&Save filtered log")); + QAction* filter = new QAction(tr("&Filter log%0").arg(m_filter_term.isEmpty() ? "" : QString(" (%0)").arg(m_filter_term))); + QAction* exclude = new QAction(tr("&Exclude%0").arg(m_exclude_term.isEmpty() ? "" : QString(" (%0)").arg(m_exclude_term))); + QAction* config = new QAction(tr("&Check config")); QAction* timestamps = new QAction(tr("&Show Timestamps")); timestamps->setCheckable(true); @@ -91,7 +110,7 @@ void log_viewer::show_context_menu(const QPoint& pos) QAction* trace_act = new QAction(tr("Trace"), log_level_acts); log_level_acts->setExclusive(false); - auto init_action = [this](QAction* act, logs::level logLevel) + const auto init_action = [this](QAction* act, logs::level logLevel) { act->setCheckable(true); act->setChecked(m_log_levels.test(static_cast(logLevel))); @@ -120,6 +139,7 @@ void log_viewer::show_context_menu(const QPoint& pos) menu.addAction(open); menu.addAction(config); menu.addAction(filter); + menu.addAction(exclude); menu.addAction(save); menu.addSeparator(); menu.addAction(timestamps); @@ -191,6 +211,18 @@ void log_viewer::show_context_menu(const QPoint& pos) QString filter_term = QInputDialog::getText(this, tr("Filter log"), tr("Enter text"), QLineEdit::EchoMode::Normal, m_filter_term, &ok); if (!ok) return; m_filter_term = std::move(filter_term); + update_title(); + filter_log(); + }); + + connect(exclude, &QAction::triggered, this, [this]() + { + bool ok = false; + QString exclude_term = QInputDialog::getText(this, tr("Exclude"), tr("Enter text (comma separated)"), QLineEdit::EchoMode::Normal, m_exclude_term, &ok); + if (!ok) return; + m_exclude_term = std::move(exclude_term); + m_exclude_terms = m_exclude_term.split(',', Qt::SkipEmptyParts); + update_title(); filter_log(); }); @@ -312,7 +344,7 @@ void log_viewer::filter_log() if (!m_log_levels.test(static_cast(logs::level::notice))) excluded_log_levels.push_back("·! "); if (!m_log_levels.test(static_cast(logs::level::trace))) excluded_log_levels.push_back("·T "); - if (m_filter_term.isEmpty() && excluded_log_levels.empty() && m_show_timestamps && m_show_threads && !m_last_actions_only) + if (m_filter_term.isEmpty() && m_exclude_terms.isEmpty() && excluded_log_levels.empty() && m_show_timestamps && m_show_threads && !m_last_actions_only) { set_text_and_keep_position(m_full_log); return; @@ -334,30 +366,40 @@ void log_viewer::filter_log() return; } } + + for (QStringView term : m_exclude_terms) + { + if (line.contains(term)) + { + return; + } + } } - if (m_filter_term.isEmpty() || line.contains(m_filter_term)) + if (!m_filter_term.isEmpty() && !line.contains(m_filter_term)) { - if (line.isEmpty()) - { - result += "\n"; - return; - } + return; + } - if (!m_show_timestamps) - { - line.remove(timestamp_regexp); - } + if (line.isEmpty()) + { + result += "\n"; + return; + } - if (!m_show_threads) - { - line.remove(thread_regexp); - } + if (!m_show_timestamps) + { + line.remove(timestamp_regexp); + } - if (!line.isEmpty()) - { - result += line + "\n"; - } + if (!m_show_threads) + { + line.remove(thread_regexp); + } + + if (!line.isEmpty()) + { + result += line + "\n"; } }; diff --git a/rpcs3/rpcs3qt/log_viewer.h b/rpcs3/rpcs3qt/log_viewer.h index 85ece2688b..d6ba2ff2a4 100644 --- a/rpcs3/rpcs3qt/log_viewer.h +++ b/rpcs3/rpcs3qt/log_viewer.h @@ -23,6 +23,7 @@ private Q_SLOTS: void show_context_menu(const QPoint& pos); private: + void update_title(); void set_text_and_keep_position(const QString& text); void filter_log(); bool is_valid_file(const QMimeData& md, bool save = false); @@ -30,6 +31,8 @@ private: std::shared_ptr m_gui_settings; QString m_path_last; QString m_filter_term; + QString m_exclude_term; + QStringList m_exclude_terms; QString m_full_log; QPlainTextEdit* m_log_text; LogHighlighter* m_log_highlighter; From 98aaafe4a4ba7f0b28686bbe91141123ccef0f46 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 19 Jan 2026 08:28:08 +0100 Subject: [PATCH 09/24] Update libpng to 1.6.54 --- 3rdparty/libpng/libpng | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/libpng/libpng b/3rdparty/libpng/libpng index 4e3f57d50f..02f2b4f469 160000 --- a/3rdparty/libpng/libpng +++ b/3rdparty/libpng/libpng @@ -1 +1 @@ -Subproject commit 4e3f57d50f552841550a36eabbb3fbcecacb7750 +Subproject commit 02f2b4f4699f0ef9111a6534f093b53732df4452 From 7a6b34cac398c415d6efb728c2789ad998319019 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 3 Feb 2026 08:47:55 +0100 Subject: [PATCH 10/24] Update Qt to 6.10.2 --- .github/workflows/rpcs3.yml | 6 +++--- BUILDING.md | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index 1cd5bfbfd8..90ee8555ad 100644 --- a/.github/workflows/rpcs3.yml +++ b/.github/workflows/rpcs3.yml @@ -134,7 +134,7 @@ jobs: runs-on: macos-14 env: CCACHE_DIR: /tmp/ccache_dir - QT_VER: '6.10.1' + QT_VER: '6.10.2' 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.1' + QT_VER: '6.10.2' QT_VER_MSVC: 'msvc2022' - QT_DATE: '202511161843' + QT_DATE: '202601261212' LLVM_VER: '19.1.7' VULKAN_VER: '1.3.268.0' VULKAN_SDK_SHA: '8459ef49bd06b697115ddd3d97c9aec729e849cd775f5be70897718a9b3b9db5' diff --git a/BUILDING.md b/BUILDING.md index 26295d0a16..b31b3fee4e 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.1](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.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) - [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.1\msvc2022_64\` +- add and set the `QTDIR` environment variable, e.g. `\6.10.2\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.1\msvc2022_64\` +- add and set the `Qt6_ROOT` environment variable to the **Qt** libs path, e.g. `\6.10.2\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.1](https://www.qt.io/download-qt-installer) +- [Qt 6.10.2](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.1\msvc2022_64`, version will fill in automatically +2) add the path to your Qt installation with compiler e.g. `\6.10.2\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 43b2ce65773d2edc00a4499b40607cbdc7e70c81 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 3 Feb 2026 09:37:39 +0100 Subject: [PATCH 11/24] MouseHandler: minor cleanup --- rpcs3/Emu/Io/MouseHandler.cpp | 6 +++--- rpcs3/Emu/Io/MouseHandler.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rpcs3/Emu/Io/MouseHandler.cpp b/rpcs3/Emu/Io/MouseHandler.cpp index 9fd37463cd..7c4edcb449 100644 --- a/rpcs3/Emu/Io/MouseHandler.cpp +++ b/rpcs3/Emu/Io/MouseHandler.cpp @@ -33,12 +33,12 @@ void MouseHandlerBase::save(utils::serial& ar) bool MouseHandlerBase::is_time_for_update(double elapsed_time_ms) { - steady_clock::time_point now = steady_clock::now(); - const double elapsed_ms = (now - last_update).count() / 1'000'000.; + const steady_clock::time_point now = steady_clock::now(); + const double elapsed_ms = (now - m_last_update).count() / 1'000'000.; if (elapsed_ms > elapsed_time_ms) { - last_update = now; + m_last_update = now; return true; } return false; diff --git a/rpcs3/Emu/Io/MouseHandler.h b/rpcs3/Emu/Io/MouseHandler.h index 6a77f44d0e..4b31a6a60d 100644 --- a/rpcs3/Emu/Io/MouseHandler.h +++ b/rpcs3/Emu/Io/MouseHandler.h @@ -128,7 +128,7 @@ class MouseHandlerBase protected: MouseInfo m_info{}; std::vector m_mice; - steady_clock::time_point last_update{}; + steady_clock::time_point m_last_update{}; bool is_time_for_update(double elapsed_time_ms = 10.0); // 4-10 ms, let's use 10 for now From ebf9374ccdfe9bc1d8a9535d6b3c5335e03e9ac6 Mon Sep 17 00:00:00 2001 From: silviolet Date: Tue, 3 Feb 2026 19:27:17 -0500 Subject: [PATCH 12/24] Fixed typos in rpcs3/System.cpp - changed string containing a typo buyes -> bytes - capitalized ps2/ps3 to PS2/PS3 --- rpcs3/Emu/System.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index b204cc38b9..717e4ab59a 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -252,7 +252,7 @@ void init_fxo_for_exec(utils::serial* ar, bool full = false) // Reserved area if (!load_and_check_reserved(*ar, advance)) { - sys_log.error("Potential failure to load savestate: padding buyes are not 0. %s", *ar); + sys_log.error("Potential failure to load savestate: padding bytes are not 0. %s", *ar); } } } @@ -310,7 +310,7 @@ static void fixup_settings(const psf::registry* _psf) if (g_cfg.net.net_active == np_internet_status::disabled && g_cfg.net.psn_status != np_psn_status::disabled) { - sys_log.warning("Net status was set to disconnected so psn status was disabled"); + sys_log.warning("Net status was set to disconnected so PSN status was disabled."); g_cfg.net.psn_status.set(np_psn_status::disabled); } } @@ -4642,7 +4642,7 @@ game_boot_result Emulator::InsertDisc(const std::string& path) else { // TODO: find out where other discs are mounted - sys_log.todo("Mounting non-ps2/ps3 disc in dev_bdvd. Is this correct? (path='%s')", disc_root); + sys_log.todo("Mounting non-PS2/PS3 disc in dev_bdvd. Is this correct? (path='%s')", disc_root); ensure(vfs::mount("/dev_bdvd", disc_root)); } From 93fd33a19aa6f9b0c3e6ff34c13af824c2e226ff Mon Sep 17 00:00:00 2001 From: Malcolm Date: Fri, 6 Feb 2026 02:22:27 +0000 Subject: [PATCH 13/24] ARM: Use ISB instead of yield in place of x86 pause ISB isn't an exact match for pause, but it does slow down execution at least a litle. Unfortunately, yield does nothing on machines without SMT (99% of aarch64 machines) --- rpcs3/util/asm.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/util/asm.hpp b/rpcs3/util/asm.hpp index 56aa955652..0aea4c10c2 100644 --- a/rpcs3/util/asm.hpp +++ b/rpcs3/util/asm.hpp @@ -175,7 +175,7 @@ namespace utils inline void pause() { #if defined(ARCH_ARM64) - __asm__ volatile("yield"); + __asm__ volatile("isb" ::: "memory"); #elif defined(ARCH_X64) _mm_pause(); #else From 93dbdead24e1474a94c59fffc1f6cfb25abe488b Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Fri, 6 Feb 2026 06:52:00 +0100 Subject: [PATCH 14/24] Improve add friend logging/dialogs --- rpcs3/Emu/NP/rpcn_client.cpp | 17 ++++---- rpcs3/Emu/NP/rpcn_client.h | 2 +- rpcs3/rpcs3qt/rpcn_settings_dialog.cpp | 54 +++++++++++++++++++------- rpcs3/rpcs3qt/rpcn_settings_dialog.h | 1 + 4 files changed, 50 insertions(+), 24 deletions(-) diff --git a/rpcs3/Emu/NP/rpcn_client.cpp b/rpcs3/Emu/NP/rpcn_client.cpp index c1126b8698..d7bb274c66 100644 --- a/rpcs3/Emu/NP/rpcn_client.cpp +++ b/rpcs3/Emu/NP/rpcn_client.cpp @@ -1467,7 +1467,7 @@ namespace rpcn return error; } - bool rpcn_client::add_friend(const std::string& friend_username) + std::optional rpcn_client::add_friend(const std::string& friend_username) { std::vector data; std::copy(friend_username.begin(), friend_username.end(), std::back_inserter(data)); @@ -1478,19 +1478,18 @@ namespace rpcn std::vector packet_data; if (!forge_send_reply(CommandType::AddFriend, req_id, data, packet_data)) { - return false; + return std::nullopt; } vec_stream reply(packet_data); - auto error = static_cast(reply.get()); + const auto error = static_cast(reply.get()); - if (error != rpcn::ErrorType::NoError) - { - return false; - } + if (error == ErrorType::NoError) + rpcn_log.success("add_friend(\"%s\") succeeded", friend_username); + else + rpcn_log.error("add_friend(\"%s\") failed with error: %s", error); - rpcn_log.success("You have successfully added \"%s\" as a friend", friend_username); - return true; + return error; } bool rpcn_client::remove_friend(const std::string& friend_username) diff --git a/rpcs3/Emu/NP/rpcn_client.h b/rpcs3/Emu/NP/rpcn_client.h index 035dc9cf3c..56ba17d04d 100644 --- a/rpcs3/Emu/NP/rpcn_client.h +++ b/rpcs3/Emu/NP/rpcn_client.h @@ -293,7 +293,7 @@ namespace rpcn ErrorType send_reset_token(std::string_view npid, std::string_view email); ErrorType reset_password(std::string_view npid, std::string_view token, std::string_view password); ErrorType delete_account(); - bool add_friend(const std::string& friend_username); + std::optional add_friend(const std::string& friend_username); bool remove_friend(const std::string& friend_username); u32 get_num_friends(); diff --git a/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp b/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp index 53097f29fc..6de6007b31 100644 --- a/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp @@ -1262,13 +1262,10 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent) connect(accept_request_action, &QAction::triggered, this, [this, str_sel_friend]() { - if (!m_rpcn->add_friend(str_sel_friend)) - { - QMessageBox::critical(this, tr("Error adding a friend!"), tr("An error occurred while trying to add a friend!"), QMessageBox::Ok); - } - else + if (add_friend_with_error_dialog(str_sel_friend)) { QMessageBox::information(this, tr("Friend added!"), tr("You've successfully added a friend!"), QMessageBox::Ok); + return; } }); @@ -1304,11 +1301,8 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent) connect(send_friend_request_action, &QAction::triggered, this, [this, str_sel_friend]() { - if (!m_rpcn->add_friend(str_sel_friend)) - { - QMessageBox::critical(this, tr("Error sending a friend request!"), tr("An error occurred while trying to send a friend request!"), QMessageBox::Ok); + if (!add_friend_with_error_dialog(str_sel_friend)) return; - } QString qstr_friend = QString::fromStdString(str_sel_friend); add_update_list(m_lst_requests, qstr_friend, m_icon_request_sent, QVariant(false)); @@ -1341,11 +1335,7 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent) QMessageBox::critical(this, tr("Error validating username!"), tr("The username you entered is invalid!"), QMessageBox::Ok); } - if (!m_rpcn->add_friend(str_friend_username)) - { - QMessageBox::critical(this, tr("Error adding friend!"), tr("An error occurred while adding a friend!"), QMessageBox::Ok); - } - else + if (add_friend_with_error_dialog(str_friend_username)) { add_update_list(m_lst_requests, QString::fromStdString(str_friend_username), m_icon_request_sent, QVariant(false)); QMessageBox::information(this, tr("Friend added!"), tr("Friend was successfully added!"), QMessageBox::Ok); @@ -1360,6 +1350,42 @@ rpcn_friends_dialog::~rpcn_friends_dialog() m_rpcn->remove_friend_cb(friend_callback, this); } +bool rpcn_friends_dialog::add_friend_with_error_dialog(const std::string& friend_username) +{ + QString err_msg; + const auto opt_error = m_rpcn->add_friend(friend_username); + + if (opt_error.has_value()) + { + const auto error = opt_error.value(); + + if (error != rpcn::ErrorType::NoError) + { + switch (error) + { + case rpcn::ErrorType::NotFound: err_msg = tr("The specified username does not exist."); break; + case rpcn::ErrorType::InvalidInput: err_msg = tr("You cannot add yourself as a friend."); break; + case rpcn::ErrorType::Blocked: err_msg = tr("You or the other user have the other blocked."); break; + case rpcn::ErrorType::AlreadyFriend: err_msg = tr("You are already friends with this user."); break; + case rpcn::ErrorType::DbFail: err_msg = tr("A database error occurred. Please try again later."); break; + default: err_msg = tr("An unexpected error occurred."); break; + } + } + } + else + { + err_msg = tr("Failed to send the friend request."); + } + + if (!err_msg.isEmpty()) + { + QMessageBox::critical(this, tr("Friend Request Failed"), err_msg, QMessageBox::Ok); + return false; + } + + return true; +} + bool rpcn_friends_dialog::is_ok() const { return m_rpcn_ok; diff --git a/rpcs3/rpcs3qt/rpcn_settings_dialog.h b/rpcs3/rpcs3qt/rpcn_settings_dialog.h index 89d8253220..c28828e410 100644 --- a/rpcs3/rpcs3qt/rpcn_settings_dialog.h +++ b/rpcs3/rpcs3qt/rpcn_settings_dialog.h @@ -121,6 +121,7 @@ public: private: void add_update_list(QListWidget* list, const QString& name, const QIcon& icon, const QVariant& data); void remove_list(QListWidget* list, const QString& name); + bool add_friend_with_error_dialog(const std::string& friend_username); private Q_SLOTS: void add_update_friend(const QString& name, bool status); From aeaa62a28c36f352d5c2bf6bb4e1cadc4600ff00 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 8 Feb 2026 14:35:28 +0300 Subject: [PATCH 15/24] rsx/sync: Do not allow short-circuiting behavior when releasing GCM labels via host queue --- rpcs3/Emu/RSX/GL/GLGSRender.cpp | 4 ++-- rpcs3/Emu/RSX/GL/GLGSRender.h | 2 +- rpcs3/Emu/RSX/NV47/HW/nv406e.cpp | 4 ++-- rpcs3/Emu/RSX/NV47/HW/nv4097.cpp | 10 +++++----- rpcs3/Emu/RSX/NV47/HW/nv47_sync.hpp | 4 ++-- rpcs3/Emu/RSX/RSXThread.h | 2 +- rpcs3/Emu/RSX/VK/VKGSRender.cpp | 11 ++++------- rpcs3/Emu/RSX/VK/VKGSRender.h | 2 +- 8 files changed, 18 insertions(+), 21 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index acf258cc56..ebff202303 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -1351,7 +1351,7 @@ void GLGSRender::notify_tile_unbound(u32 tile) } } -bool GLGSRender::release_GCM_label(u32 address, u32 args) +bool GLGSRender::release_GCM_label(u32 type, u32 address, u32 args) { if (!backend_config.supports_host_gpu_labels) { @@ -1360,7 +1360,7 @@ bool GLGSRender::release_GCM_label(u32 address, u32 args) auto host_ctx = ensure(m_host_dma_ctrl->host_ctx()); - if (host_ctx->texture_loads_completed()) + if (type == NV4097_TEXTURE_READ_SEMAPHORE_RELEASE && host_ctx->texture_loads_completed()) { // We're about to poll waiting for GPU state, ensure the context is still valid. gl::check_state(); diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.h b/rpcs3/Emu/RSX/GL/GLGSRender.h index 5627216055..a05eb0bf3d 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.h +++ b/rpcs3/Emu/RSX/GL/GLGSRender.h @@ -206,7 +206,7 @@ public: void discard_occlusion_query(rsx::reports::occlusion_query_info* query) override; // DMA - bool release_GCM_label(u32 address, u32 data) override; + bool release_GCM_label(u32 type, u32 address, u32 data) override; void enqueue_host_context_write(u32 offset, u32 size, const void* data); void on_guest_texture_read(); diff --git a/rpcs3/Emu/RSX/NV47/HW/nv406e.cpp b/rpcs3/Emu/RSX/NV47/HW/nv406e.cpp index cbb04d140a..4307cc8289 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv406e.cpp +++ b/rpcs3/Emu/RSX/NV47/HW/nv406e.cpp @@ -86,7 +86,7 @@ namespace rsx RSX(ctx)->performance_counters.idle_time += (get_system_time() - start); } - void semaphore_release(context* ctx, u32 /*reg*/, u32 arg) + void semaphore_release(context* ctx, u32 reg, u32 arg) { const u32 offset = REGS(ctx)->semaphore_offset_406e(); @@ -122,7 +122,7 @@ namespace rsx arg = 1; } - util::write_gcm_label(ctx, addr, arg); + util::write_gcm_label(ctx, reg, addr, arg); } } } diff --git a/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp b/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp index 929925bcb1..17ee040f8c 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp +++ b/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp @@ -690,7 +690,7 @@ namespace rsx }); } - void texture_read_semaphore_release(context* ctx, u32 /*reg*/, u32 arg) + void texture_read_semaphore_release(context* ctx, u32 reg, u32 arg) { // Pipeline barrier seems to be equivalent to a SHADER_READ stage barrier. // Ideally the GPU only needs to have cached all textures declared up to this point before writing the label. @@ -715,15 +715,15 @@ namespace rsx if (g_cfg.video.strict_rendering_mode) [[ unlikely ]] { - util::write_gcm_label(ctx, addr, arg); + util::write_gcm_label(ctx, reg, addr, arg); } else { - util::write_gcm_label(ctx, addr, arg); + util::write_gcm_label(ctx, reg, addr, arg); } } - void back_end_write_semaphore_release(context* ctx, u32 /*reg*/, u32 arg) + void back_end_write_semaphore_release(context* ctx, u32 reg, u32 arg) { // Full pipeline barrier. GPU must flush pipeline before writing the label @@ -744,7 +744,7 @@ namespace rsx } const u32 val = (arg & 0xff00ff00) | ((arg & 0xff) << 16) | ((arg >> 16) & 0xff); - util::write_gcm_label(ctx, addr, val); + util::write_gcm_label(ctx, reg, addr, val); } void sync(context* ctx, u32, u32) diff --git a/rpcs3/Emu/RSX/NV47/HW/nv47_sync.hpp b/rpcs3/Emu/RSX/NV47/HW/nv47_sync.hpp index 9f39f84c00..fe2522c31c 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv47_sync.hpp +++ b/rpcs3/Emu/RSX/NV47/HW/nv47_sync.hpp @@ -13,13 +13,13 @@ namespace rsx namespace util { template - static void write_gcm_label(context* ctx, u32 address, u32 data) + static void write_gcm_label(context* ctx, u32 type, u32 address, u32 data) { const bool is_flip_sema = (address == (RSX(ctx)->label_addr + 0x10) || address == (RSX(ctx)->device_addr + 0x30)); if (!is_flip_sema) { // First, queue the GPU work. If it flushes the queue for us, the following routines will be faster. - const bool handled = RSX(ctx)->get_backend_config().supports_host_gpu_labels && RSX(ctx)->release_GCM_label(address, data); + const bool handled = RSX(ctx)->get_backend_config().supports_host_gpu_labels && RSX(ctx)->release_GCM_label(type, address, data); if (vm::_ref(address) == data) { diff --git a/rpcs3/Emu/RSX/RSXThread.h b/rpcs3/Emu/RSX/RSXThread.h index 4f965dee80..4b2de0acc4 100644 --- a/rpcs3/Emu/RSX/RSXThread.h +++ b/rpcs3/Emu/RSX/RSXThread.h @@ -380,7 +380,7 @@ namespace rsx flags32_t read_barrier(u32 memory_address, u32 memory_range, bool unconditional); virtual void write_barrier(u32 /*memory_address*/, u32 /*memory_range*/) {} virtual void sync_hint(FIFO::interrupt_hint hint, reports::sync_hint_payload_t payload); - virtual bool release_GCM_label(u32 /*address*/, u32 /*value*/) { return false; } + virtual bool release_GCM_label(u32 /*type*/, u32 /*address*/, u32 /*value*/) { return false; } protected: diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index cba661a64b..8d90f9a09f 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -1541,7 +1541,7 @@ std::pair VKGSRender::map_host_object_data( return { m_host_dma_ctrl->host_ctx(), m_host_object_data->value }; } -bool VKGSRender::release_GCM_label(u32 address, u32 args) +bool VKGSRender::release_GCM_label(u32 type, u32 address, u32 args) { if (!backend_config.supports_host_gpu_labels) { @@ -1550,7 +1550,7 @@ bool VKGSRender::release_GCM_label(u32 address, u32 args) auto host_ctx = ensure(m_host_dma_ctrl->host_ctx()); - if (host_ctx->texture_loads_completed()) + if (type == NV4097_TEXTURE_READ_SEMAPHORE_RELEASE && host_ctx->texture_loads_completed()) { // All texture loads already seen by the host GPU // Wait for all previously submitted labels to be flushed @@ -1572,13 +1572,10 @@ bool VKGSRender::release_GCM_label(u32 address, u32 args) const auto release_event_id = host_ctx->on_label_acquire(); + vk::insert_global_memory_barrier(*m_current_command_buffer); + if (host_ctx->has_unflushed_texture_loads()) { - if (vk::is_renderpass_open(*m_current_command_buffer)) - { - vk::end_renderpass(*m_current_command_buffer); - } - vkCmdUpdateBuffer(*m_current_command_buffer, mapping.second->value, mapping.first, 4, &write_data); flush_command_queue(); } diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.h b/rpcs3/Emu/RSX/VK/VKGSRender.h index 0c93741fcb..f9feedc35a 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.h +++ b/rpcs3/Emu/RSX/VK/VKGSRender.h @@ -254,7 +254,7 @@ public: // Sync void write_barrier(u32 address, u32 range) override; void sync_hint(rsx::FIFO::interrupt_hint hint, rsx::reports::sync_hint_payload_t payload) override; - bool release_GCM_label(u32 address, u32 data) override; + bool release_GCM_label(u32 type, u32 address, u32 data) override; void begin_occlusion_query(rsx::reports::occlusion_query_info* query) override; void end_occlusion_query(rsx::reports::occlusion_query_info* query) override; From 3bb21db71b4c6f73d6777ec4e7b1a2c3c3cdbe57 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Mon, 9 Feb 2026 05:23:27 +0100 Subject: [PATCH 16/24] Guard async transactions against spurious wakeups --- rpcs3/Emu/NP/np_contexts.cpp | 2 +- rpcs3/Emu/NP/np_requests.cpp | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/NP/np_contexts.cpp b/rpcs3/Emu/NP/np_contexts.cpp index 46d773d127..f43d02c362 100644 --- a/rpcs3/Emu/NP/np_contexts.cpp +++ b/rpcs3/Emu/NP/np_contexts.cpp @@ -46,7 +46,7 @@ error_code generic_async_transaction_context::wait_for_completion() return *result; } - completion_cond.wait(lock); + completion_cond.wait(lock, [this] { return result.has_value(); }); return *result; } diff --git a/rpcs3/Emu/NP/np_requests.cpp b/rpcs3/Emu/NP/np_requests.cpp index 20199889dc..3f21f24cd3 100644 --- a/rpcs3/Emu/NP/np_requests.cpp +++ b/rpcs3/Emu/NP/np_requests.cpp @@ -951,13 +951,16 @@ namespace np { thread_base::set_name("NP Trans Worker"); - auto res = trans_ctx->wake_cond.wait_for(lock, std::chrono::microseconds(trans_ctx->timeout)); + bool has_value = trans_ctx->wake_cond.wait_for(lock, std::chrono::microseconds(trans_ctx->timeout), [&] + { + return trans_ctx->result.has_value(); + }); { std::lock_guard lock_threads(this->mutex_async_transactions); this->async_transactions.erase(req_id); } - if (res == std::cv_status::timeout) + if (!has_value) { trans_ctx->result = SCE_NP_COMMUNITY_ERROR_TIMEOUT; return; From 6a07c3f46d195212e64b2630fbb68e8aa1ffa515 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 9 Feb 2026 19:46:57 +0100 Subject: [PATCH 17/24] input: fix input guard button offsets for closing native overlays L2, R1 and all hat buttons were incorrectly enforcing the pad interception after closing a native dialog. --- rpcs3/Input/pad_thread.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/rpcs3/Input/pad_thread.cpp b/rpcs3/Input/pad_thread.cpp index 40887ae5fb..55652bc229 100644 --- a/rpcs3/Input/pad_thread.cpp +++ b/rpcs3/Input/pad_thread.cpp @@ -631,15 +631,18 @@ void pad_thread::operator()() if (!pad->is_connected()) continue; - for (const auto& button : pad->m_buttons) + for (const Button& button : pad->m_buttons) { if (button.m_pressed && ( - button.m_outKeyCode == CELL_PAD_CTRL_CROSS || - button.m_outKeyCode == CELL_PAD_CTRL_CIRCLE || - button.m_outKeyCode == CELL_PAD_CTRL_TRIANGLE || - button.m_outKeyCode == CELL_PAD_CTRL_SQUARE || - button.m_outKeyCode == CELL_PAD_CTRL_START || - button.m_outKeyCode == CELL_PAD_CTRL_SELECT)) + (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1 && ( + button.m_outKeyCode == CELL_PAD_CTRL_START || + button.m_outKeyCode == CELL_PAD_CTRL_SELECT)) || + (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL2 && ( + button.m_outKeyCode == CELL_PAD_CTRL_CROSS || + button.m_outKeyCode == CELL_PAD_CTRL_CIRCLE || + button.m_outKeyCode == CELL_PAD_CTRL_TRIANGLE || + button.m_outKeyCode == CELL_PAD_CTRL_SQUARE)) + )) { any_button_pressed = true; break; @@ -676,7 +679,7 @@ void pad_thread::operator()() break; } - for (const auto& button : pad->m_buttons) + for (const Button& button : pad->m_buttons) { if (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1 && button.m_outKeyCode == CELL_PAD_CTRL_PS && button.m_pressed) { @@ -735,7 +738,7 @@ void pad_thread::operator()() if (!pad->is_connected()) continue; - for (const auto& button : pad->m_buttons) + for (const Button& button : pad->m_buttons) { if (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1 && button.m_outKeyCode == CELL_PAD_CTRL_START && button.m_pressed) { From 4c234dc74443139f227374e3471da0ca2fe7904e Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 8 Feb 2026 18:04:10 +0100 Subject: [PATCH 18/24] Qt: Fix duplicate ansi highlighter creation It is created in the slot when the action is toggled in LoadSettings --- rpcs3/rpcs3qt/log_frame.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/rpcs3/rpcs3qt/log_frame.cpp b/rpcs3/rpcs3qt/log_frame.cpp index 6d99791799..0e1b0fb129 100644 --- a/rpcs3/rpcs3qt/log_frame.cpp +++ b/rpcs3/rpcs3qt/log_frame.cpp @@ -163,11 +163,6 @@ log_frame::log_frame(std::shared_ptr _gui_settings, QWidget* paren CreateAndConnectActions(); LoadSettings(); - if (m_ansi_tty) - { - m_tty_ansi_highlighter = new AnsiHighlighter(m_tty->document()); - } - m_timer = new QTimer(this); connect(m_timer, &QTimer::timeout, this, &log_frame::UpdateUI); } From 8f066541a3c3d8f0d830872f42419be3a4cbf1c3 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 8 Feb 2026 18:31:31 +0100 Subject: [PATCH 19/24] Qt: adjust ansi colors based on background color --- rpcs3/rpcs3qt/log_frame.cpp | 10 +++- rpcs3/rpcs3qt/qt_utils.cpp | 30 +++++++++- rpcs3/rpcs3qt/qt_utils.h | 11 ++-- rpcs3/rpcs3qt/syntax_highlighter.cpp | 86 ++++++++++++++++------------ rpcs3/rpcs3qt/syntax_highlighter.h | 9 ++- 5 files changed, 100 insertions(+), 46 deletions(-) diff --git a/rpcs3/rpcs3qt/log_frame.cpp b/rpcs3/rpcs3qt/log_frame.cpp index 0e1b0fb129..cf570eea7a 100644 --- a/rpcs3/rpcs3qt/log_frame.cpp +++ b/rpcs3/rpcs3qt/log_frame.cpp @@ -220,7 +220,7 @@ void log_frame::CreateAndConnectActions() // I, for one, welcome our lambda overlord // It's either this or a signal mapper // Then, probably making a list of these actions so that it's easier to iterate to generate the mapper. - auto l_initAct = [this](QAction* act, logs::level logLevel) + const auto l_initAct = [this](QAction* act, logs::level logLevel) { act->setCheckable(true); @@ -293,7 +293,7 @@ void log_frame::CreateAndConnectActions() if (m_ansi_tty && !m_tty_ansi_highlighter) { - m_tty_ansi_highlighter = new AnsiHighlighter(m_tty->document()); + m_tty_ansi_highlighter = new AnsiHighlighter(m_tty); } else if (!m_ansi_tty && m_tty_ansi_highlighter) { @@ -602,6 +602,12 @@ void log_frame::RepaintTextColors() html.replace(old_style, new_style); m_log->document()->setHtml(html); + + if (m_tty_ansi_highlighter) + { + m_tty_ansi_highlighter->update_colors(m_tty); + m_tty_ansi_highlighter->rehighlight(); + } } void log_frame::UpdateUI() diff --git a/rpcs3/rpcs3qt/qt_utils.cpp b/rpcs3/rpcs3qt/qt_utils.cpp index a957dba11c..73dbaa87b6 100644 --- a/rpcs3/rpcs3qt/qt_utils.cpp +++ b/rpcs3/rpcs3qt/qt_utils.cpp @@ -173,21 +173,45 @@ namespace gui } return res; } - - QColor get_foreground_color() + + QColor get_foreground_color(QWidget* widget) { + if (widget) + { + widget->ensurePolished(); + return widget->palette().color(QPalette::ColorRole::WindowText); + } + QLabel dummy_color; dummy_color.ensurePolished(); return dummy_color.palette().color(QPalette::ColorRole::WindowText); } - QColor get_background_color() + QColor get_background_color(QWidget* widget) { + if (widget) + { + widget->ensurePolished(); + return widget->palette().color(QPalette::ColorRole::Window); + } + QLabel dummy_color; dummy_color.ensurePolished(); return dummy_color.palette().color(QPalette::ColorRole::Window); } + QColor adjust_color_for_background(const QColor& fg, const QColor& bg) + { + const int diff = fg.lightness() - bg.lightness(); + + if (std::abs(diff) >= 40) + { + return fg; + } + + return (bg.lightness() < 128) ? fg.lighter(180) : fg.darker(180); + } + QColor get_label_color(const QString& object_name, const QColor& fallback_light, const QColor& fallback_dark, QPalette::ColorRole color_role) { if (!gui::custom_stylesheet_active || !gui::stylesheet.contains(object_name)) diff --git a/rpcs3/rpcs3qt/qt_utils.h b/rpcs3/rpcs3qt/qt_utils.h index c08c5665a7..98e0eb8cd6 100644 --- a/rpcs3/rpcs3qt/qt_utils.h +++ b/rpcs3/rpcs3qt/qt_utils.h @@ -68,11 +68,14 @@ namespace gui // Returns a list of all base names of files in dir whose complete file names contain one of the given name_filters QStringList get_dir_entries(const QDir& dir, const QStringList& name_filters, bool full_path = false); - // Returns the foreground color of QLabel with respect to the current light/dark mode. - QColor get_foreground_color(); + // Returns the foreground color of QLabel or the given widget with respect to the current light/dark mode. + QColor get_foreground_color(QWidget* widget = nullptr); - // Returns the background color of QLabel with respect to the current light/dark mode. - QColor get_background_color(); + // Returns the background color of QLabel or the given widget with respect to the current light/dark mode. + QColor get_background_color(QWidget* widget = nullptr); + + // Returns an adjusted color with better contrast, depending on the background. + QColor adjust_color_for_background(const QColor& fg, const QColor& bg); // Returns the color specified by its color_role for the QLabels with object_name QColor get_label_color(const QString& object_name, const QColor& fallback_light, const QColor& fallback_dark, QPalette::ColorRole color_role = QPalette::WindowText); diff --git a/rpcs3/rpcs3qt/syntax_highlighter.cpp b/rpcs3/rpcs3qt/syntax_highlighter.cpp index 2fd43033d5..a15e36c3a4 100644 --- a/rpcs3/rpcs3qt/syntax_highlighter.cpp +++ b/rpcs3/rpcs3qt/syntax_highlighter.cpp @@ -184,12 +184,37 @@ GlslHighlighter::GlslHighlighter(QTextDocument* parent) : Highlighter(parent) commentEndExpression = QRegularExpression("\\*/"); } -AnsiHighlighter::AnsiHighlighter(QTextDocument* parent) : Highlighter(parent) +AnsiHighlighter::AnsiHighlighter(QPlainTextEdit* text_edit) + : Highlighter(text_edit ? text_edit->document() : nullptr) { - m_escape_format.setForeground(Qt::darkGray); - m_escape_format.setFontItalic(true); + update_colors(text_edit); +} - m_foreground_color = gui::utils::get_foreground_color(); +void AnsiHighlighter::update_colors(QPlainTextEdit* text_edit) +{ + m_foreground_color = gui::utils::get_foreground_color(text_edit); + m_background_color = gui::utils::get_background_color(text_edit); + + m_foreground_colors[30] = gui::utils::adjust_color_for_background(Qt::black, m_background_color); + m_foreground_colors[31] = gui::utils::adjust_color_for_background(Qt::red, m_background_color); + m_foreground_colors[32] = gui::utils::adjust_color_for_background(Qt::darkGreen, m_background_color); + m_foreground_colors[33] = gui::utils::adjust_color_for_background(Qt::darkYellow, m_background_color); + m_foreground_colors[34] = gui::utils::adjust_color_for_background(Qt::darkBlue, m_background_color); + m_foreground_colors[35] = gui::utils::adjust_color_for_background(Qt::darkMagenta, m_background_color); + m_foreground_colors[36] = gui::utils::adjust_color_for_background(Qt::darkCyan, m_background_color); + m_foreground_colors[37] = gui::utils::adjust_color_for_background(Qt::lightGray, m_background_color); + m_foreground_colors[39] = m_foreground_color; + m_foreground_colors[90] = gui::utils::adjust_color_for_background(Qt::darkGray, m_background_color); + m_foreground_colors[91] = gui::utils::adjust_color_for_background(Qt::red, m_background_color); + m_foreground_colors[92] = gui::utils::adjust_color_for_background(Qt::green, m_background_color); + m_foreground_colors[93] = gui::utils::adjust_color_for_background(Qt::yellow, m_background_color); + m_foreground_colors[94] = gui::utils::adjust_color_for_background(Qt::blue, m_background_color); + m_foreground_colors[95] = gui::utils::adjust_color_for_background(Qt::magenta, m_background_color); + m_foreground_colors[96] = gui::utils::adjust_color_for_background(Qt::cyan, m_background_color); + m_foreground_colors[97] = gui::utils::adjust_color_for_background(Qt::white, m_background_color); + + m_escape_format.setForeground(gui::utils::adjust_color_for_background(Qt::darkGray, m_background_color)); + m_escape_format.setFontItalic(true); } void AnsiHighlighter::highlightBlock(const QString& text) @@ -235,39 +260,26 @@ void AnsiHighlighter::highlightBlock(const QString& text) if (!ok) continue; switch (code) { - case 0: - current_format = QTextCharFormat(); - current_format.setForeground(m_foreground_color); - break; - case 1: - current_format.setFontWeight(QFont::Bold); - break; - case 3: - current_format.setFontItalic(true); - break; - case 4: - current_format.setFontUnderline(true); - break; - case 30: current_format.setForeground(Qt::black); break; - case 31: current_format.setForeground(Qt::red); break; - case 32: current_format.setForeground(Qt::darkGreen); break; - case 33: current_format.setForeground(Qt::darkYellow); break; - case 34: current_format.setForeground(Qt::darkBlue); break; - case 35: current_format.setForeground(Qt::darkMagenta); break; - case 36: current_format.setForeground(Qt::darkCyan); break; - case 37: current_format.setForeground(Qt::lightGray); break; - case 39: current_format.setForeground(m_foreground_color); break; - case 90: current_format.setForeground(Qt::darkGray); break; - case 91: current_format.setForeground(Qt::red); break; - case 92: current_format.setForeground(Qt::green); break; - case 93: current_format.setForeground(Qt::yellow); break; - case 94: current_format.setForeground(Qt::blue); break; - case 95: current_format.setForeground(Qt::magenta); break; - case 96: current_format.setForeground(Qt::cyan); break; - case 97: current_format.setForeground(Qt::white); break; + case 0: + current_format = QTextCharFormat(); + current_format.setForeground(m_foreground_color); + break; + case 1: + current_format.setFontWeight(QFont::Bold); + break; + case 3: + current_format.setFontItalic(true); + break; + case 4: + current_format.setFontUnderline(true); + break; + default: // Background and extended colors not yet handled - default: - break; + if (const auto it = m_foreground_colors.find(code); it != m_foreground_colors.cend()) + { + current_format.setForeground(it->second); + } + break; } } } @@ -278,5 +290,7 @@ void AnsiHighlighter::highlightBlock(const QString& text) // Apply remaining format if (pos < text.length()) + { setFormat(pos, text.length() - pos, current_format); + } } diff --git a/rpcs3/rpcs3qt/syntax_highlighter.h b/rpcs3/rpcs3qt/syntax_highlighter.h index 3854059dd7..d94d600dd6 100644 --- a/rpcs3/rpcs3qt/syntax_highlighter.h +++ b/rpcs3/rpcs3qt/syntax_highlighter.h @@ -3,6 +3,9 @@ #include #include #include +#include + +#include // Inspired by https://doc.qt.io/qt-5/qtwidgets-richtext-syntaxhighlighter-example.html @@ -59,7 +62,9 @@ class AnsiHighlighter : public Highlighter Q_OBJECT public: - explicit AnsiHighlighter(QTextDocument* parent = nullptr); + explicit AnsiHighlighter(QPlainTextEdit* text_edit); + + void update_colors(QPlainTextEdit* text_edit); protected: const QRegularExpression ansi_re = QRegularExpression("\x1b\\[[0-9;]*m"); @@ -67,6 +72,8 @@ protected: QTextCharFormat m_escape_format; QColor m_foreground_color; + QColor m_background_color; + std::map m_foreground_colors; void highlightBlock(const QString& text) override; }; From b2469039afce9cafb979dd6191bc87ec627d7888 Mon Sep 17 00:00:00 2001 From: Malcolm Date: Sun, 1 Feb 2026 03:00:09 +0000 Subject: [PATCH 20/24] ARM64: Detect some arm features and let LLVM know if they are or aren't present via attributes - On x86, LLVM has robust detection for the CPU name. If a CPU like skylake has AVX disabled, it will fall back to something without AVX (nehalem) - On ARM, detection is not as robust. For instance, on my snapdragon 8 gen 2, it assumes that we have SVE support, as the cortex-x3 supports SVE. - If an ARM cpu is paired with other cpus from another generation which doesn't support the same instructions as the cortex-x3, or if the cortex-x3 just has SVE disabled for no apparant reason (in the case of the snapdragon 8 gen 2) - We need to actually detect that ourselves. - Beyond SVE also detect support for some instructions that might be useful SPU LLVM when optimized with intrinsics. --- Utilities/JITLLVM.cpp | 25 +++++++++ rpcs3/util/sysinfo.cpp | 114 ++++++++++++++++++++++++++++++++++++++++- rpcs3/util/sysinfo.hpp | 10 ++++ 3 files changed, 148 insertions(+), 1 deletion(-) diff --git a/Utilities/JITLLVM.cpp b/Utilities/JITLLVM.cpp index 166ee7cec2..34e1572185 100644 --- a/Utilities/JITLLVM.cpp +++ b/Utilities/JITLLVM.cpp @@ -688,6 +688,30 @@ jit_compiler::jit_compiler(const std::unordered_map& _link, co mem = std::make_unique(std::move(symbols_cement)); } + std::vector attributes; + +#if defined(ARCH_ARM64) + if (utils::has_sha3()) + attributes.push_back("+sha3"); + else + attributes.push_back("-sha3"); + + if (utils::has_dotprod()) + attributes.push_back("+dotprod"); + else + attributes.push_back("-dotprod"); + + if (utils::has_sve()) + attributes.push_back("+sve"); + else + attributes.push_back("-sve"); + + if (utils::has_sve2()) + attributes.push_back("+sve2"); + else + attributes.push_back("-sve2"); +#endif + { m_engine.reset(llvm::EngineBuilder(std::move(null_mod)) .setErrorStr(&result) @@ -699,6 +723,7 @@ jit_compiler::jit_compiler(const std::unordered_map& _link, co //.setCodeModel(llvm::CodeModel::Large) #endif .setRelocationModel(llvm::Reloc::Model::PIC_) + .setMAttrs(attributes) .setMCPU(m_cpu) .create()); } diff --git a/rpcs3/util/sysinfo.cpp b/rpcs3/util/sysinfo.cpp index 94563e8d10..e1e8ab8404 100755 --- a/rpcs3/util/sysinfo.cpp +++ b/rpcs3/util/sysinfo.cpp @@ -16,9 +16,15 @@ #else #include #include -#ifndef __APPLE__ +#ifdef __APPLE__ +#include +#else #include #include +#if defined(ARCH_ARM64) && defined(__linux__) +#include +#include +#endif #endif #endif @@ -444,6 +450,100 @@ u32 utils::get_rep_movsb_threshold() return g_value; } +#ifdef ARCH_ARM64 + +bool utils::has_neon() +{ + static const bool g_value = []() -> bool + { +#if defined(__linux__) + return (getauxval(AT_HWCAP) & HWCAP_ASIMD) != 0; +#elif defined(__APPLE__) + int val = 0; + size_t len = sizeof(val); + sysctlbyname("hw.optional.AdvSIMD", &val, &len, nullptr, 0); + return val != 0; +#elif defined(_WIN32) + return IsProcessorFeaturePresent(PF_ARM_VFP_32_REGISTERS_AVAILABLE) != 0; +#endif + }(); + return g_value; +} + +bool utils::has_sha3() +{ + static const bool g_value = []() -> bool + { +#if defined(__linux__) + return (getauxval(AT_HWCAP) & HWCAP_SHA3) != 0; +#elif defined(__APPLE__) + int val = 0; + size_t len = sizeof(val); + sysctlbyname("hw.optional.arm.FEAT_SHA3", &val, &len, nullptr, 0); + return val != 0; +#elif defined(_WIN32) + return IsProcessorFeaturePresent(PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE) != 0; +#endif + }(); + return g_value; +} + +bool utils::has_dotprod() +{ + static const bool g_value = []() -> bool + { +#if defined(__linux__) + return (getauxval(AT_HWCAP) & HWCAP_ASIMDDP) != 0; +#elif defined(__APPLE__) + int val = 0; + size_t len = sizeof(val); + sysctlbyname("hw.optional.arm.FEAT_DotProd", &val, &len, nullptr, 0); + return val != 0; +#elif defined(_WIN32) + return IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) != 0; +#endif + }(); + return g_value; +} + +bool utils::has_sve() +{ + static const bool g_value = []() -> bool + { +#if defined(__linux__) + return (getauxval(AT_HWCAP) & HWCAP_SVE) != 0; +#elif defined(__APPLE__) + int val = 0; + size_t len = sizeof(val); + sysctlbyname("hw.optional.arm.FEAT_SVE", &val, &len, nullptr, 0); + return val != 0; +#elif defined(_WIN32) + return IsProcessorFeaturePresent(PF_ARM_SVE_INSTRUCTIONS_AVAILABLE) != 0; +#endif + }(); + return g_value; +} + +bool utils::has_sve2() +{ + static const bool g_value = []() -> bool + { +#if defined(__linux__) + return (getauxval(AT_HWCAP2) & HWCAP2_SVE2) != 0; +#elif defined(__APPLE__) + int val = 0; + size_t len = sizeof(val); + sysctlbyname("hw.optional.arm.FEAT_SVE2", &val, &len, nullptr, 0); + return val != 0; +#elif defined(_WIN32) + return IsProcessorFeaturePresent(PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE) != 0; +#endif + }(); + return g_value; +} + +#endif + std::string utils::get_cpu_brand() { #if defined(ARCH_X64) @@ -496,6 +596,17 @@ std::string utils::get_system_info() { fmt::append(result, " | TSC: Disabled"); } +#ifdef ARCH_ARM64 + + if (has_neon()) + { + result += " | Neon"; + } + else + { + fmt::throw_exception("Neon support not present"); + } +#else if (has_avx()) { @@ -562,6 +673,7 @@ std::string utils::get_system_info() { result += " | TSX disabled via microcode"; } +#endif return result; } diff --git a/rpcs3/util/sysinfo.hpp b/rpcs3/util/sysinfo.hpp index fd7e810f67..d9bd0c6660 100755 --- a/rpcs3/util/sysinfo.hpp +++ b/rpcs3/util/sysinfo.hpp @@ -54,7 +54,17 @@ namespace utils bool has_appropriate_um_wait(); bool has_um_wait(); +#ifdef ARCH_ARM64 + bool has_neon(); + bool has_sha3(); + + bool has_dotprod(); + + bool has_sve(); + + bool has_sve2(); +#endif std::string get_cpu_brand(); std::string get_system_info(); From 7cfe96a1d121e12fbdfe42befe9af5bbd561be99 Mon Sep 17 00:00:00 2001 From: Ani Date: Wed, 11 Feb 2026 09:30:45 +0100 Subject: [PATCH 21/24] macOS: Check for hw.optional.neon as fallback --- rpcs3/util/sysinfo.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rpcs3/util/sysinfo.cpp b/rpcs3/util/sysinfo.cpp index e1e8ab8404..8abe584a94 100755 --- a/rpcs3/util/sysinfo.cpp +++ b/rpcs3/util/sysinfo.cpp @@ -462,7 +462,10 @@ bool utils::has_neon() int val = 0; size_t len = sizeof(val); sysctlbyname("hw.optional.AdvSIMD", &val, &len, nullptr, 0); - return val != 0; + int val_legacy = 0; + size_t len_legacy = sizeof(val_legacy); + sysctlbyname("hw.optional.neon", &val_legacy, &len_legacy, nullptr, 0); + return val != 0 || val_legacy != 0; #elif defined(_WIN32) return IsProcessorFeaturePresent(PF_ARM_VFP_32_REGISTERS_AVAILABLE) != 0; #endif From 236dc966894cb80aaaabd5ed2cc9055942411eae Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Sat, 14 Feb 2026 11:57:12 +0100 Subject: [PATCH 22/24] Microphone fixes --- rpcs3/Emu/Cell/Modules/cellMic.cpp | 2 +- rpcs3/Emu/Cell/Modules/cellMic.h | 2 +- rpcs3/Emu/Cell/lv2/sys_usbd.cpp | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellMic.cpp b/rpcs3/Emu/Cell/Modules/cellMic.cpp index 0724b48927..0c58d2ec7e 100644 --- a/rpcs3/Emu/Cell/Modules/cellMic.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMic.cpp @@ -634,7 +634,7 @@ u32 microphone_device::capture_audio() if (ALCenum err = alcGetError(micdevice.device); err != ALC_NO_ERROR) { cellMic.error("Error getting number of captured samples of device '%s' (error=%s)", micdevice.name, fmt::alc_error{micdevice.device, err}); - return CELL_MICIN_ERROR_FATAL; + return 0; } num_samples = std::min(num_samples, samples_in); diff --git a/rpcs3/Emu/Cell/Modules/cellMic.h b/rpcs3/Emu/Cell/Modules/cellMic.h index 88a2f4d937..a07cec23a8 100644 --- a/rpcs3/Emu/Cell/Modules/cellMic.h +++ b/rpcs3/Emu/Cell/Modules/cellMic.h @@ -238,7 +238,7 @@ public: if (over_size > Size) { m_tail += (over_size - Size); - if (m_tail > Size) + if (m_tail >= Size) m_tail -= Size; m_used = Size; diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp index 64f27227d3..e1c3caf9d7 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp @@ -556,6 +556,8 @@ usb_handler_thread::usb_handler_thread() switch (g_cfg.audio.microphone_type) { + case microphone_handler::null: + break; case microphone_handler::standard: usb_devices.push_back(std::make_shared(0, get_new_location(), MicType::Logitech)); break; From 7f2dec46cade6340aeda9503ba70c4da266818b0 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Sat, 14 Feb 2026 14:16:37 +0100 Subject: [PATCH 23/24] Ignore gcc strict aliasing warnings on __sync_lock_test_and_set --- rpcs3/util/atomic.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rpcs3/util/atomic.hpp b/rpcs3/util/atomic.hpp index 4503eaac61..f77aad7825 100644 --- a/rpcs3/util/atomic.hpp +++ b/rpcs3/util/atomic.hpp @@ -1011,7 +1011,12 @@ struct atomic_storage : atomic_storage static inline T exchange(T& dest, T value) { __atomic_thread_fence(__ATOMIC_ACQ_REL); + // GCC has recently started thinking using this instrinsic is breaking strict aliasing rules + // TODO: remove if this ever get fixed in GCC + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstrict-aliasing" return std::bit_cast(__sync_lock_test_and_set(reinterpret_cast(&dest), std::bit_cast(value))); + #pragma GCC diagnostic pop } static inline void store(T& dest, T value) From 9f928247218337313af5b1c2354dc77c49111399 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Sat, 14 Feb 2026 14:53:01 +0100 Subject: [PATCH 24/24] Fix glsl_invalid_program not fitting within 3 bits --- rpcs3/Emu/RSX/Program/GLSLTypes.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/Program/GLSLTypes.h b/rpcs3/Emu/RSX/Program/GLSLTypes.h index 5b1b61396e..8f7cb6fa03 100644 --- a/rpcs3/Emu/RSX/Program/GLSLTypes.h +++ b/rpcs3/Emu/RSX/Program/GLSLTypes.h @@ -9,7 +9,7 @@ namespace glsl glsl_compute_program = 2, // Meta - glsl_invalid_program = 0xff + glsl_invalid_program = 7 }; enum glsl_rules : unsigned char