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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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/69] 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 From c6f5abe59fc6bfeb52638887cb849142801a0177 Mon Sep 17 00:00:00 2001 From: Zion Nimchuk Date: Sat, 14 Feb 2026 15:24:17 -0800 Subject: [PATCH 25/69] Update docker and ccache --- .ci/setup-llvm.sh | 2 +- .ci/setup-windows.sh | 2 +- .github/workflows/llvm.yml | 2 +- .github/workflows/rpcs3.yml | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.ci/setup-llvm.sh b/.ci/setup-llvm.sh index d296d2a3e4..5d06222f04 100644 --- a/.ci/setup-llvm.sh +++ b/.ci/setup-llvm.sh @@ -1,7 +1,7 @@ #!/bin/sh -ex # Resource/dependency URLs -CCACHE_URL="https://github.com/ccache/ccache/releases/download/v4.11.2/ccache-4.11.2-windows-x86_64.zip" +CCACHE_URL="https://github.com/ccache/ccache/releases/download/v4.12.3/ccache-4.12.3-windows-x86_64.zip" DEP_URLS=" \ $CCACHE_URL" diff --git a/.ci/setup-windows.sh b/.ci/setup-windows.sh index aade55fc95..cee6d624de 100755 --- a/.ci/setup-windows.sh +++ b/.ci/setup-windows.sh @@ -17,7 +17,7 @@ QT_SVG_URL="${QT_HOST}${QT_PREFIX}${QT_PREFIX_2}qtsvg${QT_SUFFIX}" QT_TRANSLATIONS_URL="${QT_HOST}${QT_PREFIX}${QT_PREFIX_2}qttranslations${QT_SUFFIX}" LLVMLIBS_URL="https://github.com/RPCS3/llvm-mirror/releases/download/custom-build-win-${LLVM_VER}/llvmlibs_mt.7z" VULKAN_SDK_URL="https://www.dropbox.com/scl/fi/sjjh0fc4ld281pjbl2xzu/VulkanSDK-${VULKAN_VER}-Installer.exe?rlkey=f6wzc0lvms5vwkt2z3qabfv9d&dl=1" -CCACHE_URL="https://github.com/ccache/ccache/releases/download/v4.11.2/ccache-4.11.2-windows-x86_64.zip" +CCACHE_URL="https://github.com/ccache/ccache/releases/download/v4.12.3/ccache-4.12.3-windows-x86_64.zip" DEP_URLS=" \ $QT_BASE_URL \ diff --git a/.github/workflows/llvm.yml b/.github/workflows/llvm.yml index e3e3e76c50..3ed584437a 100644 --- a/.github/workflows/llvm.yml +++ b/.github/workflows/llvm.yml @@ -20,7 +20,7 @@ jobs: runs-on: windows-2025 env: COMPILER: msvc - CCACHE_SHA: '1f39f3ad5aae3fe915e99ad1302633bc8f6718e58fa7c0de2b0ba7e080f0f08c' + CCACHE_SHA: '859141059ac950e1e8cd042c66f842f26b9e3a62a1669a69fe6ba180cb58bbdf' CCACHE_BIN_DIR: 'C:\ccache_bin' CCACHE_DIR: 'C:\ccache' CCACHE_INODECACHE: 'true' diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index 90ee8555ad..0132095f41 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.7" + docker_img: "rpcs3/rpcs3-ci-jammy:1.8" 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.7" + docker_img: "rpcs3/rpcs3-ci-jammy:1.8" build_sh: "/rpcs3/.ci/build-linux.sh" compiler: gcc - os: ubuntu-24.04-arm - docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.7" + docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.8" 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.7" + docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.8" build_sh: "/rpcs3/.ci/build-linux-aarch64.sh" compiler: gcc name: RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} @@ -219,7 +219,7 @@ jobs: LLVM_VER: '19.1.7' VULKAN_VER: '1.3.268.0' VULKAN_SDK_SHA: '8459ef49bd06b697115ddd3d97c9aec729e849cd775f5be70897718a9b3b9db5' - CCACHE_SHA: '1f39f3ad5aae3fe915e99ad1302633bc8f6718e58fa7c0de2b0ba7e080f0f08c' + CCACHE_SHA: '859141059ac950e1e8cd042c66f842f26b9e3a62a1669a69fe6ba180cb58bbdf' CCACHE_BIN_DIR: 'C:\ccache_bin' CCACHE_DIR: 'C:\ccache' CCACHE_INODECACHE: 'true' From 06798ef7292352b0f2cd4bf3e4d86d7f0dbe3bfc Mon Sep 17 00:00:00 2001 From: Marin Baron Date: Sat, 14 Feb 2026 09:05:38 +0100 Subject: [PATCH 26/69] [Build] Remove quotes protoc --- 3rdparty/protobuf/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/3rdparty/protobuf/CMakeLists.txt b/3rdparty/protobuf/CMakeLists.txt index 274e6110af..f8e6bed59b 100644 --- a/3rdparty/protobuf/CMakeLists.txt +++ b/3rdparty/protobuf/CMakeLists.txt @@ -2,8 +2,8 @@ add_library(3rdparty_protobuf INTERFACE) if (USE_SYSTEM_PROTOBUF) pkg_check_modules(PROTOBUF REQUIRED IMPORTED_TARGET protobuf>=33.0.0) target_link_libraries(3rdparty_protobuf INTERFACE PkgConfig::PROTOBUF) - set(PROTOBUF_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../rpcs3/Emu/NP/generated/") - execute_process(COMMAND protoc --cpp_out="${PROTOBUF_DIR}" --proto_path="${PROTOBUF_DIR}" np2_structs.proto RESULT_VARIABLE PROTOBUF_CMD_ERROR) + set(PROTOBUF_DIR "${CMAKE_SOURCE_DIR}/rpcs3/Emu/NP/generated") + execute_process(COMMAND protoc --cpp_out=${PROTOBUF_DIR} --proto_path=${PROTOBUF_DIR} np2_structs.proto RESULT_VARIABLE PROTOBUF_CMD_ERROR) if(PROTOBUF_CMD_ERROR AND NOT PROTOBUF_CMD_ERROR EQUAL 0) message(FATAL_ERROR "protoc failed to regenerate protobuf files.") endif() From 97bbc2d41957e028c0a0b212b2d0fd331ebd7dc7 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sat, 14 Feb 2026 18:03:00 +0100 Subject: [PATCH 27/69] Update yaml-cpp to 0.9.0 --- 3rdparty/yaml-cpp/yaml-cpp | 2 +- 3rdparty/yaml-cpp/yaml-cpp.vcxproj | 1 + 3rdparty/yaml-cpp/yaml-cpp.vcxproj.filters | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/3rdparty/yaml-cpp/yaml-cpp b/3rdparty/yaml-cpp/yaml-cpp index 456c68f452..05c44fcd18 160000 --- a/3rdparty/yaml-cpp/yaml-cpp +++ b/3rdparty/yaml-cpp/yaml-cpp @@ -1 +1 @@ -Subproject commit 456c68f452da09d8ca84b375faa2b1397713eaba +Subproject commit 05c44fcd18074836e21e1eda9fc02b3a4a1529b5 diff --git a/3rdparty/yaml-cpp/yaml-cpp.vcxproj b/3rdparty/yaml-cpp/yaml-cpp.vcxproj index b1b732727c..4d10c90d7e 100644 --- a/3rdparty/yaml-cpp/yaml-cpp.vcxproj +++ b/3rdparty/yaml-cpp/yaml-cpp.vcxproj @@ -76,6 +76,7 @@ + diff --git a/3rdparty/yaml-cpp/yaml-cpp.vcxproj.filters b/3rdparty/yaml-cpp/yaml-cpp.vcxproj.filters index 60c75fa23e..f4b553ad72 100644 --- a/3rdparty/yaml-cpp/yaml-cpp.vcxproj.filters +++ b/3rdparty/yaml-cpp/yaml-cpp.vcxproj.filters @@ -94,5 +94,8 @@ Source Files + + Source Files + \ No newline at end of file From 365b2939f60a60d87050e3d852d77a2f3ff05819 Mon Sep 17 00:00:00 2001 From: Paul Plant Date: Tue, 3 Feb 2026 18:59:26 -0800 Subject: [PATCH 28/69] Updated Fedora build dependencies list --- BUILDING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BUILDING.md b/BUILDING.md index b31b3fee4e..597621e810 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -95,7 +95,7 @@ sudo apt-get install cmake #### Fedora - sudo dnf install alsa-lib-devel cmake ninja-build glew glew-devel libatomic libevdev-devel libudev-devel openal-devel qt6-qtbase-devel qt6-qtbase-private-devel vulkan-devel pipewire-jack-audio-connection-kit-devel qt6-qtmultimedia-devel qt6-qtsvg-devel llvm-devel + sudo dnf install alsa-lib-devel cmake ninja-build glew glew-devel libatomic libevdev-devel libudev-devel openal-soft-devel qt6-qtbase-devel qt6-qtbase-private-devel vulkan-devel pipewire-jack-audio-connection-kit-devel qt6-qtmultimedia-devel qt6-qtsvg-devel llvm-devel libcurl-devel #### OpenSUSE From 9290422feaa14cb50a867521f0f1fd88ca78b234 Mon Sep 17 00:00:00 2001 From: Ani Date: Sun, 15 Feb 2026 09:25:39 +0100 Subject: [PATCH 29/69] Update 7zip to 26.00 --- 3rdparty/7zip/7zip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/7zip/7zip b/3rdparty/7zip/7zip index 5e96a82794..839151eaaa 160000 --- a/3rdparty/7zip/7zip +++ b/3rdparty/7zip/7zip @@ -1 +1 @@ -Subproject commit 5e96a8279489832924056b1fa82f29d5837c9469 +Subproject commit 839151eaaad24771892afaae6bac690e31e58384 From 66c7dd1381c2e09aa3d63362da5ce438c9a91247 Mon Sep 17 00:00:00 2001 From: Ani Date: Sun, 15 Feb 2026 09:26:11 +0100 Subject: [PATCH 30/69] Update FAudio to 26.02 --- 3rdparty/FAudio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/FAudio b/3rdparty/FAudio index 633bdb772a..e67d761ead 160000 --- a/3rdparty/FAudio +++ b/3rdparty/FAudio @@ -1 +1 @@ -Subproject commit 633bdb772a593104414b4b103ec752567d57c3c1 +Subproject commit e67d761ead486de3e69fa11705456bf94df734ca From 8dfd7e126e3506dbd7cc0b50d4e8a01e47d16ff0 Mon Sep 17 00:00:00 2001 From: Ani Date: Sun, 15 Feb 2026 09:27:11 +0100 Subject: [PATCH 31/69] Update glext to 2026.01.26 --- 3rdparty/GL/glext.h | 125 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 2 deletions(-) diff --git a/3rdparty/GL/glext.h b/3rdparty/GL/glext.h index 276a962a96..16c26be10f 100644 --- a/3rdparty/GL/glext.h +++ b/3rdparty/GL/glext.h @@ -6,7 +6,7 @@ extern "C" { #endif /* -** Copyright 2013-2020 The Khronos Group Inc. +** Copyright 2013-2026 The Khronos Group Inc. ** SPDX-License-Identifier: MIT ** ** This header is generated from the Khronos OpenGL / OpenGL ES XML @@ -32,7 +32,7 @@ extern "C" { #define GLAPI extern #endif -#define GL_GLEXT_VERSION 20250203 +#define GL_GLEXT_VERSION 20260126 #include @@ -7358,6 +7358,47 @@ GLAPI void APIENTRY glFogCoordPointerEXT (GLenum type, GLsizei stride, const voi #endif #endif /* GL_EXT_fog_coord */ +#ifndef GL_EXT_fragment_shading_rate +#define GL_EXT_fragment_shading_rate 1 +#define GL_SHADING_RATE_1X1_PIXELS_EXT 0x96A6 +#define GL_SHADING_RATE_1X2_PIXELS_EXT 0x96A7 +#define GL_SHADING_RATE_2X1_PIXELS_EXT 0x96A8 +#define GL_SHADING_RATE_2X2_PIXELS_EXT 0x96A9 +#define GL_SHADING_RATE_1X4_PIXELS_EXT 0x96AA +#define GL_SHADING_RATE_4X1_PIXELS_EXT 0x96AB +#define GL_SHADING_RATE_4X2_PIXELS_EXT 0x96AC +#define GL_SHADING_RATE_2X4_PIXELS_EXT 0x96AD +#define GL_SHADING_RATE_4X4_PIXELS_EXT 0x96AE +#define GL_SHADING_RATE_EXT 0x96D0 +#define GL_SHADING_RATE_ATTACHMENT_EXT 0x96D1 +#define GL_FRAGMENT_SHADING_RATE_COMBINER_OP_KEEP_EXT 0x96D2 +#define GL_FRAGMENT_SHADING_RATE_COMBINER_OP_REPLACE_EXT 0x96D3 +#define GL_FRAGMENT_SHADING_RATE_COMBINER_OP_MIN_EXT 0x96D4 +#define GL_FRAGMENT_SHADING_RATE_COMBINER_OP_MAX_EXT 0x96D5 +#define GL_FRAGMENT_SHADING_RATE_COMBINER_OP_MUL_EXT 0x96D6 +#define GL_MIN_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_WIDTH_EXT 0x96D7 +#define GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_WIDTH_EXT 0x96D8 +#define GL_MIN_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_HEIGHT_EXT 0x96D9 +#define GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_HEIGHT_EXT 0x96DA +#define GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_TEXEL_ASPECT_RATIO_EXT 0x96DB +#define GL_MAX_FRAGMENT_SHADING_RATE_ATTACHMENT_LAYERS_EXT 0x96DC +#define GL_FRAGMENT_SHADING_RATE_WITH_SHADER_DEPTH_STENCIL_WRITES_SUPPORTED_EXT 0x96DD +#define GL_FRAGMENT_SHADING_RATE_WITH_SAMPLE_MASK_SUPPORTED_EXT 0x96DE +#define GL_FRAGMENT_SHADING_RATE_ATTACHMENT_WITH_DEFAULT_FRAMEBUFFER_SUPPORTED_EXT 0x96DF +#define GL_FRAGMENT_SHADING_RATE_NON_TRIVIAL_COMBINERS_SUPPORTED_EXT 0x8F6F +#define GL_FRAGMENT_SHADING_RATE_PRIMITIVE_RATE_WITH_MULTI_VIEWPORT_SUPPORTED_EXT 0x9780 +typedef void (APIENTRYP PFNGLGETFRAGMENTSHADINGRATESEXTPROC) (GLsizei samples, GLsizei maxCount, GLsizei *count, GLenum *shadingRates); +typedef void (APIENTRYP PFNGLSHADINGRATEEXTPROC) (GLenum rate); +typedef void (APIENTRYP PFNGLSHADINGRATECOMBINEROPSEXTPROC) (GLenum combinerOp0, GLenum combinerOp1); +typedef void (APIENTRYP PFNGLFRAMEBUFFERSHADINGRATEEXTPROC) (GLenum target, GLenum attachment, GLuint texture, GLint baseLayer, GLsizei numLayers, GLsizei texelWidth, GLsizei texelHeight); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glGetFragmentShadingRatesEXT (GLsizei samples, GLsizei maxCount, GLsizei *count, GLenum *shadingRates); +GLAPI void APIENTRY glShadingRateEXT (GLenum rate); +GLAPI void APIENTRY glShadingRateCombinerOpsEXT (GLenum combinerOp0, GLenum combinerOp1); +GLAPI void APIENTRY glFramebufferShadingRateEXT (GLenum target, GLenum attachment, GLuint texture, GLint baseLayer, GLsizei numLayers, GLsizei texelWidth, GLsizei texelHeight); +#endif +#endif /* GL_EXT_fragment_shading_rate */ + #ifndef GL_EXT_framebuffer_blit #define GL_EXT_framebuffer_blit 1 #define GL_READ_FRAMEBUFFER_EXT 0x8CA8 @@ -7816,6 +7857,86 @@ GLAPI void APIENTRY glImportMemoryWin32NameEXT (GLuint memory, GLuint64 size, GL #endif #endif /* GL_EXT_memory_object_win32 */ +#ifndef GL_EXT_mesh_shader +#define GL_EXT_mesh_shader 1 +#define GL_MESH_SHADER_EXT 0x9559 +#define GL_TASK_SHADER_EXT 0x955A +#define GL_MAX_MESH_UNIFORM_BLOCKS_EXT 0x8E60 +#define GL_MAX_MESH_TEXTURE_IMAGE_UNITS_EXT 0x8E61 +#define GL_MAX_MESH_IMAGE_UNIFORMS_EXT 0x8E62 +#define GL_MAX_MESH_UNIFORM_COMPONENTS_EXT 0x8E63 +#define GL_MAX_MESH_ATOMIC_COUNTER_BUFFERS_EXT 0x8E64 +#define GL_MAX_MESH_ATOMIC_COUNTERS_EXT 0x8E65 +#define GL_MAX_MESH_SHADER_STORAGE_BLOCKS_EXT 0x8E66 +#define GL_MAX_COMBINED_MESH_UNIFORM_COMPONENTS_EXT 0x8E67 +#define GL_MAX_TASK_UNIFORM_BLOCKS_EXT 0x8E68 +#define GL_MAX_TASK_TEXTURE_IMAGE_UNITS_EXT 0x8E69 +#define GL_MAX_TASK_IMAGE_UNIFORMS_EXT 0x8E6A +#define GL_MAX_TASK_UNIFORM_COMPONENTS_EXT 0x8E6B +#define GL_MAX_TASK_ATOMIC_COUNTER_BUFFERS_EXT 0x8E6C +#define GL_MAX_TASK_ATOMIC_COUNTERS_EXT 0x8E6D +#define GL_MAX_TASK_SHADER_STORAGE_BLOCKS_EXT 0x8E6E +#define GL_MAX_COMBINED_TASK_UNIFORM_COMPONENTS_EXT 0x8E6F +#define GL_MAX_TASK_WORK_GROUP_TOTAL_COUNT_EXT 0x9740 +#define GL_MAX_MESH_WORK_GROUP_TOTAL_COUNT_EXT 0x9741 +#define GL_MAX_MESH_WORK_GROUP_INVOCATIONS_EXT 0x9757 +#define GL_MAX_TASK_WORK_GROUP_INVOCATIONS_EXT 0x9759 +#define GL_MAX_TASK_PAYLOAD_SIZE_EXT 0x9742 +#define GL_MAX_TASK_SHARED_MEMORY_SIZE_EXT 0x9743 +#define GL_MAX_MESH_SHARED_MEMORY_SIZE_EXT 0x9744 +#define GL_MAX_TASK_PAYLOAD_AND_SHARED_MEMORY_SIZE_EXT 0x9745 +#define GL_MAX_MESH_PAYLOAD_AND_SHARED_MEMORY_SIZE_EXT 0x9746 +#define GL_MAX_MESH_OUTPUT_MEMORY_SIZE_EXT 0x9747 +#define GL_MAX_MESH_PAYLOAD_AND_OUTPUT_MEMORY_SIZE_EXT 0x9748 +#define GL_MAX_MESH_OUTPUT_VERTICES_EXT 0x9538 +#define GL_MAX_MESH_OUTPUT_PRIMITIVES_EXT 0x9756 +#define GL_MAX_MESH_OUTPUT_COMPONENTS_EXT 0x9749 +#define GL_MAX_MESH_OUTPUT_LAYERS_EXT 0x974A +#define GL_MAX_MESH_MULTIVIEW_VIEW_COUNT_EXT 0x9557 +#define GL_MESH_OUTPUT_PER_VERTEX_GRANULARITY_EXT 0x92DF +#define GL_MESH_OUTPUT_PER_PRIMITIVE_GRANULARITY_EXT 0x9543 +#define GL_MAX_PREFERRED_TASK_WORK_GROUP_INVOCATIONS_EXT 0x974B +#define GL_MAX_PREFERRED_MESH_WORK_GROUP_INVOCATIONS_EXT 0x974C +#define GL_MESH_PREFERS_LOCAL_INVOCATION_VERTEX_OUTPUT_EXT 0x974D +#define GL_MESH_PREFERS_LOCAL_INVOCATION_PRIMITIVE_OUTPUT_EXT 0x974E +#define GL_MESH_PREFERS_COMPACT_VERTEX_OUTPUT_EXT 0x974F +#define GL_MESH_PREFERS_COMPACT_PRIMITIVE_OUTPUT_EXT 0x9750 +#define GL_MAX_TASK_WORK_GROUP_COUNT_EXT 0x9751 +#define GL_MAX_MESH_WORK_GROUP_COUNT_EXT 0x9752 +#define GL_MAX_MESH_WORK_GROUP_SIZE_EXT 0x9758 +#define GL_MAX_TASK_WORK_GROUP_SIZE_EXT 0x975A +#define GL_MESH_WORK_GROUP_SIZE_EXT 0x953E +#define GL_TASK_WORK_GROUP_SIZE_EXT 0x953F +#define GL_MESH_VERTICES_OUT_EXT 0x9579 +#define GL_MESH_PRIMITIVES_OUT_EXT 0x957A +#define GL_MESH_OUTPUT_TYPE_EXT 0x957B +#define GL_UNIFORM_BLOCK_REFERENCED_BY_MESH_SHADER_EXT 0x959C +#define GL_UNIFORM_BLOCK_REFERENCED_BY_TASK_SHADER_EXT 0x959D +#define GL_REFERENCED_BY_MESH_SHADER_EXT 0x95A0 +#define GL_REFERENCED_BY_TASK_SHADER_EXT 0x95A1 +#define GL_TASK_SHADER_INVOCATIONS_EXT 0x9753 +#define GL_MESH_SHADER_INVOCATIONS_EXT 0x9754 +#define GL_MESH_PRIMITIVES_GENERATED_EXT 0x9755 +#define GL_MESH_SHADER_BIT_EXT 0x00000040 +#define GL_TASK_SHADER_BIT_EXT 0x00000080 +#define GL_MESH_SUBROUTINE_EXT 0x957C +#define GL_TASK_SUBROUTINE_EXT 0x957D +#define GL_MESH_SUBROUTINE_UNIFORM_EXT 0x957E +#define GL_TASK_SUBROUTINE_UNIFORM_EXT 0x957F +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_MESH_SHADER_EXT 0x959E +#define GL_ATOMIC_COUNTER_BUFFER_REFERENCED_BY_TASK_SHADER_EXT 0x959F +typedef void (APIENTRYP PFNGLDRAWMESHTASKSEXTPROC) (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z); +typedef void (APIENTRYP PFNGLDRAWMESHTASKSINDIRECTEXTPROC) (GLintptr indirect); +typedef void (APIENTRYP PFNGLMULTIDRAWMESHTASKSINDIRECTEXTPROC) (GLintptr indirect, GLsizei drawcount, GLsizei stride); +typedef void (APIENTRYP PFNGLMULTIDRAWMESHTASKSINDIRECTCOUNTEXTPROC) (GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +#ifdef GL_GLEXT_PROTOTYPES +GLAPI void APIENTRY glDrawMeshTasksEXT (GLuint num_groups_x, GLuint num_groups_y, GLuint num_groups_z); +GLAPI void APIENTRY glDrawMeshTasksIndirectEXT (GLintptr indirect); +GLAPI void APIENTRY glMultiDrawMeshTasksIndirectEXT (GLintptr indirect, GLsizei drawcount, GLsizei stride); +GLAPI void APIENTRY glMultiDrawMeshTasksIndirectCountEXT (GLintptr indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride); +#endif +#endif /* GL_EXT_mesh_shader */ + #ifndef GL_EXT_misc_attribute #define GL_EXT_misc_attribute 1 #endif /* GL_EXT_misc_attribute */ From 1e63088b3e0ee3c2f9a863d1f233ce80149c2aa4 Mon Sep 17 00:00:00 2001 From: Ani Date: Sun, 15 Feb 2026 09:31:07 +0100 Subject: [PATCH 32/69] Update cubeb to 2026-01-22 --- 3rdparty/cubeb/cubeb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/cubeb/cubeb b/3rdparty/cubeb/cubeb index e495bee4cd..484857522c 160000 --- a/3rdparty/cubeb/cubeb +++ b/3rdparty/cubeb/cubeb @@ -1 +1 @@ -Subproject commit e495bee4cd630c9f99907a764e16edba37a4b564 +Subproject commit 484857522c73318c06f18ba0a3e17525fa98c608 From dd490fc7255fc657b8612fa31c9f45af2d50cf84 Mon Sep 17 00:00:00 2001 From: Ani Date: Sun, 15 Feb 2026 09:33:20 +0100 Subject: [PATCH 33/69] Update libpng to 1.6.55 --- 3rdparty/libpng/libpng | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/libpng/libpng b/3rdparty/libpng/libpng index 02f2b4f469..c3e304954a 160000 --- a/3rdparty/libpng/libpng +++ b/3rdparty/libpng/libpng @@ -1 +1 @@ -Subproject commit 02f2b4f4699f0ef9111a6534f093b53732df4452 +Subproject commit c3e304954a9cfd154bc0dfbfea2b01cd61d6546d From 3df370a7e62cec29a7516b24d826006ec2e6558e Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 10 Feb 2026 01:41:57 +0300 Subject: [PATCH 34/69] rsx: Value remapping can only happen to a texel lane once per sample - Avoids double operation if for example BX2 and SNORM/SEXT are active concurrently --- .../RSXProg/RSXFragmentTextureOps.glsl | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl index f0457f0034..b95105e410 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl @@ -210,25 +210,29 @@ vec4 _process_texel(in vec4 rgba, const in uint control_bits) uvec4 mask; vec4 convert; - uint op_mask = control_bits & uint(SIGN_EXPAND_MASK); - if (op_mask != 0u) - { - // Expand to signed normalized by decompressing the signal - mask = uvec4(op_mask) & uvec4(EXPAND_R_MASK, EXPAND_G_MASK, EXPAND_B_MASK, EXPAND_A_MASK); - convert = (rgba * 2.f - 1.f); - rgba = _select(rgba, convert, notEqual(mask, uvec4(0))); - } + uint op_mask = control_bits & uint(SEXT_MASK); + uint ch_mask = 0xFu; - op_mask = control_bits & uint(SEXT_MASK); if (op_mask != 0u) { // Sign-extend the input signal mask = uvec4(op_mask) & uvec4(SEXT_R_MASK, SEXT_G_MASK, SEXT_B_MASK, SEXT_A_MASK); convert = _sext_unorm8x4(rgba); rgba = _select(rgba, convert, notEqual(mask, uvec4(0))); + ch_mask &= ~(op_mask >> SEXT_R_BIT); } - op_mask = control_bits & uint(GAMMA_CTRL_MASK); + op_mask = control_bits & uint(SIGN_EXPAND_MASK) & (ch_mask << EXPAND_R_BIT); + if (op_mask != 0u) + { + // Expand to signed normalized by decompressing the signal + mask = uvec4(op_mask) & uvec4(EXPAND_R_MASK, EXPAND_G_MASK, EXPAND_B_MASK, EXPAND_A_MASK); + convert = (rgba * 2.f - 1.f); + rgba = _select(rgba, convert, notEqual(mask, uvec4(0))); + ch_mask &= ~(op_mask >> EXPAND_R_BIT); + } + + op_mask = control_bits & uint(GAMMA_CTRL_MASK) & (ch_mask << GAMMA_R_BIT); if (op_mask != 0u) { // Gamma correction From 6b272ed5636410dec1c42310bc7cdceb62b8d17d Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 10 Feb 2026 01:42:47 +0300 Subject: [PATCH 35/69] rsx: Re-work value remapping capabilities to match real hardware - Drop heurestics and use what real hardware is doing instead --- rpcs3/Emu/RSX/Common/TextureUtils.cpp | 41 +++++++++++++++++++++++---- rpcs3/Emu/RSX/Common/TextureUtils.h | 18 +++++++++++- rpcs3/Emu/RSX/RSXThread.cpp | 28 ++++++++++++++---- 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/rpcs3/Emu/RSX/Common/TextureUtils.cpp b/rpcs3/Emu/RSX/Common/TextureUtils.cpp index 96f87111ff..5fda3c6131 100644 --- a/rpcs3/Emu/RSX/Common/TextureUtils.cpp +++ b/rpcs3/Emu/RSX/Common/TextureUtils.cpp @@ -1200,27 +1200,56 @@ namespace rsx fmt::throw_exception("Unknown format 0x%x", texture_format); } - bool is_int8_remapped_format(u32 format) + rsx::flags32_t get_format_features(u32 texture_format) { - switch (format) + switch (texture_format) { + case CELL_GCM_TEXTURE_B8: + case CELL_GCM_TEXTURE_A1R5G5B5: + case CELL_GCM_TEXTURE_A4R4G4B4: + case CELL_GCM_TEXTURE_R5G6B5: + case CELL_GCM_TEXTURE_A8R8G8B8: + case CELL_GCM_TEXTURE_COMPRESSED_DXT1: + case CELL_GCM_TEXTURE_COMPRESSED_DXT23: + case CELL_GCM_TEXTURE_COMPRESSED_DXT45: + case CELL_GCM_TEXTURE_G8B8: + case CELL_GCM_TEXTURE_COMPRESSED_B8R8_G8R8: + case CELL_GCM_TEXTURE_COMPRESSED_R8B8_R8G8: + case CELL_GCM_TEXTURE_R6G5B5: + case CELL_GCM_TEXTURE_R5G5B5A1: + case CELL_GCM_TEXTURE_D1R5G5B5: + case CELL_GCM_TEXTURE_D8R8G8B8: + // Base texture formats - everything is supported + return RSX_FORMAT_FEATURE_SIGNED_COMPONENTS | RSX_FORMAT_FEATURE_GAMMA_CORRECTION | RSX_FORMAT_FEATURE_BIASED_NORMALIZATION; + case CELL_GCM_TEXTURE_DEPTH24_D8: case CELL_GCM_TEXTURE_DEPTH24_D8_FLOAT: case CELL_GCM_TEXTURE_DEPTH16: case CELL_GCM_TEXTURE_DEPTH16_FLOAT: + // Depth textures will hang the hardware if BX2 or GAMMA is active. ARGB8_SIGNED has no impact. + return 0; + case CELL_GCM_TEXTURE_X16: case CELL_GCM_TEXTURE_Y16_X16: + // X16 | Y16 - GAMMA causes hangs. ARGB8_SIGNED is ignored. UNSIGNED_REMAP=BIASED works. + return RSX_FORMAT_FEATURE_BIASED_NORMALIZATION; + case CELL_GCM_TEXTURE_COMPRESSED_HILO8: + // GAMMA causes GPU hangs. ARGB8_SIGNED is ignored. UNSIGNED_REMAP=BIASED works. + return RSX_FORMAT_FEATURE_BIASED_NORMALIZATION; + case CELL_GCM_TEXTURE_COMPRESSED_HILO_S8: + // GAMMA causes hangs. Other flags ignored. + return 0; + case CELL_GCM_TEXTURE_W16_Z16_Y16_X16_FLOAT: case CELL_GCM_TEXTURE_W32_Z32_Y32_X32_FLOAT: case CELL_GCM_TEXTURE_X32_FLOAT: case CELL_GCM_TEXTURE_Y16_X16_FLOAT: - // NOTE: Special data formats (XY, HILO, DEPTH) are not RGB formats - return false; - default: - return true; + // Floating point textures. Nothing works. + return 0; } + fmt::throw_exception("Unknown format 0x%x", texture_format); } /** diff --git a/rpcs3/Emu/RSX/Common/TextureUtils.h b/rpcs3/Emu/RSX/Common/TextureUtils.h index cc40305721..3928e22874 100644 --- a/rpcs3/Emu/RSX/Common/TextureUtils.h +++ b/rpcs3/Emu/RSX/Common/TextureUtils.h @@ -9,6 +9,8 @@ namespace rsx { + using flags32_t = u32; + enum texture_upload_context : u32 { shader_read = 1, @@ -125,6 +127,15 @@ namespace rsx using namespace format_class_; + enum format_features : u8 + { + RSX_FORMAT_FEATURE_SIGNED_COMPONENTS = (1 << 0), + RSX_FORMAT_FEATURE_BIASED_NORMALIZATION = (1 << 1), + RSX_FORMAT_FEATURE_GAMMA_CORRECTION = (1 << 2), + }; + + using enum format_features; + // Sampled image descriptor class sampled_image_descriptor_base { @@ -257,7 +268,12 @@ namespace rsx u8 get_format_sample_count(rsx::surface_antialiasing antialias); u32 get_max_depth_value(rsx::surface_depth_format2 format); bool is_depth_stencil_format(rsx::surface_depth_format2 format); - bool is_int8_remapped_format(u32 format); // Returns true if the format is treated as INT8 by the RSX remapper. + + /** + * Format feature support. There is not simple format to determine what is supported here, results are from hw tests + * Returns a bitmask of supported features. + */ + rsx::flags32_t get_format_features(u32 texture_format); /** * Returns number of texel rows encoded in one pitch-length line of bytes diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 44e8d8fb3f..447e32db9b 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -2321,17 +2321,35 @@ namespace rsx } } - if (rsx::is_int8_remapped_format(format)) + if (const auto format_features = rsx::get_format_features(format); format_features != 0) { // Special operations applied to 8-bit formats such as gamma correction and sign conversion // NOTE: The unsigned_remap=bias flag being set flags the texture as being compressed normal (2n-1 / BX2) (UE3) // NOTE: The ARGB8_signed flag means to reinterpret the raw bytes as signed. This is different than unsigned_remap=bias which does range decompression. // This is a separate method of setting the format to signed mode without doing so per-channel - // Precedence = SNORM > GAMMA > UNSIGNED_REMAP (See Resistance 3 for GAMMA/BX2 relationship, UE3 for BX2 effect) + // Precedence = SNORM > GAMMA > UNSIGNED_REMAP/BX2 + // Games using mixed flags: (See Resistance 3 for GAMMA/BX2 relationship, UE3 for BX2 effect) + u32 argb8_signed = 0; + u32 unsigned_remap = 0; + u32 gamma = 0; + + if (format_features & RSX_FORMAT_FEATURE_SIGNED_COMPONENTS) + { + argb8_signed = tex.argb_signed(); + } + + if (format_features & RSX_FORMAT_FEATURE_GAMMA_CORRECTION) + { + gamma = tex.gamma() & ~(argb8_signed); + } + + if (format_features & RSX_FORMAT_FEATURE_BIASED_NORMALIZATION) + { + // The renormalization flag applies to all channels + unsigned_remap = (tex.unsigned_remap() == CELL_GCM_TEXTURE_UNSIGNED_REMAP_NORMAL) ? 0u : 0xF; + unsigned_remap &= ~(argb8_signed | gamma); + } - const u32 argb8_signed = tex.argb_signed(); // _SNROM - const u32 gamma = tex.gamma() & ~argb8_signed; // _SRGB - const u32 unsigned_remap = (tex.unsigned_remap() == CELL_GCM_TEXTURE_UNSIGNED_REMAP_NORMAL)? 0u : (~(gamma | argb8_signed) & 0xF); // _BX2 u32 argb8_convert = gamma; // The options are mutually exclusive From e29832fa0062a10cfa9fbb6e3ed8f9cbb78dc10e Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 10 Feb 2026 01:56:26 +0300 Subject: [PATCH 36/69] fixup [first commit] --- .../RSXProg/RSXFragmentTextureOps.glsl | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl index b95105e410..2f03cb7730 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl @@ -219,26 +219,26 @@ vec4 _process_texel(in vec4 rgba, const in uint control_bits) mask = uvec4(op_mask) & uvec4(SEXT_R_MASK, SEXT_G_MASK, SEXT_B_MASK, SEXT_A_MASK); convert = _sext_unorm8x4(rgba); rgba = _select(rgba, convert, notEqual(mask, uvec4(0))); - ch_mask &= ~(op_mask >> SEXT_R_BIT); + ch_mask &= ~(op_mask >> SEXT_A_BIT); } - op_mask = control_bits & uint(SIGN_EXPAND_MASK) & (ch_mask << EXPAND_R_BIT); + op_mask = control_bits & uint(GAMMA_CTRL_MASK) & (ch_mask << GAMMA_A_BIT); + if (op_mask != 0u) + { + // Gamma correction + mask = uvec4(op_mask) & uvec4(GAMMA_R_MASK, GAMMA_G_MASK, GAMMA_B_MASK, GAMMA_A_MASK); + convert = srgb_to_linear(rgba); + rgba = _select(rgba, convert, notEqual(mask, uvec4(0))); + ch_mask &= ~(op_mask >> GAMMA_A_BIT); + } + + op_mask = control_bits & uint(SIGN_EXPAND_MASK) & (ch_mask << EXPAND_A_BIT); if (op_mask != 0u) { // Expand to signed normalized by decompressing the signal mask = uvec4(op_mask) & uvec4(EXPAND_R_MASK, EXPAND_G_MASK, EXPAND_B_MASK, EXPAND_A_MASK); convert = (rgba * 2.f - 1.f); rgba = _select(rgba, convert, notEqual(mask, uvec4(0))); - ch_mask &= ~(op_mask >> EXPAND_R_BIT); - } - - op_mask = control_bits & uint(GAMMA_CTRL_MASK) & (ch_mask << GAMMA_R_BIT); - if (op_mask != 0u) - { - // Gamma correction - mask = uvec4(op_mask) & uvec4(GAMMA_R_MASK, GAMMA_G_MASK, GAMMA_B_MASK, GAMMA_A_MASK); - convert = srgb_to_linear(rgba); - return _select(rgba, convert, notEqual(mask, uvec4(0))); } return rgba; From d3cd3e96501c6f9b7ceba8c39b81faf46eb38af0 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 10 Feb 2026 01:58:48 +0300 Subject: [PATCH 37/69] rsx: Remove value remapping shuffle --- rpcs3/Emu/RSX/RSXThread.cpp | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 447e32db9b..86edb7be9d 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -2357,36 +2357,9 @@ namespace rsx ensure((argb8_signed & unsigned_remap) == 0); ensure((gamma & unsigned_remap) == 0); - // Helper function to apply a per-channel mask based on an input mask - const auto apply_sign_convert_mask = [&](u32 mask, u32 bit_offset) - { - // TODO: Use actual remap mask to account for 0 and 1 overrides in default mapping - // TODO: Replace this clusterfuck of texture control with matrix transformation - const auto remap_ctrl = (tex.remap() >> 8) & 0xAA; - if (remap_ctrl == 0xAA) - { - argb8_convert |= (mask & 0xFu) << bit_offset; - return; - } - - if ((remap_ctrl & 0x03) == 0x02) argb8_convert |= (mask & 0x1u) << bit_offset; - if ((remap_ctrl & 0x0C) == 0x08) argb8_convert |= (mask & 0x2u) << bit_offset; - if ((remap_ctrl & 0x30) == 0x20) argb8_convert |= (mask & 0x4u) << bit_offset; - if ((remap_ctrl & 0xC0) == 0x80) argb8_convert |= (mask & 0x8u) << bit_offset; - }; - - if (argb8_signed) - { - // Apply integer sign extension from uint8 to sint8 and renormalize - apply_sign_convert_mask(argb8_signed, texture_control_bits::SEXT_OFFSET); - } - - if (unsigned_remap) - { - // Apply sign expansion, compressed normal-map style (2n - 1) - apply_sign_convert_mask(unsigned_remap, texture_control_bits::EXPAND_OFFSET); - } - + // NOTE: Hardware tests show that remapping bypasses the channel swizzles completely + argb8_convert |= (argb8_signed << texture_control_bits::SEXT_OFFSET); + argb8_convert |= (unsigned_remap << texture_control_bits::EXPAND_OFFSET); texture_control |= argb8_convert; } From 765e72dcb76c235e0d17c6dbb989f1d03b4c2254 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 10 Feb 2026 02:58:51 +0300 Subject: [PATCH 38/69] rsx: Use 'format features' to drive BX2 texture sample remapping on the GPU - Fixes problems with BX2 kicking in when it shouldn't --- .../RSX/Program/FragmentProgramDecompiler.cpp | 2 +- rpcs3/Emu/RSX/Program/GLSLCommon.cpp | 6 ++++- rpcs3/Emu/RSX/Program/GLSLCommon.h | 6 ++++- .../RSXProg/RSXFragmentTextureOps.glsl | 25 ++++++++++++++++--- rpcs3/Emu/RSX/RSXThread.cpp | 2 ++ 5 files changed, 34 insertions(+), 7 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp index ef2029c652..c00cef47e6 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp @@ -1192,7 +1192,7 @@ bool FragmentProgramDecompiler::handle_tex_srb(u32 opcode) if (dst.exp_tex) { properties.has_exp_tex_op = true; - AddCode("_enable_texture_expand();"); + AddCode("_enable_texture_expand($_i);"); } // Shadow proj diff --git a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp index 1421b179fc..8f7cc46e66 100644 --- a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp +++ b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp @@ -360,7 +360,11 @@ namespace glsl { "FILTERED_MAG_BIT", rsx::texture_control_bits::FILTERED_MAG }, { "FILTERED_MIN_BIT", rsx::texture_control_bits::FILTERED_MIN }, { "INT_COORDS_BIT ", rsx::texture_control_bits::UNNORMALIZED_COORDS }, - { "CLAMP_COORDS_BIT", rsx::texture_control_bits::CLAMP_TEXCOORDS_BIT } + { "CLAMP_COORDS_BIT", rsx::texture_control_bits::CLAMP_TEXCOORDS_BIT }, + + { "FORMAT_FEATURE_SIGNED_BIT", rsx::texture_control_bits::FF_SIGNED_BIT }, + { "FORMAT_FEATURE_GAMMA_BIT", rsx::texture_control_bits::FF_GAMMA_BIT }, + { "FORMAT_FEATURE_BIASED_RENORMALIZATION_BIT", rsx::texture_control_bits::FF_BIASED_RENORM_BIT } }); if (props.require_texture_expand) diff --git a/rpcs3/Emu/RSX/Program/GLSLCommon.h b/rpcs3/Emu/RSX/Program/GLSLCommon.h index 942374436e..b417947002 100644 --- a/rpcs3/Emu/RSX/Program/GLSLCommon.h +++ b/rpcs3/Emu/RSX/Program/GLSLCommon.h @@ -37,12 +37,16 @@ namespace rsx WRAP_S, WRAP_T, WRAP_R, + FF_SIGNED_BIT, + FF_BIASED_RENORM_BIT, + FF_GAMMA_BIT, GAMMA_CTRL_MASK = (1 << GAMMA_R) | (1 << GAMMA_G) | (1 << GAMMA_B) | (1 << GAMMA_A), EXPAND_MASK = (1 << EXPAND_R) | (1 << EXPAND_G) | (1 << EXPAND_B) | (1 << EXPAND_A), EXPAND_OFFSET = EXPAND_A, SEXT_MASK = (1 << SEXT_R) | (1 << SEXT_G) | (1 << SEXT_B) | (1 << SEXT_A), - SEXT_OFFSET = SEXT_A + SEXT_OFFSET = SEXT_A, + FORMAT_FEATURES_OFFSET = FF_SIGNED_BIT, }; enum ROP_control_bits : u32 diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl index 2f03cb7730..7e8e88533c 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl @@ -20,13 +20,30 @@ R"( #define SEXT_MASK (SEXT_R_MASK | SEXT_G_MASK | SEXT_B_MASK | SEXT_A_MASK) #define FILTERED_MASK (FILTERED_MAG_BIT | FILTERED_MIN_BIT) +#define FORMAT_FEATURE_SIGNED (1 << FORMAT_FEATURE_SIGNED_BIT) +#define FORMAT_FEATURE_GAMMA (1 << FORMAT_FEATURE_GAMMA_BIT) +#define FORMAT_FEATURE_BIASED_RENORMALIZATION (1 << FORMAT_FEATURE_BIASED_RENORMALIZATION_BIT) +#define FORMAT_FEATURE_MASK (FORMAT_FEATURE_SIGNED | FORMAT_FEATURE_GAMMA | FORMAT_FEATURE_BIASED_RENORMALIZATION) + #ifdef _ENABLE_TEXTURE_EXPAND + // NOTE: BX2 expansion overrides GAMMA correction uint _texture_flag_override = 0; - #define _enable_texture_expand() _texture_flag_override = SIGN_EXPAND_MASK - #define _disable_texture_expand() _texture_flag_override = 0 - #define TEX_FLAGS(index) (TEX_PARAM(index).flags | _texture_flag_override) + uint _texture_flag_erase = 0; + #define _enable_texture_expand(index) \ + do { \ + if (_test_bit(TEX_PARAM(index).flags, FORMAT_FEATURE_BIASED_RENORMALIZATION_BIT)) { \ + _texture_flag_override = SIGN_EXPAND_MASK; \ + _texture_flag_erase = GAMMA_CTRL_MASK; \ + } \ + } while (false) + #define _disable_texture_expand() \ + do { \ + _texture_flag_override = 0; \ + _texture_flag_erase = 0; \ + } while (false) + #define TEX_FLAGS(index) ((TEX_PARAM(index).flags & ~(FORMAT_FEATURE_MASK | _texture_flag_erase)) | _texture_flag_override) #else - #define TEX_FLAGS(index) TEX_PARAM(index).flags + #define TEX_FLAGS(index) (TEX_PARAM(index).flags & ~FORMAT_FEATURE_MASK) #endif #define TEX_NAME(index) tex##index diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 86edb7be9d..984f64afcd 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -2361,6 +2361,8 @@ namespace rsx argb8_convert |= (argb8_signed << texture_control_bits::SEXT_OFFSET); argb8_convert |= (unsigned_remap << texture_control_bits::EXPAND_OFFSET); texture_control |= argb8_convert; + + texture_control |= format_features << texture_control_bits::FORMAT_FEATURES_OFFSET; } current_fragment_program.texture_params[i].control = texture_control; From 3f321c61f379baec2e416ac5abdae973b665d78c Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 10 Feb 2026 03:11:47 +0300 Subject: [PATCH 39/69] rsx: Fix remapping behavior for depth textures (sampled as depth) --- rpcs3/Emu/RSX/Common/TextureUtils.cpp | 3 ++- rpcs3/Emu/RSX/RSXThread.cpp | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/Common/TextureUtils.cpp b/rpcs3/Emu/RSX/Common/TextureUtils.cpp index 5fda3c6131..ddfbb99b9b 100644 --- a/rpcs3/Emu/RSX/Common/TextureUtils.cpp +++ b/rpcs3/Emu/RSX/Common/TextureUtils.cpp @@ -1227,7 +1227,8 @@ namespace rsx case CELL_GCM_TEXTURE_DEPTH16: case CELL_GCM_TEXTURE_DEPTH16_FLOAT: // Depth textures will hang the hardware if BX2 or GAMMA is active. ARGB8_SIGNED has no impact. - return 0; + // UNSIGNED_REMAP=BIASED works on all formats including the float variants. + return RSX_FORMAT_FEATURE_BIASED_NORMALIZATION; case CELL_GCM_TEXTURE_X16: case CELL_GCM_TEXTURE_Y16_X16: diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 984f64afcd..0d9955b4d4 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -2323,7 +2323,6 @@ namespace rsx if (const auto format_features = rsx::get_format_features(format); format_features != 0) { - // Special operations applied to 8-bit formats such as gamma correction and sign conversion // NOTE: The unsigned_remap=bias flag being set flags the texture as being compressed normal (2n-1 / BX2) (UE3) // NOTE: The ARGB8_signed flag means to reinterpret the raw bytes as signed. This is different than unsigned_remap=bias which does range decompression. // This is a separate method of setting the format to signed mode without doing so per-channel From 2f5a66581fc8570b9a770ac5ddf5752fc2b1d3d2 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Thu, 12 Feb 2026 01:23:13 +0300 Subject: [PATCH 40/69] rsx/fp: Fix biased renormalization without BX2 flag - With biased renorm, a value of 0 gives a value slightly less than -1 - Biased renormalization does not have a "clamped" mode unlike sext --- .../GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl index 7e8e88533c..cb89f718bf 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl @@ -29,17 +29,20 @@ R"( // NOTE: BX2 expansion overrides GAMMA correction uint _texture_flag_override = 0; uint _texture_flag_erase = 0; + bool _texture_bx2_active = false; #define _enable_texture_expand(index) \ do { \ if (_test_bit(TEX_PARAM(index).flags, FORMAT_FEATURE_BIASED_RENORMALIZATION_BIT)) { \ _texture_flag_override = SIGN_EXPAND_MASK; \ _texture_flag_erase = GAMMA_CTRL_MASK; \ + _texture_bx2_active = true; \ } \ } while (false) #define _disable_texture_expand() \ do { \ _texture_flag_override = 0; \ _texture_flag_erase = 0; \ + _texture_bx2_active = false; \ } while (false) #define TEX_FLAGS(index) ((TEX_PARAM(index).flags & ~(FORMAT_FEATURE_MASK | _texture_flag_erase)) | _texture_flag_override) #else @@ -200,7 +203,7 @@ vec4 _sext_unorm8x4(const in vec4 x) vec4 _process_texel(in vec4 rgba, const in uint control_bits) { - if (control_bits == 0) + if ((control_bits & ~FORMAT_FEATURE_MASK) == 0u) { return rgba; } @@ -254,7 +257,12 @@ vec4 _process_texel(in vec4 rgba, const in uint control_bits) { // Expand to signed normalized by decompressing the signal mask = uvec4(op_mask) & uvec4(EXPAND_R_MASK, EXPAND_G_MASK, EXPAND_B_MASK, EXPAND_A_MASK); - convert = (rgba * 2.f - 1.f); +#ifdef _ENABLE_TEXTURE_EXPAND + if (_texture_bx2_active) + convert = (rgba * 2.f - 1.f); + else +#endif + convert = (floor(fma(rgba, vec4(255.f), vec4(0.5f))) - 128.f) / 127.f; rgba = _select(rgba, convert, notEqual(mask, uvec4(0))); } From 49029c9b9810ef4118773c1ab108c86a867154a9 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Thu, 12 Feb 2026 02:22:34 +0300 Subject: [PATCH 41/69] rsx: Handle 16-bit format remapping --- rpcs3/Emu/RSX/Common/TextureUtils.cpp | 6 ++-- rpcs3/Emu/RSX/Common/TextureUtils.h | 1 + rpcs3/Emu/RSX/Program/GLSLCommon.cpp | 35 ++++++++++--------- rpcs3/Emu/RSX/Program/GLSLCommon.h | 1 + .../RSXProg/RSXFragmentTextureOps.glsl | 34 +++++++++++++----- 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/rpcs3/Emu/RSX/Common/TextureUtils.cpp b/rpcs3/Emu/RSX/Common/TextureUtils.cpp index ddfbb99b9b..7fe0431baf 100644 --- a/rpcs3/Emu/RSX/Common/TextureUtils.cpp +++ b/rpcs3/Emu/RSX/Common/TextureUtils.cpp @@ -1231,9 +1231,11 @@ namespace rsx return RSX_FORMAT_FEATURE_BIASED_NORMALIZATION; case CELL_GCM_TEXTURE_X16: + // X16 - GAMMA causes hangs. ARGB8_SIGNED is ignored. UNSIGNED_REMAP=BIASED works. + return RSX_FORMAT_FEATURE_BIASED_NORMALIZATION | RSX_FORMAT_FEATURE_16BIT_CHANNELS; case CELL_GCM_TEXTURE_Y16_X16: - // X16 | Y16 - GAMMA causes hangs. ARGB8_SIGNED is ignored. UNSIGNED_REMAP=BIASED works. - return RSX_FORMAT_FEATURE_BIASED_NORMALIZATION; + // X16 | Y16 - GAMMA causes hangs. ARGB8_SIGNED works. UNSIGNED_REMAP=BIASED also works. + return RSX_FORMAT_FEATURE_SIGNED_COMPONENTS | RSX_FORMAT_FEATURE_BIASED_NORMALIZATION | RSX_FORMAT_FEATURE_16BIT_CHANNELS; case CELL_GCM_TEXTURE_COMPRESSED_HILO8: // GAMMA causes GPU hangs. ARGB8_SIGNED is ignored. UNSIGNED_REMAP=BIASED works. diff --git a/rpcs3/Emu/RSX/Common/TextureUtils.h b/rpcs3/Emu/RSX/Common/TextureUtils.h index 3928e22874..65f4ef5c6f 100644 --- a/rpcs3/Emu/RSX/Common/TextureUtils.h +++ b/rpcs3/Emu/RSX/Common/TextureUtils.h @@ -132,6 +132,7 @@ namespace rsx RSX_FORMAT_FEATURE_SIGNED_COMPONENTS = (1 << 0), RSX_FORMAT_FEATURE_BIASED_NORMALIZATION = (1 << 1), RSX_FORMAT_FEATURE_GAMMA_CORRECTION = (1 << 2), + RSX_FORMAT_FEATURE_16BIT_CHANNELS = (1 << 3), // Complements RSX_FORMAT_FEATURE_SIGNED_COMPONENTS }; using enum format_features; diff --git a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp index 8f7cc46e66..ebf73e935a 100644 --- a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp +++ b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp @@ -337,21 +337,21 @@ namespace glsl // Declare special texture control flags program_common::define_glsl_constants(OS, { - { "GAMMA_R_BIT " , rsx::texture_control_bits::GAMMA_R }, - { "GAMMA_G_BIT " , rsx::texture_control_bits::GAMMA_G }, - { "GAMMA_B_BIT " , rsx::texture_control_bits::GAMMA_B }, - { "GAMMA_A_BIT " , rsx::texture_control_bits::GAMMA_A }, - { "EXPAND_R_BIT" , rsx::texture_control_bits::EXPAND_R }, - { "EXPAND_G_BIT" , rsx::texture_control_bits::EXPAND_G }, - { "EXPAND_B_BIT" , rsx::texture_control_bits::EXPAND_B }, - { "EXPAND_A_BIT" , rsx::texture_control_bits::EXPAND_A }, - { "SEXT_R_BIT" , rsx::texture_control_bits::SEXT_R }, - { "SEXT_G_BIT" , rsx::texture_control_bits::SEXT_G }, - { "SEXT_B_BIT" , rsx::texture_control_bits::SEXT_B }, - { "SEXT_A_BIT" , rsx::texture_control_bits::SEXT_A }, - { "WRAP_S_BIT", rsx::texture_control_bits::WRAP_S }, - { "WRAP_T_BIT", rsx::texture_control_bits::WRAP_T }, - { "WRAP_R_BIT", rsx::texture_control_bits::WRAP_R }, + { "GAMMA_R_BIT ", rsx::texture_control_bits::GAMMA_R }, + { "GAMMA_G_BIT ", rsx::texture_control_bits::GAMMA_G }, + { "GAMMA_B_BIT ", rsx::texture_control_bits::GAMMA_B }, + { "GAMMA_A_BIT ", rsx::texture_control_bits::GAMMA_A }, + { "EXPAND_R_BIT", rsx::texture_control_bits::EXPAND_R }, + { "EXPAND_G_BIT", rsx::texture_control_bits::EXPAND_G }, + { "EXPAND_B_BIT", rsx::texture_control_bits::EXPAND_B }, + { "EXPAND_A_BIT", rsx::texture_control_bits::EXPAND_A }, + { "SEXT_R_BIT", rsx::texture_control_bits::SEXT_R }, + { "SEXT_G_BIT", rsx::texture_control_bits::SEXT_G }, + { "SEXT_B_BIT", rsx::texture_control_bits::SEXT_B }, + { "SEXT_A_BIT", rsx::texture_control_bits::SEXT_A }, + { "WRAP_S_BIT", rsx::texture_control_bits::WRAP_S }, + { "WRAP_T_BIT", rsx::texture_control_bits::WRAP_T }, + { "WRAP_R_BIT", rsx::texture_control_bits::WRAP_R }, { "ALPHAKILL ", rsx::texture_control_bits::ALPHAKILL }, { "RENORMALIZE ", rsx::texture_control_bits::RENORMALIZE }, @@ -363,8 +363,9 @@ namespace glsl { "CLAMP_COORDS_BIT", rsx::texture_control_bits::CLAMP_TEXCOORDS_BIT }, { "FORMAT_FEATURE_SIGNED_BIT", rsx::texture_control_bits::FF_SIGNED_BIT }, - { "FORMAT_FEATURE_GAMMA_BIT", rsx::texture_control_bits::FF_GAMMA_BIT }, - { "FORMAT_FEATURE_BIASED_RENORMALIZATION_BIT", rsx::texture_control_bits::FF_BIASED_RENORM_BIT } + { "FORMAT_FEATURE_GAMMA_BIT", rsx::texture_control_bits::FF_GAMMA_BIT }, + { "FORMAT_FEATURE_BIASED_RENORMALIZATION_BIT", rsx::texture_control_bits::FF_BIASED_RENORM_BIT }, + { "FORMAT_FEATURE_16BIT_CHANNELS_BIT", rsx::texture_control_bits::FF_16BIT_CHANNELS_BIT } }); if (props.require_texture_expand) diff --git a/rpcs3/Emu/RSX/Program/GLSLCommon.h b/rpcs3/Emu/RSX/Program/GLSLCommon.h index b417947002..ae22464f12 100644 --- a/rpcs3/Emu/RSX/Program/GLSLCommon.h +++ b/rpcs3/Emu/RSX/Program/GLSLCommon.h @@ -40,6 +40,7 @@ namespace rsx FF_SIGNED_BIT, FF_BIASED_RENORM_BIT, FF_GAMMA_BIT, + FF_16BIT_CHANNELS_BIT, GAMMA_CTRL_MASK = (1 << GAMMA_R) | (1 << GAMMA_G) | (1 << GAMMA_B) | (1 << GAMMA_A), EXPAND_MASK = (1 << EXPAND_R) | (1 << EXPAND_G) | (1 << EXPAND_B) | (1 << EXPAND_A), diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl index cb89f718bf..a59e8ae3a1 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl @@ -23,7 +23,8 @@ R"( #define FORMAT_FEATURE_SIGNED (1 << FORMAT_FEATURE_SIGNED_BIT) #define FORMAT_FEATURE_GAMMA (1 << FORMAT_FEATURE_GAMMA_BIT) #define FORMAT_FEATURE_BIASED_RENORMALIZATION (1 << FORMAT_FEATURE_BIASED_RENORMALIZATION_BIT) -#define FORMAT_FEATURE_MASK (FORMAT_FEATURE_SIGNED | FORMAT_FEATURE_GAMMA | FORMAT_FEATURE_BIASED_RENORMALIZATION) +#define FORMAT_FEATURE_16BIT_CHANNELS (1 << FORMAT_FEATURE_16BIT_CHANNELS_BIT) +#define FORMAT_FEATURE_MASK (FORMAT_FEATURE_SIGNED | FORMAT_FEATURE_GAMMA | FORMAT_FEATURE_BIASED_RENORMALIZATION | FORMAT_FEATURE_16BIT_CHANNELS) #ifdef _ENABLE_TEXTURE_EXPAND // NOTE: BX2 expansion overrides GAMMA correction @@ -44,9 +45,9 @@ R"( _texture_flag_erase = 0; \ _texture_bx2_active = false; \ } while (false) - #define TEX_FLAGS(index) ((TEX_PARAM(index).flags & ~(FORMAT_FEATURE_MASK | _texture_flag_erase)) | _texture_flag_override) + #define TEX_FLAGS(index) ((TEX_PARAM(index).flags & ~(_texture_flag_erase)) | _texture_flag_override) #else - #define TEX_FLAGS(index) (TEX_PARAM(index).flags & ~FORMAT_FEATURE_MASK) + #define TEX_FLAGS(index) (TEX_PARAM(index).flags) #endif #define TEX_NAME(index) tex##index @@ -195,10 +196,19 @@ vec4 _texcoord_xform_shadow(const in vec4 coord4, const in sampler_info params) vec4 _sext_unorm8x4(const in vec4 x) { // TODO: Handle clamped sign-extension - const vec4 bits = floor(fma(x, vec4(255.f), vec4(0.5f))); - const bvec4 sign_check = lessThan(bits, vec4(128.f)); - const vec4 ret = _select(bits - 256.f, bits, sign_check); - return ret / 127.f; + const uint shift = 32 - 8; // sext 8-bit value into 32-bit container + const uvec4 ubits = uvec4(floor(fma(x, vec4(255.f), vec4(0.5f)))); + const ivec4 ibits = ivec4(ubits << shift); + return (ibits >> shift) / 127.f; +} + +vec4 _sext_unorm16x4(const in vec4 x) +{ + // TODO: Handle clamped sign-extension + const uint shift = 32 - 16; // sext 16-bit value into 32-bit container + const uvec4 ubits = uvec4(floor(fma(x, vec4(65535.f), vec4(0.5f)))); + const ivec4 ibits = ivec4(ubits << shift); + return (ibits >> shift) / 32767.f; } vec4 _process_texel(in vec4 rgba, const in uint control_bits) @@ -237,7 +247,10 @@ vec4 _process_texel(in vec4 rgba, const in uint control_bits) { // Sign-extend the input signal mask = uvec4(op_mask) & uvec4(SEXT_R_MASK, SEXT_G_MASK, SEXT_B_MASK, SEXT_A_MASK); - convert = _sext_unorm8x4(rgba); + if (_test_bit(control_bits, FORMAT_FEATURE_16BIT_CHANNELS_BIT)) + convert = _sext_unorm16x4(rgba); + else + convert = _sext_unorm8x4(rgba); rgba = _select(rgba, convert, notEqual(mask, uvec4(0))); ch_mask &= ~(op_mask >> SEXT_A_BIT); } @@ -262,7 +275,10 @@ vec4 _process_texel(in vec4 rgba, const in uint control_bits) convert = (rgba * 2.f - 1.f); else #endif - convert = (floor(fma(rgba, vec4(255.f), vec4(0.5f))) - 128.f) / 127.f; + if (_test_bit(control_bits, FORMAT_FEATURE_16BIT_CHANNELS_BIT)) + convert = (floor(fma(rgba, vec4(65535.f), vec4(0.5f))) - 32768.f) / 32767.f; + else + convert = (floor(fma(rgba, vec4(255.f), vec4(0.5f))) - 128.f) / 127.f; rgba = _select(rgba, convert, notEqual(mask, uvec4(0))); } From ed3d2a096c5545d9a99ab47582eaec899dbbf8f3 Mon Sep 17 00:00:00 2001 From: Ani Date: Sun, 15 Feb 2026 10:15:46 +0100 Subject: [PATCH 42/69] audio: Default buffering to 34ms --- 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 518e6eeb6e..aaa889ce1f 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -248,7 +248,7 @@ struct cfg_root : cfg::node cfg::string audio_device{ this, "Audio Device", "@@@default@@@", true }; cfg::_int<0, 200> volume{ this, "Master Volume", 100, true }; cfg::_bool enable_buffering{ this, "Enable Buffering", true, true }; - cfg::_int <4, 250> desired_buffer_duration{ this, "Desired Audio Buffer Duration", 100, true }; + cfg::_int <4, 250> desired_buffer_duration{ this, "Desired Audio Buffer Duration", 34, true }; cfg::_bool enable_time_stretching{ this, "Enable Time Stretching", false, true }; cfg::_bool disable_sampling_skip{ this, "Disable Sampling Skip", false, true }; cfg::_int<0, 100> time_stretching_threshold{ this, "Time Stretching Threshold", 75, true }; From 27fe5c66042e819a18efbd48e4dbf0c1575a2521 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 15 Feb 2026 16:46:32 +0300 Subject: [PATCH 43/69] rsx: Fix input-side vs output-side remapping flags - Sign bit and biased renorm apply on input, Gamma on output --- rpcs3/Emu/RSX/RSXThread.cpp | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 0d9955b4d4..b7c1d2ca77 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -2332,19 +2332,47 @@ namespace rsx u32 unsigned_remap = 0; u32 gamma = 0; + auto remap_channel_bits = [](const rsx::texture_channel_remap_t& remap, u32 bits) -> u32 + { + if (!bits || remap.encoded == RSX_TEXTURE_REMAP_IDENTITY) [[ likely ]] + { + return bits; + } + + u32 result = 0; + for (u8 channel = 0; channel < 4; ++channel) + { + switch (remap.control_map[channel]) + { + case CELL_GCM_TEXTURE_REMAP_REMAP: + if (bits & (1u << remap.channel_map[channel])) + { + result |= (1u << channel); + } + break; + default: + break; + } + } + return result; + }; + + const auto texture_remap = tex.decoded_remap(); if (format_features & RSX_FORMAT_FEATURE_SIGNED_COMPONENTS) { - argb8_signed = tex.argb_signed(); + // Tests show this is applied pre-readout. It's just a property of the incoming bytes and is therefore subject to remap. + argb8_signed = remap_channel_bits(texture_remap, tex.argb_signed()); } if (format_features & RSX_FORMAT_FEATURE_GAMMA_CORRECTION) { + // Tests show this is applied post-readout. It's a property of the final value stored in the register and is not remapped. It overwrites even constant channels (REMAP_ZERO | REMAP_ONE) gamma = tex.gamma() & ~(argb8_signed); } if (format_features & RSX_FORMAT_FEATURE_BIASED_NORMALIZATION) { - // The renormalization flag applies to all channels + // The renormalization flag applies to all channels. It is weaker than the other flags. unsigned_remap = (tex.unsigned_remap() == CELL_GCM_TEXTURE_UNSIGNED_REMAP_NORMAL) ? 0u : 0xF; unsigned_remap &= ~(argb8_signed | gamma); } From 8a1b3237497c02e055c3b18ba3be4245e1a0e4d5 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 15 Feb 2026 17:34:00 +0300 Subject: [PATCH 44/69] rsx/fp: Rework FP prepass analyzer to capture more information about bx2 expansion --- rpcs3/Emu/RSX/Program/ProgramStateCache.cpp | 84 ++++++++++----------- rpcs3/Emu/RSX/Program/ProgramStateCache.h | 1 + rpcs3/Emu/RSX/Program/RSXFragmentProgram.h | 8 ++ 3 files changed, 48 insertions(+), 45 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp b/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp index 45b2da4af6..15546afd7e 100644 --- a/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp +++ b/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "ProgramStateCache.h" +#include "FragmentProgramDecompiler.h" #include "Emu/system_config.h" #include "Emu/RSX/Core/RSXDriverState.h" #include "util/sysinfo.hpp" @@ -637,58 +638,51 @@ fragment_program_utils::fragment_program_metadata fragment_program_utils::analys while (true) { const auto inst = v128::loadu(instBuffer, index); + const auto d0 = OPDEST::from_be32(inst._u32[0]); + const auto opcode = static_cast(d0.opcode); - // Check for opcode high bit which indicates a branch instructions (opcode 0x40...0x45) - if (inst._u32[2] & (1 << 23)) + switch (opcode) { + case RSX_FP_OPCODE_TEX: + case RSX_FP_OPCODE_TEXBEM: + case RSX_FP_OPCODE_TXP: + case RSX_FP_OPCODE_TXPBEM: + case RSX_FP_OPCODE_TXD: + case RSX_FP_OPCODE_TXB: + case RSX_FP_OPCODE_TXL: + result.referenced_textures_mask |= (1 << d0.tex_num); + result.has_tex_bx2_conv |= !!d0.exp_tex; + break; + case RSX_FP_OPCODE_PK4: + case RSX_FP_OPCODE_UP4: + case RSX_FP_OPCODE_PK2: + case RSX_FP_OPCODE_UP2: + case RSX_FP_OPCODE_PKB: + case RSX_FP_OPCODE_UPB: + case RSX_FP_OPCODE_PK16: + case RSX_FP_OPCODE_UP16: + case RSX_FP_OPCODE_PKG: + case RSX_FP_OPCODE_UPG: + result.has_pack_instructions = true; + break; + case RSX_FP_OPCODE_BRK: + case RSX_FP_OPCODE_CAL: + case RSX_FP_OPCODE_IFE: + case RSX_FP_OPCODE_LOOP: + case RSX_FP_OPCODE_REP: + case RSX_FP_OPCODE_RET: // NOTE: Jump instructions are not yet proved to work outside of loops and if/else blocks // Otherwise we would need to follow the execution chain result.has_branch_instructions = true; + break; } - else - { - const u32 opcode = (inst._u32[0] >> 16) & 0x3F; - if (opcode) - { - switch (opcode) - { - case RSX_FP_OPCODE_TEX: - case RSX_FP_OPCODE_TEXBEM: - case RSX_FP_OPCODE_TXP: - case RSX_FP_OPCODE_TXPBEM: - case RSX_FP_OPCODE_TXD: - case RSX_FP_OPCODE_TXB: - case RSX_FP_OPCODE_TXL: - { - //Bits 17-20 of word 1, swapped within u16 sections - //Bits 16-23 are swapped into the upper 8 bits (24-31) - const u32 tex_num = (inst._u32[0] >> 25) & 15; - result.referenced_textures_mask |= (1 << tex_num); - break; - } - case RSX_FP_OPCODE_PK4: - case RSX_FP_OPCODE_UP4: - case RSX_FP_OPCODE_PK2: - case RSX_FP_OPCODE_UP2: - case RSX_FP_OPCODE_PKB: - case RSX_FP_OPCODE_UPB: - case RSX_FP_OPCODE_PK16: - case RSX_FP_OPCODE_UP16: - case RSX_FP_OPCODE_PKG: - case RSX_FP_OPCODE_UPG: - { - result.has_pack_instructions = true; - break; - } - } - } - if (is_any_src_constant(inst)) - { - //Instruction references constant, skip one slot occupied by data - index++; - result.program_constants_buffer_length += 16; - } + if (rsx::assembler::FP::get_operand_count(opcode) > 0 && + is_any_src_constant(inst)) + { + // Instruction references constant, skip one slot occupied by data + index++; + result.program_constants_buffer_length += 16; } index++; diff --git a/rpcs3/Emu/RSX/Program/ProgramStateCache.h b/rpcs3/Emu/RSX/Program/ProgramStateCache.h index efd5dd326a..ce5be4a425 100644 --- a/rpcs3/Emu/RSX/Program/ProgramStateCache.h +++ b/rpcs3/Emu/RSX/Program/ProgramStateCache.h @@ -59,6 +59,7 @@ namespace program_hash_util bool has_pack_instructions; bool has_branch_instructions; + bool has_tex_bx2_conv; bool is_nop_shader; // Does this affect Z-pass testing??? }; diff --git a/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h b/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h index d93ec760e6..7bb8517b85 100644 --- a/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h +++ b/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h @@ -54,6 +54,14 @@ union OPDEST u32 : 9; u32 write_mask : 4; }; + + static OPDEST from_be32(u32 be_word) + { + const u32 _hex = + ((be_word & 0x00FF00FF) << 8) | + ((be_word & 0xFF00FF00) >> 8); + return OPDEST{ .HEX = _hex }; + } }; union SRC0 From b30a44c1367635af0675f61504e1ca45263655c6 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 15 Feb 2026 18:15:42 +0300 Subject: [PATCH 45/69] rsx: Make BX2 flag respect constant overrides --- .../RSXProg/RSXFragmentTextureOps.glsl | 2 +- rpcs3/Emu/RSX/RSXThread.cpp | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl index a59e8ae3a1..43dcf9e6eb 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl @@ -34,7 +34,7 @@ R"( #define _enable_texture_expand(index) \ do { \ if (_test_bit(TEX_PARAM(index).flags, FORMAT_FEATURE_BIASED_RENORMALIZATION_BIT)) { \ - _texture_flag_override = SIGN_EXPAND_MASK; \ + _texture_flag_override = SIGN_EXPAND_MASK & (_get_bits(TEX_PARAM(index).remap, 16, 4) << EXPAND_A_BIT); \ _texture_flag_erase = GAMMA_CTRL_MASK; \ _texture_bx2_active = true; \ } \ diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index b7c1d2ca77..5b4b732c4d 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -2366,15 +2366,19 @@ namespace rsx if (format_features & RSX_FORMAT_FEATURE_GAMMA_CORRECTION) { - // Tests show this is applied post-readout. It's a property of the final value stored in the register and is not remapped. It overwrites even constant channels (REMAP_ZERO | REMAP_ONE) + // Tests show this is applied post-readout. It's a property of the final value stored in the register and is not remapped. + // NOTE: GAMMA correction has no algorithmic effect on constants (0 and 1) so we need not mask it out for correctness. gamma = tex.gamma() & ~(argb8_signed); } if (format_features & RSX_FORMAT_FEATURE_BIASED_NORMALIZATION) { // The renormalization flag applies to all channels. It is weaker than the other flags. - unsigned_remap = (tex.unsigned_remap() == CELL_GCM_TEXTURE_UNSIGNED_REMAP_NORMAL) ? 0u : 0xF; - unsigned_remap &= ~(argb8_signed | gamma); + // This applies on input and is subject to remap overrides + if (tex.unsigned_remap() == CELL_GCM_TEXTURE_UNSIGNED_REMAP_BIASED) + { + unsigned_remap = remap_channel_bits(texture_remap, 0xF) & ~(argb8_signed | gamma); + } } u32 argb8_convert = gamma; @@ -2390,6 +2394,13 @@ namespace rsx texture_control |= argb8_convert; texture_control |= format_features << texture_control_bits::FORMAT_FEATURES_OFFSET; + + if (current_fp_metadata.has_tex_bx2_conv) + { + const u32 remap_hi = remap_channel_bits(texture_remap, 0xFu); + current_fragment_program.texture_params[i].remap &= ~(0xFu << 16u); + current_fragment_program.texture_params[i].remap |= (remap_hi << 16u); + } } current_fragment_program.texture_params[i].control = texture_control; From 0faa8945bc20956ce5ddc3b00aa0cd5ad33110bb Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 18 Feb 2026 02:25:13 +0300 Subject: [PATCH 46/69] rsx/vk: Fix bug with custom border color sampler storage - If an app used multiple border colors, only the first one was being applied. --- rpcs3/Emu/RSX/VK/VKDraw.cpp | 2 +- rpcs3/Emu/RSX/VK/vkutils/sampler.cpp | 14 +++----------- rpcs3/Emu/RSX/VK/vkutils/sampler.h | 18 +++++++++++++++++- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKDraw.cpp b/rpcs3/Emu/RSX/VK/VKDraw.cpp index 59f9bfb40a..491078cc93 100644 --- a/rpcs3/Emu/RSX/VK/VKDraw.cpp +++ b/rpcs3/Emu/RSX/VK/VKDraw.cpp @@ -275,7 +275,7 @@ void VKGSRender::load_texture_env() auto get_border_color = [&](const rsx::Texture auto& tex) { - return m_device->get_custom_border_color_support().require_border_color_remap + return m_device->get_custom_border_color_support().require_border_color_remap ? tex.remapped_border_color() : rsx::decode_border_color(tex.border_color()); }; diff --git a/rpcs3/Emu/RSX/VK/vkutils/sampler.cpp b/rpcs3/Emu/RSX/VK/vkutils/sampler.cpp index 073fc514d0..53066e2f4a 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/sampler.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/sampler.cpp @@ -182,16 +182,8 @@ namespace vk return found == m_generic_sampler_pool.end() ? nullptr : found->second.get(); } - const auto block = m_custom_color_sampler_pool.equal_range(key.base_key); - for (auto it = block.first; it != block.second; ++it) - { - if (it->second->key.border_color_key == key.border_color_key) - { - return it->second.get(); - } - } - - return nullptr; + const auto found = m_custom_color_sampler_pool.find(key); + return found == m_custom_color_sampler_pool.end() ? nullptr : found->second.get(); } cached_sampler_object_t* sampler_pool_t::emplace(const sampler_pool_key_t& key, std::unique_ptr& object) @@ -204,7 +196,7 @@ namespace vk return iterator->second.get(); } - const auto [iterator, _unused] = m_custom_color_sampler_pool.emplace(key.base_key, std::move(object)); + const auto [iterator, _unused] = m_custom_color_sampler_pool.emplace(key, std::move(object)); return iterator->second.get(); } diff --git a/rpcs3/Emu/RSX/VK/vkutils/sampler.h b/rpcs3/Emu/RSX/VK/vkutils/sampler.h index f1d81d542f..232f1073d7 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/sampler.h +++ b/rpcs3/Emu/RSX/VK/vkutils/sampler.h @@ -64,6 +64,22 @@ namespace vk { u64 base_key; u64 border_color_key; + + bool operator == (const sampler_pool_key_t& that) const + { + return this->base_key == that.base_key && + this->border_color_key == that.border_color_key; + } + }; + + struct sampler_pool_key_hash + { + size_t operator()(const vk::sampler_pool_key_t& k) const noexcept + { + usz result = k.base_key; + result ^= k.border_color_key + 0x9e3779b97f4a7c15ULL + (result << 6) + (result >> 2); + return result; + } }; struct cached_sampler_object_t : public vk::sampler, public rsx::ref_counted @@ -75,7 +91,7 @@ namespace vk class sampler_pool_t { std::unordered_map> m_generic_sampler_pool; - std::unordered_map> m_custom_color_sampler_pool; + std::unordered_map, sampler_pool_key_hash> m_custom_color_sampler_pool; public: From dc63f2fc1f75316987f4e4b898f0792db91d8f65 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 18 Feb 2026 03:07:50 +0300 Subject: [PATCH 47/69] rsx: Correct border-color register value for shader texel remapping - PS3 border color does not correctly support signed channels and instead treats them as simple compression - We instead perform the reverse conversion before sending it to our shader pipeline --- rpcs3/Emu/RSX/RSXTexture.cpp | 73 +++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/RSXTexture.cpp b/rpcs3/Emu/RSX/RSXTexture.cpp index 52f8183545..e770923696 100644 --- a/rpcs3/Emu/RSX/RSXTexture.cpp +++ b/rpcs3/Emu/RSX/RSXTexture.cpp @@ -4,6 +4,15 @@ #include "rsx_utils.h" #include "Emu/system_config.h" +#include "util/simd.hpp" + +#if defined(ARCH_ARM64) +#if !defined(_MSC_VER) +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif +#undef FORCE_INLINE +#include "Emu/CPU/sse2neon.h" +#endif namespace rsx { @@ -291,7 +300,69 @@ namespace rsx u32 fragment_texture::border_color() const { - return registers[NV4097_SET_TEXTURE_BORDER_COLOR + (m_index * 8)]; + const u32 raw = registers[NV4097_SET_TEXTURE_BORDER_COLOR + (m_index * 8)]; + const u32 sext = argb_signed(); + + if (!sext) [[ likely ]] + { + return raw; + } + + // Border color is broken on PS3. The SNORM behavior is completely broken and behaves like BIASED renormalization instead. + // To solve the mismatch, we need to first do a bit expansion on the value then store it as sign extended. The second part is a natural part of numbers on a binary system, so we only need to do the former. + static constexpr u32 expand4_lut[16] = + { + 0x00000000u, // 0000 + 0xFF000000u, // 0001 + 0x00FF0000u, // 0010 + 0xFFFF0000u, // 0011 + 0x0000FF00u, // 0100 + 0xFF00FF00u, // 0101 + 0x00FFFF00u, // 0110 + 0xFFFFFF00u, // 0111 + 0x000000FFu, // 1000 + 0xFF0000FFu, // 1001 + 0x00FF00FFu, // 1010 + 0xFFFF00FFu, // 1011 + 0x0000FFFFu, // 1100 + 0xFF00FFFFu, // 1101 + 0x00FFFFFFu, // 1110 + 0xFFFFFFFFu // 1111 + }; + + // Bit pattern expand and reverse BE -> LE using LUT + const u32 mask = expand4_lut[sext]; + + // Now we perform the compensation operation + // BIAS operation = (V - 128 / 127) + + // Load + const __m128i _0 = _mm_setzero_si128(); + const __m128i _128 = _mm_set1_epi32(128); + const __m128i _127 = _mm_set1_epi32(127); + const __m128i _255 = _mm_set1_epi32(255); + + const auto be_raw = be_t(raw); + __m128i v = _mm_cvtsi32_si128(static_cast(be_raw)); + v = _mm_unpacklo_epi8(v, _0); + v = _mm_unpacklo_epi16(v, _0); // [ 0, 64, 255, 128 ] + + // Conversion: x = (y - 128) + v = _mm_sub_epi32(v, _128); // [ -128, -64, 127, 0 ] + + // Convert to signed encoding (reverse sext) + v = _mm_slli_epi32(v, 24); + v = _mm_srli_epi32(v, 24); + + // Pack down + v = _mm_packs_epi32(v, _0); + v = _mm_packus_epi16(v, _0); + + // Read + const u32 conv = _mm_cvtsi128_si32(v); + + // Merge + return (conv & mask) | (raw & ~mask); } color4f fragment_texture::remapped_border_color() const From 2064bd87e37da1d0946f7c5b62f2d38003abff8e Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 18 Feb 2026 03:26:51 +0300 Subject: [PATCH 48/69] rsx: Fix GCC build --- rpcs3/Emu/RSX/RSXTexture.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rpcs3/Emu/RSX/RSXTexture.cpp b/rpcs3/Emu/RSX/RSXTexture.cpp index e770923696..b3f9dc1962 100644 --- a/rpcs3/Emu/RSX/RSXTexture.cpp +++ b/rpcs3/Emu/RSX/RSXTexture.cpp @@ -6,6 +6,11 @@ #include "Emu/system_config.h" #include "util/simd.hpp" +#if !defined(_MSC_VER) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#endif + #if defined(ARCH_ARM64) #if !defined(_MSC_VER) #pragma GCC diagnostic ignored "-Wstrict-aliasing" From 53dcf2ba00b0f1395d6e782841fdd6a8558e121a Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Thu, 19 Feb 2026 22:18:34 +0100 Subject: [PATCH 49/69] rpcn_client: Misc fixes Fix missing freeaddrinfo Fix wrong return value in a rpcn_client::connect() path Fix missing 0 presence validation in vec_stream::get_string() --- rpcs3/Emu/NP/rpcn_client.cpp | 6 ++++-- rpcs3/Emu/NP/rpcn_client.h | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/NP/rpcn_client.cpp b/rpcs3/Emu/NP/rpcn_client.cpp index d7bb274c66..38a4ad95ca 100644 --- a/rpcs3/Emu/NP/rpcn_client.cpp +++ b/rpcs3/Emu/NP/rpcn_client.cpp @@ -896,7 +896,7 @@ namespace rpcn return error_and_disconnect("Failed to send all the bytes"); } - res = 0; + continue; } n_sent += res; } @@ -1055,6 +1055,8 @@ namespace rpcn found = found->ai_next; } + freeaddrinfo(addr_info); + if (!found_ipv4) { rpcn_log.error("connect: Failed to find IPv4 for %s", host); @@ -1156,7 +1158,7 @@ namespace rpcn if (!connected || terminate) { state = rpcn_state::failure_other; - return true; + return false; } if (received_version != RPCN_PROTOCOL_VERSION) diff --git a/rpcs3/Emu/NP/rpcn_client.h b/rpcs3/Emu/NP/rpcn_client.h index 56ba17d04d..377cd9c898 100644 --- a/rpcs3/Emu/NP/rpcn_client.h +++ b/rpcs3/Emu/NP/rpcn_client.h @@ -79,6 +79,14 @@ public: res.push_back(vec[i]); i++; } + + // Make sure we hit terminating 0 + if (i >= vec.size()) + { + error = true; + return {}; + } + i++; if (!empty && res.empty()) From b7b0e06a5404306b37299f204e11082684c3729e Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Thu, 19 Feb 2026 23:44:10 +0100 Subject: [PATCH 50/69] sys_net/lv2_socket_native: Various fixes Fix is_socket_connected(always returned false) Implement SYS_NET_IP_TTLCHK and SYS_NET_IP_MAXTTL Fix lv2_socket_native::sendmsg, it was only sending the first iovec Fix sys_net_bnet_sendto addrlen check Fix assert in lv2_socket_native::recvfrom() --- rpcs3/Emu/Cell/lv2/sys_net.cpp | 2 +- rpcs3/Emu/Cell/lv2/sys_net.h | 2 +- .../Cell/lv2/sys_net/lv2_socket_native.cpp | 50 ++++++++++--------- .../Emu/Cell/lv2/sys_net/lv2_socket_native.h | 4 ++ 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/rpcs3/Emu/Cell/lv2/sys_net.cpp b/rpcs3/Emu/Cell/lv2/sys_net.cpp index 827a4c98f2..27731e44ca 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net.cpp @@ -992,7 +992,7 @@ error_code sys_net_bnet_sendto(ppu_thread& ppu, s32 s, vm::cptr buf, u32 l fmt::throw_exception("sys_net_bnet_sendto(s=%d): unknown flags (0x%x)", flags); } - if (addr && addrlen < 8) + if (addr && addrlen < sizeof(sys_net_sockaddr)) { sys_net.error("sys_net_bnet_sendto(s=%d): bad addrlen (%u)", s, addrlen); return -SYS_NET_EINVAL; diff --git a/rpcs3/Emu/Cell/lv2/sys_net.h b/rpcs3/Emu/Cell/lv2/sys_net.h index b1f9ef7268..68e7041df0 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net.h +++ b/rpcs3/Emu/Cell/lv2/sys_net.h @@ -107,7 +107,7 @@ enum lv2_ip_option : s32 SYS_NET_IP_MULTICAST_LOOP = 11, SYS_NET_IP_ADD_MEMBERSHIP = 12, SYS_NET_IP_DROP_MEMBERSHIP = 13, - SYS_NET_IP_TTLCHK = 23, + SYS_NET_IP_TTLCHK = 23, // This is probably the equivalent of IP_MINTTL on FreeBSD SYS_NET_IP_MAXTTL = 24, SYS_NET_IP_DONTFRAG = 26 }; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp index d420f23cc8..2eb47d7b55 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp @@ -551,12 +551,14 @@ std::tuple lv2_socket_native::getsockopt(s32 } case SYS_NET_IP_TTLCHK: { - sys_net.error("sys_net_bnet_getsockopt(IPPROTO_IP, SYS_NET_IP_TTLCHK): stubbed option"); + out_val._int = min_ttl; + out_len = sizeof(s32); return {CELL_OK, out_val, out_len}; } case SYS_NET_IP_MAXTTL: { - sys_net.error("sys_net_bnet_getsockopt(IPPROTO_IP, SYS_NET_IP_MAXTTL): stubbed option"); + out_val._int = max_ttl; + out_len = sizeof(s32); return {CELL_OK, out_val, out_len}; } case SYS_NET_IP_DONTFRAG: @@ -834,13 +836,13 @@ s32 lv2_socket_native::setsockopt(s32 level, s32 optname, const std::vector& } case SYS_NET_IP_TTLCHK: { - sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): Stubbed option (0x%x) (SYS_NET_IP_TTLCHK)", lv2_id, optname); - break; + min_ttl = native_int; + return {}; } case SYS_NET_IP_MAXTTL: { - sys_net.error("sys_net_bnet_setsockopt(s=%d, IPPROTO_IP): Stubbed option (0x%x) (SYS_NET_IP_MAXTTL)", lv2_id, optname); - break; + max_ttl = native_int; + return {}; } case SYS_NET_IP_DONTFRAG: { @@ -910,7 +912,7 @@ std::optional, sys_net_sockaddr>> lv2_socket_nat { auto& nph = g_fxo->get>(); const auto packet = dnshook.get_dns_packet(lv2_id); - ensure(packet.size() < len); + ensure(packet.size() <= len); memcpy(res_buf.data(), packet.data(), packet.size()); native_addr.ss_family = AF_INET; (reinterpret_cast<::sockaddr_in*>(&native_addr))->sin_port = std::bit_cast>(53); // htons(53) @@ -1069,18 +1071,20 @@ std::optional lv2_socket_native::sendmsg(s32 flags, const sys_net_msghdr& m return {-SYS_NET_ECONNRESET}; } + std::vector buf_copy; for (int i = 0; i < msg.msg_iovlen; i++) { auto iov_base = msg.msg_iov[i].iov_base; const u32 len = msg.msg_iov[i].iov_len; - const std::vector buf_copy(vm::_ptr(iov_base.addr()), vm::_ptr(iov_base.addr()) + len); + const auto* src = vm::_ptr(iov_base.addr()); + buf_copy.insert(buf_copy.end(), src, src + len); + } - native_result = ::send(native_socket, reinterpret_cast(buf_copy.data()), ::narrow(buf_copy.size()), native_flags); + native_result = ::send(native_socket, reinterpret_cast(buf_copy.data()), ::narrow(buf_copy.size()), native_flags); - if (native_result >= 0) - { - return {native_result}; - } + if (native_result >= 0) + { + return {native_result}; } result = get_last_error(!so_nbio && (flags & SYS_NET_MSG_DONTWAIT) == 0); @@ -1232,16 +1236,16 @@ bool lv2_socket_native::is_socket_connected() return false; } - fd_set readfds, writefds; - struct timeval timeout{0, 0}; // Zero timeout + pollfd pfd{}; + pfd.fd = native_socket; + pfd.events = POLLIN | POLLOUT; - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_SET(native_socket, &readfds); - FD_SET(native_socket, &writefds); - - // Use select to check for readability and writability - const int result = ::select(1, &readfds, &writefds, NULL, &timeout); + // Use poll to check for readability and writability +#ifdef _WIN32 + const int result = WSAPoll(&pfd, 1, 0); +#else + const int result = ::poll(&pfd, 1, 0); +#endif if (result < 0) { @@ -1250,5 +1254,5 @@ bool lv2_socket_native::is_socket_connected() } // Socket is connected if it's readable or writable - return FD_ISSET(native_socket, &readfds) || FD_ISSET(native_socket, &writefds); + return (pfd.revents & (POLLIN | POLLOUT)) != 0; } diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h index cf07dfcb76..84f4218cbd 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h @@ -70,6 +70,10 @@ private: s32 so_reuseaddr = 0; s32 so_reuseport = 0; #endif + // Those values come from FreeBSD + s32 min_ttl = 1; + s32 max_ttl = 64; + u16 bound_port = 0; bool feign_tcp_conn_failure = false; // Savestate load related }; From aaf84a844542cf0697c19cb0d8579eff705b10c5 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Fri, 20 Feb 2026 00:53:12 +0100 Subject: [PATCH 51/69] sys_net/P2P: Various fixes Fix RTT adjustment Fix possible dead pointer interaction in lv2_socket_native::recvfrom() Fix iterator invalidation in nt_p2p_port::recv_data() --- rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp | 5 ++--- rpcs3/Emu/Cell/lv2/sys_net/nt_p2p_port.cpp | 10 ++++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp index c0790a2e33..771402809e 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp @@ -112,7 +112,6 @@ public: // reply is late, increases rtt auto& msg = it->second; - const auto addr = msg.dst_addr.sin_addr.s_addr; rtt_info rtt = rtts[msg.sock_id]; // Only increases rtt once per loop(in case a big number of packets are sent at once) if (!rtt_increased.count(msg.sock_id)) @@ -120,7 +119,7 @@ public: rtt.num_retries += 1; // Increases current rtt by 10% rtt.rtt_time += (rtt.rtt_time / 10); - rtts[addr] = rtt; + rtts[msg.sock_id] = rtt; rtt_increased.emplace(msg.sock_id); } @@ -625,7 +624,7 @@ std::tuple, sys_net_sockaddr> lv2_socket_p2ps: sys_net_sockaddr ps3_addr{}; auto* paddr = reinterpret_cast(&ps3_addr); - lv2_socket_p2ps* sock_client = reinterpret_cast(idm::check_unlocked(p2ps_client)); + auto sock_client = static_cast>(idm::get_unlocked(p2ps_client)); { std::lock_guard lock(sock_client->mutex); paddr->sin_family = SYS_NET_AF_INET; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/nt_p2p_port.cpp b/rpcs3/Emu/Cell/lv2/sys_net/nt_p2p_port.cpp index 61039c7856..67be0df63b 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/nt_p2p_port.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/nt_p2p_port.cpp @@ -249,8 +249,9 @@ bool nt_p2p_port::recv_data() auto& bound_sockets = ::at32(bound_p2p_vports, dst_vport); - for (const auto sock_id : bound_sockets) + for (auto it = bound_sockets.begin(); it != bound_sockets.end();) { + s32 sock_id = *it; const auto sock = idm::check(sock_id, [&](lv2_socket& sock) { ensure(sock.get_type() == SYS_NET_SOCK_DGRAM_P2P); @@ -262,12 +263,17 @@ bool nt_p2p_port::recv_data() if (!sock) { sys_net.error("Socket %d found in bound_p2p_vports didn't exist!", sock_id); - bound_sockets.erase(sock_id); + it = bound_sockets.erase(it); if (bound_sockets.empty()) { bound_p2p_vports.erase(dst_vport); + break; } } + else + { + it++; + } } return true; From ff992a67c8e1c1eb83c39ab619c1eab564e80c36 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 19 Feb 2026 23:58:34 +0100 Subject: [PATCH 52/69] win/fs: check file handle with GetFileInformationByHandle when creating fs::file On empty mounted drives CreateFileW may return a valid handle even if the drive is not usable. We have to check the file handle early. Otherwise other functions may fail later as a result. --- Utilities/File.cpp | 24 +++++++++++++++++------- Utilities/File.h | 14 +++++++------- rpcs3/Loader/ISO.h | 16 ++++++++-------- 3 files changed, 32 insertions(+), 22 deletions(-) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index 30e6414675..d2adb22c61 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -398,12 +398,11 @@ namespace fs class windows_file final : public file_base { HANDLE m_handle; - atomic_t m_pos; + atomic_t m_pos {0}; public: windows_file(HANDLE handle) : m_handle(handle) - , m_pos(0) { } @@ -417,10 +416,10 @@ namespace fs stat_t get_stat() override { - FILE_BASIC_INFO basic_info; + FILE_BASIC_INFO basic_info {}; ensure(GetFileInformationByHandleEx(m_handle, FileBasicInfo, &basic_info, sizeof(FILE_BASIC_INFO))); // "file::stat" - stat_t info; + stat_t info {}; info.is_directory = (basic_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; info.is_writable = (basic_info.FileAttributes & FILE_ATTRIBUTE_READONLY) == 0; info.size = this->size(); @@ -441,7 +440,7 @@ namespace fs bool trunc(u64 length) override { - FILE_END_OF_FILE_INFO _eof; + FILE_END_OF_FILE_INFO _eof {}; _eof.EndOfFile.QuadPart = length; if (!SetFileInformationByHandle(m_handle, FileEndOfFileInfo, &_eof, sizeof(_eof))) @@ -563,6 +562,7 @@ namespace fs u64 size() override { + // NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso). LARGE_INTEGER size; ensure(GetFileSizeEx(m_handle, &size)); // "file::size" @@ -579,7 +579,7 @@ namespace fs file_id id{"windows_file"}; id.data.resize(sizeof(FILE_ID_INFO)); - FILE_ID_INFO info; + FILE_ID_INFO info {}; if (!GetFileInformationByHandleEx(m_handle, FileIdInfo, &info, sizeof(info))) { @@ -625,7 +625,7 @@ namespace fs struct ::stat file_info; ensure(::fstat(m_fd, &file_info) == 0); // "file::stat" - stat_t info; + stat_t info {}; info.is_directory = S_ISDIR(file_info.st_mode); info.is_writable = file_info.st_mode & 0200; // HACK: approximation info.size = file_info.st_size; @@ -1656,6 +1656,16 @@ fs::file::file(const std::string& path, bs_t mode) return; } + // Check if the handle is actually valid. + // This can fail on empty mounted drives (e.g. with ERROR_NOT_READY or ERROR_INVALID_FUNCTION). + BY_HANDLE_FILE_INFORMATION info; + if (!GetFileInformationByHandle(handle, &info)) + { + CloseHandle(handle); + g_tls_error = to_error(GetLastError()); + return; + } + m_file = std::make_unique(handle); #else int flags = O_CLOEXEC; // Ensures all files are closed on execl for auto updater diff --git a/Utilities/File.h b/Utilities/File.h index 7e6356da7b..dd2db42a46 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -66,13 +66,13 @@ namespace fs // File attributes (TODO) struct stat_t { - bool is_directory; - bool is_symlink; - bool is_writable; - u64 size; - s64 atime; - s64 mtime; - s64 ctime; + bool is_directory = false; + bool is_symlink = false; + bool is_writable = false; + u64 size = 0; + s64 atime = 0; + s64 mtime = 0; + s64 ctime = 0; using enable_bitcopy = std::true_type; diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index 3af4732aad..588e4b8491 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -13,16 +13,16 @@ void unload_iso(); struct iso_extent_info { - u64 start; - u64 size; + u64 start = 0; + u64 size = 0; }; struct iso_fs_metadata { std::string name; - s64 time; - bool is_directory; - bool has_multiple_extents; + s64 time = 0; + bool is_directory = false; + bool has_multiple_extents = false; std::vector extents; u64 size() const; @@ -30,7 +30,7 @@ struct iso_fs_metadata struct iso_fs_node { - iso_fs_metadata metadata; + iso_fs_metadata metadata {}; std::vector> children; }; @@ -38,7 +38,7 @@ class iso_file : public fs::file_base { private: fs::file m_file; - iso_fs_metadata m_meta; + iso_fs_metadata m_meta {}; u64 m_pos = 0; std::pair get_extent_pos(u64 pos) const; @@ -80,7 +80,7 @@ class iso_archive { private: std::string m_path; - iso_fs_node m_root; + iso_fs_node m_root {}; fs::file m_file; public: From a29758de6e14cf148e2a2af89c7a74fea8979411 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Fri, 20 Feb 2026 14:48:03 +0100 Subject: [PATCH 53/69] signaling_handler: bugfix and cleanup --- rpcs3/Emu/NP/np_cache.cpp | 4 ++-- rpcs3/Emu/NP/np_handler.cpp | 4 ++-- rpcs3/Emu/NP/np_helpers.cpp | 7 +++++++ rpcs3/Emu/NP/np_helpers.h | 1 + rpcs3/Emu/NP/np_notifications.cpp | 2 +- rpcs3/Emu/NP/rpcn_client.cpp | 2 +- rpcs3/Emu/NP/signaling_handler.cpp | 12 ++++++------ 7 files changed, 20 insertions(+), 12 deletions(-) diff --git a/rpcs3/Emu/NP/np_cache.cpp b/rpcs3/Emu/NP/np_cache.cpp index 04223da545..4731e0cede 100644 --- a/rpcs3/Emu/NP/np_cache.cpp +++ b/rpcs3/Emu/NP/np_cache.cpp @@ -377,7 +377,7 @@ namespace np if (!rooms.contains(room_id)) { - np_cache.error("np_cache::get_memberid cache miss room_id: room_id(%d)/npid(%s)", room_id, static_cast(npid.handle.data)); + np_cache.error("np_cache::get_memberid cache miss room_id: room_id(%d)/npid(%s)", room_id, np::npid_to_string(npid)); return std::nullopt; } @@ -389,7 +389,7 @@ namespace np return id; } - np_cache.error("np_cache::get_memberid cache miss member_id: room_id(%d)/npid(%s)", room_id, static_cast(npid.handle.data)); + np_cache.error("np_cache::get_memberid cache miss member_id: room_id(%d)/npid(%s)", room_id, np::npid_to_string(npid)); return std::nullopt; } diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index c4db3350c0..3041f24424 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -1361,7 +1361,7 @@ namespace np player_history& np_handler::get_player_and_set_timestamp(const SceNpId& npid, u64 timestamp) { - std::string npid_str = std::string(npid.handle.data); + std::string npid_str = np::npid_to_string(npid); if (!players_history.contains(npid_str)) { @@ -1641,7 +1641,7 @@ namespace np return SCE_NP_BASIC_ERROR_NOT_CONNECTED; } - auto friend_infos = rpcn->get_friend_presence_by_npid(std::string(npid.handle.data)); + auto friend_infos = rpcn->get_friend_presence_by_npid(np::npid_to_string(npid)); if (!friend_infos) { return SCE_NP_BASIC_ERROR_INVALID_ARGUMENT; diff --git a/rpcs3/Emu/NP/np_helpers.cpp b/rpcs3/Emu/NP/np_helpers.cpp index 79f61ca627..39c59afdb0 100644 --- a/rpcs3/Emu/NP/np_helpers.cpp +++ b/rpcs3/Emu/NP/np_helpers.cpp @@ -89,6 +89,13 @@ namespace np // npid->reserved[0] = 1; } + std::string npid_to_string(const SceNpId& npid) + { + char npid_str[17]{}; + std::memcpy(npid_str, npid.handle.data, 16); + return std::string(npid_str); + } + void string_to_online_name(std::string_view str, SceNpOnlineName& online_name) { memset(&online_name, 0, sizeof(online_name)); diff --git a/rpcs3/Emu/NP/np_helpers.h b/rpcs3/Emu/NP/np_helpers.h index c33b4ca001..a51499ca64 100644 --- a/rpcs3/Emu/NP/np_helpers.h +++ b/rpcs3/Emu/NP/np_helpers.h @@ -13,6 +13,7 @@ namespace np std::optional string_to_communication_id(std::string_view str); void string_to_npid(std::string_view str, SceNpId& npid); + std::string npid_to_string(const SceNpId& npid); void string_to_online_name(std::string_view str, SceNpOnlineName& online_name); void string_to_avatar_url(std::string_view str, SceNpAvatarUrl& avatar_url); void strings_to_userinfo(std::string_view npid, std::string_view online_name, std::string_view avatar_url, SceNpUserInfo& user_info); diff --git a/rpcs3/Emu/NP/np_notifications.cpp b/rpcs3/Emu/NP/np_notifications.cpp index d5756a8351..92072d2b5a 100644 --- a/rpcs3/Emu/NP/np_notifications.cpp +++ b/rpcs3/Emu/NP/np_notifications.cpp @@ -54,7 +54,7 @@ namespace np const u16 member_id = notif_data->roomMemberDataInternal->memberId; const SceNpId& npid = notif_data->roomMemberDataInternal->userInfo.npId; - rpcn_log.notice("Join notification told to connect to member(%d=%s) of room(%d): %s:%d", member_id, reinterpret_cast(npid.handle.data), room_id, ip_to_string(addr_p2p), port_p2p); + rpcn_log.notice("Join notification told to connect to member(%d=%s) of room(%d): %s:%d", member_id, np::npid_to_string(npid), room_id, ip_to_string(addr_p2p), port_p2p); // Attempt Signaling auto& sigh = g_fxo->get>(); diff --git a/rpcs3/Emu/NP/rpcn_client.cpp b/rpcs3/Emu/NP/rpcn_client.cpp index 38a4ad95ca..071afa2b3f 100644 --- a/rpcs3/Emu/NP/rpcn_client.cpp +++ b/rpcs3/Emu/NP/rpcn_client.cpp @@ -2318,7 +2318,7 @@ namespace rpcn { np2_structs::GetScoreGameDataRequest pb_req; pb_req.set_boardid(board_id); - pb_req.set_npid(reinterpret_cast(npid.handle.data)); + pb_req.set_npid(np::npid_to_string(npid)); pb_req.set_pcid(pc_id); std::string serialized; diff --git a/rpcs3/Emu/NP/signaling_handler.cpp b/rpcs3/Emu/NP/signaling_handler.cpp index 2e4ac5df56..e6d79f1fca 100644 --- a/rpcs3/Emu/NP/signaling_handler.cpp +++ b/rpcs3/Emu/NP/signaling_handler.cpp @@ -426,9 +426,10 @@ void signaling_handler::operator()() if (sig.sig_info->time_last_msg_recvd < now - 60s && cmd != signal_info) { // We had no connection to opponent for 60 seconds, consider the connection dead + auto retire_info = sig.sig_info; sign_log.notice("Timeout disconnection"); - update_si_status(sig.sig_info, SCE_NP_SIGNALING_CONN_STATUS_INACTIVE, SCE_NP_SIGNALING_ERROR_TIMEOUT); - retire_packet(sig.sig_info, signal_ping); // Retire ping packet if necessary + update_si_status(retire_info, SCE_NP_SIGNALING_CONN_STATUS_INACTIVE, SCE_NP_SIGNALING_ERROR_TIMEOUT); + retire_packet(retire_info, signal_ping); // Retire ping packet if necessary break; // qpackets has been emptied of all packets for this user so we're requeuing } @@ -784,7 +785,7 @@ void signaling_handler::send_information_packets(u32 addr, u16 port, const SceNp u32 signaling_handler::get_always_conn_id(const SceNpId& npid) { - std::string npid_str(reinterpret_cast(npid.handle.data)); + std::string npid_str = np::npid_to_string(npid); if (npid_to_conn_id.contains(npid_str)) return ::at32(npid_to_conn_id, npid_str); @@ -810,9 +811,8 @@ u32 signaling_handler::init_sig1(const SceNpId& npid) sig_peers[conn_id]->conn_status = SCE_NP_SIGNALING_CONN_STATUS_PENDING; // Request peer infos from RPCN - std::string npid_str(reinterpret_cast(npid.handle.data)); auto& nph = g_fxo->get>(); - nph.req_sign_infos(npid_str, conn_id); + nph.req_sign_infos(np::npid_to_string(npid), conn_id); } return conn_id; @@ -839,7 +839,7 @@ std::optional signaling_handler::get_conn_id_from_npid(const SceNpId& npid) { std::lock_guard lock(data_mutex); - std::string npid_str(reinterpret_cast(npid.handle.data)); + std::string npid_str = np::npid_to_string(npid); if (npid_to_conn_id.contains(npid_str)) return ::at32(npid_to_conn_id, npid_str); From a6b5c7e79f57c666b0f1e0f9f4fe3e8e3b886a00 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Fri, 20 Feb 2026 15:45:15 +0100 Subject: [PATCH 54/69] Generalize np::npid_to_string() use --- rpcs3/Emu/Cell/Modules/sceNp.cpp | 8 ++++---- rpcs3/Emu/NP/clans_client.cpp | 18 +++++++++--------- rpcs3/Emu/NP/np_handler.cpp | 2 +- rpcs3/Emu/NP/np_notifications.cpp | 6 +++--- rpcs3/Emu/NP/np_requests.cpp | 2 +- rpcs3/Emu/NP/np_structs_extra.cpp | 13 +++++++------ rpcs3/Emu/NP/pb_helpers.cpp | 2 +- rpcs3/Emu/NP/rpcn_client.cpp | 12 ++++++------ rpcs3/Emu/NP/signaling_handler.cpp | 8 ++------ 9 files changed, 34 insertions(+), 37 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/sceNp.cpp b/rpcs3/Emu/Cell/Modules/sceNp.cpp index 2a2075f0ca..3eee01cf9a 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNp.cpp @@ -1199,7 +1199,7 @@ error_code _sceNpBasicSendMessage(vm::cptr to, vm::cptr data, u32 .msgFeatures = {}, .data = std::vector(static_cast(data.get_ptr()), static_cast(data.get_ptr()) + size)}; std::set npids; - npids.insert(std::string(to->handle.data)); + npids.insert(np::npid_to_string(*to)); nph.send_message(msg_data, npids); @@ -1228,7 +1228,7 @@ error_code sceNpBasicSendMessageGui(ppu_thread& ppu, vm::cptrmsgId, msg->mainType, msg->subType, msg->msgFeatures, msg->count, msg->npids); for (u32 i = 0; i < msg->count && msg->npids; i++) { - sceNp.trace("sceNpBasicSendMessageGui: NpId[%d] = %s", i, static_cast(&msg->npids[i].handle.data[0])); + sceNp.trace("sceNpBasicSendMessageGui: NpId[%d] = %s", i, np::npid_to_string(msg->npids[i])); } sceNp.notice("sceNpBasicSendMessageGui: subject: %s", msg->subject); sceNp.notice("sceNpBasicSendMessageGui: body: %s", msg->body); @@ -1398,7 +1398,7 @@ error_code sceNpBasicSendMessageGui(ppu_thread& ppu, vm::cptrcount; i++) { - npids.insert(std::string(msg->npids[i].handle.data)); + npids.insert(np::npid_to_string(msg->npids[i])); } } @@ -7144,7 +7144,7 @@ error_code sceNpUtilCanonicalizeNpIdForPsp(vm::ptr npId) error_code sceNpUtilCmpNpId(vm::ptr id1, vm::ptr id2) { - sceNp.trace("sceNpUtilCmpNpId(id1=*0x%x(%s), id2=*0x%x(%s))", id1, id1 ? id1->handle.data : "", id2, id2 ? id2->handle.data : ""); + sceNp.trace("sceNpUtilCmpNpId(id1=*0x%x(%s), id2=*0x%x(%s))", id1, id1 ? np::npid_to_string(*id1) : std::string(), id2, id2 ? np::npid_to_string(*id2) : std::string()); if (!id1 || !id2) { diff --git a/rpcs3/Emu/NP/clans_client.cpp b/rpcs3/Emu/NP/clans_client.cpp index da3f506c91..e2d9fa9de0 100644 --- a/rpcs3/Emu/NP/clans_client.cpp +++ b/rpcs3/Emu/NP/clans_client.cpp @@ -464,7 +464,7 @@ namespace clan clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id)); clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_document response = pugi::xml_document(); @@ -656,7 +656,7 @@ namespace clan clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id)); clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_document response = pugi::xml_document(); @@ -674,7 +674,7 @@ namespace clan clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id)); clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_document response = pugi::xml_document(); @@ -832,7 +832,7 @@ namespace clan clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id)); clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_document response = pugi::xml_document(); @@ -850,7 +850,7 @@ namespace clan clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id)); clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_document response = pugi::xml_document(); @@ -868,7 +868,7 @@ namespace clan clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id)); clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_document response = pugi::xml_document(); @@ -902,7 +902,7 @@ namespace clan clan.append_child("id").text().set(clan_id); pugi::xml_node role = clan.append_child("onlinename"); - role.text().set(nph.get_npid().handle.data); + role.text().set(np::npid_to_string(nph.get_npid()).c_str()); pugi::xml_node description = clan.append_child("description"); description.text().set(info.description); @@ -990,7 +990,7 @@ namespace clan clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id)); clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_document response = pugi::xml_document(); @@ -1008,7 +1008,7 @@ namespace clan clan.append_child("ticket").text().set(ticket.c_str()); clan.append_child("id").text().set(clan_id); - const std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + const std::string jid_str = fmt::format(JID_FORMAT, np::npid_to_string(np_id)); clan.append_child("jid").text().set(jid_str.c_str()); pugi::xml_node role_node = clan.append_child("role"); diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index 3041f24424..1fc9342329 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -1024,7 +1024,7 @@ namespace np } } - nph_log.notice("basic_event: event:%d, from:%s(%s), size:%d", *event, static_cast(from->userId.handle.data), static_cast(from->name.data), *size); + nph_log.notice("basic_event: event:%d, from:%s(%s), size:%d", *event, np::npid_to_string(from->userId), static_cast(from->name.data), *size); return CELL_OK; } diff --git a/rpcs3/Emu/NP/np_notifications.cpp b/rpcs3/Emu/NP/np_notifications.cpp index 92072d2b5a..6d6d511ee0 100644 --- a/rpcs3/Emu/NP/np_notifications.cpp +++ b/rpcs3/Emu/NP/np_notifications.cpp @@ -41,7 +41,7 @@ namespace np return; } - rpcn_log.notice("Received notification that user %s(%d) joined the room(%d)", notif_data->roomMemberDataInternal->userInfo.npId.handle.data, notif_data->roomMemberDataInternal->memberId, room_id); + rpcn_log.notice("Received notification that user %s(%d) joined the room(%d)", np::npid_to_string(notif_data->roomMemberDataInternal->userInfo.npId), notif_data->roomMemberDataInternal->memberId, room_id); extra_nps::print_SceNpMatching2RoomMemberDataInternal(notif_data->roomMemberDataInternal.get_ptr()); // We initiate signaling if necessary @@ -98,7 +98,7 @@ namespace np return; } - rpcn_log.notice("Received notification that user %s(%d) left the room(%d)", notif_data->roomMemberDataInternal->userInfo.npId.handle.data, notif_data->roomMemberDataInternal->memberId, room_id); + rpcn_log.notice("Received notification that user %s(%d) left the room(%d)", np::npid_to_string(notif_data->roomMemberDataInternal->userInfo.npId), notif_data->roomMemberDataInternal->memberId, room_id); extra_nps::print_SceNpMatching2RoomMemberDataInternal(notif_data->roomMemberDataInternal.get_ptr()); if (room_event_cb) @@ -204,7 +204,7 @@ namespace np return; } - rpcn_log.notice("Received notification that user's %s(%d) room (%d) data was updated", notif_data->newRoomMemberDataInternal->userInfo.npId.handle.data, notif_data->newRoomMemberDataInternal->memberId, room_id); + rpcn_log.notice("Received notification that user's %s(%d) room (%d) data was updated", np::npid_to_string(notif_data->newRoomMemberDataInternal->userInfo.npId), notif_data->newRoomMemberDataInternal->memberId, room_id); extra_nps::print_SceNpMatching2RoomMemberDataInternal(notif_data->newRoomMemberDataInternal.get_ptr()); if (room_event_cb) diff --git a/rpcs3/Emu/NP/np_requests.cpp b/rpcs3/Emu/NP/np_requests.cpp index 3f21f24cd3..ed7023cc89 100644 --- a/rpcs3/Emu/NP/np_requests.cpp +++ b/rpcs3/Emu/NP/np_requests.cpp @@ -310,7 +310,7 @@ namespace np if (npid_res != CELL_OK) continue; - rpcn_log.notice("JoinRoomResult told to connect to member(%d=%s) of room(%d): %s:%d", member_id, reinterpret_cast(npid_p2p->handle.data), room_id, ip_to_string(addr_p2p), port_p2p); + rpcn_log.notice("JoinRoomResult told to connect to member(%d=%s) of room(%d): %s:%d", member_id, np::npid_to_string(*npid_p2p), room_id, ip_to_string(addr_p2p), port_p2p); // Attempt Signaling auto& sigh = g_fxo->get>(); diff --git a/rpcs3/Emu/NP/np_structs_extra.cpp b/rpcs3/Emu/NP/np_structs_extra.cpp index 58770c450f..6774b20bc4 100644 --- a/rpcs3/Emu/NP/np_structs_extra.cpp +++ b/rpcs3/Emu/NP/np_structs_extra.cpp @@ -2,6 +2,7 @@ #include "stdafx.h" #include #include "np_structs_extra.h" +#include "np_helpers.h" LOG_CHANNEL(sceNp); LOG_CHANNEL(sceNp2); @@ -13,7 +14,7 @@ namespace extra_nps void print_SceNpUserInfo2(const SceNpUserInfo2* user) { sceNp2.warning("SceNpUserInfo2:"); - sceNp2.warning("npid: %s", static_cast(user->npId.handle.data)); + sceNp2.warning("npid: %s", np::npid_to_string(user->npId)); sceNp2.warning("onlineName: *0x%x(%s)", user->onlineName, user->onlineName ? static_cast(user->onlineName->data) : ""); sceNp2.warning("avatarUrl: *0x%x(%s)", user->avatarUrl, user->avatarUrl ? static_cast(user->avatarUrl->data) : ""); } @@ -208,7 +209,7 @@ namespace extra_nps { sceNp2.warning("SceNpMatching2RoomMemberDataInternal:"); sceNp2.warning("next: *0x%x", member->next); - sceNp2.warning("npId: %s", member->userInfo.npId.handle.data); + sceNp2.warning("npId: %s", np::npid_to_string(member->userInfo.npId)); sceNp2.warning("onlineName: %s", member->userInfo.onlineName ? member->userInfo.onlineName->data : ""); sceNp2.warning("avatarUrl: %s", member->userInfo.avatarUrl ? member->userInfo.avatarUrl->data : ""); sceNp2.warning("joinDate: %lld", member->joinDate.tick); @@ -460,7 +461,7 @@ namespace extra_nps void print_SceNpScoreRankData(const SceNpScoreRankData* data) { sceNp.warning("sceNpScoreRankData:"); - sceNp.warning("npId: %s", static_cast(data->npId.handle.data)); + sceNp.warning("npId: %s", np::npid_to_string(data->npId)); sceNp.warning("onlineName: %s", static_cast(data->onlineName.data)); sceNp.warning("pcId: %d", data->pcId); sceNp.warning("serialRank: %d", data->serialRank); @@ -474,7 +475,7 @@ namespace extra_nps void print_SceNpScoreRankData_deprecated(const SceNpScoreRankData_deprecated* data) { sceNp.warning("sceNpScoreRankData_deprecated:"); - sceNp.warning("npId: %s", static_cast(data->npId.handle.data)); + sceNp.warning("npId: %s", np::npid_to_string(data->npId)); sceNp.warning("onlineName: %s", static_cast(data->onlineName.data)); sceNp.warning("serialRank: %d", data->serialRank); sceNp.warning("rank: %d", data->rank); @@ -542,7 +543,7 @@ namespace extra_nps void print_SceNpUserInfo(const SceNpUserInfo* data) { - sceNp.warning("userId: %s", data->userId.handle.data); + sceNp.warning("userId: %s", np::npid_to_string(data->userId)); sceNp.warning("name: %s", data->name.data); sceNp.warning("icon: %s", data->icon.data); } @@ -576,7 +577,7 @@ namespace extra_nps if (data->kick_actor) { - sceNp.warning("kick_actor: %s", data->kick_actor->handle.data); + sceNp.warning("kick_actor: %s", np::npid_to_string(*data->kick_actor)); } sceNp.warning("opt: 0x%x", data->kick_actor); diff --git a/rpcs3/Emu/NP/pb_helpers.cpp b/rpcs3/Emu/NP/pb_helpers.cpp index 41b0ca4579..83170426ff 100644 --- a/rpcs3/Emu/NP/pb_helpers.cpp +++ b/rpcs3/Emu/NP/pb_helpers.cpp @@ -263,7 +263,7 @@ namespace np for (u32 i = 0; i < room_info->memberList.membersNum; i++) { SceNpMatching2RoomMemberDataInternal* sce_member = &room_info->memberList.members[i]; - if (strcmp(sce_member->userInfo.npId.handle.data, npid.handle.data) == 0) + if (strncmp(sce_member->userInfo.npId.handle.data, npid.handle.data, 16) == 0) { room_info->memberList.me = room_info->memberList.members + i; edata.add_relocation(room_info->memberList.me); diff --git a/rpcs3/Emu/NP/rpcn_client.cpp b/rpcs3/Emu/NP/rpcn_client.cpp index 071afa2b3f..86a688705e 100644 --- a/rpcs3/Emu/NP/rpcn_client.cpp +++ b/rpcs3/Emu/NP/rpcn_client.cpp @@ -1756,7 +1756,7 @@ namespace rpcn { continue; } - pb_req.add_alloweduser(req->allowedUser[i].handle.data); + pb_req.add_alloweduser(np::npid_to_string(req->allowedUser[i])); } } @@ -1768,7 +1768,7 @@ namespace rpcn { continue; } - pb_req.add_blockeduser(req->blockedUser[i].handle.data); + pb_req.add_blockeduser(np::npid_to_string(req->blockedUser[i])); } } @@ -2267,7 +2267,7 @@ namespace rpcn for (usz i = 0; i < npids.size(); i++) { auto* npid_entry = pb_req.add_npids(); - npid_entry->set_npid(static_cast(npids[i].first.handle.data)); + npid_entry->set_npid(np::npid_to_string(npids[i].first)); npid_entry->set_pcid(npids[i].second); } @@ -2414,7 +2414,7 @@ namespace rpcn if (option->isLastChangedAuthorId) { - pb_req.set_islastchangedauthorid(option->isLastChangedAuthorId->handle.data); + pb_req.set_islastchangedauthorid(np::npid_to_string(*option->isLastChangedAuthorId)); } } @@ -2443,7 +2443,7 @@ namespace rpcn if (option->isLastChangedAuthorId) { - pb_req.set_islastchangedauthorid(option->isLastChangedAuthorId->handle.data); + pb_req.set_islastchangedauthorid(np::npid_to_string(*option->isLastChangedAuthorId)); } if (option->compareValue) @@ -2499,7 +2499,7 @@ namespace rpcn if (option->isLastChangedAuthorId) { - pb_req.set_islastchangedauthorid(option->isLastChangedAuthorId->handle.data); + pb_req.set_islastchangedauthorid(np::npid_to_string(*option->isLastChangedAuthorId)); } } diff --git a/rpcs3/Emu/NP/signaling_handler.cpp b/rpcs3/Emu/NP/signaling_handler.cpp index e6d79f1fca..97a2673e0f 100644 --- a/rpcs3/Emu/NP/signaling_handler.cpp +++ b/rpcs3/Emu/NP/signaling_handler.cpp @@ -256,9 +256,7 @@ void signaling_handler::process_incoming_messages() addr.s_addr = op_addr; char ip_str[16]; inet_ntop(AF_INET, &addr, ip_str, sizeof(ip_str)); - std::string_view npid(sp->npid.handle.data); - - sign_log.trace("SP %s from %s:%d(npid: %s)", sp->command, ip_str, op_port, npid); + sign_log.trace("SP %s from %s:%d(npid: %s)", sp->command, ip_str, op_port, np::npid_to_string(sp->npid)); } bool reply = false, schedule_repeat = false; @@ -675,9 +673,7 @@ std::shared_ptr signaling_handler::get_signaling_ptr(const signa { u32 conn_id; - char npid_buf[17]{}; - memcpy(npid_buf, sp->npid.handle.data, 16); - std::string npid(npid_buf); + std::string npid = np::npid_to_string(sp->npid); if (!npid_to_conn_id.contains(npid)) return nullptr; From 48667d68b2165e295e0c9a8e2d97ff94afad6cd8 Mon Sep 17 00:00:00 2001 From: Ani Date: Fri, 20 Feb 2026 12:33:54 +0100 Subject: [PATCH 55/69] rpcs3: Target c++23 --- CMakeLists.txt | 8 ++++---- rpcs3/CMakeLists.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 65e415bdb7..738d9bbf17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,12 +13,12 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 11) - message(FATAL_ERROR "RPCS3 requires at least gcc-11.") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13) + message(FATAL_ERROR "RPCS3 requires at least gcc-13.") endif() elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") - if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0) - message(FATAL_ERROR "RPCS3 requires at least clang-12.0.") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 19.0) + message(FATAL_ERROR "RPCS3 requires at least clang-19.0.") endif() endif() diff --git a/rpcs3/CMakeLists.txt b/rpcs3/CMakeLists.txt index e32597f792..41597127b8 100644 --- a/rpcs3/CMakeLists.txt +++ b/rpcs3/CMakeLists.txt @@ -8,7 +8,7 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/git-version.cmake) include(ConfigureCompiler) include(CheckFunctionExists) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) if(UNIX AND NOT APPLE AND NOT ANDROID) add_compile_definitions(DATADIR="${CMAKE_INSTALL_FULL_DATADIR}/rpcs3") From 6de8861b4eeb32c1adea3180c45c2f4aa9ed1a1e Mon Sep 17 00:00:00 2001 From: Ani Date: Fri, 20 Feb 2026 13:21:31 +0100 Subject: [PATCH 56/69] rpcs3: Target c++23 (msvc) --- buildfiles/msvc/common_default.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildfiles/msvc/common_default.props b/buildfiles/msvc/common_default.props index 04f6502839..bfddbb5465 100644 --- a/buildfiles/msvc/common_default.props +++ b/buildfiles/msvc/common_default.props @@ -13,7 +13,7 @@ stdcpplatest - stdcpp20 + stdcpp23 _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING=1;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions) false -d2FH4- %(AdditionalOptions) From fec2be87f615e1ca912f05aaacebc6bfe7075e64 Mon Sep 17 00:00:00 2001 From: Ani Date: Fri, 20 Feb 2026 13:40:00 +0100 Subject: [PATCH 57/69] types.hpp: #include Co-Authored-By: RipleyTom --- rpcs3/util/types.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/rpcs3/util/types.hpp b/rpcs3/util/types.hpp index 4a2ef5baea..a1a07bac69 100644 --- a/rpcs3/util/types.hpp +++ b/rpcs3/util/types.hpp @@ -13,6 +13,7 @@ #include #include #include +#include #if defined(__SSE2__) || defined(_M_X64) || defined(_M_AMD64) || defined(__x86_64__) || defined(__amd64__) #define ARCH_X64 1 From c7d7f2d03b7a613b28117e8eca618541db8d8b6b Mon Sep 17 00:00:00 2001 From: Ani Date: Fri, 20 Feb 2026 14:15:39 +0100 Subject: [PATCH 58/69] Thread: Move include to .h Co-Authored-By: RipleyTom --- Utilities/Thread.cpp | 1 - Utilities/Thread.h | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Utilities/Thread.cpp b/Utilities/Thread.cpp index 810b8fd7c5..08dce12315 100644 --- a/Utilities/Thread.cpp +++ b/Utilities/Thread.cpp @@ -8,7 +8,6 @@ #include "Emu/RSX/RSXThread.h" #include "Thread.h" #include "Utilities/JIT.h" -#include #include #ifdef ARCH_ARM64 diff --git a/Utilities/Thread.h b/Utilities/Thread.h index 02e5db56ff..7cd9a7c7ea 100644 --- a/Utilities/Thread.h +++ b/Utilities/Thread.h @@ -4,6 +4,7 @@ #include "util/atomic.hpp" #include "util/shared_ptr.hpp" +#include #include // Hardware core layout From e075170cc485b803a9d91f67728cd6349a9b10da Mon Sep 17 00:00:00 2001 From: Ani Date: Fri, 20 Feb 2026 14:17:41 +0100 Subject: [PATCH 59/69] JITASM: include on APPLE Co-Authored-By: RipleyTom --- Utilities/JITASM.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Utilities/JITASM.cpp b/Utilities/JITASM.cpp index acb5f40b04..90c09bb0bf 100644 --- a/Utilities/JITASM.cpp +++ b/Utilities/JITASM.cpp @@ -14,6 +14,10 @@ #define CAN_OVERCOMMIT #endif +#if defined(__APPLE__) +#include +#endif + LOG_CHANNEL(jit_log, "JIT"); void jit_announce(uptr func, usz size, std::string_view name) From bfea622999c9207c3a2d1561fecc61c4f92622d0 Mon Sep 17 00:00:00 2001 From: Ani Date: Fri, 20 Feb 2026 14:50:35 +0100 Subject: [PATCH 60/69] sys_time: include on NOT WIN32 --- rpcs3/Emu/Cell/lv2/sys_time.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/rpcs3/Emu/Cell/lv2/sys_time.cpp b/rpcs3/Emu/Cell/lv2/sys_time.cpp index 539bb9dedf..a71a11ba31 100644 --- a/rpcs3/Emu/Cell/lv2/sys_time.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_time.cpp @@ -118,6 +118,7 @@ static int clock_gettime(int clk_id, struct timespec* tp) #ifndef _WIN32 +#include #include static struct timespec start_time = []() From 89efca9f63c14d631bb07025ff83158ebad6a7be Mon Sep 17 00:00:00 2001 From: Ani Date: Fri, 20 Feb 2026 15:26:22 +0100 Subject: [PATCH 61/69] system_utils: include --- rpcs3/Emu/system_utils.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/rpcs3/Emu/system_utils.hpp b/rpcs3/Emu/system_utils.hpp index d8f4f59d8a..fd8717f4f9 100644 --- a/rpcs3/Emu/system_utils.hpp +++ b/rpcs3/Emu/system_utils.hpp @@ -3,6 +3,7 @@ #include "util/types.hpp" #include #include +#include enum class game_content_type { From 6bfb33279f6989f3ff9ea2dc40ef45d6aeb57ef8 Mon Sep 17 00:00:00 2001 From: Ani Date: Fri, 20 Feb 2026 16:11:11 +0100 Subject: [PATCH 62/69] test_fmt: include --- rpcs3/tests/test_fmt.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/rpcs3/tests/test_fmt.cpp b/rpcs3/tests/test_fmt.cpp index 95c31d17c6..069051bd1a 100644 --- a/rpcs3/tests/test_fmt.cpp +++ b/rpcs3/tests/test_fmt.cpp @@ -1,3 +1,4 @@ +#include #include #include "Utilities/StrUtil.h" From 3585881a6c6d8a5fad78ec1949be89a7a20b9be3 Mon Sep 17 00:00:00 2001 From: oltolm Date: Sun, 22 Feb 2026 01:59:55 +0100 Subject: [PATCH 63/69] cellSaveData: fix "Your comparator is not a valid strict-weak ordering" --- rpcs3/Emu/Cell/Modules/cellSaveData.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp index a4d160fdcb..7878e86642 100644 --- a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp @@ -876,39 +876,42 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v // Sort the entries { - const u32 order = setList->sortOrder; const u32 type = setList->sortType; - std::sort(save_entries.begin(), save_entries.end(), [order, type](const SaveDataEntry& entry1, const SaveDataEntry& entry2) -> bool + auto comp = [type](const SaveDataEntry& entry1, const SaveDataEntry& entry2) -> bool { const bool mtime_lower = entry1.mtime < entry2.mtime; const bool mtime_equal = entry1.mtime == entry2.mtime; const bool subtitle_lower = entry1.subtitle < entry2.subtitle; const bool subtitle_equal = entry1.subtitle == entry2.subtitle; - const bool revert_order = order == CELL_SAVEDATA_SORTORDER_DESCENT; if (type == CELL_SAVEDATA_SORTTYPE_MODIFIEDTIME) { if (mtime_equal) { - return subtitle_lower != revert_order; + return subtitle_lower; } - return mtime_lower != revert_order; + return mtime_lower; } else if (type == CELL_SAVEDATA_SORTTYPE_SUBTITLE) { if (subtitle_equal) { - return mtime_lower != revert_order; + return mtime_lower; } - return subtitle_lower != revert_order; + return subtitle_lower; } ensure(false); return true; - }); + }; + + if (setList->sortOrder == CELL_SAVEDATA_SORTORDER_ASCENT) + std::sort(save_entries.begin(), save_entries.end(), comp); + else + std::sort(save_entries.rbegin(), save_entries.rend(), comp); } // Fill the listGet->dirList array From e22c83f4cec4b3f70815ffc780ffa22b6d91818c Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 21 Feb 2026 14:03:04 +0300 Subject: [PATCH 64/69] rsx: Improve comments/documentation around border color SEXT convert --- rpcs3/Emu/RSX/RSXTexture.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/rpcs3/Emu/RSX/RSXTexture.cpp b/rpcs3/Emu/RSX/RSXTexture.cpp index b3f9dc1962..e6483b51d7 100644 --- a/rpcs3/Emu/RSX/RSXTexture.cpp +++ b/rpcs3/Emu/RSX/RSXTexture.cpp @@ -315,6 +315,7 @@ namespace rsx // Border color is broken on PS3. The SNORM behavior is completely broken and behaves like BIASED renormalization instead. // To solve the mismatch, we need to first do a bit expansion on the value then store it as sign extended. The second part is a natural part of numbers on a binary system, so we only need to do the former. + // Note that the input color is in BE order (BGRA) so we reverse the mask to match. static constexpr u32 expand4_lut[16] = { 0x00000000u, // 0000 @@ -335,7 +336,7 @@ namespace rsx 0xFFFFFFFFu // 1111 }; - // Bit pattern expand and reverse BE -> LE using LUT + // Bit pattern expand const u32 mask = expand4_lut[sext]; // Now we perform the compensation operation @@ -344,16 +345,14 @@ namespace rsx // Load const __m128i _0 = _mm_setzero_si128(); const __m128i _128 = _mm_set1_epi32(128); - const __m128i _127 = _mm_set1_epi32(127); - const __m128i _255 = _mm_set1_epi32(255); - const auto be_raw = be_t(raw); - __m128i v = _mm_cvtsi32_si128(static_cast(be_raw)); + // Explode the bytes. + __m128i v = _mm_cvtsi32_si128(raw); v = _mm_unpacklo_epi8(v, _0); - v = _mm_unpacklo_epi16(v, _0); // [ 0, 64, 255, 128 ] + v = _mm_unpacklo_epi16(v, _0); // Conversion: x = (y - 128) - v = _mm_sub_epi32(v, _128); // [ -128, -64, 127, 0 ] + v = _mm_sub_epi32(v, _128); // Convert to signed encoding (reverse sext) v = _mm_slli_epi32(v, 24); From 2b7bb9ef0ac7f7559822b6dd30a6cf2cb7eb065a Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 21 Feb 2026 16:49:01 +0300 Subject: [PATCH 65/69] rsx: Add format_ex utility --- rpcs3/Emu/RSX/Common/TextureUtils.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/rpcs3/Emu/RSX/Common/TextureUtils.h b/rpcs3/Emu/RSX/Common/TextureUtils.h index 65f4ef5c6f..c32152fecd 100644 --- a/rpcs3/Emu/RSX/Common/TextureUtils.h +++ b/rpcs3/Emu/RSX/Common/TextureUtils.h @@ -137,6 +137,22 @@ namespace rsx using enum format_features; + struct texture_format_ex + { + texture_format_ex() = default; + texture_format_ex(u32 bits) + : format_bits(bits) + {} + + bool valid() const { return format_bits != 0; } + u32 format() const { return format_bits & ~(CELL_GCM_TEXTURE_LN | CELL_GCM_TEXTURE_UN); } + + //private: + u32 format_bits = 0; + u32 features = 0; + u32 texel_remap_control = 0; + }; + // Sampled image descriptor class sampled_image_descriptor_base { @@ -179,6 +195,7 @@ namespace rsx u64 surface_cache_tag = 0; texcoord_xform_t texcoord_xform; + texture_format_ex format_ex; }; struct typeless_xfer From 2d7b72a769b4085b893bfa3430fe7ac66c7f8f2b Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 21 Feb 2026 17:44:06 +0300 Subject: [PATCH 66/69] rsx: Implement format_ex decoding for FS inputs --- rpcs3/Emu/RSX/RSXTexture.cpp | 61 ++++++++++++++++++++++++++++++++++++ rpcs3/Emu/RSX/RSXTexture.h | 3 ++ rpcs3/Emu/RSX/color_utils.h | 28 +++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/rpcs3/Emu/RSX/RSXTexture.cpp b/rpcs3/Emu/RSX/RSXTexture.cpp index e6483b51d7..6f8eec0e3b 100644 --- a/rpcs3/Emu/RSX/RSXTexture.cpp +++ b/rpcs3/Emu/RSX/RSXTexture.cpp @@ -2,6 +2,8 @@ #include "RSXTexture.h" #include "rsx_utils.h" +#include "Common/TextureUtils.h" +#include "Program/GLSLCommon.h" #include "Emu/system_config.h" #include "util/simd.hpp" @@ -63,6 +65,65 @@ namespace rsx return ((registers[NV4097_SET_TEXTURE_FORMAT + (m_index * 8)] >> 8) & 0xff); } + texture_format_ex fragment_texture::format_ex() const + { + const auto format_bits = format(); + const auto base_format = format_bits & ~(CELL_GCM_TEXTURE_UN | CELL_GCM_TEXTURE_LN); + const auto format_features = rsx::get_format_features(base_format); + if (format_features == 0) + { + return { format_bits }; + } + + // NOTE: The unsigned_remap=bias flag being set flags the texture as being compressed normal (2n-1 / BX2) (UE3) + // NOTE: The ARGB8_signed flag means to reinterpret the raw bytes as signed. This is different than unsigned_remap=bias which does range decompression. + // This is a separate method of setting the format to signed mode without doing so per-channel + // Precedence = SNORM > GAMMA > UNSIGNED_REMAP/BX2 + // Games using mixed flags: (See Resistance 3 for GAMMA/BX2 relationship, UE3 for BX2 effect) + u32 argb_signed_ = 0; + u32 unsigned_remap_ = 0; + u32 gamma_ = 0; + + if (format_features & RSX_FORMAT_FEATURE_SIGNED_COMPONENTS) + { + // Tests show this is applied pre-readout. It's just a property of the incoming bytes and is therefore subject to remap. + argb_signed_ = decoded_remap().shuffle_mask_bits(argb_signed()); + } + + if (format_features & RSX_FORMAT_FEATURE_GAMMA_CORRECTION) + { + // Tests show this is applied post-readout. It's a property of the final value stored in the register and is not remapped. + // NOTE: GAMMA correction has no algorithmic effect on constants (0 and 1) so we need not mask it out for correctness. + gamma_ = gamma() & ~(argb_signed_); + } + + if (format_features & RSX_FORMAT_FEATURE_BIASED_NORMALIZATION) + { + // The renormalization flag applies to all channels. It is weaker than the other flags. + // This applies on input and is subject to remap overrides + if (unsigned_remap() == CELL_GCM_TEXTURE_UNSIGNED_REMAP_BIASED) + { + unsigned_remap_ = decoded_remap().shuffle_mask_bits(0xFu) & ~(argb_signed_ | gamma_); + } + } + + u32 format_convert = gamma_; + + // The options are mutually exclusive + ensure((argb_signed_ & gamma_) == 0); + ensure((argb_signed_ & unsigned_remap_) == 0); + ensure((gamma_ & unsigned_remap_) == 0); + + // NOTE: Hardware tests show that remapping bypasses the channel swizzles completely + format_convert |= (argb_signed_ << texture_control_bits::SEXT_OFFSET); + format_convert |= (unsigned_remap_ << texture_control_bits::EXPAND_OFFSET); + + texture_format_ex result { format_bits }; + result.features = format_features; + result.texel_remap_control = format_convert; + return result; + } + bool fragment_texture::is_compressed_format() const { int texture_format = format() & ~(CELL_GCM_TEXTURE_LN | CELL_GCM_TEXTURE_UN); diff --git a/rpcs3/Emu/RSX/RSXTexture.h b/rpcs3/Emu/RSX/RSXTexture.h index 5517a39e3f..3b0ccdebce 100644 --- a/rpcs3/Emu/RSX/RSXTexture.h +++ b/rpcs3/Emu/RSX/RSXTexture.h @@ -4,6 +4,8 @@ namespace rsx { + struct texture_format_ex; + class fragment_texture { protected: @@ -33,6 +35,7 @@ namespace rsx // cubemap as a separate dimension. rsx::texture_dimension_extended get_extended_texture_dimension() const; u8 format() const; + texture_format_ex format_ex() const; bool is_compressed_format() const; u16 mipmap() const; diff --git a/rpcs3/Emu/RSX/color_utils.h b/rpcs3/Emu/RSX/color_utils.h index fd3156bd3e..40335c07e3 100644 --- a/rpcs3/Emu/RSX/color_utils.h +++ b/rpcs3/Emu/RSX/color_utils.h @@ -39,6 +39,34 @@ namespace rsx return remapped; } + + /** + * Remap color channel bits based on a remap vector. The output is a normalized selector of each color channel with spread. + * The input bits are an action selector. e.g a mask of channels that need to be interpreted as SNORM or BX2 + * The output is a final mask on which post-sampling channels the operation applies to. + * Examples: + * - If we have remap as [ 1 R R R ] and mask of R (0010) then we get 1110. Remapper spreads 'R' action to all channels where it should apply. + */ + u32 shuffle_mask_bits(u32 bits) const + { + if (!bits || encoded == RSX_TEXTURE_REMAP_IDENTITY) [[likely]] + { + return bits; + } + + u32 result = 0; + for (u8 channel = 0; channel < 4; ++channel) + { + if (control_map[channel] != CELL_GCM_TEXTURE_REMAP_REMAP || // Channel not read from input + (bits & (1u << channel_map[channel])) == 0) // Input channel is not enabled in the mask + { + continue; + } + result |= (1u << channel); + } + return result; + } + template requires std::is_integral_v || std::is_floating_point_v std::array remap(const std::array& components) const From f5cf818bcc02ce940e38cfd772ca1681f5b66054 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 21 Feb 2026 17:47:46 +0300 Subject: [PATCH 67/69] rsx: Disable interpolation for SNORM emulated textures --- rpcs3/Emu/RSX/GL/GLDraw.cpp | 16 ++++++-- rpcs3/Emu/RSX/RSXThread.cpp | 77 ++----------------------------------- rpcs3/Emu/RSX/VK/VKDraw.cpp | 8 +++- 3 files changed, 22 insertions(+), 79 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLDraw.cpp b/rpcs3/Emu/RSX/GL/GLDraw.cpp index e8ea4bc714..475507ee78 100644 --- a/rpcs3/Emu/RSX/GL/GLDraw.cpp +++ b/rpcs3/Emu/RSX/GL/GLDraw.cpp @@ -2,6 +2,7 @@ #include "GLGSRender.h" #include "../rsx_methods.h" #include "../Common/BufferUtils.h" +#include "../Program/GLSLCommon.h" #include "Emu/RSX/NV47/HW/context_accessors.define.h" @@ -315,6 +316,8 @@ void GLGSRender::load_texture_env() if (sampler_state->validate()) { + sampler_state->format_ex = tex.format_ex(); + if (m_textures_dirty[i]) { m_fs_sampler_states[i].apply(tex, fs_sampler_state[i].get()); @@ -324,12 +327,17 @@ void GLGSRender::load_texture_env() m_graphics_state |= rsx::fragment_program_state_dirty; } - if (const auto texture_format = tex.format() & ~(CELL_GCM_TEXTURE_UN | CELL_GCM_TEXTURE_LN); - sampler_state->format_class != rsx::classify_format(texture_format) && - (texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8)) + const auto texture_format = sampler_state->format_ex.format(); + // Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking. + // If accurate graphics are desired, force a bitcast to COLOR as a workaround. + const bool is_depth_reconstructed = sampler_state->format_class != rsx::classify_format(texture_format) && + (texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8); + // SNORM conversion required in shader. Do not interpolate to avoid introducing discontinuities due to how negative numbers work + const bool is_snorm = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) != 0; + + if (is_depth_reconstructed || is_snorm) { // Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking. - // If accurate graphics are desired, force a bitcast to COLOR as a workaround. m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MIN_FILTER, GL_NEAREST); m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MAG_FILTER, GL_NEAREST); } diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 5b4b732c4d..5bfc60ee2a 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -2321,83 +2321,14 @@ namespace rsx } } - if (const auto format_features = rsx::get_format_features(format); format_features != 0) + if (const auto& format_ex = sampler_descriptors[i]->format_ex; format_ex.features != 0) { - // NOTE: The unsigned_remap=bias flag being set flags the texture as being compressed normal (2n-1 / BX2) (UE3) - // NOTE: The ARGB8_signed flag means to reinterpret the raw bytes as signed. This is different than unsigned_remap=bias which does range decompression. - // This is a separate method of setting the format to signed mode without doing so per-channel - // Precedence = SNORM > GAMMA > UNSIGNED_REMAP/BX2 - // Games using mixed flags: (See Resistance 3 for GAMMA/BX2 relationship, UE3 for BX2 effect) - u32 argb8_signed = 0; - u32 unsigned_remap = 0; - u32 gamma = 0; - - auto remap_channel_bits = [](const rsx::texture_channel_remap_t& remap, u32 bits) -> u32 - { - if (!bits || remap.encoded == RSX_TEXTURE_REMAP_IDENTITY) [[ likely ]] - { - return bits; - } - - u32 result = 0; - for (u8 channel = 0; channel < 4; ++channel) - { - switch (remap.control_map[channel]) - { - case CELL_GCM_TEXTURE_REMAP_REMAP: - if (bits & (1u << remap.channel_map[channel])) - { - result |= (1u << channel); - } - break; - default: - break; - } - } - return result; - }; - - const auto texture_remap = tex.decoded_remap(); - if (format_features & RSX_FORMAT_FEATURE_SIGNED_COMPONENTS) - { - // Tests show this is applied pre-readout. It's just a property of the incoming bytes and is therefore subject to remap. - argb8_signed = remap_channel_bits(texture_remap, tex.argb_signed()); - } - - if (format_features & RSX_FORMAT_FEATURE_GAMMA_CORRECTION) - { - // Tests show this is applied post-readout. It's a property of the final value stored in the register and is not remapped. - // NOTE: GAMMA correction has no algorithmic effect on constants (0 and 1) so we need not mask it out for correctness. - gamma = tex.gamma() & ~(argb8_signed); - } - - if (format_features & RSX_FORMAT_FEATURE_BIASED_NORMALIZATION) - { - // The renormalization flag applies to all channels. It is weaker than the other flags. - // This applies on input and is subject to remap overrides - if (tex.unsigned_remap() == CELL_GCM_TEXTURE_UNSIGNED_REMAP_BIASED) - { - unsigned_remap = remap_channel_bits(texture_remap, 0xF) & ~(argb8_signed | gamma); - } - } - - u32 argb8_convert = gamma; - - // The options are mutually exclusive - ensure((argb8_signed & gamma) == 0); - ensure((argb8_signed & unsigned_remap) == 0); - ensure((gamma & unsigned_remap) == 0); - - // NOTE: Hardware tests show that remapping bypasses the channel swizzles completely - argb8_convert |= (argb8_signed << texture_control_bits::SEXT_OFFSET); - argb8_convert |= (unsigned_remap << texture_control_bits::EXPAND_OFFSET); - texture_control |= argb8_convert; - - texture_control |= format_features << texture_control_bits::FORMAT_FEATURES_OFFSET; + texture_control |= format_ex.texel_remap_control; + texture_control |= format_ex.features << texture_control_bits::FORMAT_FEATURES_OFFSET; if (current_fp_metadata.has_tex_bx2_conv) { - const u32 remap_hi = remap_channel_bits(texture_remap, 0xFu); + const u32 remap_hi = tex.decoded_remap().shuffle_mask_bits(0xFu); current_fragment_program.texture_params[i].remap &= ~(0xFu << 16u); current_fragment_program.texture_params[i].remap |= (remap_hi << 16u); } diff --git a/rpcs3/Emu/RSX/VK/VKDraw.cpp b/rpcs3/Emu/RSX/VK/VKDraw.cpp index 491078cc93..7a9d6aae6e 100644 --- a/rpcs3/Emu/RSX/VK/VKDraw.cpp +++ b/rpcs3/Emu/RSX/VK/VKDraw.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "../Common/BufferUtils.h" +#include "../Program/GLSLCommon.h" #include "../rsx_methods.h" #include "VKAsyncScheduler.h" @@ -307,6 +308,8 @@ void VKGSRender::load_texture_env() if (sampler_state->validate()) { + sampler_state->format_ex = tex.format_ex(); + if (sampler_state->is_cyclic_reference) { check_for_cyclic_refs |= true; @@ -324,7 +327,7 @@ void VKGSRender::load_texture_env() f32 min_lod = 0.f, max_lod = 0.f; f32 lod_bias = 0.f; - const u32 texture_format = tex.format() & ~(CELL_GCM_TEXTURE_UN | CELL_GCM_TEXTURE_LN); + const u32 texture_format = sampler_state->format_ex.format(); VkBool32 compare_enabled = VK_FALSE; VkCompareOp depth_compare_mode = VK_COMPARE_OP_NEVER; @@ -350,7 +353,8 @@ void VKGSRender::load_texture_env() if (sampler_state->format_class == RSX_FORMAT_CLASS_COLOR) [[likely]] { // Most PS3-like formats can be linearly filtered without problem - can_sample_linear = true; + // Exclude textures that require SNORM conversion however + can_sample_linear = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) == 0; } else if (sampler_state->format_class != rsx::classify_format(texture_format) && (texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8)) From 3b494202059f7ff94f27b44414d8b173496b31fd Mon Sep 17 00:00:00 2001 From: oltolm Date: Sun, 22 Feb 2026 12:26:27 +0100 Subject: [PATCH 68/69] cellVdec: replace std::aligned_union_t with a union --- rpcs3/Emu/Cell/Modules/cellVdec.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/Cell/Modules/cellVdec.cpp b/rpcs3/Emu/Cell/Modules/cellVdec.cpp index 5cf5e64a51..3205afd786 100644 --- a/rpcs3/Emu/Cell/Modules/cellVdec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellVdec.cpp @@ -1462,7 +1462,12 @@ error_code cellVdecGetPicItem(ppu_thread& ppu, u32 handle, vm::pptr picInfo; + union + { + CellVdecAvcInfo avcInfo; + CellVdecDivxInfo divxInfo; + CellVdecMpeg2Info mpeg2Info; + } picInfo; }; AVFrame* frame{}; From 260c98618614c070067c6b0b1a82f2e8199d7d4a Mon Sep 17 00:00:00 2001 From: silviolet Date: Wed, 11 Feb 2026 13:27:40 -0500 Subject: [PATCH 69/69] Implemented sorted emit_data for YAML nodes - emit_data now Recursively sorts each node in a YAML node and outputs them as per the function description, originally a TODO - The default config is still unsorted so when you click save or apply the sorted file will appear --- rpcs3/rpcs3qt/emu_settings.cpp | 37 ++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 20bb58ba93..ccb2308509 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -25,8 +25,41 @@ namespace { static NEVER_INLINE void emit_data(YAML::Emitter& out, const YAML::Node& node) { - // TODO - out << node; + if (!node || node.IsNull()) + { + // I chose to output a null when nothing is present so that recursive YAML Value calls can be matched to a null value instead of nothing + out << YAML::Null; + return; + } + if (node.IsMap()) + { + std::vector keys; + keys.reserve(node.size()); + // generate vector of strings to be sorted using the as function from YAML documentation + for (const auto& pair : node) + { + keys.push_back(pair.first.as()); + } + std::sort(keys.begin(), keys.end()); + // recursively generate sorted maps + // alternative implementations could have stops at specified recursion levels or maybe just the first two levels would be sorted + out << YAML::BeginMap; + for (const std::string& key : keys) + { + out << YAML::Key << key; + out << YAML::Value; + emit_data(out, node[key]); + } + out << YAML::EndMap; + } + // alternatively: an else statement could be used however I wanted to follow a similar format to the += operator so the YAML Undefined class can be ignored + else if (node.IsScalar() || node.IsSequence()) + { + out << node; + } + // this exists to preserve the same functionality as before where Undefined nodes would still be output, can be removed or consolidated with the else if branch + else + out << node; } // Incrementally load YAML