From 74bcbd9816345d0811491011360c7406ad97dab4 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 28 Apr 2026 10:05:24 +0200 Subject: [PATCH 001/283] Update 7zip to 26.01 --- 3rdparty/7zip/7zip | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/7zip/7zip b/3rdparty/7zip/7zip index 839151eaaa..8c63d71ff8 160000 --- a/3rdparty/7zip/7zip +++ b/3rdparty/7zip/7zip @@ -1 +1 @@ -Subproject commit 839151eaaad24771892afaae6bac690e31e58384 +Subproject commit 8c63d71ff886bda90c86db28466287f977374237 From e05d35972192f7cb9a3af39dac83d6bd402c6861 Mon Sep 17 00:00:00 2001 From: shinra-electric <50119606+shinra-electric@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:12:20 +0100 Subject: [PATCH 002/283] Bump setup-nuget to v4 --- .github/workflows/rpcs3.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index eb42b7aae1..2738698fb4 100644 --- a/.github/workflows/rpcs3.yml +++ b/.github/workflows/rpcs3.yml @@ -235,7 +235,7 @@ jobs: fetch-depth: 0 - name: Setup NuGet - uses: nuget/setup-nuget@v2 + uses: nuget/setup-nuget@v4 - name: Restore NuGet packages run: nuget restore rpcs3.sln From b212935c702d228b29bbacf11f6fe02e86b655d8 Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Wed, 29 Apr 2026 04:42:49 +0200 Subject: [PATCH 003/283] Add support to play from a BD Drive (currently only on Windows) (#18648) Follow up of #18345 to add support (currently only on `Windows`; see notes below) for playing a PS3 disc game directly from a Blu-Ray Disc Drive. ### HOW IT WORKS: - The BD drive can be added as any other game so from `VFS games` or from `Add Games` menu. In case it is selected from `VFS games`, any attempt to write files is discarded, e.g. file `Disc Games Can Be Put Here For Automatic Detection.txt` - It scans the default redump keys folder `/data/redump` (it currently needs to be manually created due it is not yet provided by rpcs3 installation) to find a matching decryption key ### NOTES: - Support is currently provided on `Windows` where I can fully test it. I cannot test under other OS. However, the additions needed for the other OS are limited only on `fs::file.h/cpp`. In particular inside the following new functions: - `bool is_optical_raw_device(const std::string& path);` - `bool get_optical_raw_device(const std::string& path, std::string* raw_device = nullptr);` - Icons etc. are always refreshed (ISO cache cannot be used due `mtime` on raw device is not available and any cache check would always fail) - Code in `ISO.h/cpp` needed some rework to properly manage a read on a raw device (alignment on offset, size and memory is mandatory). The BD drive needs to be detected as a file, not as a folder ### MINOR FIXES: - Fixed wrong specifier used in logging on `ISO.h/cpp` --- Utilities/File.cpp | 106 ++++- Utilities/File.h | 6 + rpcs3/Emu/System.cpp | 22 +- rpcs3/Loader/ISO.cpp | 626 +++++++++++++++++++++++------- rpcs3/Loader/ISO.h | 41 +- rpcs3/rpcs3qt/game_list_frame.cpp | 31 +- rpcs3/rpcs3qt/game_list_grid.cpp | 2 +- rpcs3/rpcs3qt/game_list_table.cpp | 8 +- rpcs3/rpcs3qt/main_window.cpp | 2 +- rpcs3/rpcs3qt/qt_utils.cpp | 16 +- rpcs3/rpcs3qt/qt_video_source.cpp | 12 +- rpcs3/rpcs3qt/shortcut_utils.cpp | 8 +- 12 files changed, 668 insertions(+), 212 deletions(-) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index aff4537dea..d58331e0e2 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -38,6 +38,17 @@ static std::unique_ptr to_wchar(std::string_view source) // Buffer for max possible output length std::unique_ptr buffer(new wchar_t[buf_size + 8 + 32768]); + // If path points to an optical raw device, copy it AS IS + if (fs::is_optical_raw_device(std::string(source))) + { + ensure(MultiByteToWideChar(CP_UTF8, 0, source.data(), size, buffer.get() + 32768, size)); // "to_wchar" + + // Canonicalize wide path (replace '/', ".", "..", \\ repetitions, etc) + ensure(GetFullPathNameW(buffer.get() + 32768, 32768, buffer.get(), nullptr) - 1 < 32768 - 1); // "to_wchar" + + return buffer; + } + // Prepend wide path prefix (4 characters) std::memcpy(buffer.get() + 32768, L"\\\\\?\\", 4 * sizeof(wchar_t)); @@ -400,11 +411,12 @@ namespace fs class windows_file final : public file_base { HANDLE m_handle; + bool m_raw_device; atomic_t m_pos {0}; public: - windows_file(HANDLE handle) - : m_handle(handle) + windows_file(HANDLE handle, bool raw_device = false) + : m_handle(handle), m_raw_device(raw_device) { } @@ -564,11 +576,20 @@ 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" + if (!m_raw_device) + { + // NOTE: this can fail if we access a mounted empty drive (e.g. after unmounting an iso). + LARGE_INTEGER size; - return size.QuadPart; + ensure(GetFileSizeEx(m_handle, &size)); // "file::size" + return size.QuadPart; + } + + // For a raw device, we need to use DeviceIoControl. + DISK_GEOMETRY_EX geometry; + + ensure(DeviceIoControl(m_handle, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, nullptr, 0, &geometry, sizeof(geometry), nullptr, nullptr)); + return geometry.DiskSize.QuadPart; } native_handle get_handle() override @@ -1091,6 +1112,68 @@ bool fs::is_symlink(const std::string& path) return true; } +bool fs::is_optical_raw_device(const std::string& path) +{ +#ifdef _WIN32 + if (path.starts_with("\\\\.\\")) + { + return true; + } + + return false; +#endif + return false; +} + +bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device) +{ + if (fs::is_optical_raw_device(path)) + { + if (raw_device) + { + *raw_device = path; + } + + return true; + } + +#ifdef _WIN32 + constexpr u32 BUF_SIZE = 1000; + WCHAR drive_list[BUF_SIZE] = {0}; + + // GetLogicalDriveStrings() returns a double-null terminated list of null-terminated strings. + // E.g. A:\B:\C:\ + const DWORD copied = GetLogicalDriveStrings(BUF_SIZE, drive_list); + + if (copied == 0 || copied > BUF_SIZE) + { + return false; + } + + for (const WCHAR* drive = drive_list; drive && *drive; drive += wcslen(drive) + 1) + { + if (GetDriveType(drive) == DRIVE_CDROM) + { + const std::wstring ws(drive); + const std::string s = std::string(ws.begin(), ws.end() - 1); + + if (path.starts_with(s)) + { + if (raw_device) + { + *raw_device = "\\\\.\\" + s; + } + + return true; + } + } + } + + return false; +#endif + return false; +} + bool fs::statfs(const std::string& path, fs::device_stat& info) { if (auto device = get_virtual_device(path)) @@ -1658,9 +1741,18 @@ fs::file::file(const std::string& path, bs_t mode) return; } + // If path points to an optical raw device, complete the file opening + // (the following GetFileInformationByHandle() would always fail on a raw device). + if (is_optical_raw_device(path)) + { + m_file = std::make_unique(handle, true); + 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)) { const DWORD last_error = GetLastError(); @@ -1671,7 +1763,7 @@ fs::file::file(const std::string& path, bs_t mode) g_tls_error = fs::error::isdir; return; } - + g_tls_error = to_error(last_error); return; } diff --git a/Utilities/File.h b/Utilities/File.h index 3d332dd0be..778bfacd48 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -213,6 +213,12 @@ namespace fs // Check whether the path points to an existing symlink bool is_symlink(const std::string& path); + // Check whether the path points to a raw device + bool is_optical_raw_device(const std::string& path); + + // Check whether the path points to an optical drive. If so, provide the raw device in "raw_device" if requested + bool get_optical_raw_device(const std::string& path, std::string* raw_device = nullptr); + // Get filesystem information bool statfs(const std::string& path, device_stat& info); diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 492aed799a..7f0437648b 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -638,7 +638,7 @@ void Emulator::Init() const std::string games_common_dir = g_cfg_vfs.get(g_cfg_vfs.games_dir, emu_dir); - if (make_path_verbose(games_common_dir, true)) + if (!is_iso_file(games_common_dir) && make_path_verbose(games_common_dir, true)) { fs::write_file(games_common_dir + "/Disc Games Can Be Put Here For Automatic Detection.txt", fs::create + fs::excl + fs::write, ""s); @@ -985,7 +985,7 @@ game_boot_result Emulator::BootGame(const std::string& path, const std::string& m_db_config = db_config; // Handle files and special paths inside Load unmodified - if (direct || !fs::is_dir(path)) + if (direct || !fs::is_dir(path) || fs::get_optical_raw_device(path)) { m_path = path; @@ -1201,7 +1201,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, std::string disc_info; m_ar->serialize(argv.emplace_back(), disc_info, klic.emplace_back(), m_game_dir, hdd1); - launching_from_disc_archive = is_file_iso(disc_info); + launching_from_disc_archive = is_iso_file(disc_info); sys_log.notice("Savestate: is iso archive = %d ('%s')", launching_from_disc_archive, disc_info); @@ -1218,7 +1218,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, // Load /dev_bdvd/ from game list if available if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty()) { - if (is_file_iso(game_path)) + if (is_iso_file(game_path)) { game_path = iso_device::virtual_device_name + "/PS3_GAME/./"; } @@ -1389,7 +1389,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, title_path = std::move(game_path); } - if (is_file_iso(title_path)) + if (is_iso_file(title_path)) { m_path = std::move(title_path); ok = true; @@ -1480,7 +1480,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, } const std::string resolved_path = GetCallbacks().resolve_path(m_path); - if (!launching_from_disc_archive && is_file_iso(m_path)) + if (!launching_from_disc_archive && is_iso_file(m_path)) { sys_log.notice("Loading iso archive '%s'", m_path); @@ -1933,7 +1933,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, // Load /dev_bdvd/ from game list if available if (std::string game_path = m_games_config.get_path(m_title_id); !game_path.empty()) { - if (is_file_iso(game_path)) + if (is_iso_file(game_path)) { sys_log.notice("Loading iso archive for patch ('%s')", game_path); @@ -4237,7 +4237,7 @@ u32 Emulator::AddGamesFromDir(const std::string& path) // search direct subdirectories, that way we can drop one folder containing all games for (; path_it != entries.end(); ++path_it) { - auto dir_entry = std::move(*path_it); + const auto dir_entry = std::move(*path_it); if (dir_entry.name == "." || dir_entry.name == "..") { @@ -4246,7 +4246,7 @@ u32 Emulator::AddGamesFromDir(const std::string& path) const std::string dir_path = path + '/' + dir_entry.name; - if (!dir_entry.is_directory && !is_file_iso(dir_path)) + if (!dir_entry.is_directory && !is_iso_file(dir_path)) { continue; } @@ -4283,7 +4283,7 @@ u32 Emulator::AddGamesFromDir(const std::string& path) game_boot_result Emulator::AddGame(const std::string& path) { // Handle files directly - if (!fs::is_dir(path)) + if (!fs::is_dir(path) || fs::get_optical_raw_device(path)) { return AddGameToYml(path); } @@ -4354,7 +4354,7 @@ game_boot_result Emulator::AddGameToYml(const std::string& path) } std::unique_ptr archive; - if (is_file_iso(path)) + if (is_iso_file(path)) { archive = std::make_unique(path); } diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index e79373fcb4..f5ba9f839c 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -10,6 +10,7 @@ #include #include #include +#include LOG_CHANNEL(sys_log, "SYS"); LOG_CHANNEL(iso_log, "ISO"); @@ -22,32 +23,83 @@ struct iso_sector u64 address_aligned; u64 offset_aligned; u64 size_aligned; - std::array buf; }; -bool is_file_iso(const std::string& path) +static void* get_aligned_buf() { - if (path.empty() || fs::is_dir(path)) + static thread_local struct aligned_buf { - return false; - } + void* buf; - return is_file_iso(fs::file(path)); + aligned_buf() noexcept + { + // IMPORTANT NOTE: It must be aligned (probably enough on multiple of 4) to support raw device, otherwise any read from file will fail +#if defined(_WIN32) + buf = _aligned_malloc(ISO_SECTOR_SIZE, ISO_SECTOR_SIZE * 2); +#else + buf = std::aligned_alloc(ISO_SECTOR_SIZE * 2, ISO_SECTOR_SIZE); +#endif + } + + ~aligned_buf() noexcept + { +#if defined(_WIN32) + _aligned_free(buf); +#else + std::free(buf); +#endif + } + } s_aligned_buf {}; + + return s_aligned_buf.buf; } -bool is_file_iso(const fs::file& file) +static bool is_iso_file(const fs::file& file, u64* size = nullptr) { - if (!file || file.size() < 32768 + 6) + if (!file || file.size() < 32768ULL + 6) { return false; } - file.seek(32768); - char magic[5]; - file.read_at(32768 + 1, magic, 5); - return magic[0] == 'C' && magic[1] == 'D' && magic[2] == '0' && magic[3] == '0' && magic[4] == '1'; + file.read_at(32768ULL + 1, magic, 5); + + const bool ret = magic[0] == 'C' && magic[1] == 'D' && magic[2] == '0' && magic[3] == '0' && magic[4] == '1'; + + if (size && ret) + { + *size = file.size(); + } + + return ret; +} + +bool is_iso_file(const std::string& path, u64* size, bool* is_raw_device) +{ + if (path.empty()) + { + return false; + } + + std::string new_path = path; + + // "new_path" is updated with the raw device path in case "path" points to a BD drive + const bool raw_device = fs::get_optical_raw_device(path, &new_path); + + if (!raw_device && fs::is_dir(path)) + { + return false; + } + + if (is_raw_device) + { + *is_raw_device = raw_device; + } + + fs::file file(std::make_unique(new_path, fs::read)); + + return is_iso_file(file, size); } // Convert 4 bytes in big-endian format to an unsigned integer @@ -68,7 +120,7 @@ static void reset_iv(std::array& iv, u32 lba) } // Main function that will decrypt the sector(s) -static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u64 size) +static bool decrypt_data(aes_context& aes, u64 offset, const unsigned char* buffer, unsigned char* out_buffer, u64 size) { // The following preliminary checks are good to be provided. // Commented out to gain a bit of performance, just because we know the caller is providing values in the expected range @@ -80,7 +132,7 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u6 //if ((size % 16) != 0) //{ - // iso_log.error("decrypt_data: Requested ciphertext blocks' size must be a multiple of 16 (%ull)", size); + // iso_log.error("decrypt_data: Requested ciphertext blocks' size must be a multiple of 16 (%llu)", size); // return; //} @@ -97,7 +149,7 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u6 // Otherwise, the IV is based on sector's LBA if (sector_offset != 0) { - memcpy(iv.data(), buffer, 16); + std::memcpy(iv.data(), buffer, 16); cur_offset = 16; } else @@ -110,7 +162,7 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u6 cur_size -= cur_offset; // Partial (or even full) first sector - if (aes_crypt_cbc(&aes, AES_DECRYPT, cur_size, iv.data(), &buffer[cur_offset], &buffer[cur_offset]) != 0) + if (aes_crypt_cbc(&aes, AES_DECRYPT, cur_size, iv.data(), &buffer[cur_offset], &out_buffer[cur_offset]) != 0) { iso_log.error("decrypt_data: Error decrypting data on first sector read"); return false; @@ -130,7 +182,7 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u6 { reset_iv(iv, ++cur_sector_lba); // Next sector's IV - if (aes_crypt_cbc(&aes, AES_DECRYPT, ISO_SECTOR_SIZE, iv.data(), &buffer[cur_offset], &buffer[cur_offset]) != 0) + if (aes_crypt_cbc(&aes, AES_DECRYPT, ISO_SECTOR_SIZE, iv.data(), &buffer[cur_offset], &out_buffer[cur_offset]) != 0) { iso_log.error("decrypt_data: Error decrypting data on inner sector(s) read"); return false; @@ -142,7 +194,7 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u6 reset_iv(iv, ++cur_sector_lba); // Next sector's IV // Partial (or even full) last sector - if (aes_crypt_cbc(&aes, AES_DECRYPT, size - cur_offset, iv.data(), &buffer[cur_offset], &buffer[cur_offset]) != 0) + if (aes_crypt_cbc(&aes, AES_DECRYPT, size - cur_offset, iv.data(), &buffer[cur_offset], &out_buffer[cur_offset]) != 0) { iso_log.error("decrypt_data: Error decrypting data on last sector read"); return false; @@ -151,45 +203,9 @@ static bool decrypt_data(aes_context& aes, u64 offset, unsigned char* buffer, u6 return true; } -void iso_file_decryption::reset() +iso_type_status iso_file_decryption::get_key(const std::string& key_path, aes_context* aes_ctx) { - m_enc_type = iso_encryption_type::NONE; - m_region_info.clear(); -} - -iso_type_status iso_file_decryption::check_type(const std::string& path, std::string& key_path, aes_context* aes_ctx) -{ - if (!is_file_iso(path)) - { - return iso_type_status::NOT_ISO; - } - - // Remove file extension from file path - const usz ext_pos = path.rfind('.'); - const std::string name_path = ext_pos == umax ? path : path.substr(0, ext_pos); - - // Detect file name (with no parent folder and no file extension) - const usz name_pos = name_path.rfind('/'); - const std::string name = name_pos == umax ? name_path : name_path.substr(name_pos); - fs::file key_file; - - const std::array key_paths { - name_path + ".dkey", - name_path + ".key", - rpcs3::utils::get_redump_key_dir() + name + ".dkey", - rpcs3::utils::get_redump_key_dir() + name + ".key" - }; - - for (const std::string& path : key_paths) - { - key_file = fs::file(path); - - if (key_file) - { - key_path = path; - break; - } - } + fs::file key_file(key_path); // If no ".dkey" and ".key" file exists if (!key_file) @@ -208,7 +224,7 @@ iso_type_status iso_file_decryption::check_type(const std::string& path, std::st // binary (".key") and so not needing any further conversion from hex string to bytes if (key_len == sizeof(key)) { - memcpy(key.data(), key_str, sizeof(key)); + std::memcpy(key.data(), key_str, sizeof(key)); } else { @@ -234,32 +250,152 @@ iso_type_status iso_file_decryption::check_type(const std::string& path, std::st return iso_type_status::ERROR_PROCESSING_KEY; } -bool iso_file_decryption::init(const std::string& path) +iso_type_status iso_file_decryption::retrieve_key(iso_archive& archive, std::string& key_path, aes_context& aes_ctx) { - reset(); + // + // Find the first existing file in the archive present on the list of well known encrypted files to use for testing a matching key + // - if (!is_file_iso(path)) + const std::map dec_magics { + {"PS3_GAME/LICDIR/LIC.DAT", "PS3LICDA"}, + {"PS3_GAME/USRDIR/EBOOT.BIN", "SCE"} + }; + + iso_fs_node* node = nullptr; + std::string magic_value; + + for (const auto& magic : dec_magics) { - return false; + if (node = archive.retrieve(magic.first)) + { + magic_value = magic.second; + break; + } } + if (!node) + { + return iso_type_status::ERROR_OPENING_KEY; + } + + // + // Read the first encrypted sector to use for testing a matching key + // + + std::array enc_sec; + std::array dec_sec; + iso_file iso_file(archive.path(), fs::read, *node); + + if (!iso_file || iso_file.read(enc_sec.data(), ISO_SECTOR_SIZE) != ISO_SECTOR_SIZE) + { + return iso_type_status::NOT_ISO; + } + + // + // Scan all the key files present in the redump keys folder, decrypt the read sector and test for a match with file's magic value + // + + std::vector entries; + + for (auto&& dir_entry : fs::dir(rpcs3::utils::get_redump_key_dir())) + { + // Prefetch entries, it is unsafe to keep fs::dir for a long time or for many operations + entries.emplace_back(std::move(dir_entry)); + } + + for (auto path_it = entries.begin(); path_it != entries.end(); path_it++) + { + const auto dir_entry = std::move(*path_it); + + if (dir_entry.name == "." || dir_entry.name == ".." || dir_entry.is_directory) + { + continue; + } + + key_path = rpcs3::utils::get_redump_key_dir() + dir_entry.name; + + // If no valid key is present on the file + if (get_key(key_path, &aes_ctx) != iso_type_status::REDUMP_ISO) + { + continue; + } + + // If the decryption fails + if (!decrypt_data(aes_ctx, iso_file.file_offset(0), enc_sec.data(), dec_sec.data(), ISO_SECTOR_SIZE)) + { + continue; + } + + // If the decrypted data match the magic value + if (std::memcmp(magic_value.data(), dec_sec.data(), magic_value.size()) == 0) + { + return iso_type_status::REDUMP_ISO; + } + } + + return iso_type_status::ERROR_OPENING_KEY; +} + +iso_type_status iso_file_decryption::check_type(const std::string& path, std::string& key_path, aes_context* aes_ctx) +{ + if (!is_iso_file(path)) + { + return iso_type_status::NOT_ISO; + } + + // Remove file extension from file path + const usz ext_pos = path.rfind('.'); + const std::string name_path = ext_pos == umax ? path : path.substr(0, ext_pos); + + // Detect file name (with no parent folder and no file extension) + const usz name_pos = name_path.rfind('/'); + const std::string name = name_pos == umax ? name_path : name_path.substr(name_pos); + + const std::array key_paths { + name_path + ".dkey", + name_path + ".key", + rpcs3::utils::get_redump_key_dir() + name + ".dkey", + rpcs3::utils::get_redump_key_dir() + name + ".key" + }; + + for (const std::string& path : key_paths) + { + if (fs::is_file(path)) + { + key_path = path; + return get_key(key_path, aes_ctx); + } + } + + return iso_type_status::ERROR_OPENING_KEY; +} + +bool iso_file_decryption::init(const std::string& path, iso_archive* archive) +{ + // Reset attributes first + m_enc_type = iso_encryption_type::NONE; + m_region_info.clear(); + // // Store the ISO region information (needed by both the "Redump" type (only on "decrypt()" method) and "3k3y" type) // - fs::file iso_file(path); + fs::file iso_file(std::make_unique(path, fs::read)); - if (!iso_file) + if (!is_iso_file(iso_file)) { - iso_log.error("init: Failed to open file: %s", path); + iso_log.error("init: Failed to recognize ISO file: %s", path); return false; } - std::array sec0_sec1 {}; + // Reset the file position after it was changed by is_iso_file() + iso_file.seek(0); + + std::array sec0_sec1; if (iso_file.size() < sec0_sec1.size()) { - iso_log.error("init: Found only %ull sector(s) (minimum required is 2): %s", iso_file.size(), path); + iso_log.error("init: Found only %llu sector(s) (minimum required is 2): %s", iso_file.size(), path); return false; } @@ -279,13 +415,13 @@ bool iso_file_decryption::init(const std::string& path) // Ensure the region count is a proper value if (region_count < 1 || region_count > 127) // It's non-PS3ISO { - iso_log.error("init: Failed to read region information: '%s' (region_count=%d)", path, region_count); + iso_log.error("init: Failed to read region information (region_count=%lu): %s", region_count, path); return false; } m_region_info.resize(region_count * 2 - 1); - for (size_t i = 0; i < m_region_info.size(); ++i) + for (size_t i = 0; i < m_region_info.size(); i++) { // Store the region information in address format m_region_info[i].encrypted = (i % 2 == 1); @@ -298,15 +434,28 @@ bool iso_file_decryption::init(const std::string& path) // Check for Redump type // + iso_type_status status; std::string key_path; - // Try to detect the Redump type. If so, the decryption context is set into "m_aes_dec" - switch (check_type(path, key_path, &m_aes_dec)) + // If raw device and requested by the caller ("archive" provided), scan the redump keys folder and retrieve + // (if present) the first key that allows decrypting a sector of the ISO file + if (fs::is_optical_raw_device(path) && archive) + { + status = retrieve_key(*archive, key_path, m_aes_dec); + } + else + { + // Try to detect the Redump type. If so, the decryption context is set into "m_aes_dec" + status = check_type(path, key_path, &m_aes_dec); + } + + switch (status) { case iso_type_status::NOT_ISO: iso_log.warning("init: Failed to recognize ISO file: %s", path); break; case iso_type_status::REDUMP_ISO: + iso_log.warning("init: Found matching key file: %s", key_path); m_enc_type = iso_encryption_type::REDUMP; // SET ENCRYPTION TYPE: REDUMP break; case iso_type_status::ERROR_OPENING_KEY: @@ -332,12 +481,12 @@ bool iso_file_decryption::init(const std::string& path) static const unsigned char k3k3y_dec_watermark[16] = {0x44, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x20, 0x33, 0x4B, 0x20, 0x42, 0x4C, 0x44}; - if (memcmp(&k3k3y_enc_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_enc_watermark)) == 0) + if (std::memcmp(&k3k3y_enc_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_enc_watermark)) == 0) { // Grab D1 from the 3k3y sector unsigned char key[16]; - memcpy(key, &sec0_sec1[0xF80], 0x10); + std::memcpy(key, &sec0_sec1[0xF80], 0x10); // Convert D1 to KEY and generate the "m_aes_dec" context unsigned char key_d1[] = {0x38, 11, 0xcf, 11, 0x53, 0x45, 0x5b, 60, 120, 0x17, 0xab, 0x4f, 0xa3, 0xba, 0x90, 0xed}; @@ -361,7 +510,7 @@ bool iso_file_decryption::init(const std::string& path) iso_log.error("init: Failed to set encryption type to ENC_3K3Y: %s", path); } } - else if (memcmp(&k3k3y_dec_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_dec_watermark)) == 0) + else if (std::memcmp(&k3k3y_dec_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_dec_watermark)) == 0) { m_enc_type = iso_encryption_type::DEC_3K3Y; // SET ENCRYPTION TYPE: DEC_3K3Y } @@ -425,7 +574,7 @@ bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std: } // Decrypt the region before sending it back - decrypt_data(m_aes_dec, offset, reinterpret_cast(buffer), size); + decrypt_data(m_aes_dec, offset, reinterpret_cast(buffer), reinterpret_cast(buffer), size); return true; } @@ -441,6 +590,159 @@ bool iso_file_decryption::decrypt(u64 offset, void* buffer, u64 size, const std: return true; } +iso_file_encrypted::iso_file_encrypted(const std::string& path, bs_t mode, const iso_fs_node& node, std::shared_ptr dec) + : iso_file(path, mode, node), m_dec(dec) +{ +} + +u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) +{ + // IMPORTANT NOTES: + // - For a raw device, we must use a support buffer aligned (probably enough on multiple of 4), otherwise any read from file will fail. + // For that reason, we don't use directly "buffer" (not guaranteeing any alignment) + // - "iso_file_decryption::decrypt()" method requires that offset and size are multiple of 16 bytes (ciphertext block's size) + // and that a previous ciphertext block (used as IV) is read in case offset is not a multiple of ISO_SECTOR_SIZE + // + // ---------------------------------------------------------------------- + // file on ISO archive: | ' ' | + // ---------------------------------------------------------------------- + // ' ' + // --------------------------------------------- + // buffer: | | + // --------------------------------------------- + // ' ' ' ' + // ------------------------------------------------------------------------------------------------------------------------------------- + // ISO archive: | sec 0 | sec 1 |xxxxx######'###########'###########'###########'##xxxxxxxxx| | ... | sec n-1 | sec n | + // ------------------------------------------------------------------------------------------------------------------------------------- + // 16 Bytes x block read: | | | | | | | '#######'###########'###########'###########'###| | | | | | | | | | | | | | | + // ' ' ' ' + // | first sec | inner sec(s) | last sec | + + const u64 max_size = std::min(size, local_extent_remaining(offset)); + + if (max_size == 0) + { + return 0; + } + + const u64 archive_first_offset = file_offset(offset); + const u64 archive_last_offset = archive_first_offset + max_size - 1; + iso_sector first_sec, last_sec; + void* aligned_buf = get_aligned_buf(); // thread-safe buffer + + first_sec.lba_address = (archive_first_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; + first_sec.offset = archive_first_offset % ISO_SECTOR_SIZE; + first_sec.size = first_sec.offset + max_size <= ISO_SECTOR_SIZE ? max_size : ISO_SECTOR_SIZE - first_sec.offset; + + last_sec.lba_address = last_sec.address_aligned = (archive_last_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; + // last_sec.offset = last_sec.offset_aligned = 0; // Always 0 so no need to set and use those attributes + last_sec.size = (archive_last_offset % ISO_SECTOR_SIZE) + 1; + + // + // First sector + // + + u64 offset_aligned_first_out = 0; + + if (!m_raw_device) + { + const u64 offset_aligned = first_sec.offset & ~0xF; + offset_aligned_first_out = (first_sec.offset + first_sec.size) & ~0xF; + + first_sec.offset_aligned = offset_aligned != 0 ? offset_aligned - 16 : 0; // Eventually include the previous block (used as IV) + first_sec.size_aligned = offset_aligned_first_out != (first_sec.offset + first_sec.size) ? + offset_aligned_first_out + 16 - first_sec.offset_aligned : + offset_aligned_first_out - first_sec.offset_aligned; + first_sec.address_aligned = first_sec.lba_address + first_sec.offset_aligned; + } + else + { + first_sec.offset_aligned = 0; + first_sec.size_aligned = ISO_SECTOR_SIZE; + first_sec.address_aligned = first_sec.lba_address; + } + + u64 total_read = m_file.read_at(first_sec.address_aligned, &reinterpret_cast(aligned_buf)[first_sec.offset_aligned], first_sec.size_aligned); + + m_dec->decrypt(first_sec.address_aligned, &reinterpret_cast(aligned_buf)[first_sec.offset_aligned], first_sec.size_aligned, m_meta.name); + std::memcpy(buffer, &reinterpret_cast(aligned_buf)[first_sec.offset], first_sec.size); + + const u64 sector_count = (last_sec.lba_address - first_sec.lba_address) / ISO_SECTOR_SIZE + 1; + + if (sector_count < 2) // If no more sector(s) + { + if (total_read != first_sec.size_aligned) + { + iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, total_read, first_sec.size_aligned); + return 0; + } + + return max_size; + } + + // + // Inner sector(s), if any + // + + if (sector_count > 2) // If inner sector(s) are present + { + if (!m_raw_device) + { + const u64 inner_sector_size = (sector_count - 2) * ISO_SECTOR_SIZE; + + total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast(buffer)[first_sec.size], inner_sector_size); + + m_dec->decrypt(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast(buffer)[first_sec.size], inner_sector_size, m_meta.name); + } + else + { + u64 inner_sector_offset = 0; + + for (u64 i = 0; i < sector_count - 2; i++, inner_sector_offset += ISO_SECTOR_SIZE) + { + total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE + inner_sector_offset, aligned_buf, ISO_SECTOR_SIZE); + + m_dec->decrypt(first_sec.lba_address + ISO_SECTOR_SIZE + inner_sector_offset, aligned_buf, ISO_SECTOR_SIZE, m_meta.name); + std::memcpy(&reinterpret_cast(buffer)[first_sec.size + inner_sector_offset], aligned_buf, ISO_SECTOR_SIZE); + } + } + } + + // + // Last sector + // + + if (!m_raw_device) + { + offset_aligned_first_out = last_sec.size & ~0xF; + + last_sec.size_aligned = offset_aligned_first_out != last_sec.size ? offset_aligned_first_out + 16 : offset_aligned_first_out; + } + else + { + last_sec.size_aligned = ISO_SECTOR_SIZE; + } + + total_read += m_file.read_at(last_sec.address_aligned, aligned_buf, last_sec.size_aligned); + + m_dec->decrypt(last_sec.address_aligned, aligned_buf, last_sec.size_aligned, m_meta.name); + std::memcpy(&reinterpret_cast(buffer)[max_size - last_sec.size], aligned_buf, last_sec.size); + + // + // As last, check for an unlikely reading error (decoding also failed due to use of partially initialized buffer) + // + + if (total_read != first_sec.size_aligned + last_sec.size_aligned + (sector_count - 2) * ISO_SECTOR_SIZE) + { + iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, + total_read, ISO_SECTOR_SIZE + ISO_SECTOR_SIZE + (sector_count - 2) * ISO_SECTOR_SIZE); + + return 0; + } + + return max_size; +} + template inline T retrieve_endian_int(const u8* buf) { @@ -539,7 +841,7 @@ static std::optional iso_read_directory_entry(fs::file& entry, utf16.resize(header.file_name_length / 2); - for (usz i = 0; i < utf16.size(); ++i, raw++) + for (usz i = 0; i < utf16.size(); i++, raw++) { utf16[i] = *reinterpret_cast*>(raw); } @@ -661,23 +963,30 @@ u64 iso_fs_metadata::size() const iso_archive::iso_archive(const std::string& path) { m_path = path; - m_file = fs::file(path); - m_dec = std::make_shared(); - if (!m_dec->init(path)) + // "m_path" is updated with the raw device path in case "path" points to a BD drive + fs::get_optical_raw_device(path, &m_path); + + fs::file iso_file(std::make_unique(m_path, fs::read)); + + if (!iso_file || !is_iso_file(iso_file)) { - // Not ISO... TODO: throw something?? + // Not ISO... TODO: throw something? + iso_log.error("iso_archive: Failed to recognize ISO file: %s", path); return; } + // Reset the file position after it was changed by is_iso_file() + iso_file.seek(0); + u8 descriptor_type = -2; bool use_ucs2_decoding = false; do { - const auto descriptor_start = m_file.pos(); + const auto descriptor_start = iso_file.pos(); - descriptor_type = m_file.read(); + descriptor_type = iso_file.read(); // 1 = primary vol descriptor, 2 = joliet SVD if (descriptor_type == 1 || descriptor_type == 2) @@ -685,9 +994,9 @@ iso_archive::iso_archive(const std::string& path) use_ucs2_decoding = descriptor_type == 2; // Skip the rest of descriptor's data - m_file.seek(155, fs::seek_cur); + iso_file.seek(155, fs::seek_cur); - const auto node = iso_read_directory_entry(m_file, use_ucs2_decoding); + const auto node = iso_read_directory_entry(iso_file, use_ucs2_decoding); if (node) { @@ -698,11 +1007,20 @@ iso_archive::iso_archive(const std::string& path) } } - m_file.seek(descriptor_start + ISO_SECTOR_SIZE); + iso_file.seek(descriptor_start + ISO_SECTOR_SIZE); } while (descriptor_type != 255); - iso_form_hierarchy(m_file, m_root, use_ucs2_decoding); + iso_form_hierarchy(iso_file, m_root, use_ucs2_decoding); + + // Only when the archive object is fully set, we can finally initialize the decryption object needing the archive object + m_dec = std::make_shared(); + + if (!m_dec->init(m_path, this)) + { + // TODO: throw something? + return; + } } iso_fs_node* iso_archive::retrieve(const std::string& passed_path) @@ -799,29 +1117,69 @@ bool iso_archive::is_file(const std::string& path) return !file_node->metadata.is_directory; } -iso_file iso_archive::open(const std::string& path) +std::unique_ptr iso_archive::get_iso_file(const std::string& path, bs_t mode, const iso_fs_node& node) { - return iso_file(fs::file(m_path), m_dec, *ensure(retrieve(path))); + if (m_dec->get_enc_type() == iso_encryption_type::NONE) + { + return std::make_unique(path, mode, node); + } + + return std::make_unique(path, mode, node, m_dec); +} + +std::unique_ptr iso_archive::open(const std::string& path) +{ + return get_iso_file(m_path, fs::read, *ensure(retrieve(path))); } psf::registry iso_archive::open_psf(const std::string& path) { - auto* archive_file = retrieve(path); + const auto node = retrieve(path); - if (!archive_file) + if (!node) { return psf::registry(); } - const fs::file psf_file(std::make_unique(fs::file(m_path), m_dec, *archive_file)); + const fs::file psf_file(get_iso_file(m_path, fs::read, *node)); return psf::load_object(psf_file, path); } -iso_file::iso_file(fs::file&& iso_handle, std::shared_ptr iso_dec, const iso_fs_node& node) - : m_file(std::move(iso_handle)), m_dec(iso_dec), m_meta(node.metadata) +iso_file::iso_file(const std::string& path, bs_t mode) { - m_file.seek(node.metadata.extents[0].start * ISO_SECTOR_SIZE); + m_file = fs::file(path, mode); + + if (!m_file) + { + // Should never happen... TODO: throw something? + iso_log.error("iso_file: Failed to open file: %s", path); + return; + } + + m_meta.name = path; + m_meta.extents.push_back({0, m_file.size()}); + + m_file.seek(m_meta.extents[0].start * ISO_SECTOR_SIZE); + + m_raw_device = fs::is_optical_raw_device(path); +} + +iso_file::iso_file(const std::string& path, bs_t mode, const iso_fs_node& node) + : m_meta(node.metadata) +{ + m_file = fs::file(path, mode); + + if (!m_file) + { + // Should never happen... TODO: throw something? + iso_log.error("iso_file: Failed to open file: %s", path); + return; + } + + m_file.seek(m_meta.extents[0].start * ISO_SECTOR_SIZE); + + m_raw_device = fs::is_optical_raw_device(path); } fs::stat_t iso_file::get_stat() @@ -898,29 +1256,27 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) } const u64 archive_first_offset = file_offset(offset); - const u64 total_size = this->size(); - u64 total_read; - // If it's a non-encrypted type - if (m_dec->get_enc_type() == iso_encryption_type::NONE) + // If it's not a raw device + if (!m_raw_device) { - total_read = m_file.read_at(archive_first_offset, buffer, max_size); + u64 total_read = m_file.read_at(archive_first_offset, buffer, max_size); - if (size > total_read && (offset + total_read) < total_size) + if (total_read != max_size) { - total_read += read_at(offset + total_read, reinterpret_cast(buffer) + total_read, size - total_read); + iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, total_read, max_size); + return 0; } - return total_read; + return max_size; } - // If it's an encrypted type + // If it's a raw device // IMPORTANT NOTE: // - // "iso_file_decryption::decrypt()" method requires that offset and size are multiple of 16 bytes - // (ciphertext block's size) and that a previous ciphertext block (used as IV) is read in case - // offset is not a multiple of ISO_SECTOR_SIZE + // For a raw device, we must use a support buffer aligned (probably enough on multiple of 4), otherwise any read from file will fail. + // For that reason, we don't use directly "buffer" (not guaranteeing any alignment) // // ---------------------------------------------------------------------- // file on ISO archive: | ' ' | @@ -933,50 +1289,36 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) // ------------------------------------------------------------------------------------------------------------------------------------- // ISO archive: | sec 0 | sec 1 |xxxxx######'###########'###########'###########'##xxxxxxxxx| | ... | sec n-1 | sec n | // ------------------------------------------------------------------------------------------------------------------------------------- - // 16 Bytes x block read: | | | | | | | '#######'###########'###########'###########'###| | | | | | | | | | | | | | | // ' ' ' ' // | first sec | inner sec(s) | last sec | const u64 archive_last_offset = archive_first_offset + max_size - 1; iso_sector first_sec, last_sec; - u64 offset_aligned; - u64 offset_aligned_first_out; + void* aligned_buf = get_aligned_buf(); // thread-safe buffer first_sec.lba_address = (archive_first_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; first_sec.offset = archive_first_offset % ISO_SECTOR_SIZE; first_sec.size = first_sec.offset + max_size <= ISO_SECTOR_SIZE ? max_size : ISO_SECTOR_SIZE - first_sec.offset; last_sec.lba_address = last_sec.address_aligned = (archive_last_offset / ISO_SECTOR_SIZE) * ISO_SECTOR_SIZE; - //last_sec.offset = last_sec.offset_aligned = 0; // Always 0 so no need to set and use those attributes + // last_sec.offset = last_sec.offset_aligned = 0; // Always 0 so no need to set and use those attributes last_sec.size = (archive_last_offset % ISO_SECTOR_SIZE) + 1; // // First sector // - offset_aligned = first_sec.offset & ~0xF; - offset_aligned_first_out = (first_sec.offset + first_sec.size) & ~0xF; + u64 total_read = m_file.read_at(first_sec.lba_address, aligned_buf, ISO_SECTOR_SIZE); - first_sec.offset_aligned = offset_aligned != 0 ? offset_aligned - 16 : 0; // Eventually include the previous block (used as IV) - first_sec.size_aligned = offset_aligned_first_out != (first_sec.offset + first_sec.size) ? - offset_aligned_first_out + 16 - first_sec.offset_aligned : - offset_aligned_first_out - first_sec.offset_aligned; - first_sec.address_aligned = first_sec.lba_address + first_sec.offset_aligned; + std::memcpy(buffer, &reinterpret_cast(aligned_buf)[first_sec.offset], first_sec.size); - total_read = m_file.read_at(first_sec.address_aligned, &first_sec.buf.data()[first_sec.offset_aligned], first_sec.size_aligned); - - m_dec->decrypt(first_sec.address_aligned, &first_sec.buf.data()[first_sec.offset_aligned], first_sec.size_aligned, m_meta.name); - memcpy(buffer, &first_sec.buf.data()[first_sec.offset], first_sec.size); - - u64 sector_count = (last_sec.lba_address - first_sec.lba_address) / ISO_SECTOR_SIZE + 1; + const u64 sector_count = (last_sec.lba_address - first_sec.lba_address) / ISO_SECTOR_SIZE + 1; if (sector_count < 2) // If no more sector(s) { - if (total_read != first_sec.size_aligned) + if (total_read != ISO_SECTOR_SIZE) { - iso_log.error("read_at: %s: Error reading from file", m_meta.name); - - seek(m_pos, fs::seek_set); + iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, total_read, ISO_SECTOR_SIZE); return 0; } @@ -987,38 +1329,35 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) // Inner sector(s), if any // - u64 expected_inner_sector_read = 0; - if (sector_count > 2) // If inner sector(s) are present { - u64 inner_sector_size = expected_inner_sector_read = (sector_count - 2) * ISO_SECTOR_SIZE; + u64 sector_offset = 0; - total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast(buffer)[first_sec.size], inner_sector_size); + for (u64 i = 0; i < sector_count - 2; i++, sector_offset += ISO_SECTOR_SIZE) + { + total_read += m_file.read_at(first_sec.lba_address + ISO_SECTOR_SIZE + sector_offset, aligned_buf, ISO_SECTOR_SIZE); - m_dec->decrypt(first_sec.lba_address + ISO_SECTOR_SIZE, &reinterpret_cast(buffer)[first_sec.size], inner_sector_size, m_meta.name); + std::memcpy(&reinterpret_cast(buffer)[first_sec.size + sector_offset], aligned_buf, ISO_SECTOR_SIZE); + } } // // Last sector // - offset_aligned_first_out = last_sec.size & ~0xF; - last_sec.size_aligned = offset_aligned_first_out != last_sec.size ? offset_aligned_first_out + 16 : offset_aligned_first_out; + total_read += m_file.read_at(last_sec.address_aligned, aligned_buf, ISO_SECTOR_SIZE); - total_read += m_file.read_at(last_sec.address_aligned, last_sec.buf.data(), last_sec.size_aligned); - - m_dec->decrypt(last_sec.address_aligned, last_sec.buf.data(), last_sec.size_aligned, m_meta.name); - memcpy(&reinterpret_cast(buffer)[max_size - last_sec.size], last_sec.buf.data(), last_sec.size); + std::memcpy(&reinterpret_cast(buffer)[max_size - last_sec.size], aligned_buf, last_sec.size); // - // As last, check for an unlikely reading error (decoding also failed due to use of partially initialized buffer) + // As last, check for an unlikely reading error // - if (total_read != first_sec.size_aligned + last_sec.size_aligned + expected_inner_sector_read) + if (total_read != ISO_SECTOR_SIZE + ISO_SECTOR_SIZE + (sector_count - 2) * ISO_SECTOR_SIZE) { - iso_log.error("read_at: %s: Error reading from file", m_meta.name); + iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, + total_read, ISO_SECTOR_SIZE + ISO_SECTOR_SIZE + (sector_count - 2) * ISO_SECTOR_SIZE); - seek(m_pos, fs::seek_set); return 0; } @@ -1033,11 +1372,10 @@ u64 iso_file::write(const void* /*buffer*/, u64 /*size*/) u64 iso_file::seek(s64 offset, fs::seek_mode whence) { - const s64 total_size = size(); const s64 new_pos = whence == fs::seek_set ? offset : whence == fs::seek_cur ? offset + m_pos : - whence == fs::seek_end ? offset + total_size : -1; + whence == fs::seek_end ? offset + size() : -1; if (new_pos < 0) { @@ -1169,7 +1507,7 @@ std::unique_ptr iso_device::open(const std::string& path, bs_t(fs::file(m_path, mode), m_archive.get_dec(), *node); + return m_archive.get_iso_file(m_archive.path(), mode, *node); } std::unique_ptr iso_device::open_dir(const std::string& path) diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index 52cc33065a..ba39956239 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -6,8 +6,7 @@ #include "util/types.hpp" #include "Crypto/aes.h" -bool is_file_iso(const std::string& path); -bool is_file_iso(const fs::file& path); +bool is_iso_file(const std::string& path, u64* size = nullptr, bool* is_raw_device = nullptr); void load_iso(const std::string& path); void unload_iso(); @@ -58,6 +57,8 @@ enum class iso_type_status ERROR_PROCESSING_KEY }; +class iso_archive; + // ISO file decryption class class iso_file_decryption { @@ -66,14 +67,15 @@ private: iso_encryption_type m_enc_type = iso_encryption_type::NONE; std::vector m_region_info; - void reset(); + static iso_type_status get_key(const std::string& key_path, aes_context* aes_ctx = nullptr); + static iso_type_status retrieve_key(iso_archive& archive, std::string& key_path, aes_context& aes_ctx); public: static iso_type_status check_type(const std::string& path, std::string& key_path, aes_context* aes_ctx = nullptr); iso_encryption_type get_enc_type() const { return m_enc_type; } - bool init(const std::string& path); + bool init(const std::string& path, iso_archive* archive = nullptr); bool decrypt(u64 offset, void* buffer, u64 size, const std::string& name); }; @@ -102,10 +104,10 @@ struct iso_fs_node class iso_file : public fs::file_base { -private: +protected: fs::file m_file; - std::shared_ptr m_dec; iso_fs_metadata m_meta; + bool m_raw_device = false; u64 m_pos = 0; std::pair get_extent_pos(u64 pos) const; @@ -114,7 +116,10 @@ private: u64 file_offset(u64 pos) const; public: - iso_file(fs::file&& iso_handle, std::shared_ptr iso_dec, const iso_fs_node& node); + iso_file(const std::string& path, bs_t mode); + iso_file(const std::string& path, bs_t mode, const iso_fs_node& node); + + explicit operator bool() const { return m_file.operator bool(); } fs::stat_t get_stat() override; bool trunc(u64 length) override; @@ -125,6 +130,19 @@ public: u64 size() override; void release() override; + + friend class iso_file_decryption; +}; + +class iso_file_encrypted : public iso_file +{ +private: + std::shared_ptr m_dec; + +public: + iso_file_encrypted(const std::string& path, bs_t mode, const iso_fs_node& node, std::shared_ptr dec); + + u64 read_at(u64 offset, void* buffer, u64 size) override; }; class iso_dir : public fs::dir_base @@ -147,22 +165,23 @@ class iso_archive { private: std::string m_path; - fs::file m_file; - std::shared_ptr m_dec; iso_fs_node m_root {}; + std::shared_ptr m_dec; public: iso_archive(const std::string& path); const std::string& path() const { return m_path; } - const std::shared_ptr get_dec() { return m_dec; } iso_fs_node* retrieve(const std::string& path); bool exists(const std::string& path); bool is_file(const std::string& path); - iso_file open(const std::string& path); + std::unique_ptr get_iso_file(const std::string& path, bs_t mode, const iso_fs_node& node); + std::unique_ptr open(const std::string& path); psf::registry open_psf(const std::string& path); + + friend class iso_file; }; class iso_device : public fs::device_base diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 32d36d7bd1..6e745f448c 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -551,13 +551,14 @@ void game_list_frame::OnParsingFinished() { std::unique_ptr archive; iso_metadata_cache_entry cache_entry{}; - const bool is_iso = is_file_iso(dir_or_elf); + bool is_raw_device = false; + const bool is_archive = is_iso_file(dir_or_elf, nullptr, &is_raw_device); - if (is_iso) + if (is_archive) { - // Only construct iso_archive (which walks the full directory tree) - // when no valid cache entry exists for this ISO path + mtime. - if (!iso_cache::load(dir_or_elf, cache_entry)) + // Only construct iso_archive (which walks the full directory tree) in case of raw device or + // when no valid cache entry exists for this ISO path + mtime + if (is_raw_device || !iso_cache::load(dir_or_elf, cache_entry)) { archive = std::make_unique(dir_or_elf); } @@ -738,9 +739,9 @@ void game_list_frame::OnParsingFinished() } } - // On cache miss for an ISO, persist the resolved metadata so subsequent - // launches skip iso_archive construction entirely. - if (archive && is_iso) + // With the exception of raw device, on cache miss for an ISO, persist the resolved metadata so subsequent + // launches skip iso_archive construction entirely + if (archive && is_archive && !is_raw_device) { fs::stat_t iso_stat{}; if (fs::get_stat(dir_or_elf, iso_stat)) @@ -755,11 +756,11 @@ void game_list_frame::OnParsingFinished() if (game.icon_in_archive) { auto icon_file = archive->open(game.info.icon_path); - const auto icon_size = icon_file.size(); + const auto icon_size = icon_file->size(); if (icon_size > 0) { cache_entry.icon_data.resize(icon_size); - icon_file.read(cache_entry.icon_data.data(), icon_size); + icon_file->read(cache_entry.icon_data.data(), icon_size); } } @@ -854,7 +855,11 @@ void game_list_frame::OnParsingFinished() if (entry.is_from_yml) { - if (fs::is_file(entry.path + "/PARAM.SFO")) + if (is_iso_file(entry.path)) + { + push_path(entry.path, legit_paths); + } + else if (fs::is_file(entry.path + "/PARAM.SFO")) { push_path(entry.path, legit_paths); } @@ -887,10 +892,6 @@ void game_list_frame::OnParsingFinished() add_disc_dir(entry.path, legit_paths); } - else if (is_file_iso(entry.path)) - { - push_path(entry.path, legit_paths); - } else { game_list_log.trace("Invalid game path registered: %s", entry.path); diff --git a/rpcs3/rpcs3qt/game_list_grid.cpp b/rpcs3/rpcs3qt/game_list_grid.cpp index 903fdf016a..04dd3a4e98 100644 --- a/rpcs3/rpcs3qt/game_list_grid.cpp +++ b/rpcs3/rpcs3qt/game_list_grid.cpp @@ -124,7 +124,7 @@ void game_list_grid::populate( check_iso |= !fs::exists(game->info.audio_path); } - if (check_iso && is_file_iso(game->info.path)) + if (check_iso && is_iso_file(game->info.path)) { item->set_iso_path(game->info.path); } diff --git a/rpcs3/rpcs3qt/game_list_table.cpp b/rpcs3/rpcs3qt/game_list_table.cpp index ec88561580..ca157c5183 100644 --- a/rpcs3/rpcs3qt/game_list_table.cpp +++ b/rpcs3/rpcs3qt/game_list_table.cpp @@ -283,12 +283,8 @@ void game_list_table::populate( // Do not report size of apps inside /dev_flash (it does not make sense to do so) game->info.size_on_disk = 0; } - else if (is_file_iso(game->info.path)) + else if (is_iso_file(game->info.path, &game->info.size_on_disk)) // If iso file, game->info.size_on_disk is also set { - fs::stat_t iso_stat; - fs::get_stat(game->info.path, iso_stat); - - game->info.size_on_disk = iso_stat.size; } else { @@ -317,7 +313,7 @@ void game_list_table::populate( check_iso |= !fs::exists(game->info.audio_path); } - if (check_iso && is_file_iso(game->info.path)) + if (check_iso && is_iso_file(game->info.path)) { icon_item->set_iso_path(game->info.path); } diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index e44bed7f81..b651ea8799 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -4080,7 +4080,7 @@ main_window::drop_type main_window::IsValidFile(const QMimeData& md, QStringList const QString suffix_lo = info.suffix().toLower(); // check for directories first, only valid if all other paths led to directories until now. - if (info.isDir() || is_file_iso(path.toStdString())) + if (info.isDir() || is_iso_file(path.toStdString())) { if (type != drop_type::drop_dir && type != drop_type::drop_error) { diff --git a/rpcs3/rpcs3qt/qt_utils.cpp b/rpcs3/rpcs3qt/qt_utils.cpp index 7ef0317ec2..b648688bf8 100644 --- a/rpcs3/rpcs3qt/qt_utils.cpp +++ b/rpcs3/rpcs3qt/qt_utils.cpp @@ -403,7 +403,7 @@ namespace gui // Get Icon for the gs_frame from path. this handles presumably all possible use cases std::vector path_list; - const bool is_archive = is_file_iso(path); + const bool is_archive = is_iso_file(path); if (is_archive) { icon_path = "PS3_GAME/ICON0.PNG"; @@ -708,11 +708,15 @@ namespace gui bool load_iso_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path) { if (icon_path.empty() || archive_path.empty()) return false; - if (!is_file_iso(archive_path)) return false; - // Check cache first — avoids constructing a full iso_archive just for the icon. + bool is_raw_device = false; + const bool is_archive = is_iso_file(archive_path, nullptr, &is_raw_device); + + if (!is_archive) return false; + + // With the exception of raw device, check cache first — avoids constructing a full iso_archive just for the icon. iso_metadata_cache_entry cache_entry{}; - if (iso_cache::load(archive_path, cache_entry) && !cache_entry.icon_data.empty()) + if (!is_raw_device && iso_cache::load(archive_path, cache_entry) && !cache_entry.icon_data.empty()) { const QByteArray data(reinterpret_cast(cache_entry.icon_data.data()), static_cast(cache_entry.icon_data.size())); @@ -723,11 +727,11 @@ namespace gui if (!archive.exists(icon_path)) return false; auto icon_file = archive.open(icon_path); - const auto icon_size = icon_file.size(); + const auto icon_size = icon_file->size(); if (icon_size == 0) return false; QByteArray data(icon_size, 0); - icon_file.read(data.data(), icon_size); + icon_file->read(data.data(), icon_size); return icon.loadFromData(data); } diff --git a/rpcs3/rpcs3qt/qt_video_source.cpp b/rpcs3/rpcs3qt/qt_video_source.cpp index 760369ad74..14cda830fb 100644 --- a/rpcs3/rpcs3qt/qt_video_source.cpp +++ b/rpcs3/rpcs3qt/qt_video_source.cpp @@ -113,11 +113,11 @@ void qt_video_source::init_movie() { iso_archive archive(m_iso_path); auto movie_file = archive.open(m_video_path.toStdString()); - const auto movie_size = movie_file.size(); + const auto movie_size = movie_file->size(); if (movie_size == 0) return; m_video_data = QByteArray(movie_size, 0); - movie_file.read(m_video_data.data(), movie_size); + movie_file->read(m_video_data.data(), movie_size); m_video_buffer = std::make_unique(&m_video_data); m_video_buffer->open(QIODevice::ReadOnly); @@ -156,14 +156,14 @@ void qt_video_source::init_movie() { iso_archive archive(m_iso_path); auto movie_file = archive.open(m_video_path.toStdString()); - const auto movie_size = movie_file.size(); + const auto movie_size = movie_file->size(); if (movie_size == 0) { return; } m_video_data = QByteArray(movie_size, 0); - movie_file.read(m_video_data.data(), movie_size); + movie_file->read(m_video_data.data(), movie_size); } if (m_video_data.isEmpty()) @@ -280,12 +280,12 @@ void qt_video_source::start_audio() { iso_archive archive(m_iso_path); auto audio_file = archive.open(m_audio_path.toStdString()); - const auto audio_size = audio_file.size(); + const auto audio_size = audio_file->size(); if (audio_size == 0) return; std::unique_ptr old_audio_data = std::move(audio.data); audio.data = std::make_unique(audio_size, 0); - audio_file.read(audio.data->data(), audio_size); + audio_file->read(audio.data->data(), audio_size); if (!audio.buffer) { diff --git a/rpcs3/rpcs3qt/shortcut_utils.cpp b/rpcs3/rpcs3qt/shortcut_utils.cpp index d7b5586e75..2112dfe0e9 100644 --- a/rpcs3/rpcs3qt/shortcut_utils.cpp +++ b/rpcs3/rpcs3qt/shortcut_utils.cpp @@ -72,7 +72,7 @@ namespace gui::utils return false; } - const bool is_archive = is_file_iso(path); + const bool is_archive = is_iso_file(path); QPixmap icon; if (!load_icon(icon, src_icon_path, is_archive ? path : "")) @@ -478,7 +478,7 @@ namespace gui::utils std::string gameid_token_value; const std::string dev_flash = g_cfg_vfs.get_dev_flash(); - const bool is_iso = is_file_iso(game->info.path); + const bool is_archive = is_iso_file(game->info.path); std::shared_ptr archive; const auto file_exists = [&archive](const std::string& path) @@ -486,7 +486,7 @@ namespace gui::utils return archive ? archive->is_file(path) : fs::is_file(path); }; - if (is_iso) + if (is_archive) { gameid_token_value = game->info.serial; archive = std::make_shared(game->info.path); @@ -542,7 +542,7 @@ namespace gui::utils if (location == shortcut_location::steam) { // Try to find a nice banner for steam - const std::string sfo_dir = is_iso ? "PS3_GAME" : rpcs3::utils::get_sfo_dir_from_game_path(game->info.path); + const std::string sfo_dir = is_archive ? "PS3_GAME" : rpcs3::utils::get_sfo_dir_from_game_path(game->info.path); for (const std::string& filename : {"PIC1.PNG"s, "PIC3.PNG"s, "PIC0.PNG"s, "PIC2.PNG"s, "ICON0.PNG"s}) { From 3c9a4417f2fd88d3387826a0f6e0ae79e3930abb Mon Sep 17 00:00:00 2001 From: schm1dtmac Date: Wed, 29 Apr 2026 17:41:02 +0100 Subject: [PATCH 004/283] Revert "Remove redundant Apple ARM64 ifdef blocks" This reverts commit e1734b51c3e24e60403e46efaf47fe143a3add62. --- rpcs3/util/vm_native.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rpcs3/util/vm_native.cpp b/rpcs3/util/vm_native.cpp index 4f11a107fe..d569a26645 100644 --- a/rpcs3/util/vm_native.cpp +++ b/rpcs3/util/vm_native.cpp @@ -346,7 +346,15 @@ namespace utils ensure(::VirtualFree(pointer, size, MEM_DECOMMIT)); #else const u64 ptr64 = reinterpret_cast(pointer); +#if defined(__APPLE__) && defined(ARCH_ARM64) + // Use MAP_FIXED without MAP_JIT to atomically replace the mapping. + // Apple rejects MAP_FIXED | MAP_JIT, but MAP_FIXED alone works and + // avoids the race condition of the previous munmap+mmap approach + // where another thread could claim the address range between the two calls. ensure(::mmap(pointer, size, PROT_NONE, MAP_FIXED | MAP_ANON | MAP_PRIVATE | c_map_noreserve, -1, 0) != reinterpret_cast(uptr{umax})); +#else + ensure(::mmap(pointer, size, PROT_NONE, MAP_FIXED | MAP_ANON | MAP_PRIVATE | c_map_noreserve, -1, 0) != reinterpret_cast(uptr{umax})); +#endif if constexpr (c_madv_no_dump != 0) { @@ -371,7 +379,13 @@ namespace utils memory_commit(pointer, size, prot); #else const u64 ptr64 = reinterpret_cast(pointer); +#if defined(__APPLE__) && defined(ARCH_ARM64) + // Use MAP_FIXED without MAP_JIT to atomically replace the mapping. + // See memory_decommit for details on why the munmap+mmap approach is unsafe. ensure(::mmap(pointer, size, +prot, MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0) != reinterpret_cast(uptr{umax})); +#else + ensure(::mmap(pointer, size, +prot, MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0) != reinterpret_cast(uptr{umax})); +#endif if constexpr (c_madv_hugepage != 0) { From f3cf1da7b771d9077e68987dc845aa1eb2952fd1 Mon Sep 17 00:00:00 2001 From: schm1dtmac Date: Wed, 29 Apr 2026 17:41:02 +0100 Subject: [PATCH 005/283] Revert "Fix race condition in memory_decommit/memory_reset on Apple ARM64" This reverts commit 4cac76caadda064ea5cafc6f530133acb2302c50. --- rpcs3/util/vm_native.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rpcs3/util/vm_native.cpp b/rpcs3/util/vm_native.cpp index d569a26645..5821300fc3 100644 --- a/rpcs3/util/vm_native.cpp +++ b/rpcs3/util/vm_native.cpp @@ -347,11 +347,12 @@ namespace utils #else const u64 ptr64 = reinterpret_cast(pointer); #if defined(__APPLE__) && defined(ARCH_ARM64) - // Use MAP_FIXED without MAP_JIT to atomically replace the mapping. - // Apple rejects MAP_FIXED | MAP_JIT, but MAP_FIXED alone works and - // avoids the race condition of the previous munmap+mmap approach - // where another thread could claim the address range between the two calls. - ensure(::mmap(pointer, size, PROT_NONE, MAP_FIXED | MAP_ANON | MAP_PRIVATE | c_map_noreserve, -1, 0) != reinterpret_cast(uptr{umax})); + // Hack: on macOS, Apple explicitly fails mmap if you combine MAP_FIXED and MAP_JIT. + // So we unmap the space and just hope it maps to the same address we got before instead. + // The Xcode manpage says the pointer is a hint and the OS will try to map at the hint location + // so this isn't completely undefined behavior. + ensure(::munmap(pointer, size) != -1); + ensure(::mmap(pointer, size, PROT_NONE, MAP_ANON | MAP_PRIVATE | MAP_JIT, -1, 0) == pointer); #else ensure(::mmap(pointer, size, PROT_NONE, MAP_FIXED | MAP_ANON | MAP_PRIVATE | c_map_noreserve, -1, 0) != reinterpret_cast(uptr{umax})); #endif @@ -380,9 +381,8 @@ namespace utils #else const u64 ptr64 = reinterpret_cast(pointer); #if defined(__APPLE__) && defined(ARCH_ARM64) - // Use MAP_FIXED without MAP_JIT to atomically replace the mapping. - // See memory_decommit for details on why the munmap+mmap approach is unsafe. - ensure(::mmap(pointer, size, +prot, MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0) != reinterpret_cast(uptr{umax})); + ensure(::munmap(pointer, size) != -1); + ensure(::mmap(pointer, size, +prot, MAP_ANON | MAP_PRIVATE | MAP_JIT, -1, 0) == pointer); #else ensure(::mmap(pointer, size, +prot, MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0) != reinterpret_cast(uptr{umax})); #endif From 570b2bddca6734997bc6a00d20dbe4f4993d546f Mon Sep 17 00:00:00 2001 From: digant73 Date: Wed, 29 Apr 2026 20:30:59 +0200 Subject: [PATCH 006/283] Avoid to open bd drive if no content is present --- Utilities/File.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index d58331e0e2..0059673336 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -1745,6 +1745,17 @@ fs::file::file(const std::string& path, bs_t mode) // (the following GetFileInformationByHandle() would always fail on a raw device). if (is_optical_raw_device(path)) { + DISK_GEOMETRY_EX geometry; + + // Try to retrieve information on content. If it fails, no disc is probably mounted so abort the file opening + if (!DeviceIoControl(handle, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, nullptr, 0, &geometry, sizeof(geometry), nullptr, nullptr)) + { + const DWORD last_error = GetLastError(); + CloseHandle(handle); + g_tls_error = to_error(last_error); + return; + } + m_file = std::make_unique(handle, true); return; } From 79d0e0eb3c3af97a5c0afa9b5b64ffaca420d369 Mon Sep 17 00:00:00 2001 From: digant73 Date: Wed, 29 Apr 2026 23:36:14 +0200 Subject: [PATCH 007/283] reduce check for raw device --- Utilities/File.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index 0059673336..59ef528942 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -1127,6 +1127,13 @@ bool fs::is_optical_raw_device(const std::string& path) bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device) { + // Skip a useless check to detect an optical raw device if navigating on subfolders (e.g. C:/subfolder_1/subfolder_2/), it means we are on a hdd/ssd. + // A path for an optical drive should include only the drive letter (e.g. E:/) + if (path.find_first_of(":") != path.find_last_not_of(delim)) + { + return false; + } + if (fs::is_optical_raw_device(path)) { if (raw_device) From 7bc08d05c6edf463ab27b65dc2225ca1916bd265 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 29 Apr 2026 22:24:37 +0200 Subject: [PATCH 008/283] Add some more logging for use of database config --- rpcs3/Emu/System.cpp | 10 +++++++++- rpcs3/main_application.cpp | 13 ++++++++++--- rpcs3/rpcs3qt/main_window.cpp | 1 + 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 7f0437648b..d96f43bfae 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -935,6 +935,8 @@ game_boot_result Emulator::GetElfPathFromDir(std::string& elf_path, const std::s game_boot_result Emulator::BootGame(const std::string& path, const std::string& title_id, bool direct, cfg_mode config_mode, const std::string& config_path, const std::optional& db_config) { + sys_log.notice("Emulator::BootGame: path='%s', title_id='%s', direct=%d, config_mode='%s', config_path='%s', db_config=(set=%d, valid=%d)", path, title_id, direct, config_mode, config_path, db_config.has_value(), db_config && !db_config->empty()); + if (m_restrict_emu_state_change) { return game_boot_result::currently_restricted; @@ -1625,7 +1627,13 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, { g_cfg.name = config_path; m_config_path = config_path; - m_add_database_config = false; // A custom config exists. Do not add the database config. + + if (m_add_database_config) + { + // A custom config exists. Do not add the database config. + sys_log.notice("Found custom config. Ignoring database config"); + m_add_database_config = false; + } break; } diff --git a/rpcs3/main_application.cpp b/rpcs3/main_application.cpp index ce1f917059..03426d9a97 100644 --- a/rpcs3/main_application.cpp +++ b/rpcs3/main_application.cpp @@ -412,16 +412,22 @@ EmuCallbacks main_application::CreateCallbacks() callbacks.get_database_config = [](const std::string& title_id) { - if (title_id.empty()) - return std::string(); - sys_log.notice("Trying to retrieve database config for: '%s'", title_id); + if (title_id.empty()) + { + sys_log.warning("Cannot retrieve database config for empty title_id"); + return std::string(); + } + config_database config_db(nullptr); config_db.request_config_database(false); if (!config_db.has_config(title_id)) + { + sys_log.notice("Cannot find database config for: '%s'", title_id); return std::string(); + } if (const auto config = config_db.get_config(title_id)) { @@ -429,6 +435,7 @@ EmuCallbacks main_application::CreateCallbacks() return config.value(); } + sys_log.error("Failed to retrieve database config for: '%s'", title_id); return std::string(); }; diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index b651ea8799..405197af4b 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -569,6 +569,7 @@ void main_window::Boot(const std::string& path, const std::string& title_id, boo return; } + gui_log.notice("Found database config for: '%s'", title_id); db_config = *config; } From 14ca11fdf4625dd21921598dac5ad3d67ce961e0 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 29 Apr 2026 22:37:04 +0200 Subject: [PATCH 009/283] Fix uninizialized member warning --- rpcs3/Emu/System.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index d96f43bfae..39172da368 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -121,6 +121,7 @@ namespace rsx } Emulator::Emulator() noexcept + : m_default_renderer(video_renderer::null) { s_emulator_available = true; } From 985703058bc4769acbec28a64ffce2d9ff91fec9 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 29 Apr 2026 23:10:58 +0200 Subject: [PATCH 010/283] ISO: Improve path logging --- rpcs3/Loader/ISO.cpp | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index f5ba9f839c..585e7d279c 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -384,7 +384,7 @@ bool iso_file_decryption::init(const std::string& path, iso_archive* archive) if (!is_iso_file(iso_file)) { - iso_log.error("init: Failed to recognize ISO file: %s", path); + iso_log.error("init: Failed to recognize ISO file: '%s'", path); return false; } @@ -395,13 +395,13 @@ bool iso_file_decryption::init(const std::string& path, iso_archive* archive) if (iso_file.size() < sec0_sec1.size()) { - iso_log.error("init: Found only %llu sector(s) (minimum required is 2): %s", iso_file.size(), path); + iso_log.error("init: Found only %llu sector(s) (minimum required is 2): '%s'", iso_file.size(), path); return false; } if (iso_file.read(sec0_sec1.data(), sec0_sec1.size()) != sec0_sec1.size()) { - iso_log.error("init: Failed to read file: %s", path); + iso_log.error("init: Failed to read file: '%s'", path); return false; } @@ -415,7 +415,7 @@ bool iso_file_decryption::init(const std::string& path, iso_archive* archive) // Ensure the region count is a proper value if (region_count < 1 || region_count > 127) // It's non-PS3ISO { - iso_log.error("init: Failed to read region information (region_count=%lu): %s", region_count, path); + iso_log.error("init: Failed to read region information (region_count=%lu): '%s'", region_count, path); return false; } @@ -452,17 +452,17 @@ bool iso_file_decryption::init(const std::string& path, iso_archive* archive) switch (status) { case iso_type_status::NOT_ISO: - iso_log.warning("init: Failed to recognize ISO file: %s", path); + iso_log.warning("init: Failed to recognize ISO file: '%s'", path); break; case iso_type_status::REDUMP_ISO: - iso_log.warning("init: Found matching key file: %s", key_path); + iso_log.warning("init: Found matching key file: '%s'", key_path); m_enc_type = iso_encryption_type::REDUMP; // SET ENCRYPTION TYPE: REDUMP break; case iso_type_status::ERROR_OPENING_KEY: - iso_log.warning("init: Failed to open, or missing, key file: %s", key_path); + iso_log.warning("init: Failed to open, or missing, key file: '%s'", key_path); break; case iso_type_status::ERROR_PROCESSING_KEY: - iso_log.error("init: Failed to process key file: %s", key_path); + iso_log.error("init: Failed to process key file: '%s'", key_path); break; default: break; @@ -507,7 +507,7 @@ bool iso_file_decryption::init(const std::string& path, iso_archive* archive) if (m_enc_type == iso_encryption_type::NONE) // If encryption type was not set to ENC_3K3Y for any reason { - iso_log.error("init: Failed to set encryption type to ENC_3K3Y: %s", path); + iso_log.error("init: Failed to set encryption type to ENC_3K3Y: '%s'", path); } } else if (std::memcmp(&k3k3y_dec_watermark[0], &sec0_sec1[0xF70], sizeof(k3k3y_dec_watermark)) == 0) @@ -519,16 +519,16 @@ bool iso_file_decryption::init(const std::string& path, iso_archive* archive) switch (m_enc_type) { case iso_encryption_type::REDUMP: - iso_log.warning("init: Set 'enc type': REDUMP, 'reg count': %u: %s", m_region_info.size(), path); + iso_log.warning("init: Set 'enc type': REDUMP, 'reg count': %u: '%s'", m_region_info.size(), path); break; case iso_encryption_type::ENC_3K3Y: - iso_log.warning("init: Set 'enc type': ENC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); + iso_log.warning("init: Set 'enc type': ENC_3K3Y, 'reg count': %u: '%s'", m_region_info.size(), path); break; case iso_encryption_type::DEC_3K3Y: - iso_log.warning("init: Set 'enc type': DEC_3K3Y, 'reg count': %u: %s", m_region_info.size(), path); + iso_log.warning("init: Set 'enc type': DEC_3K3Y, 'reg count': %u: '%s'", m_region_info.size(), path); break; case iso_encryption_type::NONE: // If encryption type was not set for any reason - iso_log.warning("init: Set 'enc type': NONE, 'reg count': %u: %s", m_region_info.size(), path); + iso_log.warning("init: Set 'enc type': NONE, 'reg count': %u: '%s'", m_region_info.size(), path); break; } @@ -972,7 +972,7 @@ iso_archive::iso_archive(const std::string& path) if (!iso_file || !is_iso_file(iso_file)) { // Not ISO... TODO: throw something? - iso_log.error("iso_archive: Failed to recognize ISO file: %s", path); + iso_log.error("iso_archive: Failed to recognize ISO file: '%s'", path); return; } @@ -1153,7 +1153,7 @@ iso_file::iso_file(const std::string& path, bs_t mode) if (!m_file) { // Should never happen... TODO: throw something? - iso_log.error("iso_file: Failed to open file: %s", path); + iso_log.error("iso_file: Failed to open file: '%s'", path); return; } @@ -1173,7 +1173,7 @@ iso_file::iso_file(const std::string& path, bs_t mode, const iso_ if (!m_file) { // Should never happen... TODO: throw something? - iso_log.error("iso_file: Failed to open file: %s", path); + iso_log.error("iso_file: Failed to open file: '%s'", path); return; } From 7ea0a96d02099ba099a4ea8f91f086d4bd52f7b7 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 30 Apr 2026 11:26:36 +0200 Subject: [PATCH 011/283] Only set db_config if we boot with a title_id --- rpcs3/rpcs3qt/main_window.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 405197af4b..4333934cda 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -554,7 +554,7 @@ void main_window::Boot(const std::string& path, const std::string& title_id, boo m_app_icon = gui::utils::get_app_icon_from_path(path, title_id); - std::string db_config; + std::optional db_config = std::nullopt; // Get database config if possible or if we are in database_config mode (to ensure we see an error on invalid use) if (config_database* db = m_game_list_frame->GetConfigDatabase(); @@ -572,6 +572,10 @@ void main_window::Boot(const std::string& path, const std::string& title_id, boo gui_log.notice("Found database config for: '%s'", title_id); db_config = *config; } + else if (!title_id.empty()) + { + db_config = std::string(); // Set empty string to prevent another check in the emulator code + } if (const auto error = Emu.BootGame(path, title_id, direct, config_mode, config_path, db_config); error != game_boot_result::no_errors) { @@ -4229,6 +4233,8 @@ void main_window::dropEvent(QDropEvent* event) const std::string path = drop_paths.first().toStdString(); + gui_log.notice("Booting from drag and drop..."); + if (const auto error = Emu.BootGame(path, "", true); error != game_boot_result::no_errors) { gui_log.error("Boot failed: reason: %s, path: %s", error, path); From d40a4ee04ee791cba086c2bc976194d6283b4bd7 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 25 Apr 2026 20:57:31 +0300 Subject: [PATCH 012/283] vk: Add iGPU detection --- rpcs3/Emu/RSX/VK/vkutils/device.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rpcs3/Emu/RSX/VK/vkutils/device.h b/rpcs3/Emu/RSX/VK/vkutils/device.h index 5ca00eb622..57b11f5114 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/device.h +++ b/rpcs3/Emu/RSX/VK/vkutils/device.h @@ -133,6 +133,8 @@ namespace vk operator VkPhysicalDevice() const; operator VkInstance() const; + + bool is_integrated_gpu() const { return props.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; } }; class render_device From 28ab9913020f88912c3f381b261407d9780361c7 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 25 Apr 2026 21:09:59 +0300 Subject: [PATCH 013/283] vk: Support more heap types - Support for GPU shadow heap (manual flush) - Support for Fixed-Length data heaps --- rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp | 29 ++++++++++++++++++++++---- rpcs3/Emu/RSX/VK/vkutils/data_heap.h | 9 ++++++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp b/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp index e3207875f8..f92532ccf7 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp @@ -19,6 +19,9 @@ namespace vk const auto& memory_map = g_render_device->get_memory_mapping(); + ensure(std::popcount(flags & (heap_pool_force_vram_shadow | heap_pool_low_latency)) <= 1, + "Invalid data heap flag combination detected"); + if ((flags & heap_pool_low_latency) && g_cfg.video.vk.use_rebar_upload_heap) { // Prefer uploading to BAR if low latency is desired. @@ -39,9 +42,15 @@ namespace vk VkFlags memory_flags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; auto memory_index = m_prefer_writethrough ? memory_map.device_bar : memory_map.host_visible_coherent; - if (!(get_heap_compatible_buffer_types() & usage)) + const bool vram_shadow_requested = !!(flags & heap_pool_force_vram_shadow); + const bool use_shadow = vram_shadow_requested || !(get_heap_compatible_buffer_types() & usage); + + if (use_shadow) { - rsx_log.warning("Buffer usage %u is not heap-compatible using this driver, explicit staging buffer in use", usage); + if (!vram_shadow_requested) + { + rsx_log.warning("Buffer usage %u is not heap-compatible using this driver, explicit staging buffer in use", usage); + } shadow = std::make_unique(*g_render_device, size, memory_index, memory_flags, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 0, VMM_ALLOCATION_POOL_SYSTEM); usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; @@ -70,6 +79,7 @@ namespace vk heap = std::make_unique(*g_render_device, size, memory_map.host_visible_coherent, memory_flags, usage, 0, VMM_ALLOCATION_POOL_SYSTEM); } + m_flags = flags; initial_size = size; notify_on_grow = bool(notify); } @@ -88,7 +98,7 @@ namespace vk bool data_heap::grow(usz size) { // Create new heap. All sizes are aligned up by 64M, upto 1GiB - const usz size_limit = 1024 * 0x100000; + const usz size_limit = (m_flags & heap_pool_fixed_size) ? initial_size : 1024 * 0x100000; usz aligned_new_size = utils::align(m_size + size, 64 * 0x100000); if (aligned_new_size >= size_limit) @@ -192,7 +202,18 @@ namespace vk if (shadow) { - dirty_ranges.push_back({ offset, offset, size }); + bool insert = true; + if (!dirty_ranges.empty() && (dirty_ranges.back().dstOffset + dirty_ranges.back().size) == offset) [[ likely ]] + { + dirty_ranges.back().size += size; + insert = false; + } + + if (insert) + { + dirty_ranges.push_back({ offset, offset, size }); + } + raise_status_interrupt(runtime_state::heap_dirty); } diff --git a/rpcs3/Emu/RSX/VK/vkutils/data_heap.h b/rpcs3/Emu/RSX/VK/vkutils/data_heap.h index 6732f639dc..13ec2a5fdc 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/data_heap.h +++ b/rpcs3/Emu/RSX/VK/vkutils/data_heap.h @@ -13,8 +13,10 @@ namespace vk { enum data_heap_pool_flags { - heap_pool_default = 0, - heap_pool_low_latency = 1, + heap_pool_default = 0, + heap_pool_low_latency = (1 << 0), + heap_pool_fixed_size = (1 << 1), + heap_pool_force_vram_shadow = (1 << 2), }; class data_heap : public ::data_heap @@ -24,6 +26,8 @@ namespace vk bool mapped = false; void* _ptr = nullptr; + rsx::flags32_t m_flags = heap_pool_default; + bool notify_on_grow = false; bool m_prefer_writethrough = false; @@ -60,6 +64,7 @@ namespace vk // Properties bool is_dirty() const; + bool has_shadow() const { return !!shadow; } }; extern data_heap* get_upload_heap(); From 48e2d808c96be344b63850bfc630215b0d098b58 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 25 Apr 2026 22:48:41 +0300 Subject: [PATCH 014/283] rsx/util: Add simple_array::filter --- rpcs3/Emu/RSX/Common/simple_array.hpp | 15 +++++++++++++++ rpcs3/tests/test_simple_array.cpp | 22 +++++++++++++++++++++- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/Common/simple_array.hpp b/rpcs3/Emu/RSX/Common/simple_array.hpp index a37df9dd54..f2dc15ccdb 100644 --- a/rpcs3/Emu/RSX/Common/simple_array.hpp +++ b/rpcs3/Emu/RSX/Common/simple_array.hpp @@ -569,6 +569,21 @@ namespace rsx return ret; } + simple_array filter(std::predicate auto predicate) const + { + simple_array result; + result.reserve(size()); + + for (auto it = begin(); it != end(); ++it) + { + if (predicate(*it)) + { + result.push_back(*it); + } + } + return result; + } + simple_array& sort(std::predicate auto predicate) { if (_size < 2) diff --git a/rpcs3/tests/test_simple_array.cpp b/rpcs3/tests/test_simple_array.cpp index ebedff861d..f08455ae00 100644 --- a/rpcs3/tests/test_simple_array.cpp +++ b/rpcs3/tests/test_simple_array.cpp @@ -181,10 +181,30 @@ namespace rsx EXPECT_FALSE(arr.any([](const int& val) { return val > 5; })); } + TEST(SimpleArray, Filter) + { + const rsx::simple_array arr{ 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -2 }; + const auto result = arr.filter(FN(x > 5)); + const auto result2 = arr.filter(FN(x < 0)); + const auto result3 = arr.filter(FN(x == 10)); + + EXPECT_EQ(result.size(), 4); + EXPECT_EQ(result[0], 6); + EXPECT_EQ(result[1], 7); + EXPECT_EQ(result[2], 8); + EXPECT_EQ(result[3], 9); + + EXPECT_EQ(result2.size(), 2); + EXPECT_EQ(result2[0], -1); + EXPECT_EQ(result2[1], -2); + + EXPECT_EQ(result3.size(), 0); + } + TEST(SimpleArray, Sort) { rsx::simple_array arr{ 5, 3, 1, 4, 2 }; - arr.sort([](const int& a, const int& b) { return a < b; }); + arr.sort(FN(x < y)); for (u32 i = 0; i < arr.size(); ++i) { From 278daddd2a4fc07a38a1252c9201ef8b136c313b Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 25 Apr 2026 23:03:09 +0300 Subject: [PATCH 015/283] rsx/util: Add a simple wrapper for simple_array::for_each - Just calls std::for_each behind the scenes but it's a lot more convenient --- rpcs3/Emu/RSX/Common/simple_array.hpp | 6 ++++++ rpcs3/tests/test_simple_array.cpp | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/rpcs3/Emu/RSX/Common/simple_array.hpp b/rpcs3/Emu/RSX/Common/simple_array.hpp index f2dc15ccdb..d3535c71ff 100644 --- a/rpcs3/Emu/RSX/Common/simple_array.hpp +++ b/rpcs3/Emu/RSX/Common/simple_array.hpp @@ -634,5 +634,11 @@ namespace rsx } return accumulate; } + + template + void for_each(F&& func) + { + std::for_each(begin(), end(), func); + } }; } diff --git a/rpcs3/tests/test_simple_array.cpp b/rpcs3/tests/test_simple_array.cpp index f08455ae00..0222b4fd0d 100644 --- a/rpcs3/tests/test_simple_array.cpp +++ b/rpcs3/tests/test_simple_array.cpp @@ -378,6 +378,21 @@ namespace rsx } } + TEST(SimpleArray, ForEach) + { + rsx::simple_array arr{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + }; + arr.for_each(FN(x += 10)); + + EXPECT_EQ(arr.size(), 10); + + for (int i = 0; i < 10; ++i) + { + EXPECT_EQ(arr[i], i + 10); + } + } + TEST(AlignedAllocator, Alloc) { auto ptr = rsx::aligned_allocator::malloc<256>(16); From cbbf52878a8a55fc05d802038a08c6397dd6b877 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 25 Apr 2026 23:15:48 +0300 Subject: [PATCH 016/283] vk: Simplify manual heap flush for shadowed blocks --- rpcs3/Emu/RSX/VK/VKDataHeapManager.cpp | 8 +++++++ rpcs3/Emu/RSX/VK/VKDataHeapManager.h | 4 ++++ rpcs3/Emu/RSX/VK/VKGSRender.cpp | 30 +++++++------------------- rpcs3/Emu/RSX/VK/VKGSRender.h | 28 +++++++++++++----------- 4 files changed, 35 insertions(+), 35 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKDataHeapManager.cpp b/rpcs3/Emu/RSX/VK/VKDataHeapManager.cpp index 109cb57bc9..56fc3dcd7e 100644 --- a/rpcs3/Emu/RSX/VK/VKDataHeapManager.cpp +++ b/rpcs3/Emu/RSX/VK/VKDataHeapManager.cpp @@ -8,6 +8,14 @@ namespace vk::data_heap_manager { std::unordered_set g_managed_heaps; + rsx::simple_array to_list() + { + rsx::simple_array result; + result.resize(::size32(g_managed_heaps)); + std::copy(g_managed_heaps.begin(), g_managed_heaps.end(), result.begin()); + return result; + } + void register_ring_buffer(vk::data_heap& heap) { g_managed_heaps.insert(&heap); diff --git a/rpcs3/Emu/RSX/VK/VKDataHeapManager.h b/rpcs3/Emu/RSX/VK/VKDataHeapManager.h index 14a140fefb..cc5c22bc8d 100644 --- a/rpcs3/Emu/RSX/VK/VKDataHeapManager.h +++ b/rpcs3/Emu/RSX/VK/VKDataHeapManager.h @@ -1,6 +1,7 @@ #pragma once #include +#include "Emu/RSX/Common/simple_array.hpp" #include @@ -29,5 +30,8 @@ namespace vk // Cleanup void reset(); + + // Retrieve as list + rsx::simple_array to_list(); } } diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 8da72284d9..885bc7cea2 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -535,6 +535,8 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar) std::ref(m_instancing_buffer_ring_info) }); + m_flushable_data_heaps = vk::data_heap_manager::to_list().filter(FN(x->has_shadow())); + const auto shadermode = g_cfg.video.shadermode.get(); if (shadermode == shader_mode::async_with_interpreter || shadermode == shader_mode::interpreter_only) @@ -2297,32 +2299,16 @@ void VKGSRender::close_and_submit_command_buffer(vk::fence* pFence, VkSemaphore if (vk::test_status_interrupt(vk::heap_dirty)) { - if (m_attrib_ring_info.is_dirty() || - m_fragment_env_ring_info.is_dirty() || - m_vertex_env_ring_info.is_dirty() || - m_fragment_texture_params_ring_info.is_dirty() || - m_vertex_layout_ring_info.is_dirty() || - m_fragment_constants_ring_info.is_dirty() || - m_index_buffer_ring_info.is_dirty() || - m_transform_constants_ring_info.is_dirty() || - m_texture_upload_buffer_ring_info.is_dirty() || - m_raster_env_ring_info.is_dirty() || - m_instancing_buffer_ring_info.is_dirty()) + if (const auto dirty_list = m_flushable_data_heaps.filter(FN(x->is_dirty())); + !dirty_list.empty()) { auto secondary_command_buffer = m_secondary_cb_list.next(); secondary_command_buffer->begin(); - m_attrib_ring_info.sync(*secondary_command_buffer); - m_fragment_env_ring_info.sync(*secondary_command_buffer); - m_vertex_env_ring_info.sync(*secondary_command_buffer); - m_fragment_texture_params_ring_info.sync(*secondary_command_buffer); - m_vertex_layout_ring_info.sync(*secondary_command_buffer); - m_fragment_constants_ring_info.sync(*secondary_command_buffer); - m_index_buffer_ring_info.sync(*secondary_command_buffer); - m_transform_constants_ring_info.sync(*secondary_command_buffer); - m_texture_upload_buffer_ring_info.sync(*secondary_command_buffer); - m_raster_env_ring_info.sync(*secondary_command_buffer); - m_instancing_buffer_ring_info.sync(*secondary_command_buffer); + for (auto& heap : dirty_list) + { + heap->sync(*secondary_command_buffer); + } secondary_command_buffer->end(); diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.h b/rpcs3/Emu/RSX/VK/VKGSRender.h index f9feedc35a..da1491afcc 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.h +++ b/rpcs3/Emu/RSX/VK/VKGSRender.h @@ -122,20 +122,22 @@ private: u64 m_last_heap_sync_time = 0; u32 m_texbuffer_view_size = 0; - vk::data_heap m_attrib_ring_info; // Vertex data - vk::data_heap m_fragment_constants_ring_info; // Fragment program constants - vk::data_heap m_transform_constants_ring_info; // Transform program constants - vk::data_heap m_fragment_env_ring_info; // Fragment environment params - vk::data_heap m_vertex_env_ring_info; // Vertex environment params - vk::data_heap m_fragment_texture_params_ring_info; // Fragment texture params - vk::data_heap m_vertex_layout_ring_info; // Vertex layout structure - vk::data_heap m_index_buffer_ring_info; // Index data - vk::data_heap m_texture_upload_buffer_ring_info; // Texture upload heap - vk::data_heap m_raster_env_ring_info; // Raster control such as polygon and line stipple - vk::data_heap m_instancing_buffer_ring_info; // Instanced rendering data (constants indirection table + instanced constants) + vk::data_heap m_attrib_ring_info; // Vertex data + vk::data_heap m_fragment_constants_ring_info; // Fragment program constants + vk::data_heap m_transform_constants_ring_info; // Transform program constants + vk::data_heap m_fragment_env_ring_info; // Fragment environment params + vk::data_heap m_vertex_env_ring_info; // Vertex environment params + vk::data_heap m_fragment_texture_params_ring_info; // Fragment texture params + vk::data_heap m_vertex_layout_ring_info; // Vertex layout structure + vk::data_heap m_index_buffer_ring_info; // Index data + vk::data_heap m_texture_upload_buffer_ring_info; // Texture upload heap + vk::data_heap m_raster_env_ring_info; // Raster control such as polygon and line stipple + vk::data_heap m_instancing_buffer_ring_info; // Instanced rendering data (constants indirection table + instanced constants) - vk::data_heap m_fragment_instructions_buffer; - vk::data_heap m_vertex_instructions_buffer; + vk::data_heap m_fragment_instructions_buffer; // Interpreter FP block + vk::data_heap m_vertex_instructions_buffer; // Interpreter VP block + + rsx::simple_array m_flushable_data_heaps; // List of heaps that can be 'dirty' and need manual flush VkDescriptorBufferInfoEx m_vertex_env_buffer_info {}; VkDescriptorBufferInfoEx m_fragment_env_buffer_info {}; From 46bbbd2056033200f06bf3eb357e073d6b19a015 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 26 Apr 2026 00:17:02 +0300 Subject: [PATCH 017/283] vk: Fix missing TRANSFER->FRAGMENT barrier for shadowed blocks --- rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp b/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp index f92532ccf7..1ba47abf8b 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp @@ -236,17 +236,19 @@ namespace vk void data_heap::sync(const vk::command_buffer& cmd) { - if (!dirty_ranges.empty()) + if (dirty_ranges.empty()) { - ensure(shadow); - ensure(heap); - vkCmdCopyBuffer(cmd, shadow->value, heap->value, ::size32(dirty_ranges), dirty_ranges.data()); - dirty_ranges.clear(); - - insert_buffer_memory_barrier(cmd, heap->value, 0, heap->size(), - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); + return; } + + ensure(shadow); + ensure(heap); + vkCmdCopyBuffer(cmd, shadow->value, heap->value, ::size32(dirty_ranges), dirty_ranges.data()); + dirty_ranges.clear(); + + insert_buffer_memory_barrier(cmd, heap->value, 0, heap->size(), + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); } bool data_heap::is_dirty() const From 3829802699ef7d0616ccb1ee6ef4b508f0967ace Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 26 Apr 2026 20:16:52 +0300 Subject: [PATCH 018/283] 3rdparty: Update glslang to release 16.2.0 --- 3rdparty/glslang/glslang | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/glslang/glslang b/3rdparty/glslang/glslang index fc9889c889..f0bd0257c3 160000 --- a/3rdparty/glslang/glslang +++ b/3rdparty/glslang/glslang @@ -1 +1 @@ -Subproject commit fc9889c889561c5882e83819dcaffef5ed45529b +Subproject commit f0bd0257c308b9a26562c1a30c4748a0219cc951 From d1c80731063dfd78959b3205446350ab43b104b9 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 26 Apr 2026 20:35:58 +0300 Subject: [PATCH 019/283] rsx: Upgrade SPIRV generation to SPIRV 1.5 for vulkan. --- rpcs3/Emu/RSX/Program/SPIRVCommon.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/SPIRVCommon.cpp b/rpcs3/Emu/RSX/Program/SPIRVCommon.cpp index c32d3542f2..c33f73b7ae 100644 --- a/rpcs3/Emu/RSX/Program/SPIRVCommon.cpp +++ b/rpcs3/Emu/RSX/Program/SPIRVCommon.cpp @@ -135,27 +135,30 @@ namespace spirv glslang::EShClient client; glslang::EShTargetClientVersion target_version; + glslang::EShTargetLanguageVersion spirv_version; EShMessages msg; if (rules == ::glsl::glsl_rules_vulkan) { client = glslang::EShClientVulkan; - target_version = glslang::EShTargetClientVersion::EShTargetVulkan_1_0; + target_version = glslang::EShTargetClientVersion::EShTargetVulkan_1_2; + spirv_version = glslang::EShTargetLanguageVersion::EShTargetSpv_1_5; msg = static_cast(EShMsgVulkanRules | EShMsgSpvRules | EShMsgEnhanced); } else { client = glslang::EShClientOpenGL; target_version = glslang::EShTargetClientVersion::EShTargetOpenGL_450; + spirv_version = glslang::EShTargetLanguageVersion::EShTargetSpv_1_0; msg = static_cast(EShMsgDefault | EShMsgSpvRules | EShMsgEnhanced); } glslang::TProgram program; glslang::TShader shader_object(lang); - shader_object.setEnvInput(glslang::EShSourceGlsl, lang, client, 100); + shader_object.setEnvInput(glslang::EShSourceGlsl, lang, client, 100); // NOTE: 100 here is the spec revision of GL_KHR_vulkan_glsl. There is only one version (100) for the past 10 years. shader_object.setEnvClient(client, target_version); - shader_object.setEnvTarget(glslang::EshTargetSpv, glslang::EShTargetLanguageVersion::EShTargetSpv_1_0); + shader_object.setEnvTarget(glslang::EshTargetSpv, spirv_version); bool success = false; const char* shader_text = shader.data(); From 82a4ff051e87754f99b414fe5da31f07b630ddf2 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 26 Apr 2026 20:38:35 +0300 Subject: [PATCH 020/283] vk: Upgrade Vulkan SDK to 1.4.341 --- .ci/setup-windows.sh | 2 +- .github/workflows/rpcs3.yml | 4 ++-- BUILDING.md | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.ci/setup-windows.sh b/.ci/setup-windows.sh index d8016d8c13..6673851390 100755 --- a/.ci/setup-windows.sh +++ b/.ci/setup-windows.sh @@ -16,7 +16,7 @@ QT_MM_URL="${QT_HOST}${QT_PREFIX}addons.qtmultimedia.${QT_PREFIX_2}qtmultimedia$ 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" +VULKAN_SDK_URL="https://sdk.lunarg.com/sdk/download/1.4.341.1/windows/vulkansdk-windows-X64-${VULKAN_VER}.exe" CCACHE_URL="https://github.com/ccache/ccache/releases/download/v4.12.3/ccache-4.12.3-windows-x86_64.zip" DEP_URLS=" \ diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index 2738698fb4..06392776d5 100644 --- a/.github/workflows/rpcs3.yml +++ b/.github/workflows/rpcs3.yml @@ -217,8 +217,8 @@ jobs: QT_VER_MSVC: 'msvc2022' QT_DATE: '202603180535' LLVM_VER: '19.1.7' - VULKAN_VER: '1.3.268.0' - VULKAN_SDK_SHA: '8459ef49bd06b697115ddd3d97c9aec729e849cd775f5be70897718a9b3b9db5' + VULKAN_VER: '1.4.341.1' + VULKAN_SDK_SHA: 'bcf2d75aa9556889ab974858666e20b3655b6055a0db704ccb47279ff33b5bfe' CCACHE_SHA: '859141059ac950e1e8cd042c66f842f26b9e3a62a1669a69fe6ba180cb58bbdf' CCACHE_BIN_DIR: 'C:\ccache_bin' CCACHE_DIR: 'C:\ccache' diff --git a/BUILDING.md b/BUILDING.md index c1774908fd..38637a9534 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -21,7 +21,7 @@ The following tools are required to build RPCS3 on Windows 10 or later: - [Python 3.6+](https://www.python.org/downloads/) (add to PATH) - [Qt 6.11.0](https://www.qt.io/download-qt-installer) In case you can't download from the official installer, you can use [Another Qt installer](https://github.com/miurahr/aqtinstall) (In that case you will need to manually add the "qtmultimedia" module when installing Qt) -- [Vulkan SDK 1.3.268.0](https://vulkan.lunarg.com/sdk/home) (see "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/windows/getting_started.html)) for now future SDKs don't work. You need precisely 1.3.268.0. +- [Vulkan SDK 1.4.341.1](https://vulkan.lunarg.com/sdk/home) (see "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/windows/getting_started.html)). Note that future SDKs may not work. 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. @@ -40,7 +40,7 @@ These are the essentials tools to build RPCS3 on Linux. Some of them can be inst - Clang 17+ or GCC 13+ - [CMake 3.28.0+](https://www.cmake.org/download/) - [Qt 6.11.0](https://www.qt.io/download-qt-installer) -- [Vulkan SDK 1.3.268.0](https://vulkan.lunarg.com/sdk/home) (See "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/linux/getting_started.html)) for now future SDKs don't work. You need precisely 1.3.268.0. +- [Vulkan SDK 1.4.341.1](https://vulkan.lunarg.com/sdk/home) (See "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/linux/getting_started.html)). Note that future SDKs may not work. - [SDL3](https://github.com/libsdl-org/SDL/releases) (for the FAudio backend) **If you have an NVIDIA GPU, you may need to install the libglvnd package.** From 108276c076518b665b7da73bd6a1301d3fe3e11d Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 26 Apr 2026 20:41:07 +0300 Subject: [PATCH 021/283] vk/data-heap: Implement a caching sliding window buffer view system --- rpcs3/Emu/RSX/VK/vkutils/data_heap.h | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/rpcs3/Emu/RSX/VK/vkutils/data_heap.h b/rpcs3/Emu/RSX/VK/vkutils/data_heap.h index 13ec2a5fdc..4836db9356 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/data_heap.h +++ b/rpcs3/Emu/RSX/VK/vkutils/data_heap.h @@ -4,6 +4,7 @@ #include "../VulkanAPI.h" #include "buffer_object.h" #include "commands.h" +#include "ex.h" #include #include @@ -34,6 +35,8 @@ namespace vk std::unique_ptr shadow; std::vector dirty_ranges; + mutable utils::address_range64 m_cached_buffer_range{}; + protected: bool grow(usz size) override; bool can_allocate_heap(const vk::memory_type_info& target_heap, usz size, int max_usage_percent); @@ -62,6 +65,35 @@ namespace vk void sync(const vk::command_buffer& cmd); + template + VkDescriptorBufferInfoEx window(usz offset, usz range, u64 window_size) const + { + if (window_size > size()) + { + // Driver specific. AMD allows viewing upto 4GB as UBO no problem. + return { *heap, 0, VK_WHOLE_SIZE }; + } + + if (utils::address_range64::start_length(offset, range).inside(m_cached_buffer_range)) [[ likely ]] + { + return { *heap, m_cached_buffer_range.start, m_cached_buffer_range.length() }; + } + + const u64 aligned_window_size = static_cast(window_size / Alignment) * Alignment; + const u64 start_partition = offset / aligned_window_size; + const u64 end_partition = (offset + range - 1) / aligned_window_size; + + if (start_partition == end_partition) [[ likely ]] + { + m_cached_buffer_range = utils::address_range64::start_length(start_partition * aligned_window_size, aligned_window_size); + return { *heap, m_cached_buffer_range.start, m_cached_buffer_range.length() }; + } + + // We have a partition straddler. Invalidate caching and return exact range. + m_cached_buffer_range = {}; + return { *heap, offset, range }; + } + // Properties bool is_dirty() const; bool has_shadow() const { return !!shadow; } From 9c725935dcf225684239e1c2ba1cb7454e6f225d Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 26 Apr 2026 21:12:29 +0300 Subject: [PATCH 022/283] CI: use dropbox for vulkan SDK installer --- .ci/setup-windows.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/setup-windows.sh b/.ci/setup-windows.sh index 6673851390..9487ff29ac 100755 --- a/.ci/setup-windows.sh +++ b/.ci/setup-windows.sh @@ -16,7 +16,7 @@ QT_MM_URL="${QT_HOST}${QT_PREFIX}addons.qtmultimedia.${QT_PREFIX_2}qtmultimedia$ 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://sdk.lunarg.com/sdk/download/1.4.341.1/windows/vulkansdk-windows-X64-${VULKAN_VER}.exe" +VULKAN_SDK_URL="https://www.dropbox.com/scl/fi/g2w92mv46pjxztcyrw9w2/vulkansdk-windows-X64-${VULKAN_VER}.exe?rlkey=cxih47sjebecumdt96qp74gj3&st=7eqiymt7&dl=1" CCACHE_URL="https://github.com/ccache/ccache/releases/download/v4.12.3/ccache-4.12.3-windows-x86_64.zip" DEP_URLS=" \ @@ -88,7 +88,7 @@ for url in $DEP_URLS; do *qt*) checksum=$(curl -fL "${url}.sha1"); algo="sha1"; outDir="$QTDIR/" ;; *llvm*) checksum=$(curl -fL "${url}.sha256"); algo="sha256"; outDir="./build/lib_ext/Release-x64" ;; *ccache*) checksum=$CCACHE_SHA; algo="sha256"; outDir="$CCACHE_BIN_DIR" ;; - *Vulkan*) + *vulkansdk*) # Vulkan setup needs to be run in batch environment # Need to subshell this or else it doesn't wait download_and_verify "$url" "$VULKAN_SDK_SHA" "sha256" "$fileName" From 5839ad9c7057dcf3f026221426e99ded520ffc40 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 27 Apr 2026 13:00:48 +0300 Subject: [PATCH 023/283] vk: Use UBOs for critical per-fragment data --- rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp | 18 +++++++---- rpcs3/Emu/RSX/VK/VKGSRender.cpp | 18 ++++++++--- rpcs3/Emu/RSX/VK/vkutils/device.cpp | 45 ++++++++++++++------------ 3 files changed, 49 insertions(+), 32 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp index 02709d9b5c..853f9f8683 100644 --- a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp @@ -97,7 +97,11 @@ void VKFragmentDecompilerThread::insertHeader(std::stringstream & OS) { prepareBindingTable(); - std::vector required_extensions; + std::vector required_extensions = + { + "GL_EXT_scalar_block_layout", + "GL_EXT_uniform_buffer_unsized_array" + }; if (device_props.has_native_half_support) { @@ -251,7 +255,7 @@ void VKFragmentDecompilerThread::insertConstants(std::stringstream & OS) if (!properties.constant_offsets.empty()) { - OS << "layout(std430, set=1, binding=" << vk_prog->binding_table.cbuf_location << ") readonly buffer FragmentConstantsBuffer\n"; + OS << "layout(std430, set=1, binding=" << vk_prog->binding_table.cbuf_location << ") uniform FragmentConstantsBuffer\n"; OS << "{\n"; OS << " vec4 fc[];\n"; OS << "};\n"; @@ -259,12 +263,12 @@ void VKFragmentDecompilerThread::insertConstants(std::stringstream & OS) } OS << - "layout(std430, set=1, binding=" << vk_prog->binding_table.context_buffer_location << ") readonly buffer FragmentStateBuffer\n" + "layout(std430, set=1, binding=" << vk_prog->binding_table.context_buffer_location << ") uniform FragmentStateBuffer\n" "{\n" " fragment_context_t fs_contexts[];\n" "};\n\n"; - OS << "layout(std430, set=1, binding=" << vk_prog->binding_table.tex_param_location << ") readonly buffer TextureParametersBuffer\n"; + OS << "layout(std430, set=1, binding=" << vk_prog->binding_table.tex_param_location << ") uniform TextureParametersBuffer\n"; OS << "{\n"; OS << " sampler_info texture_parameters[];\n"; OS << "};\n\n"; @@ -284,18 +288,18 @@ void VKFragmentDecompilerThread::insertConstants(std::stringstream & OS) { in.location = vk_prog->binding_table.cbuf_location; in.name = "FragmentConstantsBuffer"; - in.type = vk::glsl::input_type_storage_buffer, + in.type = vk::glsl::input_type_uniform_buffer, inputs.push_back(in); } in.location = vk_prog->binding_table.context_buffer_location; in.name = "FragmentStateBuffer"; - in.type = vk::glsl::input_type_storage_buffer; + in.type = vk::glsl::input_type_uniform_buffer; inputs.push_back(in); in.location = vk_prog->binding_table.tex_param_location; in.name = "TextureParametersBuffer"; - in.type = vk::glsl::input_type_storage_buffer; + in.type = vk::glsl::input_type_uniform_buffer; inputs.push_back(in); in.location = vk_prog->binding_table.polygon_stipple_params_location; diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 885bc7cea2..1fd277effb 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -508,11 +508,11 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar) // VRAM allocation // This first set is bound persistently, so grow notifications are enabled. m_attrib_ring_info.create(VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT, VK_ATTRIB_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_default, "attrib buffer", 0x400000, VK_TRUE); - m_fragment_env_ring_info.create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "fragment env buffer", 0x10000, VK_TRUE); + m_fragment_env_ring_info.create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "fragment env buffer", 0x10000, VK_TRUE); m_vertex_env_ring_info.create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "vertex env buffer", 0x10000, VK_TRUE); - m_fragment_texture_params_ring_info.create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "fragment texture params buffer", 0x10000, VK_TRUE); + m_fragment_texture_params_ring_info.create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "fragment texture params buffer", 0x10000, VK_TRUE); m_vertex_layout_ring_info.create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "vertex layout buffer", 0x10000, VK_TRUE); - m_fragment_constants_ring_info.create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "fragment constants buffer", 0x10000, VK_TRUE); + m_fragment_constants_ring_info.create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "fragment constants buffer", 0x10000, VK_TRUE); m_transform_constants_ring_info.create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_TRANSFORM_CONSTANTS_BUFFER_SIZE_M * 0x100000, vk::heap_pool_default, "transform constants buffer", 0x10000, VK_TRUE); m_raster_env_ring_info.create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "raster env buffer", 0x10000, VK_TRUE); // Below here, we do not bind these persistently. Each draw call specifies the range manually so we do not need heap_grow notifications. @@ -1934,6 +1934,7 @@ void VKGSRender::load_program_env() } const auto& ctx = REGS(m_ctx); + const auto& gpu_limits = m_device->gpu().get_limits(); const u32 fragment_constants_size = current_fp_metadata.program_constants_buffer_length; const bool is_interpreter = m_shader_interpreter.is_interpreter(m_program); @@ -2022,6 +2023,9 @@ void VKGSRender::load_program_env() *ensure(m_fragment_prog), current_fragment_program, true); m_fragment_constants_ring_info.unmap(); + + m_fragment_constants_buffer_info = m_fragment_constants_ring_info.window<16>(m_fragment_constants_dynamic_offset, fragment_constants_size, gpu_limits.maxUniformBufferRange); + m_fragment_constants_dynamic_offset -= m_fragment_constants_buffer_info.offset; } } @@ -2032,15 +2036,21 @@ void VKGSRender::load_program_env() m_draw_processor.fill_fragment_state_buffer(buf, current_fragment_program); m_fragment_env_ring_info.unmap(); + + m_fragment_env_buffer_info = m_fragment_env_ring_info.window<32>(m_fragment_env_dynamic_offset, 32, gpu_limits.maxUniformBufferRange); + m_fragment_env_dynamic_offset -= m_fragment_env_buffer_info.offset; } if (update_fragment_texture_env) { - m_texture_parameters_dynamic_offset = m_fragment_texture_params_ring_info.static_alloc<16, 768>(); + m_texture_parameters_dynamic_offset = m_fragment_texture_params_ring_info.static_alloc<256, 768>(); auto buf = m_fragment_texture_params_ring_info.map(m_texture_parameters_dynamic_offset, 768); current_fragment_program.texture_params.write_to(buf, current_fp_metadata.referenced_textures_mask); m_fragment_texture_params_ring_info.unmap(); + + m_fragment_texture_params_buffer_info = m_fragment_texture_params_ring_info.window<768>(m_texture_parameters_dynamic_offset, 768, gpu_limits.maxUniformBufferRange); + m_texture_parameters_dynamic_offset -= m_fragment_texture_params_buffer_info.offset; } if (update_raster_env) diff --git a/rpcs3/Emu/RSX/VK/vkutils/device.cpp b/rpcs3/Emu/RSX/VK/vkutils/device.cpp index f7b7ffb19c..2c37298440 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/device.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/device.cpp @@ -703,26 +703,15 @@ namespace vk device.ppEnabledExtensionNames = requested_extensions.data(); device.pEnabledFeatures = &enabled_features; - VkPhysicalDeviceFloat16Int8FeaturesKHR shader_support_info{}; - if (pgpu->shader_types_support.allow_float16) - { - // Allow use of f16 type in shaders if possible - shader_support_info.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FLOAT16_INT8_FEATURES_KHR; - shader_support_info.shaderFloat16 = VK_TRUE; - shader_support_info.pNext = const_cast(device.pNext); - device.pNext = &shader_support_info; + VkPhysicalDeviceVulkan12Features vulkan12_features{ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES }; + vulkan12_features.runtimeDescriptorArray = VK_TRUE; + vulkan12_features.uniformBufferStandardLayout = VK_TRUE; + vulkan12_features.pNext = const_cast(device.pNext); + device.pNext = &vulkan12_features; - rsx_log.notice("GPU/driver supports float16 data types natively. Using native float16_t variables if possible."); - } - else - { - rsx_log.notice("GPU/driver lacks support for float16 data types. All float16_t arithmetic will be emulated with float32_t."); - } - - VkPhysicalDeviceDescriptorIndexingFeatures indexing_features{}; if (pgpu->descriptor_indexing_support) { -#define SET_DESCRIPTOR_BITFLAG(field, bit) if (pgpu->descriptor_indexing_support.update_after_bind_mask & (1ull << bit)) indexing_features.field = VK_TRUE +#define SET_DESCRIPTOR_BITFLAG(field, bit) if (pgpu->descriptor_indexing_support.update_after_bind_mask & (1ull << bit)) vulkan12_features.field = VK_TRUE SET_DESCRIPTOR_BITFLAG(descriptorBindingUniformBufferUpdateAfterBind, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER); SET_DESCRIPTOR_BITFLAG(descriptorBindingSampledImageUpdateAfterBind, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER); SET_DESCRIPTOR_BITFLAG(descriptorBindingSampledImageUpdateAfterBind, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE); @@ -731,12 +720,26 @@ namespace vk SET_DESCRIPTOR_BITFLAG(descriptorBindingUniformTexelBufferUpdateAfterBind, VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER); SET_DESCRIPTOR_BITFLAG(descriptorBindingStorageTexelBufferUpdateAfterBind, VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER); #undef SET_DESCRIPTOR_BITFLAG - - indexing_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT; - indexing_features.pNext = const_cast(device.pNext); - device.pNext = &indexing_features; } + if (pgpu->shader_types_support.allow_float16) + { + // Allow use of f16 type in shaders if possible + vulkan12_features.shaderFloat16 = VK_TRUE; + rsx_log.notice("GPU/driver supports float16 data types natively. Using native float16_t variables if possible."); + } + else + { + rsx_log.notice("GPU/driver lacks support for float16 data types. All float16_t arithmetic will be emulated with float32_t."); + } + + // FIXME: Fall back to something. Idk how that would even work though, this really is a hard requirement + VkPhysicalDeviceShaderUniformBufferUnsizedArrayFeaturesEXT ubo_unsized_array_feature{}; + ubo_unsized_array_feature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNIFORM_BUFFER_UNSIZED_ARRAY_FEATURES_EXT; + ubo_unsized_array_feature.shaderUniformBufferUnsizedArray = VK_TRUE; + ubo_unsized_array_feature.pNext = const_cast(device.pNext); + device.pNext = &ubo_unsized_array_feature; + VkPhysicalDeviceCustomBorderColorFeaturesEXT custom_border_color_features{}; if (pgpu->custom_border_color_support) { From ed7f1aa17cd1c838d1bb11ceefdebdf9ff8de3d2 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 28 Apr 2026 01:16:08 +0300 Subject: [PATCH 024/283] vk: Fix UBO alignment mismatch on fragment constants binding - Fragment constants are of dynamic size, so this isn't totally unexpected. - NVIDIA GPUs have terrible UBO alignment requirements --- rpcs3/Emu/RSX/VK/VKGSRender.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 1fd277effb..e928493f4b 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -2016,7 +2016,7 @@ void VKGSRender::load_program_env() // Fragment constants if (fragment_constants_size) { - m_fragment_constants_dynamic_offset = m_fragment_constants_ring_info.alloc<16>(fragment_constants_size); + m_fragment_constants_dynamic_offset = m_fragment_constants_ring_info.alloc<256>(fragment_constants_size); auto buf = m_fragment_constants_ring_info.map(m_fragment_constants_dynamic_offset, fragment_constants_size); m_prog_buffer->fill_fragment_constants_buffer({ reinterpret_cast(buf), fragment_constants_size }, From 41e6cbbb5ad29d6cccae119d83e9bf79986f55e8 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 28 Apr 2026 01:24:24 +0300 Subject: [PATCH 025/283] vk: Fix build on linux CI using outdated SDK --- rpcs3/Emu/RSX/VK/VulkanAPI.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/rpcs3/Emu/RSX/VK/VulkanAPI.h b/rpcs3/Emu/RSX/VK/VulkanAPI.h index 411d653807..ad40634659 100644 --- a/rpcs3/Emu/RSX/VK/VulkanAPI.h +++ b/rpcs3/Emu/RSX/VK/VulkanAPI.h @@ -40,6 +40,19 @@ constexpr VkDriverId VK_DRIVER_ID_MESA_HONEYKRISP = static_cast(26); #endif +#if VK_HEADER_VERSION < 332 +#define VK_EXT_shader_uniform_buffer_unsized_array 1 +#define VK_EXT_SHADER_UNIFORM_BUFFER_UNSIZED_ARRAY_SPEC_VERSION 1 +#define VK_EXT_SHADER_UNIFORM_BUFFER_UNSIZED_ARRAY_EXTENSION_NAME "VK_EXT_shader_uniform_buffer_unsized_array" +typedef struct VkPhysicalDeviceShaderUniformBufferUnsizedArrayFeaturesEXT { + VkStructureType sType; + void* pNext; + VkBool32 shaderUniformBufferUnsizedArray; +} VkPhysicalDeviceShaderUniformBufferUnsizedArrayFeaturesEXT; + +constexpr VkStructureType VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNIFORM_BUFFER_UNSIZED_ARRAY_FEATURES_EXT = static_cast(1000642000); +#endif + #define DECLARE_VK_FUNCTION_HEADER 1 #include "VKProcTable.h" From 8b02f46e676f74b523adb9e9c9c665d857091e42 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 28 Apr 2026 22:36:17 +0300 Subject: [PATCH 026/283] rsx: Handle edge case where window size falls outside memory range --- rpcs3/Emu/RSX/VK/vkutils/data_heap.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/VK/vkutils/data_heap.h b/rpcs3/Emu/RSX/VK/vkutils/data_heap.h index 4836db9356..a8aa8e5141 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/data_heap.h +++ b/rpcs3/Emu/RSX/VK/vkutils/data_heap.h @@ -85,7 +85,9 @@ namespace vk if (start_partition == end_partition) [[ likely ]] { - m_cached_buffer_range = utils::address_range64::start_length(start_partition * aligned_window_size, aligned_window_size); + const u64 block_addr = start_partition * aligned_window_size; + const u64 block_end = std::min(block_addr + aligned_window_size, size()); + m_cached_buffer_range = utils::address_range64::start_end(block_addr, block_end - 1); return { *heap, m_cached_buffer_range.start, m_cached_buffer_range.length() }; } From 3b1abec40593b1f0bceb69b0413cc232af71149a Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 29 Apr 2026 00:04:26 +0300 Subject: [PATCH 027/283] rsx/vk: Implement bulk aligned allocator - Avoids wasting space and allows use of natural arrays in shaders --- rpcs3/Emu/RSX/Common/ring_buffer_helper.h | 329 ++++++++++++---------- rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp | 6 +- rpcs3/Emu/RSX/VK/vkutils/data_heap.h | 15 +- 3 files changed, 195 insertions(+), 155 deletions(-) diff --git a/rpcs3/Emu/RSX/Common/ring_buffer_helper.h b/rpcs3/Emu/RSX/Common/ring_buffer_helper.h index 2aebd546c3..94b8da6e8f 100644 --- a/rpcs3/Emu/RSX/Common/ring_buffer_helper.h +++ b/rpcs3/Emu/RSX/Common/ring_buffer_helper.h @@ -3,167 +3,200 @@ #include "Utilities/StrFmt.h" #include "util/asm.hpp" -/** - * Ring buffer memory helper : - * There are 2 "pointers" (offset inside a memory buffer to be provided by class derivative) - * PUT pointer "points" to the start of allocatable space. - * GET pointer "points" to the start of memory in use by the GPU. - * Space between GET and PUT is used by the GPU ; this structure check that this memory is not overwritten. - * User has to update the GET pointer when synchronisation happens. - */ -class data_heap +namespace rsx { -protected: /** - * Internal implementation of allocation test - * Does alloc cross get position? - */ - bool can_alloc_impl(usz aligned_put_pos, usz aligned_alloc_size) const - { - const usz alloc_end = aligned_put_pos + aligned_alloc_size; - if (alloc_end < m_size) [[ likely ]] - { - // Range before get - if (alloc_end < m_get_pos) - return true; - - // Range after get - if (aligned_put_pos > m_get_pos) - return true; - - return false; - } - - // ..]....[..get.. - if (aligned_put_pos < m_get_pos) - return false; - - // ..get..]...[... - // Actually all resources extending beyond heap space starts at 0 - if (aligned_alloc_size > m_get_pos) - return false; - - return true; - } - - /** - * Does alloc cross get position? - */ - template - bool can_alloc(usz size) const - { - const usz alloc_size = utils::align(size, Alignment); - const usz aligned_put_pos = utils::align(m_put_pos, Alignment); - return can_alloc_impl(aligned_put_pos, alloc_size); - } - - // Grow the buffer to hold at least size bytes - virtual bool grow(usz /*size*/) - { - // Stub - return false; - } - - usz m_size; - usz m_put_pos; // Start of free space - usz m_get_pos; // End of free space - usz m_min_guard_size; // If an allocation touches the guard region, reset the heap to avoid going over budget - - char* m_name; -public: - data_heap() = default; - ~data_heap() = default; - data_heap(const data_heap&) = delete; - data_heap(data_heap&&) = delete; - - void init(usz heap_size, const char* buffer_name = nullptr, usz min_guard_size=0x10000) - { - m_name = const_cast(buffer_name ? buffer_name : ""); - - m_size = heap_size; - m_put_pos = 0; - m_get_pos = heap_size - 1; - - // Allocation stats - m_min_guard_size = min_guard_size; - } - - template - usz alloc(usz size) - { - const usz alloc_size = utils::align(size, Alignment); - const usz aligned_put_pos = utils::align(m_put_pos, Alignment); - - if (!can_alloc(size) && !grow(alloc_size)) - { - fmt::throw_exception("[%s] Working buffer not big enough, buffer_length=%d requested=%d guard=%d", - m_name, m_size, size, m_min_guard_size); - } - - const usz alloc_end = aligned_put_pos + alloc_size; - if (alloc_end < m_size) - { - m_put_pos = alloc_end; - return aligned_put_pos; - } - - m_put_pos = alloc_size; - return 0; - } - - /* - * For use in cases where we take a fixed amount each time + * Ring buffer memory helper : + * There are 2 "pointers" (offset inside a memory buffer to be provided by class derivative) + * PUT pointer "points" to the start of allocatable space. + * GET pointer "points" to the start of memory in use by the GPU. + * Space between GET and PUT is used by the GPU ; this structure check that this memory is not overwritten. + * User has to update the GET pointer when synchronisation happens. */ - template - usz static_alloc() + class data_heap { - static_assert((Size & (Alignment - 1)) == 0); - ensure((m_put_pos & (Alignment - 1)) == 0); - - if (!can_alloc_impl(m_put_pos, Size) && !grow(Size)) + protected: + /** + * Internal implementation of allocation test + * Does alloc cross get position? + */ + bool can_alloc_impl(usz aligned_put_pos, usz aligned_alloc_size) const { - fmt::throw_exception("[%s] Working buffer not big enough, buffer_length=%d requested=%d guard=%d", + const usz alloc_end = aligned_put_pos + aligned_alloc_size; + if (alloc_end < m_size) [[ likely ]] + { + // Range before get + if (alloc_end < m_get_pos) + return true; + + // Range after get + if (aligned_put_pos > m_get_pos) + return true; + + return false; + } + + // ..]....[..get.. + if (aligned_put_pos < m_get_pos) + return false; + + // ..get..]...[... + // Actually all resources extending beyond heap space starts at 0 + if (aligned_alloc_size > m_get_pos) + return false; + + return true; + } + + /** + * Does alloc cross get position? + */ + template + bool can_alloc(usz size) const + { + const usz alloc_size = utils::align(size, Alignment); + const usz aligned_put_pos = utils::align(m_put_pos, Alignment); + return can_alloc_impl(aligned_put_pos, alloc_size); + } + + // Grow the buffer to hold at least size bytes + virtual bool grow(usz /*size*/) + { + // Stub + return false; + } + + usz m_size; + usz m_put_pos; // Start of free space + usz m_get_pos; // End of free space + usz m_min_guard_size; // If an allocation touches the guard region, reset the heap to avoid going over budget + + char* m_name; + public: + data_heap() = default; + ~data_heap() = default; + data_heap(const data_heap&) = delete; + data_heap(data_heap&&) = delete; + + void init(usz heap_size, const char* buffer_name = nullptr, usz min_guard_size = 0x10000) + { + m_name = const_cast(buffer_name ? buffer_name : ""); + + m_size = heap_size; + m_put_pos = 0; + m_get_pos = heap_size - 1; + + // Allocation stats + m_min_guard_size = min_guard_size; + } + + template + usz alloc(usz size) + { + const usz alloc_size = utils::align(size, Alignment); + const usz aligned_put_pos = utils::align(m_put_pos, Alignment); + + if (!can_alloc(size) && !grow(alloc_size)) + { + fmt::throw_exception("[%s] Working buffer not big enough, buffer_length=%d requested=%d guard=%d", + m_name, m_size, size, m_min_guard_size); + } + + const usz alloc_end = aligned_put_pos + alloc_size; + if (alloc_end < m_size) + { + m_put_pos = alloc_end; + return aligned_put_pos; + } + + m_put_pos = alloc_size; + return 0; + } + + /* + * For use in cases where we take a fixed amount each time + */ + template + usz static_alloc() + { + static_assert((Size & (Alignment - 1)) == 0); + ensure((m_put_pos & (Alignment - 1)) == 0); + + if (!can_alloc_impl(m_put_pos, Size) && !grow(Size)) + { + fmt::throw_exception("[%s] Working buffer not big enough, buffer_length=%d requested=%d guard=%d", m_name, m_size, Size, m_min_guard_size); + } + + const usz alloc_end = m_put_pos + Size; + if (alloc_end < m_size) + { + const auto ret_pos = m_put_pos; + m_put_pos = alloc_end; + return ret_pos; + } + + m_put_pos = Size; + return 0; } - const usz alloc_end = m_put_pos + Size; - if (alloc_end < m_size) + /** + * return current putpos - 1 + */ + usz get_current_put_pos_minus_one() const { - const auto ret_pos = m_put_pos; - m_put_pos = alloc_end; - return ret_pos; + return (m_put_pos > 0) ? m_put_pos - 1 : m_size - 1; } - m_put_pos = Size; - return 0; - } + inline void set_get_pos(usz value) + { + m_get_pos = value; + } - /** - * return current putpos - 1 - */ - usz get_current_put_pos_minus_one() const - { - return (m_put_pos > 0) ? m_put_pos - 1 : m_size - 1; - } + void reset_allocation_stats() + { + m_get_pos = get_current_put_pos_minus_one(); + } - inline void set_get_pos(usz value) - { - m_get_pos = value; - } + // Updates the current_allocated_size metrics + inline void notify() + { + // @unused + } - void reset_allocation_stats() - { - m_get_pos = get_current_put_pos_minus_one(); - } + usz size() const + { + return m_size; + } - // Updates the current_allocated_size metrics - inline void notify() - { - // @unused - } + // Bulk static allocator. Allows to allocate one large block and subdivide + // [ 0, 1, 2, 3 ] [ 4, 5, 6, 7 ] ... + template + struct bulk_allocator + { + bulk_allocator(data_heap& container, u32 batch_size = 1) + : m_container(container) + , m_batch_size(batch_size) + {} - usz size() const - { - return m_size; - } -}; + usz alloc() + { + if (!m_capacity) + { + m_address = m_container.alloc(ElementSize * m_batch_size); + m_capacity = m_batch_size; + } + + m_capacity--; + return std::exchange(m_address, m_address + ElementSize); + } + + private: + data_heap& m_container; + usz m_address = 0; + + u32 m_capacity = 0; + u32 m_batch_size = 1; + }; + }; +} diff --git a/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp b/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp index 1ba47abf8b..9534b5f57c 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp @@ -15,7 +15,7 @@ namespace vk void data_heap::create(VkBufferUsageFlags usage, usz size, rsx::flags32_t flags, const char* name, usz guard, VkBool32 notify) { - ::data_heap::init(size, name, guard); + rsx::data_heap::init(size, name, guard); const auto& memory_map = g_render_device->get_memory_mapping(); @@ -135,7 +135,7 @@ namespace vk auto memory_index = m_prefer_writethrough ? memory_map.device_bar : memory_map.host_visible_coherent; // Update heap information and reset the allocator - ::data_heap::init(aligned_new_size, m_name, m_min_guard_size); + rsx::data_heap::init(aligned_new_size, m_name, m_min_guard_size); // Discard old heap and create a new one. Old heap will be garbage collected when no longer needed auto gc = get_resource_manager(); @@ -188,7 +188,7 @@ namespace vk return after_usage < limit; } - void* data_heap::map(usz offset, usz size) + void* data_heap::map_impl(usz offset, usz size) { if (!_ptr) { diff --git a/rpcs3/Emu/RSX/VK/vkutils/data_heap.h b/rpcs3/Emu/RSX/VK/vkutils/data_heap.h index a8aa8e5141..0e219714be 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/data_heap.h +++ b/rpcs3/Emu/RSX/VK/vkutils/data_heap.h @@ -20,7 +20,7 @@ namespace vk heap_pool_force_vram_shadow = (1 << 2), }; - class data_heap : public ::data_heap + class data_heap : public rsx::data_heap { private: usz initial_size = 0; @@ -41,6 +41,8 @@ namespace vk bool grow(usz size) override; bool can_allocate_heap(const vk::memory_type_info& target_heap, usz size, int max_usage_percent); + void* map_impl(usz offset, usz size); + public: std::unique_ptr heap; @@ -51,9 +53,16 @@ namespace vk void create(VkBufferUsageFlags usage, usz size, rsx::flags32_t flags, const char* name, usz guard = 0x10000, VkBool32 notify = VK_FALSE); void destroy(); - void* map(usz offset, usz size); + template + T* map(usz offset, usz size) + { + return reinterpret_cast(map_impl(offset, size)); + } + void unmap(bool force = false); + void sync(const vk::command_buffer& cmd); + template requires std::is_trivially_destructible_v std::pair alloc_and_map(usz count) @@ -63,8 +72,6 @@ namespace vk return { addr, reinterpret_cast(map(addr, size_bytes)) }; } - void sync(const vk::command_buffer& cmd); - template VkDescriptorBufferInfoEx window(usz offset, usz range, u64 window_size) const { From 5144a999b35f2cad9acd6d9396810724c392802e Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 29 Apr 2026 00:07:45 +0300 Subject: [PATCH 028/283] vk: Use UBO for vertex environment block --- rpcs3/Emu/RSX/VK/VKGSRender.cpp | 19 +++++++++++++++++-- rpcs3/Emu/RSX/VK/VKGSRender.h | 2 ++ rpcs3/Emu/RSX/VK/VKVertexProgram.cpp | 6 ++++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index e928493f4b..f382061c58 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -509,7 +509,7 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar) // This first set is bound persistently, so grow notifications are enabled. m_attrib_ring_info.create(VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT, VK_ATTRIB_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_default, "attrib buffer", 0x400000, VK_TRUE); m_fragment_env_ring_info.create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "fragment env buffer", 0x10000, VK_TRUE); - m_vertex_env_ring_info.create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "vertex env buffer", 0x10000, VK_TRUE); + m_vertex_env_ring_info.create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_default, "vertex env buffer", 0x10000, VK_TRUE); m_fragment_texture_params_ring_info.create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "fragment texture params buffer", 0x10000, VK_TRUE); m_vertex_layout_ring_info.create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "vertex layout buffer", 0x10000, VK_TRUE); m_fragment_constants_ring_info.create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "fragment constants buffer", 0x10000, VK_TRUE); @@ -563,6 +563,11 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar) const auto& limits = m_device->gpu().get_limits(); m_texbuffer_view_size = std::min(limits.maxTexelBufferElements, VK_ATTRIB_RING_BUFFER_SIZE_M * 0x100000u); + // Initialize bulk allocators + m_vertex_env_allocator = std::make_unique>( + m_vertex_env_ring_info, + std::min(limits.maxUniformBufferRange / 96u, 1024u)); + if (m_texbuffer_view_size < 0x800000) { // Warn, only possibly expected on macOS @@ -1951,7 +1956,9 @@ void VKGSRender::load_program_env() if (update_vertex_env) { // Vertex state. Note, we're now on std430 alignment here, not hardware alignment. - const auto [mem, buf] = m_vertex_env_ring_info.alloc_and_map<16, char>(96); + // Use the bulk allocator here + const auto mem = m_vertex_env_allocator->alloc(); + auto buf = m_vertex_env_ring_info.map(mem, 96); m_draw_processor.fill_scale_offset_data(buf, false); m_draw_processor.fill_user_clip_data(buf + 64); @@ -1962,6 +1969,9 @@ void VKGSRender::load_program_env() m_vertex_env_ring_info.unmap(); m_vertex_env_dynamic_offset = mem; + + m_vertex_env_buffer_info = m_vertex_env_ring_info.window<256>(m_vertex_env_dynamic_offset, 96, gpu_limits.maxUniformBufferRange); + m_vertex_env_dynamic_offset -= m_vertex_env_buffer_info.offset; } if (update_instancing_data) @@ -2288,6 +2298,11 @@ void VKGSRender::patch_transform_constants(rsx::context* /*ctx*/, u32 index, u32 rsx::io_buffer iobuf(allocate_mem); upload_transform_constants(iobuf); + + if (!iobuf.empty()) + { + m_transform_constants_ring_info.unmap(); + } } void VKGSRender::init_buffers(rsx::framebuffer_creation_context context, bool) diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.h b/rpcs3/Emu/RSX/VK/VKGSRender.h index da1491afcc..a8a6bb6745 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.h +++ b/rpcs3/Emu/RSX/VK/VKGSRender.h @@ -161,6 +161,8 @@ private: u64 m_texture_parameters_dynamic_offset = 0; u64 m_stipple_array_dynamic_offset = 0; + std::unique_ptr> m_vertex_env_allocator; + std::vector m_frame_context_storage; u32 m_max_async_frames = 0u; // Temp frame context to use if the real frame queue is overburdened. Only used for storage diff --git a/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp b/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp index 375d35e942..0186045dd1 100644 --- a/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp +++ b/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp @@ -75,6 +75,8 @@ void VKVertexDecompilerThread::insertHeader(std::stringstream &OS) OS << "#version 450\n\n" + "#extension GL_EXT_scalar_block_layout : require\n" + "#extension GL_EXT_uniform_buffer_unsized_array : require\n" "#extension GL_ARB_separate_shader_objects : enable\n\n"; glsl::insert_subheader_block(OS); @@ -88,7 +90,7 @@ void VKVertexDecompilerThread::insertHeader(std::stringstream &OS) "#define get_user_clip_config() get_vertex_context().user_clip_configuration_bits\n\n"; OS << - "layout(std430, set=0, binding=" << vk_prog->binding_table.context_buffer_location << ") readonly restrict buffer VertexContextBuffer\n" + "layout(std430, set=0, binding=" << vk_prog->binding_table.context_buffer_location << ") uniform VertexContextBuffer\n" "{\n" " vertex_context_t vertex_contexts[];\n" "};\n\n"; @@ -96,7 +98,7 @@ void VKVertexDecompilerThread::insertHeader(std::stringstream &OS) const vk::glsl::program_input context_input { .domain = glsl::glsl_vertex_program, - .type = vk::glsl::input_type_storage_buffer, + .type = vk::glsl::input_type_uniform_buffer, .set = vk::glsl::binding_set_index_vertex, .location = vk_prog->binding_table.context_buffer_location, .name = "VertexContextBuffer" From 91a8c56c3770e05b93f6a655aba2349e75c22d9a Mon Sep 17 00:00:00 2001 From: kd-11 Date: Thu, 30 Apr 2026 04:27:11 +0300 Subject: [PATCH 029/283] vk: Use UBO for basic transform constants - Instancing still uses SSBOs, but that's ok. 95% of draw calls are uninstanced anyway. --- rpcs3/Emu/RSX/VK/VKGSRender.cpp | 8 +++++--- rpcs3/Emu/RSX/VK/VKVertexProgram.cpp | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index f382061c58..c5ee9164dc 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -513,7 +513,7 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar) m_fragment_texture_params_ring_info.create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "fragment texture params buffer", 0x10000, VK_TRUE); m_vertex_layout_ring_info.create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "vertex layout buffer", 0x10000, VK_TRUE); m_fragment_constants_ring_info.create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "fragment constants buffer", 0x10000, VK_TRUE); - m_transform_constants_ring_info.create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_TRANSFORM_CONSTANTS_BUFFER_SIZE_M * 0x100000, vk::heap_pool_default, "transform constants buffer", 0x10000, VK_TRUE); + m_transform_constants_ring_info.create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_TRANSFORM_CONSTANTS_BUFFER_SIZE_M * 0x100000, vk::heap_pool_default, "transform constants buffer", 0x10000, VK_TRUE); m_raster_env_ring_info.create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "raster env buffer", 0x10000, VK_TRUE); // Below here, we do not bind these persistently. Each draw call specifies the range manually so we do not need heap_grow notifications. m_instancing_buffer_ring_info.create(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_TRANSFORM_CONSTANTS_BUFFER_SIZE_M * 0x100000, vk::heap_pool_default, "instancing data buffer"); @@ -2006,8 +2006,7 @@ void VKGSRender::load_program_env() usz mem_offset = 0; auto alloc_storage = [&](usz size) -> std::pair { - const auto alignment = m_device->gpu().get_limits().minStorageBufferOffsetAlignment; - mem_offset = m_transform_constants_ring_info.alloc<1>(utils::align(size, alignment)); + mem_offset = m_transform_constants_ring_info.alloc<256>(size); return std::make_pair(m_transform_constants_ring_info.map(mem_offset, size), size); }; @@ -2018,6 +2017,9 @@ void VKGSRender::load_program_env() { m_transform_constants_ring_info.unmap(); m_xform_constants_dynamic_offset = mem_offset; + + m_vertex_constants_buffer_info = m_transform_constants_ring_info.window<16>(m_xform_constants_dynamic_offset, io_buf.size(), gpu_limits.maxUniformBufferRange); + m_xform_constants_dynamic_offset -= m_vertex_constants_buffer_info.offset; } } diff --git a/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp b/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp index 0186045dd1..35e5f80265 100644 --- a/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp +++ b/rpcs3/Emu/RSX/VK/VKVertexProgram.cpp @@ -199,14 +199,14 @@ void VKVertexDecompilerThread::insertConstants(std::stringstream & OS, const std { if (!(m_prog.ctrl & RSX_SHADER_CONTROL_INSTANCED_CONSTANTS)) { - OS << "layout(std430, set=0, binding=" << vk_prog->binding_table.cbuf_location << ") readonly restrict buffer VertexConstantsBuffer\n"; + OS << "layout(std430, set=0, binding=" << vk_prog->binding_table.cbuf_location << ") uniform VertexConstantsBuffer\n"; OS << "{\n"; OS << " vec4 vc[];\n"; OS << "};\n\n"; in.location = vk_prog->binding_table.cbuf_location; in.name = "VertexConstantsBuffer"; - in.type = vk::glsl::input_type_storage_buffer; + in.type = vk::glsl::input_type_uniform_buffer; inputs.push_back(in); continue; From 80c1c5dd6da5995aa25b5045555a198594d0ea85 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Thu, 30 Apr 2026 12:21:34 +0300 Subject: [PATCH 030/283] vk: Use bulk allocator for FP and VP constant blocks to reduce wastage --- rpcs3/Emu/RSX/Common/ring_buffer_helper.h | 19 +++++++++++++++---- rpcs3/Emu/RSX/VK/VKGSRender.cpp | 14 ++++++++++++-- rpcs3/Emu/RSX/VK/VKGSRender.h | 2 ++ 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/rpcs3/Emu/RSX/Common/ring_buffer_helper.h b/rpcs3/Emu/RSX/Common/ring_buffer_helper.h index 94b8da6e8f..0e71b96fbe 100644 --- a/rpcs3/Emu/RSX/Common/ring_buffer_helper.h +++ b/rpcs3/Emu/RSX/Common/ring_buffer_helper.h @@ -179,16 +179,27 @@ namespace rsx , m_batch_size(batch_size) {} - usz alloc() + usz alloc(u32 element_count = 1) { - if (!m_capacity) + if (m_capacity < element_count) { + ensure(element_count <= m_batch_size); m_address = m_container.alloc(ElementSize * m_batch_size); m_capacity = m_batch_size; } - m_capacity--; - return std::exchange(m_address, m_address + ElementSize); + m_capacity -= element_count; + return std::exchange(m_address, m_address + (ElementSize * element_count)); + } + + usz alloc_bytes(usz size = ElementSize) + { + return alloc(static_cast(size / ElementSize)); + } + + u32 capacity() const + { + return m_capacity; } private: diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index c5ee9164dc..606a8bdbe9 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -568,6 +568,16 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar) m_vertex_env_ring_info, std::min(limits.maxUniformBufferRange / 96u, 1024u)); + m_transform_constants_allocator = std::make_unique>( + m_transform_constants_ring_info, + std::min(limits.maxUniformBufferRange / 16u, 8192u) + ); + + m_fragment_constants_allocator = std::make_unique>( + m_fragment_constants_ring_info, + std::min(limits.maxUniformBufferRange / 16u, 8192u) + ); + if (m_texbuffer_view_size < 0x800000) { // Warn, only possibly expected on macOS @@ -2006,7 +2016,7 @@ void VKGSRender::load_program_env() usz mem_offset = 0; auto alloc_storage = [&](usz size) -> std::pair { - mem_offset = m_transform_constants_ring_info.alloc<256>(size); + mem_offset = m_transform_constants_allocator->alloc_bytes(size); return std::make_pair(m_transform_constants_ring_info.map(mem_offset, size), size); }; @@ -2028,7 +2038,7 @@ void VKGSRender::load_program_env() // Fragment constants if (fragment_constants_size) { - m_fragment_constants_dynamic_offset = m_fragment_constants_ring_info.alloc<256>(fragment_constants_size); + m_fragment_constants_dynamic_offset = m_fragment_constants_allocator->alloc_bytes(fragment_constants_size); auto buf = m_fragment_constants_ring_info.map(m_fragment_constants_dynamic_offset, fragment_constants_size); m_prog_buffer->fill_fragment_constants_buffer({ reinterpret_cast(buf), fragment_constants_size }, diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.h b/rpcs3/Emu/RSX/VK/VKGSRender.h index a8a6bb6745..6b7ea5af13 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.h +++ b/rpcs3/Emu/RSX/VK/VKGSRender.h @@ -162,6 +162,8 @@ private: u64 m_stipple_array_dynamic_offset = 0; std::unique_ptr> m_vertex_env_allocator; + std::unique_ptr> m_transform_constants_allocator; + std::unique_ptr> m_fragment_constants_allocator; std::vector m_frame_context_storage; u32 m_max_async_frames = 0u; From 4e0995744aace665af6d1be32d0d9c8f5681eb46 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Thu, 30 Apr 2026 12:25:01 +0300 Subject: [PATCH 031/283] vk: Fix shader interpreter --- rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp index 7f6db33317..936348176a 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp @@ -179,6 +179,8 @@ namespace vk std::stringstream builder; builder << "#version 450\n" + "#extension GL_EXT_scalar_block_layout : require\n" + "#extension GL_EXT_uniform_buffer_unsized_array : require\n" "#extension GL_ARB_separate_shader_objects : enable\n\n"; ::glsl::insert_subheader_block(builder); From d773a3f94e02f1ff66879899232d75f45e2bf17e Mon Sep 17 00:00:00 2001 From: kd-11 Date: Thu, 30 Apr 2026 12:30:59 +0300 Subject: [PATCH 032/283] ci: Bump docker version and use rpcs3 dropbox URL for vulkan SDK --- .ci/setup-windows.sh | 2 +- .github/workflows/rpcs3.yml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.ci/setup-windows.sh b/.ci/setup-windows.sh index 9487ff29ac..9008860e75 100755 --- a/.ci/setup-windows.sh +++ b/.ci/setup-windows.sh @@ -16,7 +16,7 @@ QT_MM_URL="${QT_HOST}${QT_PREFIX}addons.qtmultimedia.${QT_PREFIX_2}qtmultimedia$ 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/g2w92mv46pjxztcyrw9w2/vulkansdk-windows-X64-${VULKAN_VER}.exe?rlkey=cxih47sjebecumdt96qp74gj3&st=7eqiymt7&dl=1" +VULKAN_SDK_URL="https://www.dropbox.com/scl/fi/olpvqk4346ig7i8btadk5/vulkansdk-windows-X64-${VULKAN_VER}.exe?rlkey=huvssch6sy904qkg8ti9p65br&st=pztptdin&dl=1" CCACHE_URL="https://github.com/ccache/ccache/releases/download/v4.12.3/ccache-4.12.3-windows-x86_64.zip" DEP_URLS=" \ diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index 06392776d5..d89068f219 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.11" + docker_img: "rpcs3/rpcs3-ci-jammy:1.12" 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.11" + docker_img: "rpcs3/rpcs3-ci-jammy:1.12" build_sh: "/rpcs3/.ci/build-linux.sh" compiler: gcc - os: ubuntu-24.04-arm - docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.11" + docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.12" 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.11" + docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.12" build_sh: "/rpcs3/.ci/build-linux-aarch64.sh" compiler: gcc name: RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} From 66d2bc3dd5780be4f4d0712cf29fcc6953ba4540 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 30 Apr 2026 13:19:18 +0200 Subject: [PATCH 033/283] Qt: Add timeouts and abort logic to downloader --- rpcs3/rpcs3qt/downloader.cpp | 53 ++++++++++++++++++++++++++++++------ rpcs3/rpcs3qt/downloader.h | 2 ++ 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/rpcs3/rpcs3qt/downloader.cpp b/rpcs3/rpcs3qt/downloader.cpp index 9bcdbf495a..c8e2b5896b 100644 --- a/rpcs3/rpcs3qt/downloader.cpp +++ b/rpcs3/rpcs3qt/downloader.cpp @@ -14,10 +14,16 @@ LOG_CHANNEL(network_log, "NET"); -usz curl_write_cb_compat(char* ptr, usz /*size*/, usz nmemb, void* userdata) +usz curl_write_cb(char* ptr, usz /*size*/, usz nmemb, void* userdata) { downloader* download = static_cast(userdata); - return download->update_buffer(ptr, nmemb); + return ensure(download)->update_buffer(ptr, nmemb); +} + +int curl_xferinfo_cb(void* userdata, curl_off_t /*dltotal*/, curl_off_t /*dlnow*/, curl_off_t /*ultotal*/, curl_off_t /*ulnow*/) +{ + const downloader* download = static_cast(userdata); + return ensure(download)->aborted() ? 1 : 0; } downloader::downloader(QWidget* parent) @@ -72,8 +78,8 @@ void downloader::start(const std::string& url, bool follow_location, bool show_p CURLcode err = curl_easy_setopt(m_curl->get_curl(), CURLOPT_URL, url.c_str()); if (err != CURLE_OK) network_log.error("curl_easy_setopt(CURLOPT_URL, %s) error: %s", url, curl_easy_strerror(err)); - err = curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEFUNCTION, curl_write_cb_compat); - if (err != CURLE_OK) network_log.error("curl_easy_setopt(CURLOPT_WRITEFUNCTION, curl_write_cb_compat) error: %s", curl_easy_strerror(err)); + err = curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEFUNCTION, curl_write_cb); + if (err != CURLE_OK) network_log.error("curl_easy_setopt(CURLOPT_WRITEFUNCTION, curl_write_cb) error: %s", curl_easy_strerror(err)); err = curl_easy_setopt(m_curl->get_curl(), CURLOPT_WRITEDATA, this); if (err != CURLE_OK) network_log.error("curl_easy_setopt(CURLOPT_WRITEDATA) error: %s", curl_easy_strerror(err)); @@ -81,7 +87,28 @@ void downloader::start(const std::string& url, bool follow_location, bool show_p err = curl_easy_setopt(m_curl->get_curl(), CURLOPT_FOLLOWLOCATION, follow_location ? 1 : 0); if (err != CURLE_OK) network_log.error("curl_easy_setopt(CURLOPT_FOLLOWLOCATION, %d) error: %s", follow_location, curl_easy_strerror(err)); - m_thread = QThread::create([this] + err = curl_easy_setopt(m_curl->get_curl(), CURLOPT_CONNECTTIMEOUT, 10); // seconds + if (err != CURLE_OK) network_log.error("curl_easy_setopt(CURLOPT_CONNECTTIMEOUT) error: %s", curl_easy_strerror(err)); + + err = curl_easy_setopt(m_curl->get_curl(), CURLOPT_TIMEOUT, 0); // Infinite for now + if (err != CURLE_OK) network_log.error("curl_easy_setopt(CURLOPT_TIMEOUT) error: %s", curl_easy_strerror(err)); + + err = curl_easy_setopt(m_curl->get_curl(), CURLOPT_LOW_SPEED_LIMIT, 100); // bytes/sec + if (err != CURLE_OK) network_log.error("curl_easy_setopt(CURLOPT_LOW_SPEED_LIMIT) error: %s", curl_easy_strerror(err)); + + err = curl_easy_setopt(m_curl->get_curl(), CURLOPT_LOW_SPEED_TIME, 10); // seconds (the download will fail if the speed stays below CURLOPT_LOW_SPEED_LIMIT for n seconds) + if (err != CURLE_OK) network_log.error("curl_easy_setopt(CURLOPT_LOW_SPEED_TIME) error: %s", curl_easy_strerror(err)); + + err = curl_easy_setopt(m_curl->get_curl(), CURLOPT_XFERINFOFUNCTION, curl_xferinfo_cb); + if (err != CURLE_OK) network_log.error("curl_easy_setopt(CURLOPT_XFERINFOFUNCTION) error: %s", curl_easy_strerror(err)); + + err = curl_easy_setopt(m_curl->get_curl(), CURLOPT_XFERINFODATA, this); + if (err != CURLE_OK) network_log.error("curl_easy_setopt(CURLOPT_XFERINFODATA) error: %s", curl_easy_strerror(err)); + + err = curl_easy_setopt(m_curl->get_curl(), CURLOPT_NOPROGRESS, 0); + if (err != CURLE_OK) network_log.error("curl_easy_setopt(CURLOPT_NOPROGRESS) error: %s", curl_easy_strerror(err)); + + m_thread = QThread::create([this, url] { thread_base::set_name("Downloader"); @@ -91,12 +118,20 @@ void downloader::start(const std::string& url, bool follow_location, bool show_p const CURLcode result = curl_easy_perform(m_curl->get_curl()); m_curl_success = result == CURLE_OK; - if (!m_curl_success && !m_curl_abort) + if (m_curl_success) { - const std::string error = fmt::format("curl_easy_perform(): %s", m_curl->get_verbose_error(result)); - network_log.error("%s", error); - Q_EMIT signal_download_error(QString::fromStdString(error)); + return; } + + if (m_curl_abort) + { + network_log.notice("curl_easy_perform(): aborted (url='%s')", url); + return; + } + + const std::string error = fmt::format("curl_easy_perform(): %s (url='%s')", m_curl->get_verbose_error(result), url); + network_log.error("%s", error); + Q_EMIT signal_download_error(QString::fromStdString(error)); }); connect(m_thread, &QThread::finished, this, [=, this]() diff --git a/rpcs3/rpcs3qt/downloader.h b/rpcs3/rpcs3qt/downloader.h index 35c2d5533e..10a48d7a74 100644 --- a/rpcs3/rpcs3qt/downloader.h +++ b/rpcs3/rpcs3qt/downloader.h @@ -29,6 +29,8 @@ public: progress_dialog* get_progress_dialog() const; + bool aborted() const { return m_curl_abort; } + private Q_SLOTS: void handle_buffer_update(int size, int max) const; From a8dd0535935a5c5eabdb2b348de48f192bab6db9 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 30 Apr 2026 17:08:01 +0200 Subject: [PATCH 034/283] Fix some warnings --- Utilities/Config.cpp | 5 ----- Utilities/File.cpp | 4 +--- Utilities/sync.h | 2 +- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 4 ++-- rpcs3/Emu/Cell/lv2/sys_fs.cpp | 2 +- rpcs3/Emu/RSX/Overlays/overlay_controls.cpp | 2 +- rpcs3/Emu/RSX/Program/CgBinaryProgram.cpp | 2 +- rpcs3/Input/mm_joystick_handler.cpp | 2 -- rpcs3/Loader/ISO.cpp | 3 ++- rpcs3/Loader/iso_validation.cpp | 2 +- rpcs3/Loader/iso_validation.h | 2 +- rpcs3/rpcs3qt/game_list_actions.cpp | 2 +- rpcs3/rpcs3qt/game_list_context_menu.cpp | 2 +- rpcs3/rpcs3qt/recording_settings_dialog.cpp | 3 --- 14 files changed, 13 insertions(+), 24 deletions(-) diff --git a/Utilities/Config.cpp b/Utilities/Config.cpp index f242bd6172..3abb14582c 100644 --- a/Utilities/Config.cpp +++ b/Utilities/Config.cpp @@ -253,10 +253,6 @@ bool try_to_float(f64* out, std::string_view value, f64 min, f64 max, std::strin bool try_to_string(std::string* out, f64 value, std::string_view name) { -#ifdef __APPLE__ - if (out) *out = std::to_string(value); - return true; -#else std::array str{}; if (auto [ptr, ec] = std::to_chars(str.data(), str.data() + str.size(), value, std::chars_format::fixed); ec == std::errc()) @@ -269,7 +265,6 @@ bool try_to_string(std::string* out, f64 value, std::string_view name) if (out) cfg_log.error("cfg::try_to_string('%s'): could not convert value '%f' to string. error='%s'", name, value, std::make_error_code(ec).message()); return false; } -#endif } bool cfg::try_to_enum_value(u64* out, decltype(&fmt_class_string::format) func, std::string_view value, std::string_view name) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index 59ef528942..da29fba7bd 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -1112,15 +1112,13 @@ bool fs::is_symlink(const std::string& path) return true; } -bool fs::is_optical_raw_device(const std::string& path) +bool fs::is_optical_raw_device([[maybe_unused]] const std::string& path) { #ifdef _WIN32 if (path.starts_with("\\\\.\\")) { return true; } - - return false; #endif return false; } diff --git a/Utilities/sync.h b/Utilities/sync.h index 513a45ee51..e0b8c39cc5 100644 --- a/Utilities/sync.h +++ b/Utilities/sync.h @@ -74,7 +74,7 @@ enum }; #endif -inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* timeout = nullptr, uint mask = 0) +inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* timeout = nullptr, [[maybe_unused]] uint mask = 0) { #ifdef __linux__ return syscall(SYS_futex, uaddr, futex_op, static_cast(val), timeout, nullptr, static_cast(mask)); diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index 7c21ad7aab..8b4bd15e26 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -6625,7 +6625,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s bool found_loop_dictator = false; bool found_loop_argument_for_dictator = false; - u32 null_regs_found = 0; + //u32 null_regs_found = 0; for (u32 i = 0; i < reg->regs.size() && reduced_loop->active; i++) { @@ -6730,7 +6730,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // break; // } - null_regs_found++; + //null_regs_found++; continue; } diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index 92e2011dc6..a34c0c301c 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -1101,7 +1101,7 @@ lv2_file::open_raw_result_t lv2_file::open_raw(const std::string& local_path, s3 return {.error = {}, .file = std::move(file)}; } -lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 mode, const void* arg, u64 size) +lv2_file::open_result_t lv2_file::open(std::string_view vpath, s32 flags, s32 /*mode*/, const void* arg, u64 size) { if (vpath.empty()) { diff --git a/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp b/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp index 5208efd747..21cdd69939 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_controls.cpp @@ -26,7 +26,7 @@ namespace rsx { namespace overlays { - static std::vector generate_unit_quadrant(int num_patch_points, const float offset[2], const float scale[2]) + [[maybe_unused]] static std::vector generate_unit_quadrant(int num_patch_points, const float offset[2], const float scale[2]) { ensure(num_patch_points >= 3); std::vector result(num_patch_points + 1); diff --git a/rpcs3/Emu/RSX/Program/CgBinaryProgram.cpp b/rpcs3/Emu/RSX/Program/CgBinaryProgram.cpp index d9c090addb..c565d21bda 100644 --- a/rpcs3/Emu/RSX/Program/CgBinaryProgram.cpp +++ b/rpcs3/Emu/RSX/Program/CgBinaryProgram.cpp @@ -170,7 +170,6 @@ void CgBinaryDisasm::BuildShaderBody(bool include_glsl) return; } - u32 unused; std::vector be_data; // Swap bytes. FP decompiler expects input in BE @@ -190,6 +189,7 @@ void CgBinaryDisasm::BuildShaderBody(bool include_glsl) rsx_prog.data = reinterpret_cast(be_data.data()) + metadata.program_start_offset; for (u32 i = 0; i < 16; ++i) rsx_prog.texture_state.set_dimension(rsx::texture_dimension_extended::texture_dimension_2d, i); #ifndef WITHOUT_OPENGL + u32 unused = 0; GLFragmentDecompilerThread(m_glsl_shader, param_array, rsx_prog, unused).Task(); #endif } diff --git a/rpcs3/Input/mm_joystick_handler.cpp b/rpcs3/Input/mm_joystick_handler.cpp index 0769d9db4d..710488a4f4 100644 --- a/rpcs3/Input/mm_joystick_handler.cpp +++ b/rpcs3/Input/mm_joystick_handler.cpp @@ -228,7 +228,6 @@ pad_preview_values mm_joystick_handler::get_preview_values(const std::unordered_ const auto get_key_value = [this, &data](const std::string& str) -> u16 { - bool pressed{}; u16 value{}; // The DS3 Button is considered pressed if any configured button combination is pressed @@ -256,7 +255,6 @@ pad_preview_values mm_joystick_handler::get_preview_values(const std::unordered_ if (combo_pressed) { value = std::max(value, combo_val); - pressed = value > 0; } } diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 585e7d279c..175e2fde1d 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -266,9 +266,10 @@ iso_type_status iso_file_decryption::retrieve_key(iso_archive& archive, std::str for (const auto& magic : dec_magics) { - if (node = archive.retrieve(magic.first)) + if (iso_fs_node* _node = archive.retrieve(magic.first)) { magic_value = magic.second; + node = _node; break; } } diff --git a/rpcs3/Loader/iso_validation.cpp b/rpcs3/Loader/iso_validation.cpp index 2309fc43ae..31a82beaa7 100644 --- a/rpcs3/Loader/iso_validation.cpp +++ b/rpcs3/Loader/iso_validation.cpp @@ -11,7 +11,7 @@ LOG_CHANNEL(iso_log, "ISO"); -iso_integrity_status iso_file_validation::check_integrity(const std::string& path, const std::string& hash, std::string* game_name) +iso_integrity_status iso_file_validation::check_integrity(const std::string& hash, std::string* game_name) { // // Check for Redump db diff --git a/rpcs3/Loader/iso_validation.h b/rpcs3/Loader/iso_validation.h index f8a8d6948f..56ac659ce8 100644 --- a/rpcs3/Loader/iso_validation.h +++ b/rpcs3/Loader/iso_validation.h @@ -29,7 +29,7 @@ private: iso_hash_status m_status = iso_hash_status::INITIALIZED; public: - static iso_integrity_status check_integrity(const std::string& path, const std::string& hash, std::string* game_name = nullptr); + static iso_integrity_status check_integrity(const std::string& hash, std::string* game_name = nullptr); const std::string& get_path() const { return m_path; } u64 get_size() const { return m_size; } diff --git a/rpcs3/rpcs3qt/game_list_actions.cpp b/rpcs3/rpcs3qt/game_list_actions.cpp index a199c20e66..13e9d8d29d 100644 --- a/rpcs3/rpcs3qt/game_list_actions.cpp +++ b/rpcs3/rpcs3qt/game_list_actions.cpp @@ -393,7 +393,7 @@ void game_list_actions::ShowGameIntegrityDialog(const game_info& game) { text = "Integrity check completed!\n\n"; - switch (m_iso_validator->check_integrity(m_iso_validator->get_path(), hash, &game_name)) + switch (m_iso_validator->check_integrity(hash, &game_name)) { case iso_integrity_status::NO_MATCH: text += tr("Game check NOT PASSED\n\nNo match found on DB or game corrupted:\n - Hash: %0") diff --git a/rpcs3/rpcs3qt/game_list_context_menu.cpp b/rpcs3/rpcs3qt/game_list_context_menu.cpp index 849aa6c966..eb56a76ab1 100644 --- a/rpcs3/rpcs3qt/game_list_context_menu.cpp +++ b/rpcs3/rpcs3qt/game_list_context_menu.cpp @@ -613,7 +613,7 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info& // That is to highlight a Redump ISO from a non Redump ISO if (iso_type != iso_type_status::NOT_ISO) { - const iso_integrity_status iso_integrity = iso_file_validation::check_integrity(current_game.path, ""); + const iso_integrity_status iso_integrity = iso_file_validation::check_integrity(""); QAction* check_integrity = addAction(tr("&Check ISO Integrity")); diff --git a/rpcs3/rpcs3qt/recording_settings_dialog.cpp b/rpcs3/rpcs3qt/recording_settings_dialog.cpp index befd14713a..cb16c06ff5 100644 --- a/rpcs3/rpcs3qt/recording_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/recording_settings_dialog.cpp @@ -31,9 +31,6 @@ static std::vector get_video_codecs(const AVOutputFormat* fmt) void* opaque = nullptr; while (const AVCodec* codec = av_codec_iterate(&opaque)) { - if (!codec->pix_fmts) - continue; - if (codec->capabilities & AV_CODEC_CAP_EXPERIMENTAL) continue; From c25e3e33acdcf0e62f8b52a201e5c189fbba5b16 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 30 Apr 2026 17:36:17 +0200 Subject: [PATCH 035/283] Update curl to 8.20.0 --- 3rdparty/curl/curl | 2 +- 3rdparty/curl/libcurl.vcxproj | 14 +++++++-- 3rdparty/curl/libcurl.vcxproj.filters | 42 +++++++++++++++++++++------ 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/3rdparty/curl/curl b/3rdparty/curl/curl index 8c908d2d0a..a05f34973e 160000 --- a/3rdparty/curl/curl +++ b/3rdparty/curl/curl @@ -1 +1 @@ -Subproject commit 8c908d2d0a6d32abdedda2c52e90bd56ec76c24d +Subproject commit a05f34973e6c4bb629d018f7cb51487be1c904d8 diff --git a/3rdparty/curl/libcurl.vcxproj b/3rdparty/curl/libcurl.vcxproj index 4db28782a3..9acb78a8b7 100644 --- a/3rdparty/curl/libcurl.vcxproj +++ b/3rdparty/curl/libcurl.vcxproj @@ -66,6 +66,7 @@ + @@ -107,7 +108,6 @@ - @@ -118,6 +118,7 @@ + @@ -172,6 +173,7 @@ + @@ -195,6 +197,8 @@ + + @@ -255,6 +259,7 @@ + @@ -275,7 +280,6 @@ - @@ -309,7 +313,6 @@ - @@ -322,6 +325,7 @@ + @@ -332,6 +336,7 @@ + @@ -368,6 +373,7 @@ + @@ -395,6 +401,8 @@ + + diff --git a/3rdparty/curl/libcurl.vcxproj.filters b/3rdparty/curl/libcurl.vcxproj.filters index d38316e767..482b2f1452 100644 --- a/3rdparty/curl/libcurl.vcxproj.filters +++ b/3rdparty/curl/libcurl.vcxproj.filters @@ -66,9 +66,6 @@ Source Files - - Source Files - Source Files @@ -552,6 +549,21 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + @@ -668,9 +680,6 @@ Header Files - - Header Files - Header Files @@ -1040,9 +1049,6 @@ Header Files - - Header Files - Header Files @@ -1121,6 +1127,24 @@ Header Files + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + From b6ed4201dfef5d9c3301bf6f30266e4a33674ac5 Mon Sep 17 00:00:00 2001 From: schm1dtmac Date: Fri, 1 May 2026 01:55:45 +0100 Subject: [PATCH 036/283] Correct the MVK build config --- .ci/deploy-mac.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.ci/deploy-mac.sh b/.ci/deploy-mac.sh index 0b8c1994e0..5bde5ffb74 100755 --- a/.ci/deploy-mac.sh +++ b/.ci/deploy-mac.sh @@ -7,12 +7,13 @@ cd bin git clone --revision=32dceb35e2c95b46cec501033cbc3a1ddf32d6e8 https://github.com/KhronosGroup/MoltenVK.git cd MoltenVK ./fetchDependencies --macos +sudo xcode-select -switch /Applications/Xcode_16.2.app/Contents/Developer make macos MVK_USE_METAL_PRIVATE_API=1 cd ../ mkdir -p "rpcs3.app/Contents/Resources/vulkan/icd.d" || true -cp "MoltenVK/Package/Latest/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib" -cp "MoltenVK/Package/Latest/MoltenVK/dynamic/dylib/macOS/MoltenVK_icd.json" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" +cp "MoltenVK/Package/Release/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib" +cp "MoltenVK/Package/Release/MoltenVK/dynamic/dylib/macOS/MoltenVK_icd.json" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" sed -i '' "s/.\//..\/..\/..\/Frameworks\//g" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" cp "$(realpath $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib" From e8cd6f4ef6bb4c4d468c8e4ae29263f5c9b7f733 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 5 Apr 2026 11:24:38 +0200 Subject: [PATCH 037/283] Win: Update ffmpeg to 8.1 --- .ci/build-linux-aarch64.sh | 2 +- .ci/build-linux.sh | 2 +- 3rdparty/ffmpeg | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.ci/build-linux-aarch64.sh b/.ci/build-linux-aarch64.sh index 1fe640809c..26fc1dc9d9 100755 --- a/.ci/build-linux-aarch64.sh +++ b/.ci/build-linux-aarch64.sh @@ -35,7 +35,7 @@ cmake .. \ -DUSE_SYSTEM_CURL=ON \ -DUSE_SDL=ON \ -DUSE_SYSTEM_SDL=ON \ - -DUSE_SYSTEM_FFMPEG=OFF \ + -DUSE_SYSTEM_FFMPEG=ON \ -DUSE_SYSTEM_OPENCV=ON \ -DUSE_DISCORD_RPC=ON \ -DOpenGL_GL_PREFERENCE=LEGACY \ diff --git a/.ci/build-linux.sh b/.ci/build-linux.sh index 13a9e802f3..d9f66f6a04 100755 --- a/.ci/build-linux.sh +++ b/.ci/build-linux.sh @@ -46,7 +46,7 @@ cmake .. \ -DUSE_SYSTEM_CURL=ON \ -DUSE_SDL=ON \ -DUSE_SYSTEM_SDL=ON \ - -DUSE_SYSTEM_FFMPEG=OFF \ + -DUSE_SYSTEM_FFMPEG=ON \ -DUSE_SYSTEM_OPENCV=ON \ -DUSE_DISCORD_RPC=ON \ -DOpenGL_GL_PREFERENCE=LEGACY \ diff --git a/3rdparty/ffmpeg b/3rdparty/ffmpeg index ce81114ed9..7de1c1df30 160000 --- a/3rdparty/ffmpeg +++ b/3rdparty/ffmpeg @@ -1 +1 @@ -Subproject commit ce81114ed99e5510f6cd983f59a1eac9f33bb73c +Subproject commit 7de1c1df3032a2c4fbdc529f5774cc0cdac115fa From 5dd7792c3dfff7ad94cdd2e0f31244ba2cc2117e Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sun, 3 May 2026 05:26:25 +0300 Subject: [PATCH 038/283] Remove vm_ref.h --- rpcs3/Emu/Cell/Modules/cellL10n.cpp | 1 - rpcs3/Emu/Cell/PPUThread.h | 14 -- rpcs3/Emu/Memory/vm.cpp | 1 - rpcs3/Emu/Memory/vm.h | 3 - rpcs3/Emu/Memory/vm_ptr.h | 23 ---- rpcs3/Emu/Memory/vm_ref.h | 200 ---------------------------- rpcs3/emucore.vcxproj | 1 - rpcs3/emucore.vcxproj.filters | 3 - 8 files changed, 246 deletions(-) delete mode 100644 rpcs3/Emu/Memory/vm_ref.h diff --git a/rpcs3/Emu/Cell/Modules/cellL10n.cpp b/rpcs3/Emu/Cell/Modules/cellL10n.cpp index 0f47c682e6..60fe8f3583 100644 --- a/rpcs3/Emu/Cell/Modules/cellL10n.cpp +++ b/rpcs3/Emu/Cell/Modules/cellL10n.cpp @@ -1,6 +1,5 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" -#include "Emu/Memory/vm_ref.h" #ifdef _WIN32 #include diff --git a/rpcs3/Emu/Cell/PPUThread.h b/rpcs3/Emu/Cell/PPUThread.h index cf5b91c487..9a135c00c7 100644 --- a/rpcs3/Emu/Cell/PPUThread.h +++ b/rpcs3/Emu/Cell/PPUThread.h @@ -436,20 +436,6 @@ struct ppu_gpr_cast_impl> } }; -template -struct ppu_gpr_cast_impl> -{ - static inline u64 to(const vm::_ref_base& value) - { - return ppu_gpr_cast_impl::to(value.addr()); - } - - static inline vm::_ref_base from(const u64 reg) - { - return vm::cast(ppu_gpr_cast_impl::from(reg)); - } -}; - template <> struct ppu_gpr_cast_impl { diff --git a/rpcs3/Emu/Memory/vm.cpp b/rpcs3/Emu/Memory/vm.cpp index 112b1f8354..266da6a1df 100644 --- a/rpcs3/Emu/Memory/vm.cpp +++ b/rpcs3/Emu/Memory/vm.cpp @@ -1,7 +1,6 @@ #include "stdafx.h" #include "vm_locking.h" #include "vm_ptr.h" -#include "vm_ref.h" #include "vm_reservation.h" #include "Utilities/Thread.h" diff --git a/rpcs3/Emu/Memory/vm.h b/rpcs3/Emu/Memory/vm.h index 09d2d15421..87e798aadb 100644 --- a/rpcs3/Emu/Memory/vm.h +++ b/rpcs3/Emu/Memory/vm.h @@ -377,7 +377,4 @@ namespace vm template class _ptr_base; - - template - class _ref_base; } diff --git a/rpcs3/Emu/Memory/vm_ptr.h b/rpcs3/Emu/Memory/vm_ptr.h index 5081e0701d..4e87a9a6f7 100644 --- a/rpcs3/Emu/Memory/vm_ptr.h +++ b/rpcs3/Emu/Memory/vm_ptr.h @@ -10,9 +10,6 @@ struct ppu_func_opd_t; namespace vm { - template - class _ref_base; - // Enables comparison between comparable types of pointers template concept PtrComparable = requires (T1* t1, T2* t2) { t1 == t2; }; @@ -81,26 +78,6 @@ namespace vm return vm::cast(vm::cast(m_addr) + offset32(mptr) + u32{sizeof(ET)} * index); } - // Get vm reference to a struct member - template requires PtrComparable && (!std::is_void_v) - _ref_base ref(MT T2::*const mptr) const - { - return vm::cast(vm::cast(m_addr) + offset32(mptr)); - } - - // Get vm reference to a struct member with array subscription - template > requires PtrComparable && (!std::is_void_v) - _ref_base ref(MT T2::*const mptr, u32 index) const - { - return vm::cast(vm::cast(m_addr) + offset32(mptr) + u32{sizeof(ET)} * index); - } - - // Get vm reference - _ref_base ref() const requires (!std::is_void_v) - { - return vm::cast(m_addr); - } - template T* get_ptr() const { diff --git a/rpcs3/Emu/Memory/vm_ref.h b/rpcs3/Emu/Memory/vm_ref.h deleted file mode 100644 index 59e6daa8f0..0000000000 --- a/rpcs3/Emu/Memory/vm_ref.h +++ /dev/null @@ -1,200 +0,0 @@ -#pragma once - -#include -#include "vm.h" - -#include "util/to_endian.hpp" - -#ifndef _MSC_VER -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Weffc++" -#endif - -namespace vm -{ - template - class _ptr_base; - - template - class _ref_base - { - AT m_addr; - - static_assert(!std::is_pointer_v, "vm::_ref_base<> error: invalid type (pointer)"); - static_assert(!std::is_reference_v, "vm::_ref_base<> error: invalid type (reference)"); - static_assert(!std::is_function_v, "vm::_ref_base<> error: invalid type (function)"); - static_assert(!std::is_void_v, "vm::_ref_base<> error: invalid type (void)"); - - public: - using type = T; - using addr_type = std::remove_cv_t; - - _ref_base(const _ref_base&) = default; - - _ref_base(vm::addr_t addr) - : m_addr(addr) - { - } - - addr_type addr() const - { - return m_addr; - } - - T& get_ref() const - { - return *static_cast(vm::base(vm::cast(m_addr))); - } - - // convert to vm pointer - vm::_ptr_base ptr() const - { - return vm::cast(m_addr); - } - - operator std::common_type_t() const - { - return get_ref(); - } - - operator T&() const - { - return get_ref(); - } - - T& operator =(const _ref_base& right) - { - return get_ref() = right.get_ref(); - } - - T& operator =(const std::common_type_t& right) const - { - return get_ref() = right; - } - - decltype(auto) operator ++(int) const - { - return get_ref()++; - } - - decltype(auto) operator ++() const - { - return ++get_ref(); - } - - decltype(auto) operator --(int) const - { - return get_ref()--; - } - - decltype(auto) operator --() const - { - return --get_ref(); - } - - template - decltype(auto) operator +=(const T2& right) - { - return get_ref() += right; - } - - template - decltype(auto) operator -=(const T2& right) - { - return get_ref() -= right; - } - - template - decltype(auto) operator *=(const T2& right) - { - return get_ref() *= right; - } - - template - decltype(auto) operator /=(const T2& right) - { - return get_ref() /= right; - } - - template - decltype(auto) operator %=(const T2& right) - { - return get_ref() %= right; - } - - template - decltype(auto) operator &=(const T2& right) - { - return get_ref() &= right; - } - - template - decltype(auto) operator |=(const T2& right) - { - return get_ref() |= right; - } - - template - decltype(auto) operator ^=(const T2& right) - { - return get_ref() ^= right; - } - - template - decltype(auto) operator <<=(const T2& right) - { - return get_ref() <<= right; - } - - template - decltype(auto) operator >>=(const T2& right) - { - return get_ref() >>= right; - } - }; - - // Native endianness reference to LE data - template using refl = _ref_base, AT>; - - // Native endianness reference to BE data - template using refb = _ref_base, AT>; - - // BE reference to LE data - template using brefl = _ref_base, to_be_t>; - - // BE reference to BE data - template using brefb = _ref_base, to_be_t>; - - // LE reference to LE data - template using lrefl = _ref_base, to_le_t>; - - // LE reference to BE data - template using lrefb = _ref_base, to_le_t>; - - inline namespace ps3_ - { - // default reference for PS3 HLE functions (Native endianness reference to BE data) - template using ref = refb; - - // default reference for PS3 HLE structures (BE reference to BE data) - template using bref = brefb; - } -} - -#ifndef _MSC_VER -#pragma GCC diagnostic pop -#endif - -// Change AT endianness to BE/LE -template -struct to_se, Se> -{ - using type = vm::_ref_base::type>; -}; - -// Forbid formatting -template -struct fmt_unveil> -{ - static_assert(!sizeof(T), "vm::_ref_base<>: ambiguous format argument"); -}; diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 3606a20918..d08cac380e 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -1048,7 +1048,6 @@ - diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 87bf4fed67..c6b10021cd 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1578,9 +1578,6 @@ Emu\Memory - - Emu\Memory - Emu\Memory From ca87d103fdc43adcae137098df0acff53b9e437d Mon Sep 17 00:00:00 2001 From: Vishrut Sachan Date: Sun, 3 May 2026 11:11:18 +0530 Subject: [PATCH 039/283] game_list: Add multi-game collection support for ISO format discs --- rpcs3/Emu/GameInfo.h | 1 + rpcs3/Emu/System.cpp | 4 +-- rpcs3/Emu/System.h | 2 ++ rpcs3/Loader/ISO.h | 2 +- rpcs3/Loader/iso_cache.cpp | 10 +++---- rpcs3/Loader/iso_cache.h | 4 +-- rpcs3/rpcs3qt/game_list_frame.cpp | 47 ++++++++++++++++++++++++++++--- rpcs3/rpcs3qt/main_window.cpp | 4 +++ rpcs3/rpcs3qt/qt_utils.cpp | 2 +- 9 files changed, 60 insertions(+), 16 deletions(-) diff --git a/rpcs3/Emu/GameInfo.h b/rpcs3/Emu/GameInfo.h index da8b2638ba..a99e708521 100644 --- a/rpcs3/Emu/GameInfo.h +++ b/rpcs3/Emu/GameInfo.h @@ -9,6 +9,7 @@ struct GameInfo std::string icon_path; std::string movie_path; std::string audio_path; + std::string game_dir; std::string name; std::string serial; diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 39172da368..199aad38ee 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -1495,9 +1495,9 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, // ISOs that are install discs will error if set to EBOOT.BIN // so this should cover both of them - if (fs::exists(path + "PS3_GAME/USRDIR/EBOOT.BIN")) + if (fs::exists(path + m_game_dir + "/USRDIR/EBOOT.BIN")) { - path = path + "PS3_GAME/USRDIR/EBOOT.BIN"; + path = path + m_game_dir + "/USRDIR/EBOOT.BIN"; } m_path_real = m_path; diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 8bee2dc7a5..99c570b809 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -215,6 +215,8 @@ public: m_cb = std::move(cb); } + void SetGameDir(const std::string& game_dir) { m_game_dir = game_dir; } + const auto& GetCallbacks() const { return m_cb; diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index ba39956239..7287146a2f 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -172,7 +172,7 @@ public: iso_archive(const std::string& path); const std::string& path() const { return m_path; } - + const iso_fs_node& root() const { return m_root; } iso_fs_node* retrieve(const std::string& path); bool exists(const std::string& path); bool is_file(const std::string& path); diff --git a/rpcs3/Loader/iso_cache.cpp b/rpcs3/Loader/iso_cache.cpp index 17453489e8..768948e421 100644 --- a/rpcs3/Loader/iso_cache.cpp +++ b/rpcs3/Loader/iso_cache.cpp @@ -34,7 +34,7 @@ namespace namespace iso_cache { - bool load(const std::string& iso_path, iso_metadata_cache_entry& out_entry) + bool load(const std::string& iso_path, const std::string& cache_key, iso_metadata_cache_entry& out_entry) { fs::stat_t iso_stat{}; if (!fs::get_stat(iso_path, iso_stat) || iso_stat.is_directory) @@ -42,7 +42,7 @@ namespace iso_cache return false; } - const std::string stem = get_cache_stem(iso_path); + const std::string stem = get_cache_stem(cache_key); const std::string dir = get_cache_dir(); const std::string yml_path = dir + stem + ".yml"; const std::string sfo_path = dir + stem + ".sfo"; @@ -89,11 +89,9 @@ namespace iso_cache return true; } - void save(const std::string& iso_path, const iso_metadata_cache_entry& entry) + void save(const std::string& iso_path, const std::string& cache_key, const iso_metadata_cache_entry& entry) { - iso_cache_log.notice("Saving cache for '%s'", iso_path); - - const std::string stem = get_cache_stem(iso_path); + const std::string stem = get_cache_stem(cache_key); const std::string dir = get_cache_dir(); const std::string yml_path = dir + stem + ".yml"; const std::string sfo_path = dir + stem + ".sfo"; diff --git a/rpcs3/Loader/iso_cache.h b/rpcs3/Loader/iso_cache.h index 8f6594b76c..a6c66797e6 100644 --- a/rpcs3/Loader/iso_cache.h +++ b/rpcs3/Loader/iso_cache.h @@ -22,10 +22,10 @@ struct iso_metadata_cache_entry namespace iso_cache { // Returns false if no valid cache entry exists or mtime has changed. - bool load(const std::string& iso_path, iso_metadata_cache_entry& out_entry); + bool load(const std::string& iso_path, const std::string& cache_key, iso_metadata_cache_entry& out_entry); // Persists a populated cache entry to disk. - void save(const std::string& iso_path, const iso_metadata_cache_entry& entry); + void save(const std::string& iso_path, const std::string& cache_key, const iso_metadata_cache_entry& entry); // Remove cache entries for ISOs that are no longer in the scanned set. void cleanup(const std::unordered_set& valid_iso_paths); diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 6e745f448c..ed34df171b 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -547,21 +547,24 @@ void game_list_frame::OnParsingFinished() const auto add_game = [this, localized_title, localized_icon, localized_movie, dev_flash, game_icon_path, _hdd, cat_unknown_localized = localized.category.unknown.toStdString(), cat_unknown = cat::cat_unknown.toStdString(), play_hover_movies = m_play_hover_movies, play_hover_music = m_play_hover_music, show_custom_icons = m_show_custom_icons] - (const std::string& dir_or_elf) + (const std::string& dir_or_elf, const std::string& game_dir = "PS3_GAME") { std::unique_ptr archive; iso_metadata_cache_entry cache_entry{}; bool is_raw_device = false; const bool is_archive = is_iso_file(dir_or_elf, nullptr, &is_raw_device); + std::string iso_cache_key; if (is_archive) { + iso_cache_key = (game_dir == "PS3_GAME") ? dir_or_elf : dir_or_elf + "//" + game_dir; // Only construct iso_archive (which walks the full directory tree) in case of raw device or // when no valid cache entry exists for this ISO path + mtime - if (is_raw_device || !iso_cache::load(dir_or_elf, cache_entry)) + if (is_raw_device || !iso_cache::load(dir_or_elf, iso_cache_key, cache_entry)) { archive = std::make_unique(dir_or_elf); } + // Track this ISO path for cache cleanup after scan completes. std::lock_guard lock(m_path_mutex); m_scanned_iso_paths.insert(dir_or_elf); @@ -578,10 +581,11 @@ void game_list_frame::OnParsingFinished() gui_game_info game{}; game.info.path = dir_or_elf; + game.info.game_dir = (game_dir == "PS3_GAME") ? "" : game_dir; const Localized thread_localized; - const std::string sfo_dir = (archive || !cache_entry.psf_data.empty()) ? "PS3_GAME" : rpcs3::utils::get_sfo_dir_from_game_path(dir_or_elf); + const std::string sfo_dir = (archive || !cache_entry.psf_data.empty()) ? game_dir : rpcs3::utils::get_sfo_dir_from_game_path(dir_or_elf); const std::string sfo_path = sfo_dir + "/PARAM.SFO"; // Load PSF: from archive on cache miss, rehydrate from cached SFO bytes on hit. @@ -764,7 +768,7 @@ void game_list_frame::OnParsingFinished() } } - iso_cache::save(dir_or_elf, cache_entry); + iso_cache::save(dir_or_elf, (game_dir == "PS3_GAME") ? dir_or_elf : dir_or_elf + "//" + game_dir, cache_entry); } } @@ -892,6 +896,41 @@ void game_list_frame::OnParsingFinished() add_disc_dir(entry.path, legit_paths); } + else if (is_iso_file(entry.path)) + { + iso_archive archive(entry.path); + const iso_fs_node& root = archive.root(); + const std::regex ps3_gm_regex("^PS3_GM[[:digit:]]{2}$"); + bool found = false; + + for (const auto& child : root.children) + { + if (m_refresh_watcher.isCanceled()) + { + break; + } + + if (!child->metadata.is_directory) + { + continue; + } + + const std::string& name = child->metadata.name; + + if (name == "PS3_GAME" || std::regex_match(name, ps3_gm_regex)) + { + add_game(entry.path, name); + found = true; + } + } + + if (!found) + { + add_game(entry.path); + } + + return; + } else { game_list_log.trace("Invalid game path registered: %s", entry.path); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 4333934cda..97081aca4a 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -3691,6 +3691,10 @@ void main_window::CreateDockWindows() connect(m_game_list_frame, &game_list_frame::RequestBoot, this, [this](const game_info& game, cfg_mode config_mode, const std::string& config_path, const std::string& savestate) { + if (!game->info.game_dir.empty()) + { + Emu.SetGameDir(game->info.game_dir); + } Boot(savestate.empty() ? game->info.path : savestate, game->info.serial, false, false, config_mode, config_path); }); diff --git a/rpcs3/rpcs3qt/qt_utils.cpp b/rpcs3/rpcs3qt/qt_utils.cpp index b648688bf8..84de92d692 100644 --- a/rpcs3/rpcs3qt/qt_utils.cpp +++ b/rpcs3/rpcs3qt/qt_utils.cpp @@ -716,7 +716,7 @@ namespace gui // With the exception of raw device, check cache first — avoids constructing a full iso_archive just for the icon. iso_metadata_cache_entry cache_entry{}; - if (!is_raw_device && iso_cache::load(archive_path, cache_entry) && !cache_entry.icon_data.empty()) + if (!is_raw_device && iso_cache::load(archive_path, archive_path, cache_entry) && !cache_entry.icon_data.empty()) { const QByteArray data(reinterpret_cast(cache_entry.icon_data.data()), static_cast(cache_entry.icon_data.size())); From 155883ea2a2c4705eccb818fa1755c03d79b4b14 Mon Sep 17 00:00:00 2001 From: schm1dtmac Date: Sat, 2 May 2026 18:34:10 +0100 Subject: [PATCH 040/283] Update MoltenVK and restore Game Mode support (as the relevant MVK regression has been fixed) --- .ci/deploy-mac.sh | 2 +- rpcs3/rpcs3.plist.in | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.ci/deploy-mac.sh b/.ci/deploy-mac.sh index 5bde5ffb74..23f98b2d94 100755 --- a/.ci/deploy-mac.sh +++ b/.ci/deploy-mac.sh @@ -4,7 +4,7 @@ cd build || exit 1 cd bin -git clone --revision=32dceb35e2c95b46cec501033cbc3a1ddf32d6e8 https://github.com/KhronosGroup/MoltenVK.git +git clone --revision=a075e5e417f87675ea3137b7365f3e5a99608d72 https://github.com/KhronosGroup/MoltenVK.git cd MoltenVK ./fetchDependencies --macos sudo xcode-select -switch /Applications/Xcode_16.2.app/Contents/Developer diff --git a/rpcs3/rpcs3.plist.in b/rpcs3/rpcs3.plist.in index 137c76e087..93a4f2c186 100644 --- a/rpcs3/rpcs3.plist.in +++ b/rpcs3/rpcs3.plist.in @@ -28,6 +28,8 @@ Licensed under GPLv2 NSHighResolutionCapable + LSApplicationCategoryType + public.app-category.games LSMinimumSystemVersion 14.4 NSCameraUsageDescription From a6fb0c8931b2dcfb02463295d52a3b317e092fc1 Mon Sep 17 00:00:00 2001 From: Haxy Date: Sun, 3 May 2026 00:28:47 +0100 Subject: [PATCH 041/283] Restore 0.85 compatibility for PS3 Binary Decryption 0.85 elfs break here due to them not having a version header offset. Very unlikely these elfs will ever load but its useful to be able to decrypt them at least. Co-authored-by: Elad <18193363+elad335@users.noreply.github.com> --- rpcs3/Crypto/unself.cpp | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/rpcs3/Crypto/unself.cpp b/rpcs3/Crypto/unself.cpp index 3c187400cb..c0eac8d9c7 100644 --- a/rpcs3/Crypto/unself.cpp +++ b/rpcs3/Crypto/unself.cpp @@ -807,6 +807,8 @@ bool SELFDecrypter::LoadHeaders(bool isElf32, SelfAdditionalInfo* out_info) self_f.seek(0); sce_hdr.Load(self_f); + const usz self_size = self_f.size(); + if (out_info) { *out_info = {}; @@ -880,8 +882,9 @@ bool SELFDecrypter::LoadHeaders(bool isElf32, SelfAdditionalInfo* out_info) for(u32 i = 0; i < (isElf32 ? elf32_hdr.e_phnum : elf64_hdr.e_phnum); ++i) { - if (self_f.pos() >= self_f.size()) + if (self_f.pos() >= self_size) { + // Read out of bounds (file is truncated or corrupted) return false; } @@ -889,15 +892,23 @@ bool SELFDecrypter::LoadHeaders(bool isElf32, SelfAdditionalInfo* out_info) m_seg_ext_hdr.back().Load(self_f); } - if (m_ext_hdr.version_hdr_offset == 0 || utils::add_saturate(m_ext_hdr.version_hdr_offset, sizeof(version_header)) > self_f.size()) + if (m_ext_hdr.version_hdr_offset == 0) { + // 0.85 Selfs have version_hdr_offset set to 0 + m_version_hdr = {}; + } + else if (utils::add_saturate(m_ext_hdr.version_hdr_offset, sizeof(version_header)) > self_size) + { + // Read out of bounds (file is truncated or corrupted) return false; } + else + { + // Read SCE version info. + self_f.seek(m_ext_hdr.version_hdr_offset); - // Read SCE version info. - self_f.seek(m_ext_hdr.version_hdr_offset); - - m_version_hdr.Load(self_f); + m_version_hdr.Load(self_f); + } // Read control info. m_supplemental_hdr_arr.clear(); @@ -905,8 +916,9 @@ bool SELFDecrypter::LoadHeaders(bool isElf32, SelfAdditionalInfo* out_info) for (u64 i = 0; i < m_ext_hdr.supplemental_hdr_size;) { - if (self_f.pos() >= self_f.size()) + if (self_f.pos() >= self_size) { + // Read out of bounds (file is truncated or corrupted) return false; } From 773808169e94f2a980db6fa4983b4fcb2a83d16e Mon Sep 17 00:00:00 2001 From: xperia64 Date: Sun, 3 May 2026 22:27:45 -0400 Subject: [PATCH 042/283] Move multi-game ISO parsing code path to proper location --- rpcs3/rpcs3qt/game_list_frame.cpp | 68 +++++++++++++++---------------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index ed34df171b..915eeb054d 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -861,7 +861,38 @@ void game_list_frame::OnParsingFinished() { if (is_iso_file(entry.path)) { - push_path(entry.path, legit_paths); + iso_archive archive(entry.path); + const iso_fs_node& root = archive.root(); + const std::regex ps3_gm_regex("^PS3_GM[[:digit:]]{2}$"); + bool found = false; + + for (const auto& child : root.children) + { + if (m_refresh_watcher.isCanceled()) + { + break; + } + + if (!child->metadata.is_directory) + { + continue; + } + + const std::string& name = child->metadata.name; + + if (name == "PS3_GAME" || std::regex_match(name, ps3_gm_regex)) + { + add_game(entry.path, name); + found = true; + } + } + + if (!found) + { + add_game(entry.path); + } + + return; } else if (fs::is_file(entry.path + "/PARAM.SFO")) { @@ -896,41 +927,6 @@ void game_list_frame::OnParsingFinished() add_disc_dir(entry.path, legit_paths); } - else if (is_iso_file(entry.path)) - { - iso_archive archive(entry.path); - const iso_fs_node& root = archive.root(); - const std::regex ps3_gm_regex("^PS3_GM[[:digit:]]{2}$"); - bool found = false; - - for (const auto& child : root.children) - { - if (m_refresh_watcher.isCanceled()) - { - break; - } - - if (!child->metadata.is_directory) - { - continue; - } - - const std::string& name = child->metadata.name; - - if (name == "PS3_GAME" || std::regex_match(name, ps3_gm_regex)) - { - add_game(entry.path, name); - found = true; - } - } - - if (!found) - { - add_game(entry.path); - } - - return; - } else { game_list_log.trace("Invalid game path registered: %s", entry.path); From 1899b61fa524516862f9b40e3358bd319bd0f962 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 4 May 2026 17:17:49 +0200 Subject: [PATCH 043/283] Update FAudio to 26.05 --- 3rdparty/FAudio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/FAudio b/3rdparty/FAudio index 0372329dbb..14f2875e27 160000 --- a/3rdparty/FAudio +++ b/3rdparty/FAudio @@ -1 +1 @@ -Subproject commit 0372329dbb56e7814d0dea7b6eafa7a613bd8042 +Subproject commit 14f2875e27557ef648bb5dd091adc39cbbbd6378 From 350e7b09f52c01fb31b0f0568563aefa008a81af Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 4 May 2026 17:18:58 +0200 Subject: [PATCH 044/283] Update SDL to 3.4.8 --- 3rdparty/libsdl-org/SDL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/libsdl-org/SDL b/3rdparty/libsdl-org/SDL index 5848e584a1..d9d5536704 160000 --- a/3rdparty/libsdl-org/SDL +++ b/3rdparty/libsdl-org/SDL @@ -1 +1 @@ -Subproject commit 5848e584a1b606de26e3dbd1c7e4ecbc34f807a6 +Subproject commit d9d5536704d585616d4db3c8ba3c4ff6fc2757e1 From 056a8ba1b15b191abe36958b91079a829f7a6d09 Mon Sep 17 00:00:00 2001 From: MarkosTh09 Date: Thu, 30 Apr 2026 10:52:49 +0400 Subject: [PATCH 045/283] macos: hack to fix homebrew rpath issues --- .ci/deploy-mac.sh | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/.ci/deploy-mac.sh b/.ci/deploy-mac.sh index 23f98b2d94..3e4114f75d 100755 --- a/.ci/deploy-mac.sh +++ b/.ci/deploy-mac.sh @@ -57,9 +57,26 @@ rm -f rpcs3.app/Contents/translations/qt_help_*.qm || true mv rpcs3.app RPCS3_.app mv RPCS3_.app RPCS3.app -# Hack -install_name_tool -delete_rpath /opt/homebrew/lib RPCS3.app/Contents/MacOS/rpcs3 || true -install_name_tool -delete_rpath /usr/local/lib RPCS3.app/Contents/MacOS/rpcs3 || true +# Hack to fix rpath issues +BIN="RPCS3.app/Contents/MacOS/rpcs3" +install_name_tool -delete_rpath /opt/homebrew/lib $BIN || true +install_name_tool -delete_rpath /usr/local/lib $BIN || true +install_name_tool -add_rpath @executable_path/../Frameworks "$BIN" 2>/dev/null || true + +# Fix dylib IDs +for lib in RPCS3.app/Contents/Frameworks/*.dylib; do + name=$(basename "$lib") + install_name_tool -id "@rpath/$name" "$lib" +done + +# Rewrite any hardcoded Homebrew paths to use @rpath +find "$BIN" -type f \( -perm +111 -o -name "*.dylib" \) | while read -r bin; do + otool -L "$bin" | grep -E "/opt/homebrew|/usr/local" | awk '{print $1}' | while read -r dep; do + base=$(basename "$dep") + echo "Fixing $dep -> @rpath/$base in $bin" + install_name_tool -change "$dep" "@rpath/$base" "$bin" + done +done # NOTE: "--deep" is deprecated codesign --deep -fs - RPCS3.app From 69bc124930ae1f63ed5358af9fa63445dc507c3c Mon Sep 17 00:00:00 2001 From: MarkosTh09 Date: Mon, 4 May 2026 11:35:31 +0400 Subject: [PATCH 046/283] add `/opt/homebrew` to prefix path instead Append to `CMAKE_PREFIX_PATH` and `CMAKE_SYSTEM_PREFIX` instead of using `include_directories()` and `link_directories()`. This avoids include/link path pollution and reduces clashes with vendored dependencies. This means that conflicting homebrew packages no longer have to be unlinked to build rpcs3, as system paths are searched last. --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 217c40f341..cbb2f66fc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,8 +94,8 @@ if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8) endif() if(APPLE AND CMAKE_OSX_ARCHITECTURES STREQUAL "arm64") - include_directories(/opt/homebrew/include) - link_directories(/opt/homebrew/lib) + list(APPEND CMAKE_PREFIX_PATH "/opt/homebrew") + list(APPEND CMAKE_SYSTEM_PREFIX_PATH "/opt/homebrew") endif() if(MSVC) From 4f23f5505a0847c368df543536571255956f27be Mon Sep 17 00:00:00 2001 From: MarkosTh09 Date: Mon, 4 May 2026 16:48:04 +0400 Subject: [PATCH 047/283] fix minor issues with rpath logic --- .ci/deploy-mac.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.ci/deploy-mac.sh b/.ci/deploy-mac.sh index 3e4114f75d..b37b27c1e2 100755 --- a/.ci/deploy-mac.sh +++ b/.ci/deploy-mac.sh @@ -61,7 +61,6 @@ mv RPCS3_.app RPCS3.app BIN="RPCS3.app/Contents/MacOS/rpcs3" install_name_tool -delete_rpath /opt/homebrew/lib $BIN || true install_name_tool -delete_rpath /usr/local/lib $BIN || true -install_name_tool -add_rpath @executable_path/../Frameworks "$BIN" 2>/dev/null || true # Fix dylib IDs for lib in RPCS3.app/Contents/Frameworks/*.dylib; do @@ -70,7 +69,7 @@ for lib in RPCS3.app/Contents/Frameworks/*.dylib; do done # Rewrite any hardcoded Homebrew paths to use @rpath -find "$BIN" -type f \( -perm +111 -o -name "*.dylib" \) | while read -r bin; do +find "RPCS3.app/Contents/" -type f \( -perm +111 -o -name "*.dylib" \) | while read -r bin; do otool -L "$bin" | grep -E "/opt/homebrew|/usr/local" | awk '{print $1}' | while read -r dep; do base=$(basename "$dep") echo "Fixing $dep -> @rpath/$base in $bin" From d93d9b2c5aa859d1cf2f1381cefd204fb022163a Mon Sep 17 00:00:00 2001 From: Vishrut Sachan Date: Tue, 5 May 2026 14:44:16 +0530 Subject: [PATCH 048/283] game_list: Fix ISO cache bypass in is_from_yml branch for multi-game ISOs (#18683) Fixes regression from #18546 and #18679. ## Problem The is_from_yml ISO branch constructed iso_archive unconditionally, bypassing the cache check inside add_game, making the cache write-only for yml-sourced ISOs. ## Fix Added a lightweight index cache entry (iso_path + "//index") storing the subdir list + mtime. On hit, skips archive construction entirely. On miss, walks as before and writes the index --- rpcs3/Loader/iso_cache.cpp | 95 +++++++++++++++++++++++++++++-- rpcs3/Loader/iso_cache.h | 4 ++ rpcs3/rpcs3qt/game_list_frame.cpp | 26 +++++++-- 3 files changed, 115 insertions(+), 10 deletions(-) diff --git a/rpcs3/Loader/iso_cache.cpp b/rpcs3/Loader/iso_cache.cpp index 768948e421..a37927cf39 100644 --- a/rpcs3/Loader/iso_cache.cpp +++ b/rpcs3/Loader/iso_cache.cpp @@ -19,17 +19,23 @@ namespace return dir; } - // FNV-64 hash of the ISO path used as the cache filename stem. - std::string get_cache_stem(const std::string& iso_path) + // FNV-64 hash of the given key used as the cache filename stem. + std::string get_cache_stem(std::string_view key) { usz hash = rpcs3::fnv_seed; - for (const char c : iso_path) + for (const char c : key) { hash ^= static_cast(c); hash *= rpcs3::fnv_prime; } return fmt::format("%016llx", hash); } + + // Separate stem for the per-ISO subdir index entry. + std::string get_index_stem(const std::string& iso_path) + { + return get_cache_stem(iso_path + "//index"); + } } namespace iso_cache @@ -134,15 +140,96 @@ namespace iso_cache } } + bool load_index(const std::string& iso_path, std::vector& out_subdirs) + { + fs::stat_t iso_stat{}; + if (!fs::get_stat(iso_path, iso_stat) || iso_stat.is_directory) + { + return false; + } + + const std::string dir = get_cache_dir(); + const std::string yml_path = dir + get_index_stem(iso_path) + ".yml"; + + const fs::file yml_file(yml_path); + if (!yml_file) + { + return false; + } + + const auto [node, error] = yaml_load(yml_file.to_string()); + if (!error.empty()) + { + iso_cache_log.warning("Failed to parse index YAML for '%s': %s", iso_path, error); + return false; + } + + const s64 cached_mtime = node["mtime"].as(0); + if (cached_mtime != iso_stat.mtime) + { + return false; + } + + const YAML::Node subdirs_node = node["subdirs"]; + if (!subdirs_node || !subdirs_node.IsSequence()) + { + return false; + } + + for (const auto& entry : subdirs_node) + { + std::string name = entry.as(""); + if (!name.empty()) + { + out_subdirs.push_back(std::move(name)); + } + } + + return !out_subdirs.empty(); + } + + void save_index(const std::string& iso_path, const std::vector& subdirs) + { + fs::stat_t iso_stat{}; + if (!fs::get_stat(iso_path, iso_stat)) + { + return; + } + const std::string dir = get_cache_dir(); + const std::string yml_path = dir + get_index_stem(iso_path) + ".yml"; + + YAML::Emitter out; + out << YAML::BeginMap; + out << YAML::Key << "mtime" << YAML::Value << static_cast(iso_stat.mtime); + out << YAML::Key << "subdirs" << YAML::Value << YAML::BeginSeq; + for (const std::string& s : subdirs) + { + out << s; + } + out << YAML::EndSeq; + out << YAML::EndMap; + + if (fs::pending_file yml_file(yml_path); yml_file.file) + { + yml_file.file.write(out.c_str(), out.size()); + yml_file.commit(); + } + else + { + iso_cache_log.warning("Failed to write index YAML for '%s'", iso_path); + } + } + void cleanup(const std::unordered_set& valid_iso_paths) { const std::string dir = get_cache_dir(); - // Build a set of stems that should exist. + // Build a set of stems that should exist, including index entries. std::unordered_set valid_stems; for (const std::string& path : valid_iso_paths) { valid_stems.insert(get_cache_stem(path)); + valid_stems.insert(get_index_stem(path)); } // Delete any cache files whose stem is not in the valid set. diff --git a/rpcs3/Loader/iso_cache.h b/rpcs3/Loader/iso_cache.h index a6c66797e6..88b080b1df 100644 --- a/rpcs3/Loader/iso_cache.h +++ b/rpcs3/Loader/iso_cache.h @@ -17,6 +17,7 @@ struct iso_metadata_cache_entry std::vector icon_data{}; std::string movie_path{}; std::string audio_path{}; + std::vector subdirs{}; }; namespace iso_cache @@ -27,6 +28,9 @@ namespace iso_cache // Persists a populated cache entry to disk. void save(const std::string& iso_path, const std::string& cache_key, const iso_metadata_cache_entry& entry); + bool load_index(const std::string& iso_path, std::vector& out_subdirs); + void save_index(const std::string& iso_path, const std::vector& subdirs); + // Remove cache entries for ISOs that are no longer in the scanned set. void cleanup(const std::unordered_set& valid_iso_paths); } diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 915eeb054d..6604020b79 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -861,10 +861,22 @@ void game_list_frame::OnParsingFinished() { if (is_iso_file(entry.path)) { + std::vector subdirs; + + if (iso_cache::load_index(entry.path, subdirs)) + { + for (const std::string& name : subdirs) + { + if (m_refresh_watcher.isCanceled()) break; + add_game(entry.path, name); + } + + return; + } + iso_archive archive(entry.path); const iso_fs_node& root = archive.root(); const std::regex ps3_gm_regex("^PS3_GM[[:digit:]]{2}$"); - bool found = false; for (const auto& child : root.children) { @@ -872,24 +884,26 @@ void game_list_frame::OnParsingFinished() { break; } - if (!child->metadata.is_directory) { continue; } const std::string& name = child->metadata.name; - if (name == "PS3_GAME" || std::regex_match(name, ps3_gm_regex)) { + subdirs.push_back(name); add_game(entry.path, name); - found = true; } } - - if (!found) + if (subdirs.empty()) { add_game(entry.path); + subdirs.push_back("PS3_GAME"); + } + if (!m_refresh_watcher.isCanceled()) + { + iso_cache::save_index(entry.path, subdirs); } return; From b2daaff29fdc8d52428660f0376d54ab88c7f370 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 21 Dec 2025 19:08:58 +0300 Subject: [PATCH 049/283] rsx/gl: Implement shader interpreter variant precompilation --- rpcs3/Emu/CMakeLists.txt | 1 + rpcs3/Emu/RSX/GL/GLGSRender.cpp | 30 +++++++---- rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp | 29 ++++++++--- rpcs3/Emu/RSX/GL/GLShaderInterpreter.h | 7 ++- rpcs3/Emu/RSX/Program/ShaderInterpreter.cpp | 58 +++++++++++++++++++++ rpcs3/Emu/RSX/Program/ShaderInterpreter.h | 15 +++++- 6 files changed, 121 insertions(+), 19 deletions(-) create mode 100644 rpcs3/Emu/RSX/Program/ShaderInterpreter.cpp diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index f863a2b911..0de2d08839 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -550,6 +550,7 @@ target_sources(rpcs3_emu PRIVATE RSX/Program/GLSLCommon.cpp RSX/Program/ProgramStateCache.cpp RSX/Program/program_util.cpp + RSX/Program/ShaderInterpreter.cpp RSX/Program/SPIRVCommon.cpp RSX/Program/VertexProgramDecompiler.cpp RSX/GSFrameBase.cpp diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index c1acabd601..7efcb199d1 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -332,8 +332,6 @@ void GLGSRender::on_init_thread() { m_vertex_instructions_buffer->create(gl::buffer::target::ssbo, 16 * 0x100000); m_fragment_instructions_buffer->create(gl::buffer::target::ssbo, 16 * 0x100000); - - m_shader_interpreter.create(); } if (gl_caps.vendor_AMD) @@ -410,21 +408,31 @@ void GLGSRender::on_init_thread() } ); - if (!m_overlay_manager) + if (shadermode == shader_mode::async_with_interpreter || + shadermode == shader_mode::interpreter_only) { - m_frame->hide(); - m_shaders_cache->load(nullptr); - m_frame->show(); + std::unique_ptr dlg = m_overlay_manager + ? std::make_unique(this) + : std::make_unique(); + m_shader_interpreter.create(dlg.get()); } - else - { - rsx::shader_loading_dialog_native dlg(this); - m_shaders_cache->load(&dlg); + if (shadermode != shader_mode::interpreter_only) + { + if (!m_overlay_manager) + { + m_frame->hide(); + m_shaders_cache->load(nullptr); + m_frame->show(); + } + else + { + rsx::shader_loading_dialog_native dlg(this); + m_shaders_cache->load(&dlg); + } } } - void GLGSRender::on_exit() { // Destroy internal RSX state, may call upon this->do_local_task diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index fa5b3627c4..9c19757347 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -3,9 +3,11 @@ #include "GLTextureCache.h" #include "GLVertexProgram.h" #include "GLFragmentProgram.h" -#include "../rsx_methods.h" -#include "../Program/ShaderInterpreter.h" -#include "../Program/GLSLCommon.h" + +#include "Emu/RSX/rsx_methods.h" +#include "Emu/RSX/Overlays/Shaders/shader_loading_dialog.h" +#include "Emu/RSX/Program/ShaderInterpreter.h" +#include "Emu/RSX/Program/GLSLCommon.h" namespace gl { @@ -44,10 +46,25 @@ namespace gl } } - void shader_interpreter::create() + void shader_interpreter::create(rsx::shader_loading_dialog* dlg) { - build_program(::program_common::interpreter::COMPILER_OPT_ENABLE_TEXTURES); - build_program(::program_common::interpreter::COMPILER_OPT_ENABLE_TEXTURES | ::program_common::interpreter::COMPILER_OPT_ENABLE_F32_EXPORT); + dlg->create("Precompiling interpreter variants.\nPlease wait...", "Shader Compilation"); + + const auto variants = program_common::interpreter::get_interpreter_variants(); + const u32 limit = ::size32(variants); + dlg->set_limit(0, limit); + dlg->set_limit(1, 1); + + u32 ctr = 0; + for (auto& variant : variants) + { + build_program(variant.first | variant.second); + dlg->update_msg(0, fmt::format("Building variant %u of %u...", ++ctr, limit)); + dlg->inc_value(0, 1); + } + + dlg->inc_value(1, 1); + dlg->refresh(); } void shader_interpreter::destroy() diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h index 62075dbb4b..92cce63a6b 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h @@ -5,6 +5,11 @@ #include +namespace rsx +{ + struct shader_loading_dialog; +} + namespace gl { using namespace ::glsl; @@ -78,7 +83,7 @@ namespace gl interpreter::cached_program* m_current_interpreter = nullptr; public: - void create(); + void create(rsx::shader_loading_dialog* dlg); void destroy(); void update_fragment_textures(const std::array, 16>& descriptors, u16 reference_mask, u32* out); diff --git a/rpcs3/Emu/RSX/Program/ShaderInterpreter.cpp b/rpcs3/Emu/RSX/Program/ShaderInterpreter.cpp new file mode 100644 index 0000000000..a01171b3e6 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/ShaderInterpreter.cpp @@ -0,0 +1,58 @@ +#include "stdafx.h" +#include "ShaderInterpreter.h" + +#include + +namespace program_common::interpreter +{ + std::vector get_interpreter_variants() + { + // Separable passes to fetch all possible variants + std::unordered_set fs_masks; + for (u32 fs_opt = COMPILER_OPT_FS_MIN, fs_opt_bit = fs_opt; + fs_opt <= COMPILER_OPT_FS_MAX; fs_opt++, fs_opt_bit <<= 1) + { + if (fs_opt_bit & COMPILER_OPT_ALPHA_TEST_MASK) + { + continue; + } + + fs_masks.insert(fs_opt); + } + + // Now we add in the alpha testing variants for all fs variants. + // Only one alpha test type is usable at once + std::unordered_set fs_alpha_test_masks; + for (u32 alpha_test_bit = COMPILER_OPT_ENABLE_ALPHA_TEST_GE; + alpha_test_bit <= COMPILER_OPT_ENABLE_ALPHA_TEST_NE; + alpha_test_bit <<= 1) + { + for (const auto& mask : fs_masks) + { + fs_alpha_test_masks.insert(mask | alpha_test_bit); + } + } + + // VS + std::unordered_set vs_masks; + for (u32 vs_opt = COMPILER_OPT_VS_MIN; vs_opt <= COMPILER_OPT_VS_MAX; ++vs_opt) + { + vs_masks.insert(vs_opt); + } + + // Merge all FS variants + fs_masks.merge(fs_alpha_test_masks); + + // Prepare outputs + std::vector results; + for (const auto& vs_opt : vs_masks) + { + for (const auto& fs_opt : fs_masks) + { + results.push_back({ vs_opt, fs_opt }); + } + } + + return results; + } +} diff --git a/rpcs3/Emu/RSX/Program/ShaderInterpreter.h b/rpcs3/Emu/RSX/Program/ShaderInterpreter.h index f89c058dec..d31da660b1 100644 --- a/rpcs3/Emu/RSX/Program/ShaderInterpreter.h +++ b/rpcs3/Emu/RSX/Program/ShaderInterpreter.h @@ -1,5 +1,7 @@ #pragma once +#include + namespace program_common { namespace interpreter @@ -22,7 +24,15 @@ namespace program_common COMPILER_OPT_ENABLE_INSTANCING = (1 << 13), COMPILER_OPT_ENABLE_VTX_TEXTURES = (1 << 14), - COMPILER_OPT_MAX = COMPILER_OPT_ENABLE_VTX_TEXTURES + // Meta + COMPILER_OPT_MAX = COMPILER_OPT_ENABLE_VTX_TEXTURES, + COMPILER_OPT_ALPHA_TEST_MASK = (0b111111 << COMPILER_OPT_ENABLE_ALPHA_TEST_GE), + + // Bounds + COMPILER_OPT_FS_MAX = COMPILER_OPT_ENABLE_STIPPLING, + COMPILER_OPT_FS_MIN = COMPILER_OPT_ENABLE_TEXTURES, + COMPILER_OPT_VS_MAX = COMPILER_OPT_ENABLE_VTX_TEXTURES, + COMPILER_OPT_VS_MIN = COMPILER_OPT_ENABLE_INSTANCING, }; static std::string get_vertex_interpreter() @@ -40,5 +50,8 @@ namespace program_common ; return s; } + + using interpreter_variant_t = std::pair; + std::vector get_interpreter_variants(); } } From f694000454305ea94af0bce4c0beb6b359bb1715 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Thu, 25 Dec 2025 23:56:54 +0300 Subject: [PATCH 050/283] rsx: Fix shader interpreter variants generation --- rpcs3/Emu/RSX/Program/ShaderInterpreter.cpp | 101 +++++++++++--------- rpcs3/Emu/RSX/Program/ShaderInterpreter.h | 37 +++---- rpcs3/emucore.vcxproj | 1 + rpcs3/emucore.vcxproj.filters | 3 + 4 files changed, 82 insertions(+), 60 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/ShaderInterpreter.cpp b/rpcs3/Emu/RSX/Program/ShaderInterpreter.cpp index a01171b3e6..54ba46b28a 100644 --- a/rpcs3/Emu/RSX/Program/ShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/Program/ShaderInterpreter.cpp @@ -5,54 +5,67 @@ namespace program_common::interpreter { - std::vector get_interpreter_variants() - { - // Separable passes to fetch all possible variants - std::unordered_set fs_masks; - for (u32 fs_opt = COMPILER_OPT_FS_MIN, fs_opt_bit = fs_opt; - fs_opt <= COMPILER_OPT_FS_MAX; fs_opt++, fs_opt_bit <<= 1) - { - if (fs_opt_bit & COMPILER_OPT_ALPHA_TEST_MASK) - { - continue; - } + void bitrange_foreach(u32 min, u32 max, std::function func) + { + if (max <= min) + { + return; + } - fs_masks.insert(fs_opt); - } + const u32 shift = std::countr_zero(min); + const u32 a = min >> shift; + const u32 b = (max + max) >> shift; - // Now we add in the alpha testing variants for all fs variants. - // Only one alpha test type is usable at once - std::unordered_set fs_alpha_test_masks; - for (u32 alpha_test_bit = COMPILER_OPT_ENABLE_ALPHA_TEST_GE; - alpha_test_bit <= COMPILER_OPT_ENABLE_ALPHA_TEST_NE; - alpha_test_bit <<= 1) - { - for (const auto& mask : fs_masks) - { - fs_alpha_test_masks.insert(mask | alpha_test_bit); - } - } + for (u32 acc = a; acc < b; acc++) + { + func(acc << shift); + } + } - // VS - std::unordered_set vs_masks; - for (u32 vs_opt = COMPILER_OPT_VS_MIN; vs_opt <= COMPILER_OPT_VS_MAX; ++vs_opt) - { - vs_masks.insert(vs_opt); - } + std::vector get_interpreter_variants() + { + // Separable passes to fetch all possible variants + std::unordered_set fs_masks; + fs_masks.insert(0); + bitrange_foreach(COMPILER_OPT_FS_MIN, COMPILER_OPT_FS_MAX, [&](u32 fs_opt) + { + fs_masks.insert(fs_opt); + }); - // Merge all FS variants - fs_masks.merge(fs_alpha_test_masks); + // Now we add in the alpha testing variants for all fs variants. + // Only one alpha test type is usable at once + std::unordered_set fs_alpha_test_masks; + for (u32 alpha_test_bit = COMPILER_OPT_ENABLE_ALPHA_TEST_GE; + alpha_test_bit <= COMPILER_OPT_ENABLE_ALPHA_TEST_NE; + alpha_test_bit <<= 1) + { + for (const auto& mask : fs_masks) + { + fs_alpha_test_masks.insert(mask | alpha_test_bit); + } + } - // Prepare outputs - std::vector results; - for (const auto& vs_opt : vs_masks) - { - for (const auto& fs_opt : fs_masks) - { - results.push_back({ vs_opt, fs_opt }); - } - } + // VS + std::unordered_set vs_masks; + vs_masks.insert(0); + bitrange_foreach(COMPILER_OPT_VS_MIN, COMPILER_OPT_VS_MAX, [&](u32 vs_opt) + { + vs_masks.insert(vs_opt); + }); - return results; - } + // Merge all FS variants + fs_masks.merge(fs_alpha_test_masks); + + // Prepare outputs + std::vector results; + for (const auto& vs_opt : vs_masks) + { + for (const auto& fs_opt : fs_masks) + { + results.push_back({ vs_opt, fs_opt }); + } + } + + return results; + } } diff --git a/rpcs3/Emu/RSX/Program/ShaderInterpreter.h b/rpcs3/Emu/RSX/Program/ShaderInterpreter.h index d31da660b1..0090a5978e 100644 --- a/rpcs3/Emu/RSX/Program/ShaderInterpreter.h +++ b/rpcs3/Emu/RSX/Program/ShaderInterpreter.h @@ -6,30 +6,35 @@ namespace program_common { namespace interpreter { - enum compiler_option + enum compiler_option : u32 { + // FS Mix-N-Match COMPILER_OPT_ENABLE_TEXTURES = (1 << 0), COMPILER_OPT_ENABLE_DEPTH_EXPORT = (1 << 1), COMPILER_OPT_ENABLE_F32_EXPORT = (1 << 2), - COMPILER_OPT_ENABLE_ALPHA_TEST_GE = (1 << 3), - COMPILER_OPT_ENABLE_ALPHA_TEST_G = (1 << 4), - COMPILER_OPT_ENABLE_ALPHA_TEST_LE = (1 << 5), - COMPILER_OPT_ENABLE_ALPHA_TEST_L = (1 << 6), - COMPILER_OPT_ENABLE_ALPHA_TEST_EQ = (1 << 7), - COMPILER_OPT_ENABLE_ALPHA_TEST_NE = (1 << 8), - COMPILER_OPT_ENABLE_FLOW_CTRL = (1 << 9), - COMPILER_OPT_ENABLE_PACKING = (1 << 10), - COMPILER_OPT_ENABLE_KIL = (1 << 11), - COMPILER_OPT_ENABLE_STIPPLING = (1 << 12), - COMPILER_OPT_ENABLE_INSTANCING = (1 << 13), - COMPILER_OPT_ENABLE_VTX_TEXTURES = (1 << 14), + COMPILER_OPT_ENABLE_PACKING = (1 << 3), + COMPILER_OPT_ENABLE_KIL = (1 << 4), + COMPILER_OPT_ENABLE_STIPPLING = (1 << 5), + COMPILER_OPT_ENABLE_FLOW_CTRL = (1 << 6), + + // VS Mix-N-Match + COMPILER_OPT_ENABLE_INSTANCING = (1 << 7), + COMPILER_OPT_ENABLE_VTX_TEXTURES = (1 << 8), + + // Exclusive bits. Only one can be set at a time + COMPILER_OPT_ENABLE_ALPHA_TEST_GE = (1 << 9), + COMPILER_OPT_ENABLE_ALPHA_TEST_G = (1 << 10), + COMPILER_OPT_ENABLE_ALPHA_TEST_LE = (1 << 11), + COMPILER_OPT_ENABLE_ALPHA_TEST_L = (1 << 12), + COMPILER_OPT_ENABLE_ALPHA_TEST_EQ = (1 << 13), + COMPILER_OPT_ENABLE_ALPHA_TEST_NE = (1 << 14), // Meta - COMPILER_OPT_MAX = COMPILER_OPT_ENABLE_VTX_TEXTURES, - COMPILER_OPT_ALPHA_TEST_MASK = (0b111111 << COMPILER_OPT_ENABLE_ALPHA_TEST_GE), + COMPILER_OPT_MAX = COMPILER_OPT_ENABLE_ALPHA_TEST_NE, + COMPILER_OPT_ALPHA_TEST_MASK = (0b111111 << 9), // Bounds - COMPILER_OPT_FS_MAX = COMPILER_OPT_ENABLE_STIPPLING, + COMPILER_OPT_FS_MAX = COMPILER_OPT_ENABLE_FLOW_CTRL, COMPILER_OPT_FS_MIN = COMPILER_OPT_ENABLE_TEXTURES, COMPILER_OPT_VS_MAX = COMPILER_OPT_ENABLE_VTX_TEXTURES, COMPILER_OPT_VS_MIN = COMPILER_OPT_ENABLE_INSTANCING, diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index d08cac380e..9a8bc5a76c 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -171,6 +171,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index c6b10021cd..8534c79285 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1399,6 +1399,9 @@ Emu\GPU\RSX\Program\Assembler + + Emu\GPU\RSX\Program + Emu\Io From 90ea92d93badc94d724e4c5656701e19342ffe5c Mon Sep 17 00:00:00 2001 From: kd-11 Date: Fri, 26 Dec 2025 00:21:55 +0300 Subject: [PATCH 051/283] gl: Fix crash when closing shader loading dialog --- rpcs3/Emu/RSX/GL/GLGSRender.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index 7efcb199d1..96645e7b56 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -415,6 +415,7 @@ void GLGSRender::on_init_thread() ? std::make_unique(this) : std::make_unique(); m_shader_interpreter.create(dlg.get()); + dlg->close(); } if (shadermode != shader_mode::interpreter_only) From b3ac8127ea7ffdaeba7e4c000584eb58b5efc02f Mon Sep 17 00:00:00 2001 From: kd-11 Date: Fri, 26 Dec 2025 00:46:23 +0300 Subject: [PATCH 052/283] vk: Prebuild base interpreter variants --- rpcs3/Emu/RSX/VK/VKGSRender.cpp | 10 +++++++++ rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp | 28 ++++++++++++++++++++++-- rpcs3/Emu/RSX/VK/VKShaderInterpreter.h | 7 ++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 606a8bdbe9..d5fc3396ed 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -1237,6 +1237,16 @@ void VKGSRender::on_init_thread() GSRender::on_init_thread(); zcull_ctrl.reset(static_cast<::rsx::reports::ZCULL_control*>(this)); + if (g_cfg.video.shadermode == shader_mode::async_with_interpreter || + g_cfg.video.shadermode == shader_mode::interpreter_only) + { + std::unique_ptr dlg = m_overlay_manager + ? std::make_unique(this) + : std::make_unique(); + m_shader_interpreter.preload(dlg.get()); + dlg->close(); + } + if (!m_overlay_manager) { m_frame->hide(); diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp index 936348176a..39b654b2ad 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp @@ -4,11 +4,13 @@ #include "VKCommonPipelineLayout.h" #include "VKVertexProgram.h" #include "VKFragmentProgram.h" +#include "VKHelpers.h" +#include "VKRenderPass.h" + +#include "../Overlays/Shaders/shader_loading_dialog.h" #include "../Program/GLSLCommon.h" #include "../Program/ShaderInterpreter.h" #include "../rsx_methods.h" -#include "VKHelpers.h" -#include "VKRenderPass.h" namespace vk { @@ -574,4 +576,26 @@ namespace vk auto it = m_pipeline_info_cache.insert_or_assign(compiler_opt, result); return &it.first->second; } + + void shader_interpreter::preload(rsx::shader_loading_dialog* dlg) + { + dlg->create("Precompiling interpreter variants.\nPlease wait...", "Shader Compilation"); + + const auto variants = program_common::interpreter::get_interpreter_variants(); + const u32 limit = ::size32(variants); + dlg->set_limit(0, limit); + dlg->set_limit(1, 1); + + u32 ctr = 0; + for (auto& variant : variants) + { + build_fs(variant.first | variant.second); + build_vs(variant.first | variant.second); + dlg->update_msg(0, fmt::format("Building variant %u of %u...", ++ctr, limit)); + dlg->inc_value(0, 1); + } + + dlg->inc_value(1, 1); + dlg->refresh(); + } }; diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h index 840ae9b969..864406ea08 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h @@ -8,6 +8,11 @@ class VKVertexProgram; class VKFragmentProgram; +namespace rsx +{ + struct shader_loading_dialog; +} + namespace vk { using ::program_hash_util::fragment_program_utils; @@ -70,6 +75,8 @@ namespace vk void init(const vk::render_device& dev); void destroy(); + void preload(rsx::shader_loading_dialog* dlg); + glsl::program* get( const vk::pipeline_props& properties, const program_hash_util::fragment_program_utils::fragment_program_metadata& fp_metadata, From 59f54caf080d598f5f357ac16b4d6c8efcb4e1cb Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 8 Apr 2026 02:25:07 +0300 Subject: [PATCH 053/283] gl: Strengthen RAII guarantees for some more objects --- rpcs3/Emu/RSX/GL/glutils/program.cpp | 4 ++-- rpcs3/Emu/RSX/GL/glutils/program.h | 22 +++++++++++++++++----- rpcs3/Emu/RSX/GL/glutils/ring_buffer.cpp | 14 +++++++------- rpcs3/Emu/RSX/GL/glutils/ring_buffer.h | 2 +- rpcs3/Emu/RSX/GL/glutils/sync.hpp | 6 ++++++ 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/glutils/program.cpp b/rpcs3/Emu/RSX/GL/glutils/program.cpp index 97c46abbb9..e68e4b5132 100644 --- a/rpcs3/Emu/RSX/GL/glutils/program.cpp +++ b/rpcs3/Emu/RSX/GL/glutils/program.cpp @@ -220,7 +220,7 @@ namespace gl return (found->second >= 0); } - auto result = glGetUniformLocation(m_program.id(), name.c_str()); + auto result = glGetUniformLocation(m_program->id(), name.c_str()); locations[name] = result; if (location) @@ -247,7 +247,7 @@ namespace gl } } - auto result = glGetUniformLocation(m_program.id(), name.c_str()); + auto result = glGetUniformLocation(m_program->id(), name.c_str()); if (result < 0) { diff --git a/rpcs3/Emu/RSX/GL/glutils/program.h b/rpcs3/Emu/RSX/GL/glutils/program.h index 5caca0ed98..90dcf6bf1d 100644 --- a/rpcs3/Emu/RSX/GL/glutils/program.h +++ b/rpcs3/Emu/RSX/GL/glutils/program.h @@ -55,7 +55,7 @@ namespace gl const std::string& get_source() const { return source; } - fence get_compile_fence_sync() const { return m_compiled_fence; } + const fence& get_compile_fence_sync() const { return m_compiled_fence; } bool created() const { return m_id != GL_NONE; } @@ -106,29 +106,34 @@ namespace gl class uniforms_t { - program& m_program; + program* m_program = nullptr; std::unordered_map locations; public: uniforms_t(program* program) - : m_program(*program) + : m_program(program) {} + uniforms_t(uniforms_t&&) noexcept = default; + uniforms_t& operator = (uniforms_t&&) noexcept = default; + void clear() { locations.clear(); } bool has_location(const std::string& name, int* location = nullptr); GLint location(const std::string& name); - uniform_t operator[](GLint location) { return{ m_program, location }; } + uniform_t operator[](GLint location) { return{ *m_program, location }; } - uniform_t operator[](const std::string& name) { return{ m_program, location(name) }; } + uniform_t operator[](const std::string& name) { return{ *m_program, location(name) }; } } uniforms{ this }; public: program() = default; + program(const program&) = delete; + program& operator = (const program&) = delete; ~program() { @@ -138,6 +143,13 @@ namespace gl } } + void swap(program&& other) noexcept + { + std::swap(m_id, other.m_id); + std::swap(m_fence, other.m_fence); + std::swap(uniforms, other.uniforms); + } + program& recreate() { remove(); diff --git a/rpcs3/Emu/RSX/GL/glutils/ring_buffer.cpp b/rpcs3/Emu/RSX/GL/glutils/ring_buffer.cpp index 146136e4b4..ac0cc48afe 100644 --- a/rpcs3/Emu/RSX/GL/glutils/ring_buffer.cpp +++ b/rpcs3/Emu/RSX/GL/glutils/ring_buffer.cpp @@ -283,10 +283,10 @@ namespace gl const auto range = utils::address_range32::start_length(start, length); m_barriers.erase(std::remove_if(m_barriers.begin(), m_barriers.end(), [&range](auto& barrier_) { - if (barrier_.range.overlaps(range)) + if (barrier_->range.overlaps(range)) { - barrier_.signal.server_wait_sync(); - barrier_.signal.destroy(); + barrier_->signal.server_wait_sync(); + barrier_->signal.destroy(); return true; } @@ -301,9 +301,9 @@ namespace gl return; } - barrier barrier_; - barrier_.range = utils::address_range32::start_length(start, length); - barrier_.signal.create(); - m_barriers.emplace_back(barrier_); + auto barrier_ = std::make_unique(); + barrier_->range = utils::address_range32::start_length(start, length); + barrier_->signal.create(); + m_barriers.emplace_back(std::move(barrier_)); } } diff --git a/rpcs3/Emu/RSX/GL/glutils/ring_buffer.h b/rpcs3/Emu/RSX/GL/glutils/ring_buffer.h index 37ba0e4bdf..fc9de2aa41 100644 --- a/rpcs3/Emu/RSX/GL/glutils/ring_buffer.h +++ b/rpcs3/Emu/RSX/GL/glutils/ring_buffer.h @@ -92,7 +92,7 @@ namespace gl }; buffer m_storage; - std::vector m_barriers; + std::vector> m_barriers; u64 m_alloc_pointer = 0; void pop_barrier(u32 start, u32 length); diff --git a/rpcs3/Emu/RSX/GL/glutils/sync.hpp b/rpcs3/Emu/RSX/GL/glutils/sync.hpp index 4a39bb6171..cab7d9ae66 100644 --- a/rpcs3/Emu/RSX/GL/glutils/sync.hpp +++ b/rpcs3/Emu/RSX/GL/glutils/sync.hpp @@ -15,6 +15,12 @@ namespace gl fence() = default; ~fence() = default; + fence(const fence&) = delete; + fence& operator = (const fence&) = delete; + + fence(fence&&) noexcept = default; + fence& operator = (fence&&) noexcept = default; + void create() { m_value = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); From af2af233068a1296b2ab94cfd56c16563a9034e1 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 8 Apr 2026 03:00:07 +0300 Subject: [PATCH 054/283] gl/interpreter: Implement async variant generation --- rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp | 59 ++++++++++++++++++++++-- rpcs3/Emu/RSX/GL/GLShaderInterpreter.h | 3 ++ 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index 9c19757347..9eaa3b03b1 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -3,6 +3,7 @@ #include "GLTextureCache.h" #include "GLVertexProgram.h" #include "GLFragmentProgram.h" +#include "GLPipelineCompiler.h" #include "Emu/RSX/rsx_methods.h" #include "Emu/RSX/Overlays/Shaders/shader_loading_dialog.h" @@ -55,14 +56,24 @@ namespace gl dlg->set_limit(0, limit); dlg->set_limit(1, 1); - u32 ctr = 0; + atomic_t ctr = 0; + auto progress_hook = [&](interpreter::cached_program*) { ctr++; }; + for (auto& variant : variants) { - build_program(variant.first | variant.second); - dlg->update_msg(0, fmt::format("Building variant %u of %u...", ++ctr, limit)); - dlg->inc_value(0, 1); + build_program_async(variant.first | variant.second, progress_hook); } + do + { + std::this_thread::sleep_for(16ms); + + const u32 completed = ctr.load(); + dlg->update_msg(0, fmt::format("Building variant %u of %u...", completed, limit)); + dlg->set_value(0, completed); + } + while (ctr < limit); + dlg->inc_value(1, 1); dlg->refresh(); } @@ -359,6 +370,45 @@ namespace gl attach(data->fragment_shader). link(); + post_init_hook(data, compiler_options); + return data; + } + + void shader_interpreter::build_program_async(u64 compiler_options, std::function callback) + { + auto data = new interpreter::cached_program(); + + auto post_create_hook = [=](glsl::program* prog) + { + build_fs(compiler_options, *data); + build_vs(compiler_options, *data); + + prog->attach(data->vertex_shader). + attach(data->fragment_shader); + }; + + auto storage_hook = [=](std::unique_ptr& prog) + { + data->prog.swap(std::move(*prog)); + post_init_hook(data, compiler_options); + + if (callback) + { + callback(data); + } + }; + + auto compiler = gl::get_pipe_compiler(); + compiler->compile( + gl::pipe_compiler::COMPILE_DEFERRED, + post_create_hook, + {}, + storage_hook + ); + } + + void shader_interpreter::post_init_hook(interpreter::cached_program* data, u64 compiler_options) + { data->prog.uniforms[0] = GL_STREAM_BUFFER_START + 0; data->prog.uniforms[1] = GL_STREAM_BUFFER_START + 1; @@ -381,7 +431,6 @@ namespace gl } m_program_cache[compiler_options].reset(data); - return data; } bool shader_interpreter::is_interpreter(const glsl::program* program) const diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h index 92cce63a6b..68abfecfe2 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h @@ -78,7 +78,10 @@ namespace gl void build_vs(u64 compiler_options, interpreter::cached_program& prog_data); void build_fs(u64 compiler_options, interpreter::cached_program& prog_data); + interpreter::cached_program* build_program(u64 compiler_options); + void build_program_async(u64 compiler_options, std::function callback); + void post_init_hook(interpreter::cached_program* data, u64 compiler_options); interpreter::cached_program* m_current_interpreter = nullptr; From 5c4b0a165b333bebd198a49258e955d55c0f4043 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 12 Apr 2026 14:25:49 +0300 Subject: [PATCH 055/283] gl/interpreter: Separable shader objects - We use a real cache for separate VS and FS objects --- rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp | 96 ++++++++++++++++++------ rpcs3/Emu/RSX/GL/GLShaderInterpreter.h | 25 ++++-- 2 files changed, 92 insertions(+), 29 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index 9eaa3b03b1..ed77bdb07b 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -71,8 +71,7 @@ namespace gl const u32 completed = ctr.load(); dlg->update_msg(0, fmt::format("Building variant %u of %u...", completed, limit)); dlg->set_value(0, completed); - } - while (ctr < limit); + } while (ctr < limit); dlg->inc_value(1, 1); dlg->refresh(); @@ -82,9 +81,9 @@ namespace gl { for (auto& prog : m_program_cache) { - prog.second->vertex_shader.remove(); - prog.second->fragment_shader.remove(); - prog.second->prog.remove(); + prog.second->vertex_shader->remove(); + prog.second->fragment_shader->remove(); + prog.second->prog->remove(); } } @@ -139,11 +138,22 @@ namespace gl m_current_interpreter = build_program(opt); } - return &m_current_interpreter->prog; + return m_current_interpreter->prog.get(); } void shader_interpreter::build_vs(u64 compiler_options, interpreter::cached_program& prog_data) { + { + std::lock_guard lock(m_vs_cache_lock); + + if (auto found = m_vs_cache.find(compiler_options); + found != m_vs_cache.end()) + { + prog_data.vertex_shader = found->second; + return; + } + } + ::glsl::shader_properties properties{}; properties.domain = ::glsl::program_domain::glsl_vertex_program; properties.require_lit_emulation = true; @@ -201,12 +211,36 @@ namespace gl builder << program_common::interpreter::get_vertex_interpreter(); const std::string s = builder.str(); - prog_data.vertex_shader.create(::glsl::program_domain::glsl_vertex_program, s); - prog_data.vertex_shader.compile(); + prog_data.vertex_shader = std::make_shared(); + prog_data.vertex_shader->create(::glsl::program_domain::glsl_vertex_program, s); + prog_data.vertex_shader->compile(); + + { + std::lock_guard lock(m_vs_cache_lock); + + const auto [found, inserted] = m_vs_cache.try_emplace(compiler_options, prog_data.vertex_shader); + if (!inserted) + { + // Cache hit + prog_data.vertex_shader->remove(); + prog_data.vertex_shader = found->second; + } + } } void shader_interpreter::build_fs(u64 compiler_options, interpreter::cached_program& prog_data) { + { + std::lock_guard lock(m_fs_cache_lock); + + if (auto found = m_fs_cache.find(compiler_options); + found != m_fs_cache.end()) + { + prog_data.fragment_shader = found->second; + return; + } + } + // Allocate TIUs auto& allocator = prog_data.allocator; if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_TEXTURES) @@ -355,8 +389,21 @@ namespace gl builder << program_common::interpreter::get_fragment_interpreter(); const std::string s = builder.str(); - prog_data.fragment_shader.create(::glsl::program_domain::glsl_fragment_program, s); - prog_data.fragment_shader.compile(); + prog_data.fragment_shader = std::make_shared(); + prog_data.fragment_shader->create(::glsl::program_domain::glsl_fragment_program, s); + prog_data.fragment_shader->compile(); + + { + std::lock_guard lock(m_fs_cache_lock); + + const auto [found, inserted] = m_fs_cache.try_emplace(compiler_options, prog_data.fragment_shader); + if (!inserted) + { + // Cache hit + prog_data.fragment_shader->remove(); + prog_data.fragment_shader = found->second; + } + } } interpreter::cached_program* shader_interpreter::build_program(u64 compiler_options) @@ -365,9 +412,10 @@ namespace gl build_fs(compiler_options, *data); build_vs(compiler_options, *data); - data->prog.create(). - attach(data->vertex_shader). - attach(data->fragment_shader). + data->prog = std::make_shared(); + data->prog->create(). + attach(*data->vertex_shader). + attach(*data->fragment_shader). link(); post_init_hook(data, compiler_options); @@ -383,13 +431,13 @@ namespace gl build_fs(compiler_options, *data); build_vs(compiler_options, *data); - prog->attach(data->vertex_shader). - attach(data->fragment_shader); + prog->attach(*data->vertex_shader). + attach(*data->fragment_shader); }; auto storage_hook = [=](std::unique_ptr& prog) { - data->prog.swap(std::move(*prog)); + data->prog.reset(prog.release()); post_init_hook(data, compiler_options); if (callback) @@ -409,8 +457,8 @@ namespace gl void shader_interpreter::post_init_hook(interpreter::cached_program* data, u64 compiler_options) { - data->prog.uniforms[0] = GL_STREAM_BUFFER_START + 0; - data->prog.uniforms[1] = GL_STREAM_BUFFER_START + 1; + data->prog->uniforms[0] = GL_STREAM_BUFFER_START + 0; + data->prog->uniforms[1] = GL_STREAM_BUFFER_START + 1; if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_TEXTURES) { @@ -426,7 +474,7 @@ namespace gl allocator.pools[i].allocate(assigned++); } - data->prog.uniforms[type_names[i]] = allocator.pools[i].allocated; + data->prog->uniforms[type_names[i]] = allocator.pools[i].allocated; } } @@ -435,7 +483,7 @@ namespace gl bool shader_interpreter::is_interpreter(const glsl::program* program) const { - return (program == &m_current_interpreter->prog); + return (program == m_current_interpreter->prog.get()); } void shader_interpreter::update_fragment_textures( @@ -528,9 +576,9 @@ namespace gl } } - if (allocator.pools[0].flags) m_current_interpreter->prog.uniforms["sampler1D_array"] = allocator.pools[0].allocated; - if (allocator.pools[1].flags) m_current_interpreter->prog.uniforms["sampler2D_array"] = allocator.pools[1].allocated; - if (allocator.pools[2].flags) m_current_interpreter->prog.uniforms["samplerCube_array"] = allocator.pools[2].allocated; - if (allocator.pools[3].flags) m_current_interpreter->prog.uniforms["sampler3D_array"] = allocator.pools[3].allocated; + if (allocator.pools[0].flags) m_current_interpreter->prog->uniforms["sampler1D_array"] = allocator.pools[0].allocated; + if (allocator.pools[1].flags) m_current_interpreter->prog->uniforms["sampler2D_array"] = allocator.pools[1].allocated; + if (allocator.pools[2].flags) m_current_interpreter->prog->uniforms["samplerCube_array"] = allocator.pools[2].allocated; + if (allocator.pools[3].flags) m_current_interpreter->prog->uniforms["sampler3D_array"] = allocator.pools[3].allocated; } } diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h index 68abfecfe2..b431377318 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h @@ -62,19 +62,34 @@ namespace gl void allocate(int size); }; + enum cached_program_flags + { + CACHED_PIPE_UNOPTIMIZED = 1 + }; + struct cached_program { - glsl::shader vertex_shader; - glsl::shader fragment_shader; - glsl::program prog; + u32 flags = 0; + + std::shared_ptr vertex_shader; + std::shared_ptr fragment_shader; + std::shared_ptr prog; texture_pool_allocator allocator; }; } class shader_interpreter { - using shader_cache_t = std::unordered_map>; - shader_cache_t m_program_cache; + using shader_cache_t = std::unordered_map>; + using pipeline_cache_t = std::unordered_map>; + + shared_mutex m_vs_cache_lock; + shared_mutex m_fs_cache_lock; + + shader_cache_t m_vs_cache; + shader_cache_t m_fs_cache; + + pipeline_cache_t m_program_cache; void build_vs(u64 compiler_options, interpreter::cached_program& prog_data); void build_fs(u64 compiler_options, interpreter::cached_program& prog_data); From af7ae45888510ab1591981ec359ae8427055a5bc Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 12 Apr 2026 20:00:20 +0300 Subject: [PATCH 056/283] rsx/gl: Upgrade interpreter model to use a minimal set of reusable pipelines as a base - We generate optimized variants in the background if we have a partial miss - A full miss is impossible --- rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp | 178 +++++++++++++------- rpcs3/Emu/RSX/GL/GLShaderInterpreter.h | 2 +- rpcs3/Emu/RSX/Program/ShaderInterpreter.cpp | 118 +++++++++++-- rpcs3/Emu/RSX/Program/ShaderInterpreter.h | 26 ++- rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp | 6 +- 5 files changed, 248 insertions(+), 82 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index ed77bdb07b..eb52798140 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -13,6 +13,7 @@ namespace gl { using glsl::shader; + using enum program_common::interpreter::compiler_option; namespace interpreter { @@ -52,14 +53,25 @@ namespace gl dlg->create("Precompiling interpreter variants.\nPlease wait...", "Shader Compilation"); const auto variants = program_common::interpreter::get_interpreter_variants(); - const u32 limit = ::size32(variants); - dlg->set_limit(0, limit); - dlg->set_limit(1, 1); + const u32 limit1 = ::size32(variants.base_pipelines); + const u32 limit2 = ::size32(variants.pipelines); + dlg->set_limit(0, limit1); + dlg->set_limit(1, limit2); atomic_t ctr = 0; auto progress_hook = [&](interpreter::cached_program*) { ctr++; }; - for (auto& variant : variants) + auto update_progress = [&](u32 stage) + { + const auto completed = ctr.load(); + const auto limit = stage ? limit2 : limit1; + const auto message = fmt::format("%s variant %u of %u...", stage ? "Linking" : "Building", ctr.load(), limit); + dlg->update_msg(stage, message); + dlg->set_value(stage, completed); + }; + + // We only need to build the base "compatible pipeline" pairs. + for (const auto& variant : variants.base_pipelines) { build_program_async(variant.first | variant.second, progress_hook); } @@ -67,14 +79,47 @@ namespace gl do { std::this_thread::sleep_for(16ms); + update_progress(0); + } + while (ctr < limit1); - const u32 completed = ctr.load(); - dlg->update_msg(0, fmt::format("Building variant %u of %u...", completed, limit)); - dlg->set_value(0, completed); - } while (ctr < limit); + // Show final progress + update_progress(0); - dlg->inc_value(1, 1); - dlg->refresh(); + // Second stage. Propagate base pipelines to all compatible + ctr = 0; + std::lock_guard lock(m_program_cache_lock); + + for (const auto& variant : variants.pipelines) + { + const u64 compiler_options = variant.vs_opts.shader_opt | variant.fs_opts.shader_opt; + if (m_program_cache.find(compiler_options) != m_program_cache.end()) + { + // Base variant + continue; + } + + const u64 compatible_options = variant.vs_opts.compatible_shader_opts | variant.fs_opts.compatible_shader_opts; + auto base_pipeline = m_program_cache.find(compatible_options); + if (base_pipeline == m_program_cache.end()) + { + fmt::throw_exception("Base variant was not found in the cache."); + } + + auto data = new interpreter::cached_program(); + data->flags |= interpreter::CACHED_PIPE_UNOPTIMIZED; + data->allocator = base_pipeline->second->allocator; + data->vertex_shader = base_pipeline->second->vertex_shader; + data->fragment_shader = base_pipeline->second->fragment_shader; + data->prog = base_pipeline->second->prog; + m_program_cache[compiler_options].reset(data); + } + + ctr = limit2; + update_progress(1); + + // Minor stall to avoid visual flashing + std::this_thread::sleep_for(16ms); } void shader_interpreter::destroy() @@ -85,6 +130,16 @@ namespace gl prog.second->fragment_shader->remove(); prog.second->prog->remove(); } + + for (auto& shader : m_vs_cache) + { + shader.second->remove(); + } + + for (auto& shader : m_fs_cache) + { + shader.second->remove(); + } } glsl::program* shader_interpreter::get(const interpreter::program_metadata& metadata, u32 vp_ctrl, u32 fp_ctrl) @@ -100,49 +155,51 @@ namespace gl case rsx::comparison_function::never: return nullptr; case rsx::comparison_function::greater_or_equal: - opt |= program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_GE; + opt |= COMPILER_OPT_ENABLE_ALPHA_TEST_GE; break; case rsx::comparison_function::greater: - opt |= program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_G; + opt |= COMPILER_OPT_ENABLE_ALPHA_TEST_G; break; case rsx::comparison_function::less_or_equal: - opt |= program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_LE; + opt |= COMPILER_OPT_ENABLE_ALPHA_TEST_LE; break; case rsx::comparison_function::less: - opt |= program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_L; + opt |= COMPILER_OPT_ENABLE_ALPHA_TEST_L; break; case rsx::comparison_function::equal: - opt |= program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_EQ; + opt |= COMPILER_OPT_ENABLE_ALPHA_TEST_EQ; break; case rsx::comparison_function::not_equal: - opt |= program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_NE; + opt |= COMPILER_OPT_ENABLE_ALPHA_TEST_NE; break; } } - if (fp_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT) opt |= program_common::interpreter::COMPILER_OPT_ENABLE_DEPTH_EXPORT; - if (fp_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) opt |= program_common::interpreter::COMPILER_OPT_ENABLE_F32_EXPORT; - if (fp_ctrl & RSX_SHADER_CONTROL_USES_KIL) opt |= program_common::interpreter::COMPILER_OPT_ENABLE_KIL; - if (metadata.referenced_textures_mask) opt |= program_common::interpreter::COMPILER_OPT_ENABLE_TEXTURES; - if (metadata.has_branch_instructions) opt |= program_common::interpreter::COMPILER_OPT_ENABLE_FLOW_CTRL; - if (metadata.has_pack_instructions) opt |= program_common::interpreter::COMPILER_OPT_ENABLE_PACKING; - if (rsx::method_registers.polygon_stipple_enabled()) opt |= program_common::interpreter::COMPILER_OPT_ENABLE_STIPPLING; - if (vp_ctrl & RSX_SHADER_CONTROL_INSTANCED_CONSTANTS) opt |= program_common::interpreter::COMPILER_OPT_ENABLE_INSTANCING; + if (fp_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT) opt |= COMPILER_OPT_ENABLE_DEPTH_EXPORT; + if (fp_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) opt |= COMPILER_OPT_ENABLE_F32_EXPORT; + if (fp_ctrl & RSX_SHADER_CONTROL_USES_KIL) opt |= COMPILER_OPT_ENABLE_KIL; + if (metadata.referenced_textures_mask) opt |= COMPILER_OPT_ENABLE_TEXTURES; + if (metadata.has_branch_instructions) opt |= COMPILER_OPT_ENABLE_FLOW_CTRL; + if (metadata.has_pack_instructions) opt |= COMPILER_OPT_ENABLE_PACKING; + if (rsx::method_registers.polygon_stipple_enabled()) opt |= COMPILER_OPT_ENABLE_STIPPLING; + if (vp_ctrl & RSX_SHADER_CONTROL_INSTANCED_CONSTANTS) opt |= COMPILER_OPT_ENABLE_INSTANCING; - if (auto it = m_program_cache.find(opt); it != m_program_cache.end()) [[likely]] { - m_current_interpreter = it->second.get(); - } - else - { - m_current_interpreter = build_program(opt); + std::lock_guard lock(m_program_cache_lock); + if (auto it = m_program_cache.find(opt); it != m_program_cache.end()) [[likely]] + { + m_current_interpreter = it->second.get(); + } + return m_current_interpreter->prog.get(); } + m_current_interpreter = build_program(opt); return m_current_interpreter->prog.get(); } void shader_interpreter::build_vs(u64 compiler_options, interpreter::cached_program& prog_data) { + compiler_options &= COMPILER_OPT_ALL_VS_MASK; { std::lock_guard lock(m_vs_cache_lock); @@ -165,7 +222,7 @@ namespace gl std::string shader_str; ParamArray arr; - null_prog.ctrl = (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_INSTANCING) + null_prog.ctrl = (compiler_options & COMPILER_OPT_ENABLE_INSTANCING) ? RSX_SHADER_CONTROL_INSTANCED_CONSTANTS : 0; GLVertexDecompilerThread comp(null_prog, shader_str, arr); @@ -195,7 +252,7 @@ namespace gl " uvec4 vp_instructions[];\n" "};\n\n"; - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_INSTANCING) + if (compiler_options & COMPILER_OPT_ENABLE_INSTANCING) { builder << "#define _ENABLE_INSTANCED_CONSTANTS\n"; } @@ -230,20 +287,9 @@ namespace gl void shader_interpreter::build_fs(u64 compiler_options, interpreter::cached_program& prog_data) { - { - std::lock_guard lock(m_fs_cache_lock); - - if (auto found = m_fs_cache.find(compiler_options); - found != m_fs_cache.end()) - { - prog_data.fragment_shader = found->second; - return; - } - } - // Allocate TIUs auto& allocator = prog_data.allocator; - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_TEXTURES) + if (compiler_options & COMPILER_OPT_ENABLE_TEXTURES) { allocator.create(::glsl::program_domain::glsl_fragment_program); if (allocator.max_image_units >= 32) @@ -277,6 +323,19 @@ namespace gl } } + // Cache lookup + compiler_options &= COMPILER_OPT_ALL_FS_MASK; + { + std::lock_guard lock(m_fs_cache_lock); + + if (auto found = m_fs_cache.find(compiler_options); + found != m_fs_cache.end()) + { + prog_data.fragment_shader = found->second; + return; + } + } + u32 len; ParamArray arr; std::string shader_str; @@ -291,67 +350,67 @@ namespace gl ::glsl::insert_subheader_block(builder); comp.insertConstants(builder); - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_GE) + if (compiler_options & COMPILER_OPT_ENABLE_ALPHA_TEST_GE) { builder << "#define ALPHA_TEST_GEQUAL\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_G) + if (compiler_options & COMPILER_OPT_ENABLE_ALPHA_TEST_G) { builder << "#define ALPHA_TEST_GREATER\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_LE) + if (compiler_options & COMPILER_OPT_ENABLE_ALPHA_TEST_LE) { builder << "#define ALPHA_TEST_LEQUAL\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_L) + if (compiler_options & COMPILER_OPT_ENABLE_ALPHA_TEST_L) { builder << "#define ALPHA_TEST_LESS\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_EQ) + if (compiler_options & COMPILER_OPT_ENABLE_ALPHA_TEST_EQ) { builder << "#define ALPHA_TEST_EQUAL\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_NE) + if (compiler_options & COMPILER_OPT_ENABLE_ALPHA_TEST_NE) { builder << "#define ALPHA_TEST_NEQUAL\n"; } - if (!(compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_F32_EXPORT)) + if (!(compiler_options & COMPILER_OPT_ENABLE_F32_EXPORT)) { builder << "#define WITH_HALF_OUTPUT_REGISTER\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_DEPTH_EXPORT) + if (compiler_options & COMPILER_OPT_ENABLE_DEPTH_EXPORT) { builder << "#define WITH_DEPTH_EXPORT\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_FLOW_CTRL) + if (compiler_options & COMPILER_OPT_ENABLE_FLOW_CTRL) { builder << "#define WITH_FLOW_CTRL\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_PACKING) + if (compiler_options & COMPILER_OPT_ENABLE_PACKING) { builder << "#define WITH_PACKING\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_KIL) + if (compiler_options & COMPILER_OPT_ENABLE_KIL) { builder << "#define WITH_KIL\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_STIPPLING) + if (compiler_options & COMPILER_OPT_ENABLE_STIPPLING) { builder << "#define WITH_STIPPLING\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_TEXTURES) + if (compiler_options & COMPILER_OPT_ENABLE_TEXTURES) { builder << "#define WITH_TEXTURES\n\n"; @@ -460,7 +519,7 @@ namespace gl data->prog->uniforms[0] = GL_STREAM_BUFFER_START + 0; data->prog->uniforms[1] = GL_STREAM_BUFFER_START + 1; - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_TEXTURES) + if (compiler_options & COMPILER_OPT_ENABLE_TEXTURES) { // Initialize texture bindings int assigned = 0; @@ -478,12 +537,13 @@ namespace gl } } + std::lock_guard lock(m_program_cache_lock); m_program_cache[compiler_options].reset(data); } bool shader_interpreter::is_interpreter(const glsl::program* program) const { - return (program == m_current_interpreter->prog.get()); + return (m_current_interpreter && program == m_current_interpreter->prog.get()); } void shader_interpreter::update_fragment_textures( diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h index b431377318..d6fafba388 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h @@ -85,10 +85,10 @@ namespace gl shared_mutex m_vs_cache_lock; shared_mutex m_fs_cache_lock; + shared_mutex m_program_cache_lock; shader_cache_t m_vs_cache; shader_cache_t m_fs_cache; - pipeline_cache_t m_program_cache; void build_vs(u64 compiler_options, interpreter::cached_program& prog_data); diff --git a/rpcs3/Emu/RSX/Program/ShaderInterpreter.cpp b/rpcs3/Emu/RSX/Program/ShaderInterpreter.cpp index 54ba46b28a..de150b3b15 100644 --- a/rpcs3/Emu/RSX/Program/ShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/Program/ShaderInterpreter.cpp @@ -5,6 +5,20 @@ namespace program_common::interpreter { + struct vs_variants_metadata + { + u32 vs_base_mask = 0; + std::unordered_set vs_base_opts; // Set of options that covers all possible variants regardless of optimization + std::unordered_set vs_opts; // Full set of options that covers all variants with full optimization + }; + + struct fs_variants_metadata + { + u32 fs_base_mask = 0; + std::unordered_set fs_base_opts; // Set of options that covers all possible variants regardless of optimization + std::unordered_set fs_opts; // Full set of options that covers all variants with full optimization + }; + void bitrange_foreach(u32 min, u32 max, std::function func) { if (max <= min) @@ -22,50 +36,118 @@ namespace program_common::interpreter } } - std::vector get_interpreter_variants() + vs_variants_metadata prepare_vs_variants_data() { - // Separable passes to fetch all possible variants - std::unordered_set fs_masks; - fs_masks.insert(0); + vs_variants_metadata result; + result.vs_opts.insert(0); + result.vs_base_opts.insert(0); + + const u32 base_vs_mask = COMPILER_OPT_ALL_VS_MASK & ~(COMPILER_OPT_VS_EXCL_MASK); + bitrange_foreach(COMPILER_OPT_VS_MIN, COMPILER_OPT_VS_MAX, [&](u32 vs_opt) + { + result.vs_opts.insert(vs_opt); + + if (const auto excl_mask = (vs_opt & COMPILER_OPT_VS_EXCL_MASK); + excl_mask != 0) + { + result.vs_base_opts.insert(base_vs_mask | excl_mask); + } + }); + result.vs_opts.insert(base_vs_mask); + result.vs_base_opts.insert(base_vs_mask); + + result.vs_base_mask = base_vs_mask; + return result; + } + + fs_variants_metadata prepare_fs_variants_data() + { + fs_variants_metadata result; + result.fs_opts.insert(0); + result.fs_base_opts.insert(0); + + const u32 base_fs_mask = COMPILER_OPT_BASE_FS_MASK & ~(COMPILER_OPT_FS_EXCL_MASK); bitrange_foreach(COMPILER_OPT_FS_MIN, COMPILER_OPT_FS_MAX, [&](u32 fs_opt) { - fs_masks.insert(fs_opt); + result.fs_opts.insert(fs_opt); + if (const auto excl_mask = (fs_opt & COMPILER_OPT_FS_EXCL_MASK); + excl_mask != 0) + { + result.fs_base_opts.insert(base_fs_mask | excl_mask); + } }); + result.fs_opts.insert(base_fs_mask); + result.fs_base_opts.insert(base_fs_mask); // Now we add in the alpha testing variants for all fs variants. // Only one alpha test type is usable at once std::unordered_set fs_alpha_test_masks; + std::unordered_set fs_alpha_test_base_masks; + for (u32 alpha_test_bit = COMPILER_OPT_ENABLE_ALPHA_TEST_GE; alpha_test_bit <= COMPILER_OPT_ENABLE_ALPHA_TEST_NE; alpha_test_bit <<= 1) { - for (const auto& mask : fs_masks) + for (const auto& mask : result.fs_opts) { fs_alpha_test_masks.insert(mask | alpha_test_bit); } + + for (const auto& mask : result.fs_base_opts) + { + fs_alpha_test_base_masks.insert(mask | alpha_test_bit); + } } - // VS - std::unordered_set vs_masks; - vs_masks.insert(0); - bitrange_foreach(COMPILER_OPT_VS_MIN, COMPILER_OPT_VS_MAX, [&](u32 vs_opt) - { - vs_masks.insert(vs_opt); - }); - // Merge all FS variants - fs_masks.merge(fs_alpha_test_masks); + result.fs_opts.merge(fs_alpha_test_masks); + result.fs_base_opts.merge(fs_alpha_test_base_masks); + + result.fs_base_mask = base_fs_mask; + return result; + } + + interpreter_variants_t get_interpreter_variants() + { + const auto vs_metadata = prepare_vs_variants_data(); + const auto fs_metadata = prepare_fs_variants_data(); + + const auto& vs_masks = vs_metadata.vs_opts; + const auto& fs_masks = fs_metadata.fs_opts; + + const auto base_vs_mask = vs_metadata.vs_base_mask; + const auto base_fs_mask = fs_metadata.fs_base_mask; // Prepare outputs - std::vector results; + interpreter_variants_t result; for (const auto& vs_opt : vs_masks) { for (const auto& fs_opt : fs_masks) { - results.push_back({ vs_opt, fs_opt }); + interpreter_pipeline_variant_t variant{}; + variant.vs_opts.shader_opt = vs_opt; + variant.vs_opts.compatible_shader_opts = (vs_opt & ~base_vs_mask) | base_vs_mask; + + variant.fs_opts.shader_opt = fs_opt; + variant.fs_opts.compatible_shader_opts = (fs_opt & ~base_fs_mask) | base_fs_mask; + + result.pipelines.emplace_back(std::move(variant)); } } - return results; + // Calculate base pipelines (minimal set) + const auto& vs_base_masks = vs_metadata.vs_base_opts; + const auto& fs_base_masks = fs_metadata.fs_base_opts; + + result.base_pipelines.push_back({ base_vs_mask, base_fs_mask }); + for (const u32 vs_opt : vs_base_masks) + { + for (const u32 fs_opt : fs_base_masks) + { + result.base_pipelines.push_back({ vs_opt, fs_opt }); + } + } + + return result; } } diff --git a/rpcs3/Emu/RSX/Program/ShaderInterpreter.h b/rpcs3/Emu/RSX/Program/ShaderInterpreter.h index 0090a5978e..b80f871769 100644 --- a/rpcs3/Emu/RSX/Program/ShaderInterpreter.h +++ b/rpcs3/Emu/RSX/Program/ShaderInterpreter.h @@ -32,6 +32,11 @@ namespace program_common // Meta COMPILER_OPT_MAX = COMPILER_OPT_ENABLE_ALPHA_TEST_NE, COMPILER_OPT_ALPHA_TEST_MASK = (0b111111 << 9), + COMPILER_OPT_ALL_VS_MASK = COMPILER_OPT_ENABLE_INSTANCING | COMPILER_OPT_ENABLE_VTX_TEXTURES, + COMPILER_OPT_BASE_FS_MASK = 0b1111111, + COMPILER_OPT_ALL_FS_MASK = COMPILER_OPT_BASE_FS_MASK | COMPILER_OPT_ALPHA_TEST_MASK, + COMPILER_OPT_VS_EXCL_MASK = COMPILER_OPT_ENABLE_INSTANCING, + COMPILER_OPT_FS_EXCL_MASK = COMPILER_OPT_ALPHA_TEST_MASK | COMPILER_OPT_ENABLE_STIPPLING | COMPILER_OPT_ENABLE_DEPTH_EXPORT | COMPILER_OPT_ENABLE_F32_EXPORT, // Bounds COMPILER_OPT_FS_MAX = COMPILER_OPT_ENABLE_FLOW_CTRL, @@ -56,7 +61,24 @@ namespace program_common return s; } - using interpreter_variant_t = std::pair; - std::vector get_interpreter_variants(); + struct interpreter_shader_variant_t + { + u32 shader_opt = 0; + u32 compatible_shader_opts = 0; + }; + + struct interpreter_pipeline_variant_t + { + interpreter_shader_variant_t vs_opts; + interpreter_shader_variant_t fs_opts; + }; + + struct interpreter_variants_t + { + std::vector pipelines; + std::vector> base_pipelines; + }; + + interpreter_variants_t get_interpreter_variants(); } } diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp index 39b654b2ad..3332697420 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp @@ -579,6 +579,7 @@ namespace vk void shader_interpreter::preload(rsx::shader_loading_dialog* dlg) { +#if 0 dlg->create("Precompiling interpreter variants.\nPlease wait...", "Shader Compilation"); const auto variants = program_common::interpreter::get_interpreter_variants(); @@ -589,13 +590,14 @@ namespace vk u32 ctr = 0; for (auto& variant : variants) { - build_fs(variant.first | variant.second); - build_vs(variant.first | variant.second); + //build_fs(variant.first | variant.second); + //build_vs(variant.first | variant.second); dlg->update_msg(0, fmt::format("Building variant %u of %u...", ++ctr, limit)); dlg->inc_value(0, 1); } dlg->inc_value(1, 1); dlg->refresh(); +#endif } }; From 97c7d069ac41386c3a882d337d87e5bd434edc80 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 12 Apr 2026 20:08:21 +0300 Subject: [PATCH 057/283] gl: Disable hard requirement for bindless texture to use the interpreter - I never got around to porting the implementation to use bindless correctly --- rpcs3/Emu/RSX/GL/GLGSRender.cpp | 3 ++- rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index 96645e7b56..e32759d181 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -176,7 +176,8 @@ void GLGSRender::on_init_thread() rsx_log.warning("Texture barriers are not supported by your GPU. Feedback loops will have undefined results."); } - if (!gl_caps.ARB_bindless_texture_supported) + // NOTE: We currently aren't using the bindless version of the interpreter + if (false) //!gl_caps.ARB_bindless_texture_supported) { switch (shadermode) { diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index eb52798140..a2c63b505b 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -345,7 +345,7 @@ namespace gl std::stringstream builder; builder << "#version 450\n" - "#extension GL_ARB_bindless_texture : require\n\n"; + "//#extension GL_ARB_bindless_texture : require\n\n"; ::glsl::insert_subheader_block(builder); comp.insertConstants(builder); From f3d0c3ba28caeb32a8799fa9fea2b63a7850c405 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 12 Apr 2026 20:39:55 +0300 Subject: [PATCH 058/283] gl: Implement async optimized interpreter build --- rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp | 31 +++++++++++++++++------- rpcs3/Emu/RSX/GL/GLShaderInterpreter.h | 14 ++++++----- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index a2c63b505b..c7bb6f1024 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -59,7 +59,7 @@ namespace gl dlg->set_limit(1, limit2); atomic_t ctr = 0; - auto progress_hook = [&](interpreter::cached_program*) { ctr++; }; + auto progress_hook = [&](const std::shared_ptr&) { ctr++; }; auto update_progress = [&](u32 stage) { @@ -188,9 +188,22 @@ namespace gl std::lock_guard lock(m_program_cache_lock); if (auto it = m_program_cache.find(opt); it != m_program_cache.end()) [[likely]] { - m_current_interpreter = it->second.get(); + m_current_interpreter = it->second; + } + + if (m_current_interpreter) + { + constexpr u32 test_mask = (interpreter::CACHED_PIPE_UNOPTIMIZED | interpreter::CACHED_PIPE_RECOMPILING); + constexpr u32 unoptimized_mask = interpreter::CACHED_PIPE_UNOPTIMIZED; + if ((m_current_interpreter->flags & test_mask) == unoptimized_mask) + { + // Interpreter is unoptimized and we haven't tried to recompile it + // NOTE: This operation effectively orphans the current interpreter, but since unoptimized pipelines just have a non-owning reference then it's actually fine and we don't really leak anything. + m_current_interpreter->flags |= interpreter::CACHED_PIPE_RECOMPILING; + build_program_async(opt, {}); + } + return m_current_interpreter->prog.get(); } - return m_current_interpreter->prog.get(); } m_current_interpreter = build_program(opt); @@ -465,9 +478,9 @@ namespace gl } } - interpreter::cached_program* shader_interpreter::build_program(u64 compiler_options) + std::shared_ptr shader_interpreter::build_program(u64 compiler_options) { - auto data = new interpreter::cached_program(); + auto data = std::make_shared(); build_fs(compiler_options, *data); build_vs(compiler_options, *data); @@ -481,9 +494,9 @@ namespace gl return data; } - void shader_interpreter::build_program_async(u64 compiler_options, std::function callback) + void shader_interpreter::build_program_async(u64 compiler_options, async_build_callback_t callback) { - auto data = new interpreter::cached_program(); + auto data = std::make_shared(); auto post_create_hook = [=](glsl::program* prog) { @@ -514,7 +527,7 @@ namespace gl ); } - void shader_interpreter::post_init_hook(interpreter::cached_program* data, u64 compiler_options) + void shader_interpreter::post_init_hook(const std::shared_ptr& data, u64 compiler_options) { data->prog->uniforms[0] = GL_STREAM_BUFFER_START + 0; data->prog->uniforms[1] = GL_STREAM_BUFFER_START + 1; @@ -538,7 +551,7 @@ namespace gl } std::lock_guard lock(m_program_cache_lock); - m_program_cache[compiler_options].reset(data); + m_program_cache[compiler_options] = data; } bool shader_interpreter::is_interpreter(const glsl::program* program) const diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h index d6fafba388..ef070c3fd2 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h @@ -64,7 +64,8 @@ namespace gl enum cached_program_flags { - CACHED_PIPE_UNOPTIMIZED = 1 + CACHED_PIPE_UNOPTIMIZED = (1 << 0), + CACHED_PIPE_RECOMPILING = (1 << 1), }; struct cached_program @@ -81,7 +82,8 @@ namespace gl class shader_interpreter { using shader_cache_t = std::unordered_map>; - using pipeline_cache_t = std::unordered_map>; + using pipeline_cache_t = std::unordered_map>; + using async_build_callback_t = std::function&)>; shared_mutex m_vs_cache_lock; shared_mutex m_fs_cache_lock; @@ -94,11 +96,11 @@ namespace gl void build_vs(u64 compiler_options, interpreter::cached_program& prog_data); void build_fs(u64 compiler_options, interpreter::cached_program& prog_data); - interpreter::cached_program* build_program(u64 compiler_options); - void build_program_async(u64 compiler_options, std::function callback); - void post_init_hook(interpreter::cached_program* data, u64 compiler_options); + std::shared_ptr build_program(u64 compiler_options); + void build_program_async(u64 compiler_options, async_build_callback_t callback); + void post_init_hook(const std::shared_ptr& data, u64 compiler_options); - interpreter::cached_program* m_current_interpreter = nullptr; + std::shared_ptr m_current_interpreter; public: void create(rsx::shader_loading_dialog* dlg); From a6c3ef350af9a1e2911d730b7c11131995adb06b Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 12 Apr 2026 21:08:22 +0300 Subject: [PATCH 059/283] vk: Interpreter bringup to restore previous functionality - Get basics working again along with some prep-work for enhancements --- rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp | 69 ++++++++++++------------ rpcs3/Emu/RSX/VK/VKShaderInterpreter.h | 20 +++---- 2 files changed, 44 insertions(+), 45 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp index 3332697420..8add11ebed 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp @@ -14,7 +14,7 @@ namespace vk { - u32 shader_interpreter::init(VKVertexProgram* vk_prog, u64 compiler_options) const + u32 shader_interpreter::init(std::shared_ptr& vk_prog, u64 compiler_options) const { std::memset(&vk_prog->binding_table, 0xff, sizeof(vk_prog->binding_table)); @@ -48,7 +48,7 @@ namespace vk return location; } - u32 shader_interpreter::init(VKFragmentProgram* vk_prog, u64 /*compiler_opt*/) const + u32 shader_interpreter::init(std::shared_ptr& vk_prog, u64 /*compiler_opt*/) const { std::memset(&vk_prog->binding_table, 0xff, sizeof(vk_prog->binding_table)); @@ -60,7 +60,7 @@ namespace vk return 3; } - VKVertexProgram* shader_interpreter::build_vs(u64 compiler_options) + std::shared_ptr shader_interpreter::build_vs(u64 compiler_options) { ::glsl::shader_properties properties{}; properties.domain = ::glsl::program_domain::glsl_vertex_program; @@ -72,8 +72,8 @@ namespace vk ParamArray arr; // Initialize binding layout - auto vk_prog = std::make_unique(); - const u32 vertex_instruction_start = init(vk_prog.get(), compiler_options); + auto vk_prog = std::make_shared(); + const u32 vertex_instruction_start = init(vk_prog, compiler_options); null_prog.ctrl = (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_INSTANCING) ? RSX_SHADER_CONTROL_INSTANCED_CONSTANTS @@ -153,12 +153,11 @@ namespace vk vk_prog->SetInputs(vs_inputs); - auto ret = vk_prog.get(); - m_shader_cache[compiler_options].m_vs = std::move(vk_prog); - return ret; + m_shader_cache[compiler_options].m_vs = vk_prog; + return vk_prog; } - VKFragmentProgram* shader_interpreter::build_fs(u64 compiler_options) + std::shared_ptr shader_interpreter::build_fs(u64 compiler_options) { [[maybe_unused]] ::glsl::shader_properties properties{}; properties.domain = ::glsl::program_domain::glsl_fragment_program; @@ -172,8 +171,8 @@ namespace vk frag.ctrl |= RSX_SHADER_CONTROL_INTERPRETER_MODEL; - auto vk_prog = std::make_unique(); - const u32 fragment_instruction_start = init(vk_prog.get(), compiler_options); + auto vk_prog = std::make_shared(); + const u32 fragment_instruction_start = init(vk_prog, compiler_options); const u32 fragment_textures_start = fragment_instruction_start + 1; VKFragmentDecompilerThread comp(shader_str, arr, frag, len, *vk_prog); @@ -323,9 +322,8 @@ namespace vk vk_prog->SetInputs(inputs); - auto ret = vk_prog.get(); - m_shader_cache[compiler_options].m_fs = std::move(vk_prog); - return ret; + m_shader_cache[compiler_options].m_fs = vk_prog; + return vk_prog; } void shader_interpreter::init(const vk::render_device& dev) @@ -335,19 +333,20 @@ namespace vk void shader_interpreter::destroy() { + m_current_interpreter.reset(); m_program_cache.clear(); m_shader_cache.clear(); } - glsl::program* shader_interpreter::link(const vk::pipeline_props& properties, u64 compiler_opt) + std::shared_ptr shader_interpreter::link(const vk::pipeline_props& properties, u64 compiler_opt) { - VKVertexProgram* vs; - VKFragmentProgram* fs; + std::shared_ptr vs; + std::shared_ptr fs; if (auto found = m_shader_cache.find(compiler_opt); found != m_shader_cache.end()) { - fs = found->second.m_fs.get(); - vs = found->second.m_vs.get(); + fs = found->second.m_fs; + vs = found->second.m_vs; } else { @@ -436,7 +435,7 @@ namespace vk vs->uniforms, fs->uniforms); - return program.release(); + return program; } void shader_interpreter::update_fragment_textures(const std::array& sampled_images) @@ -507,7 +506,7 @@ namespace vk if (m_current_key == key) [[likely]] { - return m_current_interpreter; + return m_current_interpreter.get(); } else { @@ -518,18 +517,18 @@ namespace vk auto found = m_program_cache.find(key); if (found != m_program_cache.end()) [[likely]] { - m_current_interpreter = found->second.get(); - return m_current_interpreter; + m_current_interpreter = found->second; + return m_current_interpreter.get(); } m_current_interpreter = link(properties, key.compiler_opt); - m_program_cache[key].reset(m_current_interpreter); - return m_current_interpreter; + m_program_cache[key] = m_current_interpreter; + return m_current_interpreter.get(); } bool shader_interpreter::is_interpreter(const glsl::program* prog) const { - return prog == m_current_interpreter; + return prog == m_current_interpreter.get(); } u32 shader_interpreter::get_vertex_instruction_location() const @@ -542,16 +541,16 @@ namespace vk return m_current_pipeline_info_ex.fragment_instruction_location; } - std::pair shader_interpreter::get_shaders() const + std::pair, std::shared_ptr> shader_interpreter::get_shaders() const { if (auto found = m_shader_cache.find(m_current_key.compiler_opt); found != m_shader_cache.end()) { - auto fs = found->second.m_fs.get(); - auto vs = found->second.m_vs.get(); + auto fs = found->second.m_fs; + auto vs = found->second.m_vs; return { vs, fs }; } - return { nullptr, nullptr }; + return {}; } const shader_interpreter::pipeline_info_ex_t* shader_interpreter::get_pipeline_info_ex(u64 compiler_opt) @@ -561,10 +560,10 @@ namespace vk return &found->second; } - VKVertexProgram vs_stub{}; - VKFragmentProgram fs_stub{}; - const auto vi_location = init(&vs_stub, compiler_opt); - const auto fi_location = init(&fs_stub, compiler_opt); + auto vs_stub = std::make_shared(); + auto fs_stub = std::make_shared(); + const auto vi_location = init(vs_stub, compiler_opt); + const auto fi_location = init(fs_stub, compiler_opt); pipeline_info_ex_t result { @@ -579,9 +578,9 @@ namespace vk void shader_interpreter::preload(rsx::shader_loading_dialog* dlg) { -#if 0 dlg->create("Precompiling interpreter variants.\nPlease wait...", "Shader Compilation"); +#if 0 const auto variants = program_common::interpreter::get_interpreter_variants(); const u32 limit = ::size32(variants); dlg->set_limit(0, limit); diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h index 864406ea08..d474b8d94a 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h @@ -21,7 +21,6 @@ namespace vk class shader_interpreter { VkDevice m_device = VK_NULL_HANDLE; - glsl::program* m_current_interpreter = nullptr; struct pipeline_key { @@ -44,8 +43,8 @@ namespace vk struct shader_cache_entry_t { - std::unique_ptr m_fs; - std::unique_ptr m_vs; + std::shared_ptr m_fs; + std::shared_ptr m_vs; }; struct pipeline_info_ex_t @@ -55,19 +54,20 @@ namespace vk u32 fragment_textures_location = 0; }; - std::unordered_map, key_hasher> m_program_cache; + std::unordered_map, key_hasher> m_program_cache; std::unordered_map m_shader_cache; std::unordered_map m_pipeline_info_cache; pipeline_key m_current_key{}; pipeline_info_ex_t m_current_pipeline_info_ex{}; + std::shared_ptr m_current_interpreter; - VKVertexProgram* build_vs(u64 compiler_opt); - VKFragmentProgram* build_fs(u64 compiler_opt); - glsl::program* link(const vk::pipeline_props& properties, u64 compiler_opt); + std::shared_ptr build_vs(u64 compiler_opt); + std::shared_ptr build_fs(u64 compiler_opt); + std::shared_ptr link(const vk::pipeline_props& properties, u64 compiler_opt); - u32 init(VKVertexProgram* vk_prog, u64 compiler_opt) const; - u32 init(VKFragmentProgram* vk_prog, u64 compiler_opt) const; + u32 init(std::shared_ptr& vk_prog, u64 compiler_opt) const; + u32 init(std::shared_ptr& vk_prog, u64 compiler_opt) const; const pipeline_info_ex_t* get_pipeline_info_ex(u64 compiler_opt); @@ -85,7 +85,7 @@ namespace vk u32 fp_ctrl); // Retrieve the shader components that make up the current interpreter - std::pair get_shaders() const; + std::pair, std::shared_ptr> get_shaders() const; bool is_interpreter(const glsl::program* prog) const; From 26db44b3b32c332f15a0b65bb3e3c7719d0e2b21 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 12 Apr 2026 21:15:20 +0300 Subject: [PATCH 060/283] gl/interpreter: Make use of r-w locking mechanisms --- rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index c7bb6f1024..d0a34a493c 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -88,7 +88,7 @@ namespace gl // Second stage. Propagate base pipelines to all compatible ctr = 0; - std::lock_guard lock(m_program_cache_lock); + reader_lock lock(m_program_cache_lock); for (const auto& variant : variants.pipelines) { @@ -185,7 +185,7 @@ namespace gl if (vp_ctrl & RSX_SHADER_CONTROL_INSTANCED_CONSTANTS) opt |= COMPILER_OPT_ENABLE_INSTANCING; { - std::lock_guard lock(m_program_cache_lock); + reader_lock lock(m_program_cache_lock); if (auto it = m_program_cache.find(opt); it != m_program_cache.end()) [[likely]] { m_current_interpreter = it->second; @@ -214,7 +214,7 @@ namespace gl { compiler_options &= COMPILER_OPT_ALL_VS_MASK; { - std::lock_guard lock(m_vs_cache_lock); + reader_lock lock(m_vs_cache_lock); if (auto found = m_vs_cache.find(compiler_options); found != m_vs_cache.end()) @@ -339,7 +339,7 @@ namespace gl // Cache lookup compiler_options &= COMPILER_OPT_ALL_FS_MASK; { - std::lock_guard lock(m_fs_cache_lock); + reader_lock lock(m_fs_cache_lock); if (auto found = m_fs_cache.find(compiler_options); found != m_fs_cache.end()) From d36a4bd8585dc20a26eb916c2f52bbcb35db0b54 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 12 Apr 2026 21:43:39 +0300 Subject: [PATCH 061/283] vk/interpreter: Separable shader cache for the interpreter --- rpcs3/Emu/RSX/VK/VKGSRender.cpp | 1 + rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp | 155 ++++++++++++++--------- rpcs3/Emu/RSX/VK/VKShaderInterpreter.h | 7 +- 3 files changed, 100 insertions(+), 63 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index d5fc3396ed..293097de24 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -2204,6 +2204,7 @@ std::pair VKGSRender::get_ } const auto& [vs, fs] = m_shader_interpreter.get_shaders(); + ensure(vs && fs, "Invalid interpreter configuration"); return { &vs->binding_table, &fs->binding_table }; } diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp index 8add11ebed..250c551a82 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp @@ -14,6 +14,8 @@ namespace vk { + using enum program_common::interpreter::compiler_option; + u32 shader_interpreter::init(std::shared_ptr& vk_prog, u64 compiler_options) const { std::memset(&vk_prog->binding_table, 0xff, sizeof(vk_prog->binding_table)); @@ -29,7 +31,7 @@ namespace vk vk_prog->binding_table.cr_pred_buffer_location = location++; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_INSTANCING) + if (compiler_options & COMPILER_OPT_ENABLE_INSTANCING) { vk_prog->binding_table.instanced_lut_buffer_location = location++; vk_prog->binding_table.instanced_cbuf_location = location++; @@ -62,6 +64,16 @@ namespace vk std::shared_ptr shader_interpreter::build_vs(u64 compiler_options) { + compiler_options &= COMPILER_OPT_ALL_VS_MASK; + { + reader_lock lock(m_vs_shader_cache_lock); + if (auto found = m_vs_shader_cache.find(compiler_options); + found != m_vs_shader_cache.end()) + { + return found->second; + } + } + ::glsl::shader_properties properties{}; properties.domain = ::glsl::program_domain::glsl_vertex_program; properties.require_lit_emulation = true; @@ -75,7 +87,7 @@ namespace vk auto vk_prog = std::make_shared(); const u32 vertex_instruction_start = init(vk_prog, compiler_options); - null_prog.ctrl = (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_INSTANCING) + null_prog.ctrl = (compiler_options & COMPILER_OPT_ENABLE_INSTANCING) ? RSX_SHADER_CONTROL_INSTANCED_CONSTANTS : 0; VKVertexDecompilerThread comp(null_prog, shader_str, arr, *vk_prog); @@ -113,13 +125,13 @@ namespace vk " uvec4 vp_instructions[];\n" "};\n\n"; - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_VTX_TEXTURES) + if (compiler_options & COMPILER_OPT_ENABLE_VTX_TEXTURES) { // FIXME: Unimplemented rsx_log.todo("Vertex textures are currently not implemented for the shader interpreter."); } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_INSTANCING) + if (compiler_options & COMPILER_OPT_ENABLE_INSTANCING) { builder << "#define _ENABLE_INSTANCED_CONSTANTS\n"; } @@ -153,12 +165,23 @@ namespace vk vk_prog->SetInputs(vs_inputs); - m_shader_cache[compiler_options].m_vs = vk_prog; + std::lock_guard lock(m_vs_shader_cache_lock); + m_vs_shader_cache[compiler_options] = vk_prog; return vk_prog; } std::shared_ptr shader_interpreter::build_fs(u64 compiler_options) { + compiler_options &= COMPILER_OPT_ALL_FS_MASK; + { + reader_lock lock(m_fs_shader_cache_lock); + if (auto found = m_fs_shader_cache.find(compiler_options); + found != m_fs_shader_cache.end()) + { + return found->second; + } + } + [[maybe_unused]] ::glsl::shader_properties properties{}; properties.domain = ::glsl::program_domain::glsl_fragment_program; properties.require_depth_conversion = true; @@ -196,68 +219,68 @@ namespace vk "#define wpos_scale fs_contexts[_fs_context_offset].wpos_scale\n" "#define wpos_bias fs_contexts[_fs_context_offset].wpos_bias\n\n"; - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_GE) + if (compiler_options & COMPILER_OPT_ENABLE_ALPHA_TEST_GE) { builder << "#define ALPHA_TEST_GEQUAL\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_G) + if (compiler_options & COMPILER_OPT_ENABLE_ALPHA_TEST_G) { builder << "#define ALPHA_TEST_GREATER\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_LE) + if (compiler_options & COMPILER_OPT_ENABLE_ALPHA_TEST_LE) { builder << "#define ALPHA_TEST_LEQUAL\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_L) + if (compiler_options & COMPILER_OPT_ENABLE_ALPHA_TEST_L) { builder << "#define ALPHA_TEST_LESS\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_EQ) + if (compiler_options & COMPILER_OPT_ENABLE_ALPHA_TEST_EQ) { builder << "#define ALPHA_TEST_EQUAL\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_NE) + if (compiler_options & COMPILER_OPT_ENABLE_ALPHA_TEST_NE) { builder << "#define ALPHA_TEST_NEQUAL\n"; } - if (!(compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_F32_EXPORT)) + if (!(compiler_options & COMPILER_OPT_ENABLE_F32_EXPORT)) { builder << "#define WITH_HALF_OUTPUT_REGISTER\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_DEPTH_EXPORT) + if (compiler_options & COMPILER_OPT_ENABLE_DEPTH_EXPORT) { builder << "#define WITH_DEPTH_EXPORT\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_FLOW_CTRL) + if (compiler_options & COMPILER_OPT_ENABLE_FLOW_CTRL) { builder << "#define WITH_FLOW_CTRL\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_PACKING) + if (compiler_options & COMPILER_OPT_ENABLE_PACKING) { builder << "#define WITH_PACKING\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_KIL) + if (compiler_options & COMPILER_OPT_ENABLE_KIL) { builder << "#define WITH_KIL\n"; } - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_STIPPLING) + if (compiler_options & COMPILER_OPT_ENABLE_STIPPLING) { builder << "#define WITH_STIPPLING\n"; } const char* type_names[] = { "sampler1D", "sampler2D", "sampler3D", "samplerCube" }; - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_TEXTURES) + if (compiler_options & COMPILER_OPT_ENABLE_TEXTURES) { builder << "#define WITH_TEXTURES\n\n"; @@ -278,14 +301,14 @@ namespace vk } builder << - "layout(std430, set=1, binding=" << fragment_instruction_start << ") readonly restrict buffer FragmentInstructionBlock\n" - "{\n" - " uint shader_control;\n" - " uint texture_control;\n" - " uint reserved1;\n" - " uint reserved2;\n" - " uvec4 fp_instructions[];\n" - "};\n\n"; + "layout(std430, set=1, binding=" << fragment_instruction_start << ") readonly restrict buffer FragmentInstructionBlock\n" + "{\n" + " uint shader_control;\n" + " uint texture_control;\n" + " uint reserved1;\n" + " uint reserved2;\n" + " uvec4 fp_instructions[];\n" + "};\n\n"; builder << " uint rop_control = fs_contexts[_fs_context_offset].rop_control;\n" @@ -309,7 +332,7 @@ namespace vk in.name = "FragmentInstructionBlock"; inputs.push_back(in); - if (compiler_options & program_common::interpreter::COMPILER_OPT_ENABLE_TEXTURES) + if (compiler_options & COMPILER_OPT_ENABLE_TEXTURES) { for (int i = 0, location = fragment_textures_start; i < 4; ++i, ++location) { @@ -322,7 +345,8 @@ namespace vk vk_prog->SetInputs(inputs); - m_shader_cache[compiler_options].m_fs = vk_prog; + std::lock_guard lock(m_fs_shader_cache_lock); + m_fs_shader_cache[compiler_options] = vk_prog; return vk_prog; } @@ -335,24 +359,14 @@ namespace vk { m_current_interpreter.reset(); m_program_cache.clear(); - m_shader_cache.clear(); + m_vs_shader_cache.clear(); + m_fs_shader_cache.clear(); } std::shared_ptr shader_interpreter::link(const vk::pipeline_props& properties, u64 compiler_opt) { - std::shared_ptr vs; - std::shared_ptr fs; - - if (auto found = m_shader_cache.find(compiler_opt); found != m_shader_cache.end()) - { - fs = found->second.m_fs; - vs = found->second.m_vs; - } - else - { - fs = build_fs(compiler_opt); - vs = build_vs(compiler_opt); - } + auto vs = build_vs(compiler_opt); + auto fs = build_fs(compiler_opt); VkPipelineShaderStageCreateInfo shader_stages[2] = {}; shader_stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; @@ -474,35 +488,35 @@ namespace vk case rsx::comparison_function::never: return nullptr; case rsx::comparison_function::greater_or_equal: - key.compiler_opt |= program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_GE; + key.compiler_opt |= COMPILER_OPT_ENABLE_ALPHA_TEST_GE; break; case rsx::comparison_function::greater: - key.compiler_opt |= program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_G; + key.compiler_opt |= COMPILER_OPT_ENABLE_ALPHA_TEST_G; break; case rsx::comparison_function::less_or_equal: - key.compiler_opt |= program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_LE; + key.compiler_opt |= COMPILER_OPT_ENABLE_ALPHA_TEST_LE; break; case rsx::comparison_function::less: - key.compiler_opt |= program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_L; + key.compiler_opt |= COMPILER_OPT_ENABLE_ALPHA_TEST_L; break; case rsx::comparison_function::equal: - key.compiler_opt |= program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_EQ; + key.compiler_opt |= COMPILER_OPT_ENABLE_ALPHA_TEST_EQ; break; case rsx::comparison_function::not_equal: - key.compiler_opt |= program_common::interpreter::COMPILER_OPT_ENABLE_ALPHA_TEST_NE; + key.compiler_opt |= COMPILER_OPT_ENABLE_ALPHA_TEST_NE; break; } } - if (fp_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT) key.compiler_opt |= program_common::interpreter::COMPILER_OPT_ENABLE_DEPTH_EXPORT; - if (fp_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) key.compiler_opt |= program_common::interpreter::COMPILER_OPT_ENABLE_F32_EXPORT; - if (fp_ctrl & RSX_SHADER_CONTROL_USES_KIL) key.compiler_opt |= program_common::interpreter::COMPILER_OPT_ENABLE_KIL; - if (fp_metadata.referenced_textures_mask) key.compiler_opt |= program_common::interpreter::COMPILER_OPT_ENABLE_TEXTURES; - if (fp_metadata.has_branch_instructions) key.compiler_opt |= program_common::interpreter::COMPILER_OPT_ENABLE_FLOW_CTRL; - if (fp_metadata.has_pack_instructions) key.compiler_opt |= program_common::interpreter::COMPILER_OPT_ENABLE_PACKING; - if (rsx::method_registers.polygon_stipple_enabled()) key.compiler_opt |= program_common::interpreter::COMPILER_OPT_ENABLE_STIPPLING; - if (vp_ctrl & RSX_SHADER_CONTROL_INSTANCED_CONSTANTS) key.compiler_opt |= program_common::interpreter::COMPILER_OPT_ENABLE_INSTANCING; - if (vp_metadata.referenced_textures_mask) key.compiler_opt |= program_common::interpreter::COMPILER_OPT_ENABLE_VTX_TEXTURES; + if (fp_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT) key.compiler_opt |= COMPILER_OPT_ENABLE_DEPTH_EXPORT; + if (fp_ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) key.compiler_opt |= COMPILER_OPT_ENABLE_F32_EXPORT; + if (fp_ctrl & RSX_SHADER_CONTROL_USES_KIL) key.compiler_opt |= COMPILER_OPT_ENABLE_KIL; + if (fp_metadata.referenced_textures_mask) key.compiler_opt |= COMPILER_OPT_ENABLE_TEXTURES; + if (fp_metadata.has_branch_instructions) key.compiler_opt |= COMPILER_OPT_ENABLE_FLOW_CTRL; + if (fp_metadata.has_pack_instructions) key.compiler_opt |= COMPILER_OPT_ENABLE_PACKING; + if (rsx::method_registers.polygon_stipple_enabled()) key.compiler_opt |= COMPILER_OPT_ENABLE_STIPPLING; + if (vp_ctrl & RSX_SHADER_CONTROL_INSTANCED_CONSTANTS) key.compiler_opt |= COMPILER_OPT_ENABLE_INSTANCING; + if (vp_metadata.referenced_textures_mask) key.compiler_opt |= COMPILER_OPT_ENABLE_VTX_TEXTURES; if (m_current_key == key) [[likely]] { @@ -543,14 +557,31 @@ namespace vk std::pair, std::shared_ptr> shader_interpreter::get_shaders() const { - if (auto found = m_shader_cache.find(m_current_key.compiler_opt); found != m_shader_cache.end()) + const auto vs_opt = m_current_key.compiler_opt & COMPILER_OPT_ALL_VS_MASK; + const auto fs_opt = m_current_key.compiler_opt & COMPILER_OPT_ALL_FS_MASK; + + std::shared_ptr vs; + std::shared_ptr fs; + { - auto fs = found->second.m_fs; - auto vs = found->second.m_vs; - return { vs, fs }; + reader_lock lock(m_vs_shader_cache_lock); + if (auto found = m_vs_shader_cache.find(vs_opt); + found != m_vs_shader_cache.end()) + { + vs = found->second; + } } - return {}; + { + reader_lock lock(m_fs_shader_cache_lock); + if (auto found = m_fs_shader_cache.find(fs_opt); + found != m_fs_shader_cache.end()) + { + fs = found->second; + } + } + + return { vs, fs }; } const shader_interpreter::pipeline_info_ex_t* shader_interpreter::get_pipeline_info_ex(u64 compiler_opt) diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h index d474b8d94a..0aa95bebfe 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h @@ -55,9 +55,14 @@ namespace vk }; std::unordered_map, key_hasher> m_program_cache; - std::unordered_map m_shader_cache; + std::unordered_map> m_vs_shader_cache; + std::unordered_map> m_fs_shader_cache; std::unordered_map m_pipeline_info_cache; + mutable shared_mutex m_program_cache_lock; + mutable shared_mutex m_vs_shader_cache_lock; + mutable shared_mutex m_fs_shader_cache_lock; + pipeline_key m_current_key{}; pipeline_info_ex_t m_current_pipeline_info_ex{}; std::shared_ptr m_current_interpreter; From 3d6773dd257ff11906759a12c8e55727faf2fa9f Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 12 Apr 2026 22:46:41 +0300 Subject: [PATCH 062/283] vk/pipe-compiler: Allow external definition of graphics properties during async compilation --- rpcs3/Emu/RSX/VK/VKPipelineCompiler.cpp | 45 +++++++++++++++++++++---- rpcs3/Emu/RSX/VK/VKPipelineCompiler.h | 34 +++++++++++++++++++ 2 files changed, 72 insertions(+), 7 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKPipelineCompiler.cpp b/rpcs3/Emu/RSX/VK/VKPipelineCompiler.cpp index 99462e0602..286a30c3bf 100644 --- a/rpcs3/Emu/RSX/VK/VKPipelineCompiler.cpp +++ b/rpcs3/Emu/RSX/VK/VKPipelineCompiler.cpp @@ -34,16 +34,22 @@ namespace vk { for (auto&& job : m_work_queue.pop_all()) { - if (job.is_graphics_job) - { - auto compiled = int_compile_graphics_pipe(job.graphics_data, job.graphics_modules, job.inputs, {}, job.flags); - job.callback_func(compiled); - } - else + if (!job.is_graphics_job) { auto compiled = int_compile_compute_pipe(job.compute_data, job.inputs, job.flags); job.callback_func(compiled); + continue; } + + if (job.create_info_func) + { + auto compiled = int_compile_graphics_pipe(job.create_info_func, job.inputs, {}, job.flags); + job.callback_func(compiled); + continue; + } + + auto compiled = int_compile_graphics_pipe(job.graphics_data, job.graphics_modules, job.inputs, {}, job.flags); + job.callback_func(compiled); } thread_ctrl::wait_on(m_work_queue); @@ -173,6 +179,16 @@ namespace vk return int_compile_graphics_pipe(info, vs_inputs, fs_inputs, flags); } + std::unique_ptr pipe_compiler::int_compile_graphics_pipe( + graphics_pipe_create_callback_t pipe_info_create_fn, + const std::vector& vs_inputs, + const std::vector& fs_inputs, + op_flags flags) + { + VkGraphicsPipelineCreateInfo create_info = pipe_info_create_fn(); + return int_compile_graphics_pipe(create_info, vs_inputs, fs_inputs, flags); + } + std::unique_ptr pipe_compiler::compile( const VkComputePipelineCreateInfo& create_info, op_flags flags, callback_t callback, @@ -194,7 +210,7 @@ namespace vk const std::vector& fs_inputs) { // It is very inefficient to defer this as all pointers need to be saved - ensure(flags & COMPILE_INLINE); + ensure(flags & COMPILE_INLINE, "Asynchronous compilation is not allowed for raw graphics pipeline input"); return int_compile_graphics_pipe(create_info, vs_inputs, fs_inputs, flags); } @@ -216,6 +232,21 @@ namespace vk return {}; } + std::unique_ptr pipe_compiler::compile( + graphics_pipe_create_callback_t get_create_info, + op_flags flags, callback_t callback, + const std::vector& vs_inputs, + const std::vector& fs_inputs) + { + if (flags & COMPILE_INLINE) + { + return int_compile_graphics_pipe(get_create_info, vs_inputs, fs_inputs, flags); + } + + m_work_queue.push(get_create_info, vs_inputs, fs_inputs, flags, callback); + return {}; + } + void initialize_pipe_compiler(int num_worker_threads) { if (num_worker_threads == 0) diff --git a/rpcs3/Emu/RSX/VK/VKPipelineCompiler.h b/rpcs3/Emu/RSX/VK/VKPipelineCompiler.h index 762e8aadfc..e1cea3b04a 100644 --- a/rpcs3/Emu/RSX/VK/VKPipelineCompiler.h +++ b/rpcs3/Emu/RSX/VK/VKPipelineCompiler.h @@ -64,6 +64,7 @@ namespace vk using op_flags = rsx::flags32_t; using callback_t = std::function&)>; + using graphics_pipe_create_callback_t = std::function; pipe_compiler(); ~pipe_compiler(); @@ -89,6 +90,12 @@ namespace vk const std::vector& vs_inputs = {}, const std::vector& fs_inputs = {}); + std::unique_ptr compile( + graphics_pipe_create_callback_t get_create_info, + op_flags flags, callback_t callback, + const std::vector& vs_inputs, + const std::vector& fs_inputs); + void operator()(); private: @@ -111,6 +118,7 @@ namespace vk { bool is_graphics_job; callback_t callback_func; + graphics_pipe_create_callback_t create_info_func; vk::pipeline_props graphics_data; compute_pipeline_props compute_data; @@ -139,6 +147,26 @@ namespace vk inputs.insert(inputs.end(), fs_in.begin(), fs_in.end()); } + pipe_compiler_job( + graphics_pipe_create_callback_t pipe_info_create_fn, + const std::vector& vs_in, + const std::vector& fs_in, + op_flags flags_, + callback_t func) + { + callback_func = func; + create_info_func = pipe_info_create_fn; + is_graphics_job = true; + flags = flags_; + + graphics_modules[0] = VK_NULL_HANDLE; + graphics_modules[1] = VK_NULL_HANDLE; + + inputs.reserve(vs_in.size() + fs_in.size()); + inputs.insert(inputs.end(), vs_in.begin(), vs_in.end()); + inputs.insert(inputs.end(), fs_in.begin(), fs_in.end()); + } + pipe_compiler_job( const VkComputePipelineCreateInfo& props, const std::vector& cs_in, @@ -177,6 +205,12 @@ namespace vk const std::vector& vs_inputs, const std::vector& fs_inputs, op_flags flags); + + std::unique_ptr int_compile_graphics_pipe( + graphics_pipe_create_callback_t pipe_info_create_fn, + const std::vector& vs_inputs, + const std::vector& fs_inputs, + op_flags flags); }; void initialize_pipe_compiler(int num_worker_threads = -1); From 99e6b19025b9d656d4c63336780edcb023d39b20 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 12 Apr 2026 23:54:00 +0300 Subject: [PATCH 063/283] gl: C++ things --- rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index d0a34a493c..68b4c9dc01 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -509,7 +509,7 @@ namespace gl auto storage_hook = [=](std::unique_ptr& prog) { - data->prog.reset(prog.release()); + data->prog = std::move(prog); post_init_hook(data, compiler_options); if (callback) From d519571e18bcd8dae5d156e1dc60d6b65ea81414 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 13 Apr 2026 00:21:21 +0300 Subject: [PATCH 064/283] vk: Implement async interpreter builds --- rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp | 282 ++++++++++++++++------- rpcs3/Emu/RSX/VK/VKShaderInterpreter.h | 5 +- 2 files changed, 201 insertions(+), 86 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp index 250c551a82..4ed492c313 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp @@ -16,6 +16,111 @@ namespace vk { using enum program_common::interpreter::compiler_option; + class async_pipe_compiler_context + { + vk::pipeline_props m_properties{}; + VkDevice m_device = VK_NULL_HANDLE; + VkPipelineCache m_pipeline_cache = VK_NULL_HANDLE; + VkShaderModule m_vs = VK_NULL_HANDLE; + VkShaderModule m_fs = VK_NULL_HANDLE; + + VkPipelineShaderStageCreateInfo m_shader_stages[2]; + VkPipelineDynamicStateCreateInfo m_dynamic_state_info{}; + VkPipelineVertexInputStateCreateInfo m_vi{ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; + VkPipelineViewportStateCreateInfo m_vp{}; + VkPipelineMultisampleStateCreateInfo m_ms{}; + VkPipelineColorBlendStateCreateInfo m_cs{}; + VkPipelineTessellationStateCreateInfo m_ts{}; + + std::vector m_dynamic_state_descriptors + { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, + VK_DYNAMIC_STATE_LINE_WIDTH, + VK_DYNAMIC_STATE_BLEND_CONSTANTS, + VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK, + VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, + VK_DYNAMIC_STATE_STENCIL_REFERENCE, + VK_DYNAMIC_STATE_DEPTH_BIAS + }; + + public: + + async_pipe_compiler_context( + const vk::pipeline_props& props, + VkDevice device, + VkPipelineCache pipeline_cache, + VkShaderModule vs, + VkShaderModule fs) + : m_properties(props) + , m_device(device) + , m_pipeline_cache(pipeline_cache) + , m_vs(vs) + , m_fs(fs) + { + } + + VkGraphicsPipelineCreateInfo compile() + { + m_shader_stages[0] = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO }; + m_shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; + m_shader_stages[0].module = m_vs; + m_shader_stages[0].pName = "main"; + + m_shader_stages[1] = { VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO }; + m_shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; + m_shader_stages[1].module = m_fs; + m_shader_stages[1].pName = "main"; + + if (vk::get_current_renderer()->get_depth_bounds_support()) + { + m_dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_DEPTH_BOUNDS); + } + + m_dynamic_state_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; + m_dynamic_state_info.pDynamicStates = m_dynamic_state_descriptors.data(); + m_dynamic_state_info.dynamicStateCount = ::size32(m_dynamic_state_descriptors); + + m_vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; + m_vp.viewportCount = 1; + m_vp.scissorCount = 1; + + m_ms = m_properties.state.ms; + ensure(m_ms.rasterizationSamples == VkSampleCountFlagBits((m_properties.renderpass_key >> 16) & 0xF)); // "Multisample state mismatch!" + if (m_ms.rasterizationSamples != VK_SAMPLE_COUNT_1_BIT) + { + // Update the sample mask pointer + m_ms.pSampleMask = &m_properties.state.temp_storage.msaa_sample_mask; + } + + // Rebase pointers from pipeline structure in case it is moved/copied + m_cs = m_properties.state.cs; + m_cs.pAttachments = m_properties.state.att_state; + + m_ts = {}; + m_ts.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO; + + VkGraphicsPipelineCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; + info.pVertexInputState = &m_vi; + info.pInputAssemblyState = &m_properties.state.ia; + info.pRasterizationState = &m_properties.state.rs; + info.pColorBlendState = &m_cs; + info.pMultisampleState = &m_ms; + info.pViewportState = &m_vp; + info.pDepthStencilState = &m_properties.state.ds; + info.pTessellationState = &m_ts; + info.stageCount = 2; + info.pStages = m_shader_stages; + info.pDynamicState = &m_dynamic_state_info; + info.layout = VK_NULL_HANDLE; + info.basePipelineIndex = -1; + info.basePipelineHandle = VK_NULL_HANDLE; + info.renderPass = vk::get_renderpass(m_device, m_properties.renderpass_key); + return info; + } + }; + u32 shader_interpreter::init(std::shared_ptr& vk_prog, u64 compiler_options) const { std::memset(&vk_prog->binding_table, 0xff, sizeof(vk_prog->binding_table)); @@ -353,6 +458,9 @@ namespace vk void shader_interpreter::init(const vk::render_device& dev) { m_device = dev; + + VkPipelineCacheCreateInfo drv_cache_info{ VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO }; + vkCreatePipelineCache(m_device, &drv_cache_info, nullptr, &m_driver_pipeline_cache); } void shader_interpreter::destroy() @@ -361,91 +469,50 @@ namespace vk m_program_cache.clear(); m_vs_shader_cache.clear(); m_fs_shader_cache.clear(); + + vkDestroyPipelineCache(m_device, m_driver_pipeline_cache, nullptr); } - std::shared_ptr shader_interpreter::link(const vk::pipeline_props& properties, u64 compiler_opt) + std::shared_ptr shader_interpreter::link(const vk::pipeline_props& properties, u64 compiler_opt, bool async, async_build_fn_callback async_callback) { auto vs = build_vs(compiler_opt); auto fs = build_fs(compiler_opt); - VkPipelineShaderStageCreateInfo shader_stages[2] = {}; - shader_stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; - shader_stages[0].module = vs->shader.get_handle(); - shader_stages[0].pName = "main"; - - shader_stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; - shader_stages[1].module = fs->shader.get_handle(); - shader_stages[1].pName = "main"; - - std::vector dynamic_state_descriptors = + async_pipe_compiler_context context{ properties, m_device, m_driver_pipeline_cache, vs->shader.get_handle(), fs->shader.get_handle() }; + auto create_graphics_info_fn = [=]() mutable { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR, - VK_DYNAMIC_STATE_LINE_WIDTH, - VK_DYNAMIC_STATE_BLEND_CONSTANTS, - VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK, - VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, - VK_DYNAMIC_STATE_STENCIL_REFERENCE, - VK_DYNAMIC_STATE_DEPTH_BIAS + return context.compile(); }; - if (vk::get_current_renderer()->get_depth_bounds_support()) + auto callback_fn = [=](std::unique_ptr& prog) { - dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_DEPTH_BOUNDS); - } + if (!async) + { + return; + } - VkPipelineDynamicStateCreateInfo dynamic_state_info = {}; - dynamic_state_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; - dynamic_state_info.pDynamicStates = dynamic_state_descriptors.data(); - dynamic_state_info.dynamicStateCount = ::size32(dynamic_state_descriptors); + pipeline_key key{}; + key.compiler_opt = compiler_opt; + key.properties = properties; - VkPipelineVertexInputStateCreateInfo vi = { VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO }; + std::shared_ptr result = std::move(prog); + std::lock_guard lock(this->m_program_cache_lock); + this->m_program_cache[key] = result; - VkPipelineViewportStateCreateInfo vp = {}; - vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; - vp.viewportCount = 1; - vp.scissorCount = 1; + if (async_callback) + { + async_callback(result); + } + }; - VkPipelineMultisampleStateCreateInfo ms = properties.state.ms; - ensure(ms.rasterizationSamples == VkSampleCountFlagBits((properties.renderpass_key >> 16) & 0xF)); // "Multisample state mismatch!" - if (ms.rasterizationSamples != VK_SAMPLE_COUNT_1_BIT) - { - // Update the sample mask pointer - ms.pSampleMask = &properties.state.temp_storage.msaa_sample_mask; - } - - // Rebase pointers from pipeline structure in case it is moved/copied - VkPipelineColorBlendStateCreateInfo cs = properties.state.cs; - cs.pAttachments = properties.state.att_state; - - VkPipelineTessellationStateCreateInfo ts = {}; - ts.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO; - - VkGraphicsPipelineCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; - info.pVertexInputState = &vi; - info.pInputAssemblyState = &properties.state.ia; - info.pRasterizationState = &properties.state.rs; - info.pColorBlendState = &cs; - info.pMultisampleState = &ms; - info.pViewportState = &vp; - info.pDepthStencilState = &properties.state.ds; - info.pTessellationState = &ts; - info.stageCount = 2; - info.pStages = shader_stages; - info.pDynamicState = &dynamic_state_info; - info.layout = VK_NULL_HANDLE; - info.basePipelineIndex = -1; - info.basePipelineHandle = VK_NULL_HANDLE; - info.renderPass = vk::get_renderpass(m_device, properties.renderpass_key); + vk::pipe_compiler::op_flags flags = vk::pipe_compiler::SEPARATE_SHADER_OBJECTS; + flags |= (async ? vk::pipe_compiler::COMPILE_DEFERRED : vk::pipe_compiler::COMPILE_INLINE); auto compiler = vk::get_pipe_compiler(); auto program = compiler->compile( - info, - vk::pipe_compiler::COMPILE_INLINE | vk::pipe_compiler::SEPARATE_SHADER_OBJECTS, - {}, + create_graphics_info_fn, + flags, + callback_fn, vs->uniforms, fs->uniforms); @@ -528,14 +595,20 @@ namespace vk m_current_key = key; } - auto found = m_program_cache.find(key); - if (found != m_program_cache.end()) [[likely]] { - m_current_interpreter = found->second; - return m_current_interpreter.get(); + reader_lock lock(m_program_cache_lock); + + auto found = m_program_cache.find(key); + if (found != m_program_cache.end()) [[likely]] + { + m_current_interpreter = found->second; + return m_current_interpreter.get(); + } } m_current_interpreter = link(properties, key.compiler_opt); + + std::lock_guard lock(m_program_cache_lock); m_program_cache[key] = m_current_interpreter; return m_current_interpreter.get(); } @@ -611,23 +684,62 @@ namespace vk { dlg->create("Precompiling interpreter variants.\nPlease wait...", "Shader Compilation"); -#if 0 - const auto variants = program_common::interpreter::get_interpreter_variants(); - const u32 limit = ::size32(variants); - dlg->set_limit(0, limit); - dlg->set_limit(1, 1); + // Create some basic pipelines that we'll use to seed the base pipeline queue + std::vector pipe_properties; - u32 ctr = 0; - for (auto& variant : variants) + vk::pipeline_props base_props{}; + base_props.state.set_attachment_count(1); + base_props.state.enable_cull_face(VK_CULL_MODE_BACK_BIT); + base_props.state.set_primitive_type(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + base_props.renderpass_key = vk::get_renderpass_key(VK_FORMAT_B8G8R8A8_UNORM); + pipe_properties.push_back(base_props); + + const auto variants = program_common::interpreter::get_interpreter_variants(); + const u32 limit1 = ::size32(variants.base_pipelines); + const u32 limit2 = ::size32(variants.pipelines); + dlg->set_limit(0, limit1 * ::size32(pipe_properties)); + dlg->set_limit(1, limit2); + + atomic_t ctr = 0; + for (auto& variant : variants.base_pipelines) { - //build_fs(variant.first | variant.second); - //build_vs(variant.first | variant.second); - dlg->update_msg(0, fmt::format("Building variant %u of %u...", ++ctr, limit)); - dlg->inc_value(0, 1); + for (const auto& props : pipe_properties) + { + link(props, variant.first | variant.second, true, [&](std::shared_ptr&) { ctr++; }); + } } - dlg->inc_value(1, 1); + do + { + std::this_thread::sleep_for(16ms); + + const auto completed = ctr.load(); + dlg->update_msg(0, fmt::format("Building base variant %u of %u...", completed, limit1)); + dlg->set_value(0, completed); + } + while (ctr.load() < limit1); + + // TODO: Propagate base pipelines for faster startup + ctr = 0; + for (auto& variant : variants.pipelines) + { + for (const auto& props : pipe_properties) + { + link(props, variant.vs_opts.shader_opt | variant.fs_opts.shader_opt, true, [&](std::shared_ptr&) { ctr++; }); + } + } + + do + { + std::this_thread::sleep_for(16ms); + + const auto completed = ctr.load(); + dlg->update_msg(1, fmt::format("Linking variant %u of %u...", completed, limit2)); + dlg->set_value(1, completed); + } + while (ctr.load() < limit2); + + dlg->set_value(1, limit2); dlg->refresh(); -#endif } }; diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h index 0aa95bebfe..a14fe3c5a8 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h @@ -20,7 +20,10 @@ namespace vk class shader_interpreter { + using async_build_fn_callback = std::function&)>; + VkDevice m_device = VK_NULL_HANDLE; + VkPipelineCache m_driver_pipeline_cache = VK_NULL_HANDLE; struct pipeline_key { @@ -69,7 +72,7 @@ namespace vk std::shared_ptr build_vs(u64 compiler_opt); std::shared_ptr build_fs(u64 compiler_opt); - std::shared_ptr link(const vk::pipeline_props& properties, u64 compiler_opt); + std::shared_ptr link(const vk::pipeline_props& properties, u64 compiler_opt, bool async = false, async_build_fn_callback async_callback = {}); u32 init(std::shared_ptr& vk_prog, u64 compiler_opt) const; u32 init(std::shared_ptr& vk_prog, u64 compiler_opt) const; From ac28c391f66439a0cf49043d5e9325613933ec45 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 13 Apr 2026 00:58:41 +0300 Subject: [PATCH 065/283] vk/interpreter: Implement base pipeline propagation --- rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp | 13 ++--- rpcs3/Emu/RSX/GL/GLShaderInterpreter.h | 6 --- rpcs3/Emu/RSX/Program/ShaderInterpreter.h | 6 +++ rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp | 63 +++++++++++++++++------ rpcs3/Emu/RSX/VK/VKShaderInterpreter.h | 8 ++- 5 files changed, 66 insertions(+), 30 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index 68b4c9dc01..6592dd4da9 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -13,7 +13,8 @@ namespace gl { using glsl::shader; - using enum program_common::interpreter::compiler_option; + using enum program_common::interpreter::compiler_option; + using enum program_common::interpreter::cached_pipeline_flags; namespace interpreter { @@ -88,7 +89,7 @@ namespace gl // Second stage. Propagate base pipelines to all compatible ctr = 0; - reader_lock lock(m_program_cache_lock); + std::lock_guard lock(m_program_cache_lock); for (const auto& variant : variants.pipelines) { @@ -107,7 +108,7 @@ namespace gl } auto data = new interpreter::cached_program(); - data->flags |= interpreter::CACHED_PIPE_UNOPTIMIZED; + data->flags |= CACHED_PIPE_UNOPTIMIZED; data->allocator = base_pipeline->second->allocator; data->vertex_shader = base_pipeline->second->vertex_shader; data->fragment_shader = base_pipeline->second->fragment_shader; @@ -193,13 +194,13 @@ namespace gl if (m_current_interpreter) { - constexpr u32 test_mask = (interpreter::CACHED_PIPE_UNOPTIMIZED | interpreter::CACHED_PIPE_RECOMPILING); - constexpr u32 unoptimized_mask = interpreter::CACHED_PIPE_UNOPTIMIZED; + constexpr u32 test_mask = (CACHED_PIPE_UNOPTIMIZED | CACHED_PIPE_RECOMPILING); + constexpr u32 unoptimized_mask = CACHED_PIPE_UNOPTIMIZED; if ((m_current_interpreter->flags & test_mask) == unoptimized_mask) { // Interpreter is unoptimized and we haven't tried to recompile it // NOTE: This operation effectively orphans the current interpreter, but since unoptimized pipelines just have a non-owning reference then it's actually fine and we don't really leak anything. - m_current_interpreter->flags |= interpreter::CACHED_PIPE_RECOMPILING; + m_current_interpreter->flags |= CACHED_PIPE_RECOMPILING; build_program_async(opt, {}); } return m_current_interpreter->prog.get(); diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h index ef070c3fd2..015e46cef8 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h @@ -62,12 +62,6 @@ namespace gl void allocate(int size); }; - enum cached_program_flags - { - CACHED_PIPE_UNOPTIMIZED = (1 << 0), - CACHED_PIPE_RECOMPILING = (1 << 1), - }; - struct cached_program { u32 flags = 0; diff --git a/rpcs3/Emu/RSX/Program/ShaderInterpreter.h b/rpcs3/Emu/RSX/Program/ShaderInterpreter.h index b80f871769..26f4877bac 100644 --- a/rpcs3/Emu/RSX/Program/ShaderInterpreter.h +++ b/rpcs3/Emu/RSX/Program/ShaderInterpreter.h @@ -45,6 +45,12 @@ namespace program_common COMPILER_OPT_VS_MIN = COMPILER_OPT_ENABLE_INSTANCING, }; + enum cached_pipeline_flags : u32 + { + CACHED_PIPE_UNOPTIMIZED = (1 << 0), + CACHED_PIPE_RECOMPILING = (1 << 1), + }; + static std::string get_vertex_interpreter() { const char* s = diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp index 4ed492c313..f0723080f5 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp @@ -15,6 +15,7 @@ namespace vk { using enum program_common::interpreter::compiler_option; + using enum program_common::interpreter::cached_pipeline_flags; class async_pipe_compiler_context { @@ -497,7 +498,13 @@ namespace vk std::shared_ptr result = std::move(prog); std::lock_guard lock(this->m_program_cache_lock); - this->m_program_cache[key] = result; + + pipeline_cache_entry_t cached_program + { + .flags = 0, + .program = result + }; + this->m_program_cache[key] = cached_program; if (async_callback) { @@ -601,15 +608,21 @@ namespace vk auto found = m_program_cache.find(key); if (found != m_program_cache.end()) [[likely]] { - m_current_interpreter = found->second; + m_current_interpreter = found->second.program; return m_current_interpreter.get(); } } m_current_interpreter = link(properties, key.compiler_opt); + pipeline_cache_entry_t cache_entry + { + .flags = 0, + .program = m_current_interpreter + }; + std::lock_guard lock(m_program_cache_lock); - m_program_cache[key] = m_current_interpreter; + m_program_cache[key] = cache_entry; return m_current_interpreter.get(); } @@ -709,6 +722,8 @@ namespace vk } } + // Drain the queue. + // FIXME: Since the queue is executing from the context of the pipe compiler, we cannot properly stop this process. do { std::this_thread::sleep_for(16ms); @@ -719,26 +734,40 @@ namespace vk } while (ctr.load() < limit1); - // TODO: Propagate base pipelines for faster startup ctr = 0; - for (auto& variant : variants.pipelines) + std::lock_guard lock(m_program_cache_lock); + + for (const auto& props : pipe_properties) { - for (const auto& props : pipe_properties) + pipeline_key base_key; + base_key.properties = props; + + for (auto& variant : variants.pipelines) { - link(props, variant.vs_opts.shader_opt | variant.fs_opts.shader_opt, true, [&](std::shared_ptr&) { ctr++; }); + // Check if we have an exact match + base_key.compiler_opt = variant.vs_opts.shader_opt | variant.fs_opts.shader_opt; + if (auto found = m_program_cache.find(base_key); + found != m_program_cache.end()) + { + // We have a perfect match, no propagation required + continue; + } + + // Find a compatible pipeline + auto compat_key = base_key; + compat_key.compiler_opt = variant.vs_opts.compatible_shader_opts | variant.fs_opts.compatible_shader_opts; + auto found = m_program_cache.find(compat_key); + ensure(found != m_program_cache.end(), "Invalid interpreter configuration."); + + pipeline_cache_entry_t cache_entry + { + .flags = CACHED_PIPE_UNOPTIMIZED, + .program = found->second.program + }; + m_program_cache[base_key] = cache_entry; } } - do - { - std::this_thread::sleep_for(16ms); - - const auto completed = ctr.load(); - dlg->update_msg(1, fmt::format("Linking variant %u of %u...", completed, limit2)); - dlg->set_value(1, completed); - } - while (ctr.load() < limit2); - dlg->set_value(1, limit2); dlg->refresh(); } diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h index a14fe3c5a8..306a48d85b 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h @@ -57,7 +57,13 @@ namespace vk u32 fragment_textures_location = 0; }; - std::unordered_map, key_hasher> m_program_cache; + struct pipeline_cache_entry_t + { + u32 flags = 0; + std::shared_ptr program; + }; + + std::unordered_map m_program_cache; std::unordered_map> m_vs_shader_cache; std::unordered_map> m_fs_shader_cache; std::unordered_map m_pipeline_info_cache; From d93f5f9f8415c5f6f160bbbd1a43fbee27c761d2 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 13 Apr 2026 01:07:31 +0300 Subject: [PATCH 066/283] vk: Implement variant recompilation for performance --- rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp index f0723080f5..316071abe7 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp @@ -471,7 +471,11 @@ namespace vk m_vs_shader_cache.clear(); m_fs_shader_cache.clear(); - vkDestroyPipelineCache(m_device, m_driver_pipeline_cache, nullptr); + if (m_driver_pipeline_cache) + { + vkDestroyPipelineCache(m_device, m_driver_pipeline_cache, nullptr); + m_driver_pipeline_cache = VK_NULL_HANDLE; + } } std::shared_ptr shader_interpreter::link(const vk::pipeline_props& properties, u64 compiler_opt, bool async, async_build_fn_callback async_callback) @@ -609,6 +613,13 @@ namespace vk if (found != m_program_cache.end()) [[likely]] { m_current_interpreter = found->second.program; + + if ((found->second.flags & (CACHED_PIPE_UNOPTIMIZED | CACHED_PIPE_RECOMPILING)) == CACHED_PIPE_UNOPTIMIZED) + { + found->second.flags |= CACHED_PIPE_RECOMPILING; + link(properties, key.compiler_opt, true, {}); + } + return m_current_interpreter.get(); } } From ca762d5b84558ae927238a9f007d24be2e87a333 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 13 Apr 2026 02:24:38 +0300 Subject: [PATCH 067/283] vk: Add extra renderpass API entry to allow generating keys with no images --- rpcs3/Emu/RSX/VK/VKRenderPass.cpp | 25 +++++++++++++++++++++++-- rpcs3/Emu/RSX/VK/VKRenderPass.h | 3 ++- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKRenderPass.cpp b/rpcs3/Emu/RSX/VK/VKRenderPass.cpp index c9fe0665d9..0f6ef23458 100644 --- a/rpcs3/Emu/RSX/VK/VKRenderPass.cpp +++ b/rpcs3/Emu/RSX/VK/VKRenderPass.cpp @@ -203,10 +203,10 @@ namespace vk return key.encoded; } - u64 get_renderpass_key(VkFormat surface_format) + u64 get_renderpass_key(VkFormat surface_format, u8 sample_count) { renderpass_key_blob key(0); - key.sample_count = 1; + key.sample_count = sample_count; switch (surface_format) { @@ -226,6 +226,27 @@ namespace vk return key.encoded; } + u64 get_renderpass_key(VkFormat color_format, VkFormat depth_format, u8 sample_count) + { + renderpass_key_blob key(0); + key.sample_count = sample_count; + + u32 image_index = 0; + if (color_format != VK_FORMAT_UNDEFINED) + { + key.set_format(color_format); + key.set_layout(image_index++, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); + } + + if (depth_format != VK_FORMAT_UNDEFINED) + { + key.set_format(depth_format); + key.set_layout(image_index++, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + } + + return key.encoded; + } + VkRenderPass get_renderpass(VkDevice dev, u64 renderpass_key) { // 99.999% of checks will go through this block once on-disk shader cache has loaded diff --git a/rpcs3/Emu/RSX/VK/VKRenderPass.h b/rpcs3/Emu/RSX/VK/VKRenderPass.h index 6b6718ef29..5c23e749df 100644 --- a/rpcs3/Emu/RSX/VK/VKRenderPass.h +++ b/rpcs3/Emu/RSX/VK/VKRenderPass.h @@ -10,7 +10,8 @@ namespace vk u64 get_renderpass_key(const std::vector& images, const std::vector& input_attachment_ids = {}); u64 get_renderpass_key(const std::vector& images, u64 previous_key); - u64 get_renderpass_key(VkFormat surface_format); + u64 get_renderpass_key(VkFormat surface_format, u8 sample_count = 1); + u64 get_renderpass_key(VkFormat color_format, VkFormat depth_format, u8 sample_count = 1); VkRenderPass get_renderpass(VkDevice dev, u64 renderpass_key); void clear_renderpass_cache(VkDevice dev); From 4e160320effc27ab0f2837cf3a84153912c08a23 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 13 Apr 2026 02:25:44 +0300 Subject: [PATCH 068/283] vk/interpreter: Precompile more variants to avoid ingame stutters - Also temporarily logs a message to make stutters more obvious --- rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp | 46 +++++++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp index 316071abe7..f1943f8712 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp @@ -624,8 +624,18 @@ namespace vk } } + const auto start = std::chrono::steady_clock::now(); + m_current_interpreter = link(properties, key.compiler_opt); + const auto end = std::chrono::steady_clock::now(); + + auto duration = std::chrono::duration_cast(end - start); + if (duration > std::chrono::milliseconds(1000)) + { + rsx_log.error("Cache miss"); + } + pipeline_cache_entry_t cache_entry { .flags = 0, @@ -710,25 +720,50 @@ namespace vk // Create some basic pipelines that we'll use to seed the base pipeline queue std::vector pipe_properties; + auto pdev = vk::get_current_renderer(); + // Base pipeline - simple color vk::pipeline_props base_props{}; base_props.state.set_attachment_count(1); base_props.state.enable_cull_face(VK_CULL_MODE_BACK_BIT); base_props.state.set_primitive_type(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST); + base_props.state.set_color_mask(0, true, true, true, true); + base_props.state.set_attachment_count(1); + base_props.state.enable_depth_bias(true); + base_props.state.enable_depth_clamp(true); + base_props.state.enable_depth_bounds_test(pdev->get_depth_bounds_support()); base_props.renderpass_key = vk::get_renderpass_key(VK_FORMAT_B8G8R8A8_UNORM); pipe_properties.push_back(base_props); + // Add in some blending + base_props.state.enable_blend(0, + VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + VK_BLEND_FACTOR_SRC_ALPHA, VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + VK_BLEND_OP_ADD, VK_BLEND_OP_ADD); + pipe_properties.push_back(base_props); + + // Add a depth buffer + const auto depth_format = pdev->get_formats_support().d24_unorm_s8 ? VK_FORMAT_D24_UNORM_S8_UINT : VK_FORMAT_D32_SFLOAT_S8_UINT; + base_props.renderpass_key = vk::get_renderpass_key(VK_FORMAT_B8G8R8A8_UNORM, depth_format); + base_props.state.enable_depth_test(VK_COMPARE_OP_LESS); + base_props.state.set_depth_mask(true); + pipe_properties.push_back(base_props); + const auto variants = program_common::interpreter::get_interpreter_variants(); - const u32 limit1 = ::size32(variants.base_pipelines); - const u32 limit2 = ::size32(variants.pipelines); - dlg->set_limit(0, limit1 * ::size32(pipe_properties)); + const u32 limit1 = ::size32(variants.base_pipelines) * ::size32(pipe_properties); + const u32 limit2 = ::size32(variants.pipelines) * ::size32(pipe_properties); + dlg->set_limit(0, limit1); dlg->set_limit(1, limit2); atomic_t ctr = 0; - for (auto& variant : variants.base_pipelines) + for (const auto& props : pipe_properties) { - for (const auto& props : pipe_properties) + for (auto& variant : variants.base_pipelines) { + pipeline_key key{}; + key.properties = props; + key.compiler_opt = variant.first | variant.second; + link(props, variant.first | variant.second, true, [&](std::shared_ptr&) { ctr++; }); } } @@ -768,6 +803,7 @@ namespace vk auto compat_key = base_key; compat_key.compiler_opt = variant.vs_opts.compatible_shader_opts | variant.fs_opts.compatible_shader_opts; auto found = m_program_cache.find(compat_key); + ensure(found != m_program_cache.end(), "Invalid interpreter configuration."); pipeline_cache_entry_t cache_entry From 9dde2bf04e8f2784f9001e193023423546f050d1 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 13 Apr 2026 02:32:18 +0300 Subject: [PATCH 069/283] vk: Skip shader cache load if we're running interpreter only --- rpcs3/Emu/RSX/VK/VKGSRender.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 293097de24..d8ac9fccf1 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -1247,18 +1247,21 @@ void VKGSRender::on_init_thread() dlg->close(); } - if (!m_overlay_manager) + if (g_cfg.video.shadermode != shader_mode::interpreter_only) { - m_frame->hide(); - m_shaders_cache->load(nullptr); - m_frame->show(); - } - else - { - rsx::shader_loading_dialog_native dlg(this); + if (!m_overlay_manager) + { + m_frame->hide(); + m_shaders_cache->load(nullptr); + m_frame->show(); + } + else + { + rsx::shader_loading_dialog_native dlg(this); - // TODO: Handle window resize messages during loading on GPUs without OUT_OF_DATE_KHR support - m_shaders_cache->load(&dlg); + // TODO: Handle window resize messages during loading on GPUs without OUT_OF_DATE_KHR support + m_shaders_cache->load(&dlg); + } } } From c350c025921430122863512789cd535956cb0939 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 13 Apr 2026 03:06:39 +0300 Subject: [PATCH 070/283] rsx: Fix build and silence warnings --- rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp | 4 ++-- rpcs3/Emu/RSX/Program/ShaderInterpreter.h | 4 ++-- rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index 6592dd4da9..72341ea521 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -499,7 +499,7 @@ namespace gl { auto data = std::make_shared(); - auto post_create_hook = [=](glsl::program* prog) + auto post_create_hook = [=, this](glsl::program* prog) { build_fs(compiler_options, *data); build_vs(compiler_options, *data); @@ -508,7 +508,7 @@ namespace gl attach(*data->fragment_shader); }; - auto storage_hook = [=](std::unique_ptr& prog) + auto storage_hook = [=, this](std::unique_ptr& prog) { data->prog = std::move(prog); post_init_hook(data, compiler_options); diff --git a/rpcs3/Emu/RSX/Program/ShaderInterpreter.h b/rpcs3/Emu/RSX/Program/ShaderInterpreter.h index 26f4877bac..e6a3282448 100644 --- a/rpcs3/Emu/RSX/Program/ShaderInterpreter.h +++ b/rpcs3/Emu/RSX/Program/ShaderInterpreter.h @@ -51,7 +51,7 @@ namespace program_common CACHED_PIPE_RECOMPILING = (1 << 1), }; - static std::string get_vertex_interpreter() + [[maybe_unused]] static std::string get_vertex_interpreter() { const char* s = #include "../Program/GLSLInterpreter/VertexInterpreter.glsl" @@ -59,7 +59,7 @@ namespace program_common return s; } - static std::string get_fragment_interpreter() + [[maybe_unused]] static std::string get_fragment_interpreter() { const char* s = #include "../Program/GLSLInterpreter/FragmentInterpreter.glsl" diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp index f1943f8712..dd51e22631 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp @@ -1,7 +1,6 @@ #include "stdafx.h" #include "VKShaderInterpreter.h" -#include "VKCommonPipelineLayout.h" #include "VKVertexProgram.h" #include "VKFragmentProgram.h" #include "VKHelpers.h" @@ -12,6 +11,8 @@ #include "../Program/ShaderInterpreter.h" #include "../rsx_methods.h" +#include + namespace vk { using enum program_common::interpreter::compiler_option; @@ -489,7 +490,7 @@ namespace vk return context.compile(); }; - auto callback_fn = [=](std::unique_ptr& prog) + auto callback_fn = [=, this](std::unique_ptr& prog) { if (!async) { From 3fc41b6d669eb27368dd002094c00b436ff1adb4 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 14 Apr 2026 00:48:48 +0300 Subject: [PATCH 071/283] gl: Fix context race condition when preparing optimized interpreter variants - The main thread may bind the program before the compiler context has published the binding instructions - Once bound, late bindings are not updated so the program remains with uninitialized bindings and rendering breaks --- rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp | 28 +++++++++++++++++++---- rpcs3/Emu/RSX/GL/GLShaderInterpreter.h | 10 ++++++-- rpcs3/Emu/RSX/Program/ShaderInterpreter.h | 5 ++-- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index 72341ea521..fc575ffc5c 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -7,7 +7,6 @@ #include "Emu/RSX/rsx_methods.h" #include "Emu/RSX/Overlays/Shaders/shader_loading_dialog.h" -#include "Emu/RSX/Program/ShaderInterpreter.h" #include "Emu/RSX/Program/GLSLCommon.h" namespace gl @@ -108,7 +107,8 @@ namespace gl } auto data = new interpreter::cached_program(); - data->flags |= CACHED_PIPE_UNOPTIMIZED; + data->flags = base_pipeline->second->flags | CACHED_PIPE_UNOPTIMIZED; + data->build_compiler_options = base_pipeline->second->build_compiler_options; data->allocator = base_pipeline->second->allocator; data->vertex_shader = base_pipeline->second->vertex_shader; data->fragment_shader = base_pipeline->second->fragment_shader; @@ -203,6 +203,13 @@ namespace gl m_current_interpreter->flags |= CACHED_PIPE_RECOMPILING; build_program_async(opt, {}); } + + if (m_current_interpreter->flags & CACHED_PIPE_UNINITIALIZED) + { + m_current_interpreter->prog->sync(); + init_program(m_current_interpreter, m_current_interpreter->build_compiler_options); + } + return m_current_interpreter->prog.get(); } } @@ -491,7 +498,8 @@ namespace gl attach(*data->fragment_shader). link(); - post_init_hook(data, compiler_options); + init_program(data, compiler_options); + store_program(data, compiler_options); return data; } @@ -510,8 +518,11 @@ namespace gl auto storage_hook = [=, this](std::unique_ptr& prog) { + // NOTE: We need to do the program bindings in the consumer's context, so we skip the post-init hook and just set a flag + // The consumer will handle initializing the bindings + // The synchronization problem doesn't matter much on Windows driver, but Mesa drivers actually care about it. data->prog = std::move(prog); - post_init_hook(data, compiler_options); + store_program(data, compiler_options); if (callback) { @@ -528,7 +539,7 @@ namespace gl ); } - void shader_interpreter::post_init_hook(const std::shared_ptr& data, u64 compiler_options) + void shader_interpreter::init_program(const std::shared_ptr& data, u64 compiler_options) { data->prog->uniforms[0] = GL_STREAM_BUFFER_START + 0; data->prog->uniforms[1] = GL_STREAM_BUFFER_START + 1; @@ -551,6 +562,13 @@ namespace gl } } + data->flags &= ~CACHED_PIPE_UNINITIALIZED; + } + + void shader_interpreter::store_program(const std::shared_ptr& data, u64 compiler_options) + { + data->build_compiler_options = compiler_options; + std::lock_guard lock(m_program_cache_lock); m_program_cache[compiler_options] = data; } diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h index 015e46cef8..ccfcde30e1 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h @@ -1,6 +1,7 @@ #pragma once #include "glutils/program.h" #include "../Program/ProgramStateCache.h" +#include "../Program/ShaderInterpreter.h" #include "../Common/TextureUtils.h" #include @@ -64,7 +65,11 @@ namespace gl struct cached_program { - u32 flags = 0; + u32 flags = program_common::interpreter::CACHED_PIPE_UNINITIALIZED; + + // Compiler options mask - May not always match the storage compiler options in case of compatible pipelines + // However the storage mask must be a subset of this options mask + u32 build_compiler_options = 0; std::shared_ptr vertex_shader; std::shared_ptr fragment_shader; @@ -92,7 +97,8 @@ namespace gl std::shared_ptr build_program(u64 compiler_options); void build_program_async(u64 compiler_options, async_build_callback_t callback); - void post_init_hook(const std::shared_ptr& data, u64 compiler_options); + void init_program(const std::shared_ptr& data, u64 compiler_options); + void store_program(const std::shared_ptr& data, u64 compiler_options); std::shared_ptr m_current_interpreter; diff --git a/rpcs3/Emu/RSX/Program/ShaderInterpreter.h b/rpcs3/Emu/RSX/Program/ShaderInterpreter.h index e6a3282448..d08b6f00ad 100644 --- a/rpcs3/Emu/RSX/Program/ShaderInterpreter.h +++ b/rpcs3/Emu/RSX/Program/ShaderInterpreter.h @@ -47,8 +47,9 @@ namespace program_common enum cached_pipeline_flags : u32 { - CACHED_PIPE_UNOPTIMIZED = (1 << 0), - CACHED_PIPE_RECOMPILING = (1 << 1), + CACHED_PIPE_UNOPTIMIZED = (1 << 0), + CACHED_PIPE_RECOMPILING = (1 << 1), + CACHED_PIPE_UNINITIALIZED = (1 << 2), }; [[maybe_unused]] static std::string get_vertex_interpreter() From 2bb533274cb653b7876e80988681ab2d50a96e1a Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 14 Apr 2026 02:23:52 +0300 Subject: [PATCH 072/283] gl: Wait for shader compilation to complete server-side before attaching to a program --- rpcs3/Emu/RSX/GL/glutils/program.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rpcs3/Emu/RSX/GL/glutils/program.h b/rpcs3/Emu/RSX/GL/glutils/program.h index 90dcf6bf1d..3f6601be78 100644 --- a/rpcs3/Emu/RSX/GL/glutils/program.h +++ b/rpcs3/Emu/RSX/GL/glutils/program.h @@ -183,6 +183,12 @@ namespace gl program& attach(const shader& shader_) { + if (const auto& comp_fence = shader_.get_compile_fence_sync(); + !comp_fence.check_signaled()) + { + comp_fence.server_wait_sync(); + } + glAttachShader(m_id, shader_.id()); return *this; } From 58eb7d2c046f3fe1531eb70ebc4e836a719df13f Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 18 Apr 2026 20:36:00 +0300 Subject: [PATCH 073/283] gl: Implement bindless texture support --- rpcs3/Emu/RSX/GL/GLProcTable.h | 7 +++++++ rpcs3/Emu/RSX/GL/glutils/common.h | 1 + rpcs3/Emu/RSX/GL/glutils/image.cpp | 7 +++++++ rpcs3/Emu/RSX/GL/glutils/image.h | 29 +++++++++++++++++++++++++++-- rpcs3/Emu/RSX/GL/glutils/program.h | 6 +++++- 5 files changed, 47 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLProcTable.h b/rpcs3/Emu/RSX/GL/GLProcTable.h index ad8943bd4f..b4a9c1c289 100644 --- a/rpcs3/Emu/RSX/GL/GLProcTable.h +++ b/rpcs3/Emu/RSX/GL/GLProcTable.h @@ -284,6 +284,13 @@ OPENGL_PROC(PFNGLTEXTUREBARRIERNVPROC, TextureBarrierNV); // Memory barrier OPENGL_PROC(PFNGLMEMORYBARRIERPROC, MemoryBarrier); +// Bindless texture +OPENGL_PROC(PFNGLGETTEXTUREHANDLEARBPROC, GetTextureHandleARB); +OPENGL_PROC(PFNGLMAKETEXTUREHANDLERESIDENTARBPROC, MakeTextureHandleResidentARB); +OPENGL_PROC(PFNGLMAKETEXTUREHANDLENONRESIDENTARBPROC, MakeTextureHandleNonResidentARB); +OPENGL_PROC(PFNGLPROGRAMUNIFORMHANDLEUI64ARBPROC, ProgramUniformHandleui64ARB); +OPENGL_PROC(PFNGLPROGRAMUNIFORMHANDLEUI64VARBPROC, ProgramUniformHandleui64vARB); + // ARB_compute_shader OPENGL_PROC(PFNGLDISPATCHCOMPUTEPROC, DispatchCompute); diff --git a/rpcs3/Emu/RSX/GL/glutils/common.h b/rpcs3/Emu/RSX/GL/glutils/common.h index 12f54b794a..326a79b9e8 100644 --- a/rpcs3/Emu/RSX/GL/glutils/common.h +++ b/rpcs3/Emu/RSX/GL/glutils/common.h @@ -53,6 +53,7 @@ namespace gl { using flags32_t = u32; using handle32_t = u32; + using handle64_t = u64; template class save_binding_state_base diff --git a/rpcs3/Emu/RSX/GL/glutils/image.cpp b/rpcs3/Emu/RSX/GL/glutils/image.cpp index e2f02afdfa..86dec13ee6 100644 --- a/rpcs3/Emu/RSX/GL/glutils/image.cpp +++ b/rpcs3/Emu/RSX/GL/glutils/image.cpp @@ -219,6 +219,13 @@ namespace gl texture::~texture() { gl::get_command_context()->unbind_texture(static_cast(m_target), m_id); + + if (m_handle64) + { + glMakeTextureHandleNonResidentARB(m_handle64); + m_handle64 = GL_NONE; + } + glDeleteTextures(1, &m_id); m_id = GL_NONE; } diff --git a/rpcs3/Emu/RSX/GL/glutils/image.h b/rpcs3/Emu/RSX/GL/glutils/image.h index bd974c226a..d01112bade 100644 --- a/rpcs3/Emu/RSX/GL/glutils/image.h +++ b/rpcs3/Emu/RSX/GL/glutils/image.h @@ -59,7 +59,28 @@ namespace gl GLuint num_layers; }; - class texture : public named_object + template + struct bindless_texture_t : public named_object + { + handle64_t handle() const + { + if (m_handle64) + { + return m_handle64; + } + + ensure(gl::get_driver_caps().ARB_bindless_texture_supported, "Bindless handles are not supported on this device."); + m_handle64 = ensure(glGetTextureHandleARB(m_id), "Failed to get image handle from OpenGL driver."); + glMakeTextureHandleResidentARB(m_handle64); + return m_handle64; + } + + protected: + using named_object::m_id; + mutable GLuint64 m_handle64 = GL_NONE; + }; + + class texture : public bindless_texture_t { friend class texture_view; @@ -181,6 +202,8 @@ namespace gl }; protected: + mutable GLuint64 m_handle64 = GL_NONE; + GLuint m_width = 0; GLuint m_height = 0; GLuint m_depth = 0; @@ -345,9 +368,11 @@ namespace gl } }; - class texture_view : public named_object + class texture_view : public bindless_texture_t { protected: + mutable GLuint64 m_handle64 = GL_NONE; + GLenum m_target = 0; GLenum m_format = 0; GLenum m_view_format = 0; diff --git a/rpcs3/Emu/RSX/GL/glutils/program.h b/rpcs3/Emu/RSX/GL/glutils/program.h index 3f6601be78..0568bc5547 100644 --- a/rpcs3/Emu/RSX/GL/glutils/program.h +++ b/rpcs3/Emu/RSX/GL/glutils/program.h @@ -7,6 +7,8 @@ #include "Utilities/geometry.h" #include "Utilities/mutex.h" +#include + namespace gl { namespace glsl @@ -91,6 +93,7 @@ namespace gl void operator = (unsigned rhs) const { glProgramUniform1ui(m_program.id(), location(), rhs); } void operator = (float rhs) const { glProgramUniform1f(m_program.id(), location(), rhs); } void operator = (bool rhs) const { glProgramUniform1ui(m_program.id(), location(), rhs ? 1 : 0); } + void operator = (handle64_t rhs) const { glProgramUniformHandleui64ARB(m_program.id(), location(), rhs); } void operator = (const color1i& rhs) const { glProgramUniform1i(m_program.id(), location(), rhs.r); } void operator = (const color1f& rhs) const { glProgramUniform1f(m_program.id(), location(), rhs.r); } void operator = (const color2i& rhs) const { glProgramUniform2i(m_program.id(), location(), rhs.r, rhs.g); } @@ -101,7 +104,8 @@ namespace gl void operator = (const color4f& rhs) const { glProgramUniform4f(m_program.id(), location(), rhs.r, rhs.g, rhs.b, rhs.a); } void operator = (const areaf& rhs) const { glProgramUniform4f(m_program.id(), location(), rhs.x1, rhs.y1, rhs.x2, rhs.y2); } void operator = (const areai& rhs) const { glProgramUniform4i(m_program.id(), location(), rhs.x1, rhs.y1, rhs.x2, rhs.y2); } - void operator = (const std::vector& rhs) const { glProgramUniform1iv(m_program.id(), location(), ::size32(rhs), rhs.data()); } + void operator = (const std::span& rhs) const { glProgramUniform1iv(m_program.id(), location(), ::size32(rhs), rhs.data()); } + void operator = (const std::span& rhs) const { glProgramUniformHandleui64vARB(m_program.id(), location(), ::size32(rhs), rhs.data()); } }; class uniforms_t From 9c143d3d4512042f27a23f3a07b0381ef160c606 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 18 Apr 2026 20:36:15 +0300 Subject: [PATCH 074/283] gl: Use bindless textures for the shader interpreter --- rpcs3/Emu/RSX/GL/GLDraw.cpp | 87 ++++++++++ rpcs3/Emu/RSX/GL/GLGSRender.cpp | 19 +-- rpcs3/Emu/RSX/GL/GLGSRender.h | 3 +- rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp | 205 +++-------------------- rpcs3/Emu/RSX/GL/GLShaderInterpreter.h | 42 ++++- 5 files changed, 155 insertions(+), 201 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLDraw.cpp b/rpcs3/Emu/RSX/GL/GLDraw.cpp index 0abf0111e6..c57eba811e 100644 --- a/rpcs3/Emu/RSX/GL/GLDraw.cpp +++ b/rpcs3/Emu/RSX/GL/GLDraw.cpp @@ -545,6 +545,93 @@ void GLGSRender::bind_texture_env() } } +void GLGSRender::bind_interpreter_texture_env() +{ + // Bind textures and resolve external copy operations + gl::command_context cmd{ gl_state }; + const bool is_interpreter = m_shader_interpreter.is_interpreter(m_program); + + for (u32 textures_ref = current_fp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i) + { + if (!(textures_ref & 1)) + { + continue; + } + + gl::texture_view* primary_view = nullptr; + gl::texture_view* stencil_mirror = nullptr; + auto sampler_state = static_cast(fs_sampler_state[i].get()); + + if (rsx::method_registers.fragment_textures[i].enabled() && + sampler_state->validate()) + { + if (primary_view = sampler_state->image_handle; !primary_view) [[unlikely]] + { + primary_view = m_gl_texture_cache.create_temporary_subresource(cmd, sampler_state->external_subresource_desc); + } + } + + if (!primary_view) + { + const auto target = gl::get_target(current_fragment_program.get_texture_dimension(i)); + primary_view = m_null_textures[target]->get_view(rsx::default_remap_vector); + stencil_mirror = primary_view; + } + else if (current_fragment_program.texture_state.redirected_textures & (1 << i)) + { + auto root_texture = static_cast(primary_view->image()); + stencil_mirror = root_texture->get_view(rsx::default_remap_vector.with_encoding(gl::GL_REMAP_IDENTITY), gl::image_aspect::stencil); + } + + if (is_interpreter) [[ unlikely ]] + { + m_shader_interpreter.bind_fragment_texture(i, primary_view->handle(), *sampler_state); + continue; + } + + primary_view->bind(cmd, GL_FRAGMENT_TEXTURES_START + i); + + if (stencil_mirror) + { + stencil_mirror->bind(cmd, GL_STENCIL_MIRRORS_START + i); + } + } + + for (u32 textures_ref = current_vp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i) + { + if (!(textures_ref & 1)) + { + continue; + } + + auto sampler_state = static_cast(vs_sampler_state[i].get()); + gl::texture_view* view = nullptr; + + if (rsx::method_registers.vertex_textures[i].enabled() && + sampler_state->validate()) + { + if (view = sampler_state->image_handle; !view) + { + view = m_gl_texture_cache.create_temporary_subresource(cmd, sampler_state->external_subresource_desc); + } + } + + if (view) [[likely]] + { + view->bind(cmd, GL_VERTEX_TEXTURES_START + i); + } + else + { + cmd->bind_texture(GL_VERTEX_TEXTURES_START + i, GL_TEXTURE_2D, GL_NONE); + } + } + + if (is_interpreter) + { + m_shader_interpreter.flush_texture_bindings(); + } +} + void GLGSRender::emit_geometry(u32 sub_index) { const auto do_heap_cleanup = [this]() diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index e32759d181..15226ab485 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -176,8 +176,7 @@ void GLGSRender::on_init_thread() rsx_log.warning("Texture barriers are not supported by your GPU. Feedback loops will have undefined results."); } - // NOTE: We currently aren't using the bindless version of the interpreter - if (false) //!gl_caps.ARB_bindless_texture_supported) + if (!gl_caps.ARB_bindless_texture_supported) { switch (shadermode) { @@ -253,19 +252,19 @@ void GLGSRender::on_init_thread() const rsx::io_buffer src_buf = std::span(pixeldata); // 1D - auto tex1D = std::make_unique(GL_TEXTURE_1D, 1, 1, 1, 1, 1, GL_RGBA8, RSX_FORMAT_CLASS_COLOR); + auto tex1D = std::make_unique(GL_TEXTURE_1D, 1, 1, 1, 1, 1, GL_RGBA8, RSX_FORMAT_CLASS_COLOR); tex1D->copy_from(src_buf, gl::texture::format::rgba, gl::texture::type::uint_8_8_8_8, {}); // 2D - auto tex2D = std::make_unique(GL_TEXTURE_2D, 1, 1, 1, 1, 1, GL_RGBA8, RSX_FORMAT_CLASS_COLOR); + auto tex2D = std::make_unique(GL_TEXTURE_2D, 1, 1, 1, 1, 1, GL_RGBA8, RSX_FORMAT_CLASS_COLOR); tex2D->copy_from(src_buf, gl::texture::format::rgba, gl::texture::type::uint_8_8_8_8, {}); // 3D - auto tex3D = std::make_unique(GL_TEXTURE_3D, 1, 1, 1, 1, 1, GL_RGBA8, RSX_FORMAT_CLASS_COLOR); + auto tex3D = std::make_unique(GL_TEXTURE_3D, 1, 1, 1, 1, 1, GL_RGBA8, RSX_FORMAT_CLASS_COLOR); tex3D->copy_from(src_buf, gl::texture::format::rgba, gl::texture::type::uint_8_8_8_8, {}); // CUBE - auto texCUBE = std::make_unique(GL_TEXTURE_CUBE_MAP, 1, 1, 1, 1, 1, GL_RGBA8, RSX_FORMAT_CLASS_COLOR); + auto texCUBE = std::make_unique(GL_TEXTURE_CUBE_MAP, 1, 1, 1, 1, 1, GL_RGBA8, RSX_FORMAT_CLASS_COLOR); texCUBE->copy_from(src_buf, gl::texture::format::rgba, gl::texture::type::uint_8_8_8_8, {}); m_null_textures[GL_TEXTURE_1D] = std::move(tex1D); @@ -1066,7 +1065,7 @@ void GLGSRender::load_program_env() if (m_interpreter_state & rsx::fragment_program_dirty) { // Attach fragment buffer data - const auto fp_block_length = current_fp_metadata.program_ucode_length + 80; + const auto fp_block_length = current_fp_metadata.program_ucode_length + 16; auto fp_mapping = m_fragment_instructions_buffer->alloc_from_heap(fp_block_length, 16); auto fp_buf = static_cast(fp_mapping.first); @@ -1074,11 +1073,9 @@ void GLGSRender::load_program_env() const auto control_masks = reinterpret_cast(fp_buf); control_masks[0] = rsx::method_registers.shader_control(); control_masks[1] = current_fragment_program.texture_state.texture_dimensions; + control_masks[2] = current_fp_metadata.referenced_textures_mask; - // Bind textures - m_shader_interpreter.update_fragment_textures(fs_sampler_state, current_fp_metadata.referenced_textures_mask, reinterpret_cast(fp_buf + 16)); - - std::memcpy(fp_buf + 80, current_fragment_program.get_data(), current_fragment_program.ucode_length); + std::memcpy(fp_buf + 16, current_fragment_program.get_data(), current_fragment_program.ucode_length); m_fragment_instructions_buffer->bind_range(GL_INTERPRETER_FRAGMENT_BLOCK, fp_mapping.second, fp_block_length); m_fragment_instructions_buffer->notify(); diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.h b/rpcs3/Emu/RSX/GL/GLGSRender.h index 779519fee7..330838f804 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.h +++ b/rpcs3/Emu/RSX/GL/GLGSRender.h @@ -147,7 +147,7 @@ class GLGSRender : public GSRender, public ::rsx::reports::ZCULL_control shared_mutex m_sampler_mutex; atomic_t m_samplers_dirty = {true}; - std::unordered_map> m_null_textures; + std::unordered_map> m_null_textures; rsx::simple_array m_scratch_buffer; // Occlusion query type, can be SAMPLES_PASSED or ANY_SAMPLES_PASSED @@ -183,6 +183,7 @@ private: void load_texture_env(); void bind_texture_env(); + void bind_interpreter_texture_env(); gl::texture* get_present_source(gl::present_surface_info* info, const rsx::avconf& avconfig); diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index fc575ffc5c..b3c382e657 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -15,39 +15,6 @@ namespace gl using enum program_common::interpreter::compiler_option; using enum program_common::interpreter::cached_pipeline_flags; - namespace interpreter - { - void texture_pool_allocator::create(::glsl::program_domain domain) - { - GLenum pname; - switch (domain) - { - default: - rsx_log.fatal("Unexpected program domain %d", static_cast(domain)); - [[fallthrough]]; - case ::glsl::program_domain::glsl_vertex_program: - pname = GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS; break; - case ::glsl::program_domain::glsl_fragment_program: - pname = GL_MAX_TEXTURE_IMAGE_UNITS; break; - } - - glGetIntegerv(pname, &max_image_units); - } - - void texture_pool_allocator::allocate(int size) - { - if ((used + size) > max_image_units) - { - rsx_log.fatal("Out of image binding slots!"); - } - - used += size; - texture_pool pool; - pool.pool_size = size; - pools.push_back(pool); - } - } - void shader_interpreter::create(rsx::shader_loading_dialog* dlg) { dlg->create("Precompiling interpreter variants.\nPlease wait...", "Shader Compilation"); @@ -109,7 +76,6 @@ namespace gl auto data = new interpreter::cached_program(); data->flags = base_pipeline->second->flags | CACHED_PIPE_UNOPTIMIZED; data->build_compiler_options = base_pipeline->second->build_compiler_options; - data->allocator = base_pipeline->second->allocator; data->vertex_shader = base_pipeline->second->vertex_shader; data->fragment_shader = base_pipeline->second->fragment_shader; data->prog = base_pipeline->second->prog; @@ -308,42 +274,6 @@ namespace gl void shader_interpreter::build_fs(u64 compiler_options, interpreter::cached_program& prog_data) { - // Allocate TIUs - auto& allocator = prog_data.allocator; - if (compiler_options & COMPILER_OPT_ENABLE_TEXTURES) - { - allocator.create(::glsl::program_domain::glsl_fragment_program); - if (allocator.max_image_units >= 32) - { - // 16 + 4 + 4 + 4 - allocator.allocate(4); // 1D - allocator.allocate(16); // 2D - allocator.allocate(4); // CUBE - allocator.allocate(4); // 3D - } - else if (allocator.max_image_units >= 24) - { - // 16 + 4 + 2 + 2 - allocator.allocate(2); // 1D - allocator.allocate(16); // 2D - allocator.allocate(2); // CUBE - allocator.allocate(4); // 3D - } - else if (allocator.max_image_units >= 16) - { - // 10 + 2 + 2 + 2 - allocator.allocate(2); // 1D - allocator.allocate(10); // 2D - allocator.allocate(2); // CUBE - allocator.allocate(2); // 3D - } - else - { - // Unusable - rsx_log.fatal("Failed to allocate enough TIUs for shader interpreter."); - } - } - // Cache lookup compiler_options &= COMPILER_OPT_ALL_FS_MASK; { @@ -366,7 +296,7 @@ namespace gl std::stringstream builder; builder << "#version 450\n" - "//#extension GL_ARB_bindless_texture : require\n\n"; + "#extension GL_ARB_bindless_texture : require\n\n"; ::glsl::insert_subheader_block(builder); comp.insertConstants(builder); @@ -438,17 +368,17 @@ namespace gl const char* type_names[] = { "sampler1D", "sampler2D", "samplerCube", "sampler3D" }; for (int i = 0; i < 4; ++i) { - builder << "uniform " << type_names[i] << " " << type_names[i] << "_array[" << allocator.pools[i].pool_size << "];\n"; + builder << "layout(bindless_sampler) uniform " << type_names[i] << " " << type_names[i] << "_array[16];\n"; } builder << "\n" "#undef TEX_PARAM\n" "#define TEX_PARAM(index) texture_parameters[index + texture_base_index]\n" - "#define IS_TEXTURE_RESIDENT(index) (texture_handles[index] < 0xFF)\n" - "#define SAMPLER1D(index) sampler1D_array[texture_handles[index]]\n" - "#define SAMPLER2D(index) sampler2D_array[texture_handles[index]]\n" - "#define SAMPLER3D(index) sampler3D_array[texture_handles[index]]\n" - "#define SAMPLERCUBE(index) samplerCube_array[texture_handles[index]]\n\n"; + "#define IS_TEXTURE_RESIDENT(index) TEST_BIT(textures_resident, int(index))\n" + "#define SAMPLER1D(index) sampler1D_array[index]\n" + "#define SAMPLER2D(index) sampler2D_array[index]\n" + "#define SAMPLER3D(index) sampler3D_array[index]\n" + "#define SAMPLERCUBE(index) samplerCube_array[index]\n\n"; } else if (compiler_options) { @@ -460,9 +390,8 @@ namespace gl "{\n" " uint shader_control;\n" " uint texture_control;\n" - " uint reserved1;\n" + " uint textures_resident;\n" " uint reserved2;\n" - " uint texture_handles[16];\n" " uvec4 fp_instructions[];\n" "};\n\n"; @@ -547,19 +476,7 @@ namespace gl if (compiler_options & COMPILER_OPT_ENABLE_TEXTURES) { // Initialize texture bindings - int assigned = 0; - auto& allocator = data->allocator; - const char* type_names[] = { "sampler1D_array", "sampler2D_array", "samplerCube_array", "sampler3D_array" }; - - for (int i = 0; i < 4; ++i) - { - for (int j = 0; j < allocator.pools[i].pool_size; ++j) - { - allocator.pools[i].allocate(assigned++); - } - - data->prog->uniforms[type_names[i]] = allocator.pools[i].allocated; - } + flush_texture_bindings(data->prog.get()); } data->flags &= ~CACHED_PIPE_UNINITIALIZED; @@ -578,99 +495,27 @@ namespace gl return (m_current_interpreter && program == m_current_interpreter->prog.get()); } - void shader_interpreter::update_fragment_textures( - const std::array, 16>& descriptors, - u16 reference_mask, u32* out) + void shader_interpreter::bind_fragment_texture(int i, handle64_t handle, const rsx::sampled_image_descriptor_base& descriptor) { - if (reference_mask == 0 || !m_current_interpreter) + m_texture_bindings.get(descriptor.image_type)[i] = handle; + } + + void shader_interpreter::flush_texture_bindings(glsl::program* program) + { + using enum rsx::texture_dimension_extended; + + if (!program) { - return; + ensure(m_current_interpreter); + program = m_current_interpreter->prog.get(); } - // Reset allocation - auto& allocator = m_current_interpreter->allocator; - for (unsigned i = 0; i < 4; ++i) + const char* type_names[] = { "sampler1D_array", "sampler2D_array", "samplerCube_array", "sampler3D_array" }; + const rsx::texture_dimension_extended types[] = { texture_dimension_1d, texture_dimension_2d, texture_dimension_cubemap, texture_dimension_3d }; + + for (int i = 0; i < 4; ++i) { - allocator.pools[i].num_used = 0; - allocator.pools[i].flags = 0; + program->uniforms[type_names[i]] = m_texture_bindings.get(types[i]); } - - rsx::simple_array> replacement_map; - for (int i = 0; i < rsx::limits::fragment_textures_count; ++i) - { - if (reference_mask & (1 << i)) - { - auto sampler_state = static_cast(descriptors[i].get()); - ensure(sampler_state); - - int pool_id = static_cast(sampler_state->image_type); - auto& pool = allocator.pools[pool_id]; - - const int old = pool.allocated[pool.num_used]; - if (!pool.allocate(i)) - { - rsx_log.error("Could not allocate texture resource for shader interpreter."); - break; - } - - out[i] = (pool.num_used - 1); - if (old != i) - { - // Check if the candidate target has also been replaced - bool found = false; - for (auto& e : replacement_map) - { - if (e.second == old) - { - // This replacement consumed this 'old' value - e.second = i; - found = true; - break; - } - } - - if (!found) - { - replacement_map.push_back({ old, i }); - } - } - } - else - { - out[i] = 0xFF; - } - } - - // Bind TIU locations - if (replacement_map.empty()) [[likely]] - { - return; - } - - // Overlapping texture bindings are trouble. Cannot bind one TIU to two types of samplers simultaneously - for (unsigned i = 0; i < replacement_map.size(); ++i) - { - for (int j = 0; j < 4; ++j) - { - auto& pool = allocator.pools[j]; - for (int k = pool.num_used; k < pool.pool_size; ++k) - { - if (pool.allocated[k] == replacement_map[i].second) - { - pool.allocated[k] = replacement_map[i].first; - pool.flags |= static_cast(interpreter::texture_pool_flags::dirty); - - // Exit nested loop - j = 4; - break; - } - } - } - } - - if (allocator.pools[0].flags) m_current_interpreter->prog->uniforms["sampler1D_array"] = allocator.pools[0].allocated; - if (allocator.pools[1].flags) m_current_interpreter->prog->uniforms["sampler2D_array"] = allocator.pools[1].allocated; - if (allocator.pools[2].flags) m_current_interpreter->prog->uniforms["samplerCube_array"] = allocator.pools[2].allocated; - if (allocator.pools[3].flags) m_current_interpreter->prog->uniforms["sampler3D_array"] = allocator.pools[3].allocated; } } diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h index ccfcde30e1..437cbb43cf 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h @@ -53,14 +53,29 @@ namespace gl } }; - struct texture_pool_allocator + struct bindless_textures_t { - int max_image_units = 0; - int used = 0; - std::vector pools; + std::array sampler1D; + std::array sampler2D; + std::array sampler3D; + std::array samplerCUBE; - void create(::glsl::program_domain domain); - void allocate(int size); + std::span get(rsx::texture_dimension_extended type) + { + using enum rsx::texture_dimension_extended; + switch (type) + { + default: + case texture_dimension_2d: + return sampler2D; + case texture_dimension_cubemap: + return samplerCUBE; + case texture_dimension_1d: + return sampler1D; + case texture_dimension_3d: + return sampler3D; + } + } }; struct cached_program @@ -69,12 +84,11 @@ namespace gl // Compiler options mask - May not always match the storage compiler options in case of compatible pipelines // However the storage mask must be a subset of this options mask - u32 build_compiler_options = 0; + u64 build_compiler_options = 0; std::shared_ptr vertex_shader; std::shared_ptr fragment_shader; std::shared_ptr prog; - texture_pool_allocator allocator; }; } @@ -92,6 +106,9 @@ namespace gl shader_cache_t m_fs_cache; pipeline_cache_t m_program_cache; + // Texture binding information. + interpreter::bindless_textures_t m_texture_bindings{}; + void build_vs(u64 compiler_options, interpreter::cached_program& prog_data); void build_fs(u64 compiler_options, interpreter::cached_program& prog_data); @@ -103,10 +120,17 @@ namespace gl std::shared_ptr m_current_interpreter; public: + shader_interpreter() + { + std::memset(&m_texture_bindings, 0, sizeof(m_texture_bindings)); + } + void create(rsx::shader_loading_dialog* dlg); void destroy(); - void update_fragment_textures(const std::array, 16>& descriptors, u16 reference_mask, u32* out); + // Update texture bindings based on the incoming descriptor structures + void bind_fragment_texture(int i, handle64_t handle, const rsx::sampled_image_descriptor_base& descriptor); + void flush_texture_bindings(glsl::program* program = nullptr); glsl::program* get(const interpreter::program_metadata& fp_metadata, u32 vp_ctrl, u32 fp_ctrl); bool is_interpreter(const glsl::program* program) const; From 4f47fee36039f38449830eccf10e72282d680f1a Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 18 Apr 2026 21:12:37 +0300 Subject: [PATCH 075/283] gl: Dirty tracking of interpreter sampler handles --- rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp | 36 +++++++++++++++++++++++- rpcs3/Emu/RSX/GL/GLShaderInterpreter.h | 2 ++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index b3c382e657..aac9a9eabf 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -151,6 +151,8 @@ namespace gl if (rsx::method_registers.polygon_stipple_enabled()) opt |= COMPILER_OPT_ENABLE_STIPPLING; if (vp_ctrl & RSX_SHADER_CONTROL_INSTANCED_CONSTANTS) opt |= COMPILER_OPT_ENABLE_INSTANCING; + auto previous = m_current_interpreter ? m_current_interpreter.get() : nullptr; + m_current_interpreter.reset(); { reader_lock lock(m_program_cache_lock); if (auto it = m_program_cache.find(opt); it != m_program_cache.end()) [[likely]] @@ -158,6 +160,12 @@ namespace gl m_current_interpreter = it->second; } + if (!m_current_interpreter || m_current_interpreter.get() != previous) + { + // Rebind all textures + m_texture_bindings.dirty = 0xff; + } + if (m_current_interpreter) { constexpr u32 test_mask = (CACHED_PIPE_UNOPTIMIZED | CACHED_PIPE_RECOMPILING); @@ -497,7 +505,12 @@ namespace gl void shader_interpreter::bind_fragment_texture(int i, handle64_t handle, const rsx::sampled_image_descriptor_base& descriptor) { - m_texture_bindings.get(descriptor.image_type)[i] = handle; + auto& bound_handle = m_texture_bindings.get(descriptor.image_type)[i]; + if (bound_handle != handle) + { + m_texture_bindings.dirty |= 1u << static_cast(descriptor.image_type); + bound_handle = handle; + } } void shader_interpreter::flush_texture_bindings(glsl::program* program) @@ -510,12 +523,33 @@ namespace gl program = m_current_interpreter->prog.get(); } + const bool is_bound_interpreter = m_current_interpreter && m_current_interpreter->prog.get() == program; + const u32 dirty_mask = is_bound_interpreter + ? 0xff + : m_texture_bindings.dirty; + + if (!dirty_mask) + { + return; + } + const char* type_names[] = { "sampler1D_array", "sampler2D_array", "samplerCube_array", "sampler3D_array" }; const rsx::texture_dimension_extended types[] = { texture_dimension_1d, texture_dimension_2d, texture_dimension_cubemap, texture_dimension_3d }; for (int i = 0; i < 4; ++i) { + const auto type_mask = (1u << static_cast(types[i])); + if ((dirty_mask & type_mask) == 0) + { + continue; + } + program->uniforms[type_names[i]] = m_texture_bindings.get(types[i]); } + + if (is_bound_interpreter) + { + m_texture_bindings.dirty = 0; + } } } diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h index 437cbb43cf..cb33df0249 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h @@ -60,6 +60,8 @@ namespace gl std::array sampler3D; std::array samplerCUBE; + u8 dirty = 0xff; + std::span get(rsx::texture_dimension_extended type) { using enum rsx::texture_dimension_extended; From 026297334f28500c8cbf6a5f48f395196700d6d7 Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Thu, 7 May 2026 05:38:57 +0200 Subject: [PATCH 076/283] Some fixes (#18686) ### FIXES + MINOR IMPROVEMENTS: - Fixed issue18685: Not a real issue. PR18648 added some logging that made evident a missing check on the validity of the paths in `games.yml` before trying to manage the path as a possible ISO file. That missing check was already present but simply invisible due to missing log - Fixed `bytes_to_hex()` function used to store file hash hex string: In some cases the integrity check wrongly reported the check as failed - Optimized `fs::get_optical_raw_device()` function: Moved under `_WIN32` block a check valid only on `Windows` and simplified the logic to detect a CDROM / BD as raw device - Used `iso_file` objects instead of `fs:file` in `ISO.cpp`: It allows to use proper `bool()` operator on some checks --- Utilities/File.cpp | 42 +++++++----------------- rpcs3/Crypto/utils.cpp | 8 +++++ rpcs3/Loader/ISO.cpp | 29 ++++++++-------- rpcs3/Loader/ISO.h | 5 +-- rpcs3/rpcs3qt/game_list_context_menu.cpp | 3 +- 5 files changed, 39 insertions(+), 48 deletions(-) diff --git a/Utilities/File.cpp b/Utilities/File.cpp index da29fba7bd..20e25794d2 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -1125,13 +1125,6 @@ bool fs::is_optical_raw_device([[maybe_unused]] const std::string& path) bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device) { - // Skip a useless check to detect an optical raw device if navigating on subfolders (e.g. C:/subfolder_1/subfolder_2/), it means we are on a hdd/ssd. - // A path for an optical drive should include only the drive letter (e.g. E:/) - if (path.find_first_of(":") != path.find_last_not_of(delim)) - { - return false; - } - if (fs::is_optical_raw_device(path)) { if (raw_device) @@ -1143,38 +1136,27 @@ bool fs::get_optical_raw_device(const std::string& path, std::string* raw_device } #ifdef _WIN32 - constexpr u32 BUF_SIZE = 1000; - WCHAR drive_list[BUF_SIZE] = {0}; + // Skip a useless check to detect an optical raw device if navigating on subfolders (e.g. C:\subfolder_1\subfolder_2\), + // it means we are on a HDD/SSD. A path for an optical drive should include only the drive letter (e.g. E:\) + const size_t drive_delim_pos = path.find_first_of(":"); - // GetLogicalDriveStrings() returns a double-null terminated list of null-terminated strings. - // E.g. A:\B:\C:\ - const DWORD copied = GetLogicalDriveStrings(BUF_SIZE, drive_list); - - if (copied == 0 || copied > BUF_SIZE) + if (drive_delim_pos != 1 || drive_delim_pos != path.find_last_not_of(delim)) { return false; } - for (const WCHAR* drive = drive_list; drive && *drive; drive += wcslen(drive) + 1) + const std::string drive_letter = path.substr(0, drive_delim_pos + 1); // e.g. "E:" + const std::string drive_path = drive_letter + "\\"; // e.g. "E:\" + + if (GetDriveTypeA(drive_path.c_str()) == DRIVE_CDROM) { - if (GetDriveType(drive) == DRIVE_CDROM) + if (raw_device) { - const std::wstring ws(drive); - const std::string s = std::string(ws.begin(), ws.end() - 1); - - if (path.starts_with(s)) - { - if (raw_device) - { - *raw_device = "\\\\.\\" + s; - } - - return true; - } + *raw_device = "\\\\.\\" + drive_letter; } - } - return false; + return true; + } #endif return false; } diff --git a/rpcs3/Crypto/utils.cpp b/rpcs3/Crypto/utils.cpp index 51ad284c62..7c1d1df309 100644 --- a/rpcs3/Crypto/utils.cpp +++ b/rpcs3/Crypto/utils.cpp @@ -37,6 +37,14 @@ void bytes_to_hex(std::string& hex_str, const unsigned char* data, unsigned int { fmt::throw_exception("Failed to read bytes: %s", std::make_error_code(err).message()); } + + // Padding handling for values ​​< 0x10 (e.g. 0x05 becomes "5" instead of "05") + // If to_chars only writes 1 character, we move to the right and put '0' + if (ptr == &hex_str[i] + 1) + { + hex_str[i + 1] = hex_str[i]; + hex_str[i] = '0'; + } } } diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index 175e2fde1d..cfd83863ef 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -54,7 +54,7 @@ static void* get_aligned_buf() return s_aligned_buf.buf; } -static bool is_iso_file(const fs::file& file, u64* size = nullptr) +static bool is_iso_file(iso_file& file, u64* size = nullptr) { if (!file || file.size() < 32768ULL + 6) { @@ -87,7 +87,7 @@ bool is_iso_file(const std::string& path, u64* size, bool* is_raw_device) // "new_path" is updated with the raw device path in case "path" points to a BD drive const bool raw_device = fs::get_optical_raw_device(path, &new_path); - if (!raw_device && fs::is_dir(path)) + if (!raw_device && !fs::is_file(path)) { return false; } @@ -97,7 +97,7 @@ bool is_iso_file(const std::string& path, u64* size, bool* is_raw_device) *is_raw_device = raw_device; } - fs::file file(std::make_unique(new_path, fs::read)); + iso_file file(new_path); return is_iso_file(file, size); } @@ -337,7 +337,7 @@ iso_type_status iso_file_decryption::retrieve_key(iso_archive& archive, std::str return iso_type_status::ERROR_OPENING_KEY; } -iso_type_status iso_file_decryption::check_type(const std::string& path, std::string& key_path, aes_context* aes_ctx) +iso_type_status iso_file_decryption::check_type(const std::string& path, std::string* key_path, aes_context* aes_ctx) { if (!is_iso_file(path)) { @@ -363,8 +363,12 @@ iso_type_status iso_file_decryption::check_type(const std::string& path, std::st { if (fs::is_file(path)) { - key_path = path; - return get_key(key_path, aes_ctx); + if (key_path) + { + *key_path = path; + } + + return get_key(path, aes_ctx); } } @@ -381,7 +385,7 @@ bool iso_file_decryption::init(const std::string& path, iso_archive* archive) // Store the ISO region information (needed by both the "Redump" type (only on "decrypt()" method) and "3k3y" type) // - fs::file iso_file(std::make_unique(path, fs::read)); + iso_file iso_file(path); if (!is_iso_file(iso_file)) { @@ -390,7 +394,7 @@ bool iso_file_decryption::init(const std::string& path, iso_archive* archive) } // Reset the file position after it was changed by is_iso_file() - iso_file.seek(0); + iso_file.seek(0, fs::seek_set); std::array sec0_sec1; @@ -447,7 +451,7 @@ bool iso_file_decryption::init(const std::string& path, iso_archive* archive) else { // Try to detect the Redump type. If so, the decryption context is set into "m_aes_dec" - status = check_type(path, key_path, &m_aes_dec); + status = check_type(path, &key_path, &m_aes_dec); } switch (status) @@ -968,17 +972,14 @@ iso_archive::iso_archive(const std::string& path) // "m_path" is updated with the raw device path in case "path" points to a BD drive fs::get_optical_raw_device(path, &m_path); - fs::file iso_file(std::make_unique(m_path, fs::read)); - - if (!iso_file || !is_iso_file(iso_file)) + if (!is_iso_file(m_path)) { // Not ISO... TODO: throw something? iso_log.error("iso_archive: Failed to recognize ISO file: '%s'", path); return; } - // Reset the file position after it was changed by is_iso_file() - iso_file.seek(0); + fs::file iso_file(std::make_unique(m_path)); u8 descriptor_type = -2; bool use_ucs2_decoding = false; diff --git a/rpcs3/Loader/ISO.h b/rpcs3/Loader/ISO.h index 7287146a2f..0caf53f6a9 100644 --- a/rpcs3/Loader/ISO.h +++ b/rpcs3/Loader/ISO.h @@ -71,7 +71,7 @@ private: static iso_type_status retrieve_key(iso_archive& archive, std::string& key_path, aes_context& aes_ctx); public: - static iso_type_status check_type(const std::string& path, std::string& key_path, aes_context* aes_ctx = nullptr); + static iso_type_status check_type(const std::string& path, std::string* key_path = nullptr, aes_context* aes_ctx = nullptr); iso_encryption_type get_enc_type() const { return m_enc_type; } @@ -116,7 +116,7 @@ protected: u64 file_offset(u64 pos) const; public: - iso_file(const std::string& path, bs_t mode); + iso_file(const std::string& path, bs_t mode = fs::read); iso_file(const std::string& path, bs_t mode, const iso_fs_node& node); explicit operator bool() const { return m_file.operator bool(); } @@ -173,6 +173,7 @@ public: const std::string& path() const { return m_path; } const iso_fs_node& root() const { return m_root; } + iso_fs_node* retrieve(const std::string& path); bool exists(const std::string& path); bool is_file(const std::string& path); diff --git a/rpcs3/rpcs3qt/game_list_context_menu.cpp b/rpcs3/rpcs3qt/game_list_context_menu.cpp index eb56a76ab1..402286e2f1 100644 --- a/rpcs3/rpcs3qt/game_list_context_menu.cpp +++ b/rpcs3/rpcs3qt/game_list_context_menu.cpp @@ -605,8 +605,7 @@ void game_list_context_menu::show_single_selection_context_menu(const game_info& // Check integrity if (QString::fromStdString(current_game.category) == cat::cat_disc_game) { - std::string key_path; - const iso_type_status iso_type = iso_file_decryption::check_type(current_game.path, key_path); + const iso_type_status iso_type = iso_file_decryption::check_type(current_game.path); // If it's an ISO file (e.g. even a decrypted ISO), always provide the entry on the context menu but disable // it if the ISO does not support integrity check (e.g. non Redump ISO) or no integrity DB is found. From e2e1cf02f4329c78b885c359777bf4de46139da6 Mon Sep 17 00:00:00 2001 From: Florin9doi Date: Sun, 28 Dec 2025 18:03:07 +0200 Subject: [PATCH 077/283] USB: Stop sending USB replies after prx_unload --- rpcs3/Emu/Cell/PPUModule.cpp | 9 +++++++++ rpcs3/Emu/Cell/lv2/sys_usbd.cpp | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/Cell/PPUModule.cpp b/rpcs3/Emu/Cell/PPUModule.cpp index a4b94f5d51..15ff9b1fa8 100644 --- a/rpcs3/Emu/Cell/PPUModule.cpp +++ b/rpcs3/Emu/Cell/PPUModule.cpp @@ -51,6 +51,7 @@ std::unordered_map& ppu_module_manager::get() std::vector g_ppu_function_names; atomic_t liblv2_begin = 0, liblv2_end = 0; +atomic_t libusbd_active = false; extern u32 ppu_generate_id(std::string_view name) { @@ -1928,6 +1929,10 @@ shared_ptr ppu_load_prx(const ppu_prx_object& elf, bool virtual_load, c liblv2_begin = prx->segs[0].addr; liblv2_end = prx->segs[0].addr + prx->segs[0].size; } + if (prx->path.ends_with("sys/external/libusbd.sprx"sv)) + { + libusbd_active = true; + } std::vector applied; @@ -2062,6 +2067,10 @@ void ppu_unload_prx(const lv2_prx& prx) liblv2_begin = 0; liblv2_end = 0; } + if (prx.path.ends_with("sys/external/libusbd.sprx"sv)) + { + libusbd_active = false; + } // Format patch name std::string hash = fmt::format("PRX-%s", fmt::base57(prx.sha1)); diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp index dca61f3be8..031acd607b 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp @@ -56,6 +56,8 @@ cfg_guncon3 g_cfg_guncon3; cfg_topshotelite g_cfg_topshotelite; cfg_topshotfearmaster g_cfg_topshotfearmaster; +extern atomic_t libusbd_active; + template <> void fmt_class_string::format(std::string& out, u64 arg) { @@ -661,7 +663,7 @@ void usb_handler_thread::operator()() u64 delay = 1'000; // Process fake transfers - if (!fake_transfers.empty()) + if (libusbd_active && !fake_transfers.empty()) { std::lock_guard lock_tf(mutex_transfers); u64 timestamp = get_system_time() - Emu.GetPauseTime(); From f9ffce76f29fac39d8f2f5c427fda5d121576bcf Mon Sep 17 00:00:00 2001 From: Vishrut Sachan Date: Fri, 8 May 2026 00:07:13 +0530 Subject: [PATCH 078/283] game_list: Fix icon display for multi-game collection ISOs --- rpcs3/rpcs3qt/game_list_base.cpp | 2 +- rpcs3/rpcs3qt/qt_utils.cpp | 10 ++++++---- rpcs3/rpcs3qt/qt_utils.h | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/rpcs3/rpcs3qt/game_list_base.cpp b/rpcs3/rpcs3qt/game_list_base.cpp index edae4e51f6..4bc26e0ee4 100644 --- a/rpcs3/rpcs3qt/game_list_base.cpp +++ b/rpcs3/rpcs3qt/game_list_base.cpp @@ -49,7 +49,7 @@ void game_list_base::IconLoadFunction(game_info game, qreal device_pixel_ratio, static std::unordered_set warn_once_list; static shared_mutex s_mtx; - if (game->icon.isNull() && !gui::utils::load_icon(game->icon, game->info.icon_path, game->icon_in_archive ? game->info.path : "")) + if (game->icon.isNull() && !gui::utils::load_icon(game->icon, game->info.icon_path, game->icon_in_archive ? game->info.path : "", game->info.game_dir)) { if (game_list_log.warning) { diff --git a/rpcs3/rpcs3qt/qt_utils.cpp b/rpcs3/rpcs3qt/qt_utils.cpp index 84de92d692..948906fa97 100644 --- a/rpcs3/rpcs3qt/qt_utils.cpp +++ b/rpcs3/rpcs3qt/qt_utils.cpp @@ -705,7 +705,7 @@ namespace gui return QString("%1 days ago %2").arg(current_date - exctrated_date).arg(date.toString(fmt_relative)); } - bool load_iso_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path) + bool load_iso_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path, const std::string& game_dir) { if (icon_path.empty() || archive_path.empty()) return false; @@ -716,7 +716,9 @@ namespace gui // With the exception of raw device, check cache first — avoids constructing a full iso_archive just for the icon. iso_metadata_cache_entry cache_entry{}; - if (!is_raw_device && iso_cache::load(archive_path, archive_path, cache_entry) && !cache_entry.icon_data.empty()) + const std::string cache_key = game_dir.empty() ? archive_path : archive_path + "//" + game_dir; + + if (!is_raw_device && iso_cache::load(archive_path, cache_key, cache_entry) && !cache_entry.icon_data.empty()) { const QByteArray data(reinterpret_cast(cache_entry.icon_data.data()), static_cast(cache_entry.icon_data.size())); @@ -736,7 +738,7 @@ namespace gui return icon.loadFromData(data); } - bool load_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path) + bool load_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path, const std::string& game_dir) { if (icon_path.empty()) return false; @@ -745,7 +747,7 @@ namespace gui return icon.load(QString::fromStdString(icon_path)); } - return load_iso_icon(icon, icon_path, archive_path); + return load_iso_icon(icon, icon_path, archive_path, game_dir); } QString format_timestamp(s64 time, const QString& fmt) diff --git a/rpcs3/rpcs3qt/qt_utils.h b/rpcs3/rpcs3qt/qt_utils.h index 22e94109ef..64c98e1079 100644 --- a/rpcs3/rpcs3qt/qt_utils.h +++ b/rpcs3/rpcs3qt/qt_utils.h @@ -193,10 +193,10 @@ namespace gui } // Loads an icon from an (ISO) archive file. - bool load_iso_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path); + bool load_iso_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path, const std::string& game_dir = {}); // Loads an icon (optionally from an (ISO) archive file). - bool load_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path); + bool load_icon(QPixmap& icon, const std::string& icon_path, const std::string& archive_path, const std::string& game_dir = {}); template void stop_future_watcher(QFutureWatcher& watcher, bool cancel, std::shared_ptr> cancel_flag = nullptr) From 98e9b3cf3055b2b60773322497f73ee1af57e170 Mon Sep 17 00:00:00 2001 From: Arsh Kumar Singh Date: Fri, 8 May 2026 19:22:05 +0530 Subject: [PATCH 079/283] Qt: Disambiguate offset direction labels from DPad/Stick labels (#18695) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary The "Left" and "Right" strings in `pad_settings_dialog` were shared between DPad/Stick direction labels (which have ample space) and offset value labels — Squircle Values, Stick Multipliers, and Stick Interpolation (where space is tight). Translators couldn't abbreviate the offset version without also affecting the direction binding labels. ## Change Added disambiguation via `comment="Offset direction"` in the `.ui` file's `` elements for the six offset labels: - `label_squircle_left` - `label_squircle_right` - `label_stick_multi_left` - `label_stick_multi_right` - `left_stick_lerp_label` - `right_stick_lerp_label` Qt now generates separate translation entries, allowing translators to use shorter abbreviations in the tight offset areas while keeping full forms for direction bindings. **English UI is unchanged** — no visual difference for English users. Fixes #18691 --- rpcs3/rpcs3qt/pad_settings_dialog.ui | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.ui b/rpcs3/rpcs3qt/pad_settings_dialog.ui index 62f0bcf4f5..9d7204b3f1 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.ui +++ b/rpcs3/rpcs3qt/pad_settings_dialog.ui @@ -2348,7 +2348,7 @@ - Left + Left @@ -2378,7 +2378,7 @@ - Right + Right @@ -2429,7 +2429,7 @@ - Left + Left @@ -2456,7 +2456,7 @@ - Right + Right @@ -2673,7 +2673,7 @@ - Left + Left @@ -2703,7 +2703,7 @@ - Right + Right From c102d1ec0d486a615faca55807e16e06cdc32925 Mon Sep 17 00:00:00 2001 From: Gustavo Graziano Date: Fri, 8 May 2026 11:24:54 -0300 Subject: [PATCH 080/283] =?UTF-8?q?Add=20missing=20hover=20effect=20in=20?= =?UTF-8?q?=E2=80=8EGustavoGraziano's=20theme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/GuiConfigs/Windows 11 (Dark Mode) by GustavoGraziano.qss | 4 ++++ bin/GuiConfigs/Windows 11 (Light Mode) by GustavoGraziano.qss | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/bin/GuiConfigs/Windows 11 (Dark Mode) by GustavoGraziano.qss b/bin/GuiConfigs/Windows 11 (Dark Mode) by GustavoGraziano.qss index 20b8638862..c93a1bb11e 100644 --- a/bin/GuiConfigs/Windows 11 (Dark Mode) by GustavoGraziano.qss +++ b/bin/GuiConfigs/Windows 11 (Dark Mode) by GustavoGraziano.qss @@ -618,6 +618,10 @@ QComboBox QAbstractItemView::item { border-radius: 4px; } +QComboBox QAbstractItemView::item:hover { + background-color: #343434; +} + QComboBox QAbstractItemView::item:selected { background-color: #383838; } diff --git a/bin/GuiConfigs/Windows 11 (Light Mode) by GustavoGraziano.qss b/bin/GuiConfigs/Windows 11 (Light Mode) by GustavoGraziano.qss index 692691856f..f279bd0570 100644 --- a/bin/GuiConfigs/Windows 11 (Light Mode) by GustavoGraziano.qss +++ b/bin/GuiConfigs/Windows 11 (Light Mode) by GustavoGraziano.qss @@ -618,6 +618,10 @@ QComboBox QAbstractItemView::item { border-radius: 4px; } +QComboBox QAbstractItemView::item:hover { + background-color: #F3F3F3; +} + QComboBox QAbstractItemView::item:selected { background-color: #F0F0F0; } From 431222149882e63a30fdfa7fcc6b25db4e1dfc43 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 8 Apr 2026 01:12:10 +0200 Subject: [PATCH 081/283] Add anaglyph settings --- Utilities/geometry.h | 4 + Utilities/stereo_config.cpp | 160 ++++++++++ Utilities/stereo_config.h | 77 +++++ rpcs3/Crypto/unpkg.h | 4 +- rpcs3/Emu/CMakeLists.txt | 1 + rpcs3/Emu/RSX/GL/GLOverlays.cpp | 13 +- rpcs3/Emu/RSX/GL/GLOverlays.h | 2 +- rpcs3/Emu/RSX/GL/GLPresent.cpp | 2 +- rpcs3/Emu/RSX/GL/glutils/program.h | 1 + .../GLSLSnippets/VideoOutCalibrationPass.glsl | 25 +- rpcs3/Emu/RSX/VK/VKOverlays.cpp | 19 +- rpcs3/Emu/RSX/VK/VKOverlays.h | 13 +- rpcs3/Emu/RSX/VK/VKPresent.cpp | 2 +- rpcs3/Emu/system_config.h | 9 + rpcs3/Emu/system_config_types.cpp | 1 + rpcs3/Emu/system_config_types.h | 1 + rpcs3/emucore.vcxproj | 2 + rpcs3/emucore.vcxproj.filters | 6 + rpcs3/rpcs3.vcxproj | 2 + rpcs3/rpcs3.vcxproj.filters | 6 + rpcs3/rpcs3qt/CMakeLists.txt | 1 + rpcs3/rpcs3qt/anaglyph_settings_dialog.cpp | 293 ++++++++++++++++++ rpcs3/rpcs3qt/anaglyph_settings_dialog.h | 54 ++++ rpcs3/rpcs3qt/emu_settings.cpp | 7 +- rpcs3/rpcs3qt/emu_settings_type.cpp | 4 + rpcs3/rpcs3qt/emu_settings_type.h | 4 + rpcs3/rpcs3qt/gl_gs_frame.h | 6 +- rpcs3/rpcs3qt/settings_dialog.cpp | 14 +- rpcs3/rpcs3qt/settings_dialog.ui | 16 + 29 files changed, 717 insertions(+), 32 deletions(-) create mode 100644 Utilities/stereo_config.cpp create mode 100644 Utilities/stereo_config.h create mode 100644 rpcs3/rpcs3qt/anaglyph_settings_dialog.cpp create mode 100644 rpcs3/rpcs3qt/anaglyph_settings_dialog.h diff --git a/Utilities/geometry.h b/Utilities/geometry.h index 3ffbc04dd3..a5881d1cd5 100644 --- a/Utilities/geometry.h +++ b/Utilities/geometry.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -1027,3 +1028,6 @@ using color1u = color1_base; using color1i = color1_base; using color1f = color1_base; using color1d = color1_base; + +using mat3f = color3_base[3]; +static_assert(sizeof(mat3f) == sizeof(float) * 3 * 3); diff --git a/Utilities/stereo_config.cpp b/Utilities/stereo_config.cpp new file mode 100644 index 0000000000..01b1fdfab9 --- /dev/null +++ b/Utilities/stereo_config.cpp @@ -0,0 +1,160 @@ +#include "stdafx.h" +#include "stereo_config.h" +#include "Emu/system_config.h" + +stereo_config::stereo_matrices::stereo_matrices(const std::array, 3>& l, const std::array, 3>& r) +{ + for (usz i = 0; i < 3; i++) + { + left[i] = l[i]; + right[i] = r[i]; + } +} + +atomic_t stereo_config::s_live_preview_enabled = false; +stereo_config::stereo_matrices stereo_config::m_live_matrices = {}; + +const std::unordered_map stereo_config::m_matrices = +{ + {stereo_render_mode_options::anaglyph_red_cyan, stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 0) + }, + { + color3_base(0, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 1) + } + )}, + {stereo_render_mode_options::anaglyph_red_green, stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 0) + }, + { + color3_base(0, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 0) + } + )}, + {stereo_render_mode_options::anaglyph_red_blue, stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 0) + }, + { + color3_base(0, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 1) + } + )}, + {stereo_render_mode_options::anaglyph_magenta_cyan, stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 0.5f) + }, + { + color3_base(0, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 0.5f) + } + )}, + {stereo_render_mode_options::anaglyph_trioscopic, stereo_matrices( + { + color3_base(0, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 0) + }, + { + color3_base(1, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 1) + } + )}, + {stereo_render_mode_options::anaglyph_amber_blue, stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 0) + }, + { + color3_base(0, 0, 1.0f / 3.0f), + color3_base(0, 0, 1.0f / 3.0f), + color3_base(0, 0, 1.0f / 3.0f) + } + )}, +}; + +void stereo_config::update_from_config(bool stereo_enabled) +{ + m_stereo_mode = stereo_enabled ? g_cfg.video.stereo_render_mode.get() : stereo_render_mode_options::disabled; + + if (m_stereo_mode == stereo_render_mode_options::anaglyph_custom) + { + stereo_config::stereo_matrices custom_matrices {}; + stereo_config::convert_matrix(m_custom_matrices.left, g_cfg.video.custom_anaglyph_matrices.left.get_map()); + stereo_config::convert_matrix(m_custom_matrices.right, g_cfg.video.custom_anaglyph_matrices.right.get_map()); + } +} + +const stereo_config::stereo_matrices& stereo_config::matrices() const +{ + if (m_in_emulation && s_live_preview_enabled) + { + return m_live_matrices; + } + + switch (m_stereo_mode) + { + case stereo_render_mode_options::disabled: + case stereo_render_mode_options::side_by_side: + case stereo_render_mode_options::over_under: + case stereo_render_mode_options::interlaced: + break; + case stereo_render_mode_options::anaglyph_red_green: + case stereo_render_mode_options::anaglyph_red_blue: + case stereo_render_mode_options::anaglyph_red_cyan: + case stereo_render_mode_options::anaglyph_magenta_cyan: + case stereo_render_mode_options::anaglyph_trioscopic: + case stereo_render_mode_options::anaglyph_amber_blue: + return ::at32(m_matrices, m_stereo_mode); + case stereo_render_mode_options::anaglyph_custom: + return m_custom_matrices; + } + + static const stereo_matrices s_left_only_matrices = stereo_matrices( + { + color3_base(1, 0, 0), + color3_base(0, 1, 0), + color3_base(0, 0, 1) + }, + { + color3_base(0, 0, 0), + color3_base(0, 0, 0), + color3_base(0, 0, 0) + }); + return s_left_only_matrices; +} + +std::map stereo_config::get_custom_matrix(bool is_left) const +{ + return convert_matrix(is_left ? m_custom_matrices.left : m_custom_matrices.right); +} + +std::map stereo_config::convert_matrix(const mat3f& mat) +{ + std::map values {}; + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + values[fmt::format("%d%d", i, j)] = fmt::format("%f", mat[i].rgb[j]); + } + } + return values; +} diff --git a/Utilities/stereo_config.h b/Utilities/stereo_config.h new file mode 100644 index 0000000000..468ce860ab --- /dev/null +++ b/Utilities/stereo_config.h @@ -0,0 +1,77 @@ +#pragma once + +#include "geometry.h" +#include "Emu/system_config_types.h" +#include "Utilities/StrUtil.h" +#include +#include + +struct stereo_config +{ +public: + struct stereo_matrices + { + stereo_matrices() = default; + stereo_matrices(const std::array, 3>& l, const std::array, 3>& r); + + mat3f left {}; + mat3f right {}; + }; + + stereo_config(bool in_emulation) : m_in_emulation(in_emulation) {} + + void set_stereo_mode(stereo_render_mode_options mode) { m_stereo_mode = mode; } + stereo_render_mode_options stereo_mode() const { return m_stereo_mode; } + + const stereo_matrices& matrices() const; + + void update_from_config(bool stereo_enabled); + + void set_custom_matrices(const stereo_matrices& matrices) { m_custom_matrices = matrices; } + static void set_live_matrices(const stereo_matrices& matrices) { m_live_matrices = matrices; } + + std::map get_custom_matrix(bool is_left) const; + + template + static void convert_matrix(mat3f& mat, const T& values) + { + mat[0] = {}; + mat[1] = {}; + mat[2] = {}; + + if (values.empty()) + { + return; + } + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + const std::string key = fmt::format("%d%d", i, j); + const auto it = values.find(key); + if (it == values.cend()) continue; + + const std::string& val_s = it->second; + if (val_s.empty()) continue; + + if (f64 val = 0.0f; try_to_float(&val, val_s, -10.0f, 10.0f)) + { + mat[i].rgb[j] = static_cast(val); + } + } + } + } + + static std::map convert_matrix(const mat3f& mat); + + static atomic_t s_live_preview_enabled; + +private: + stereo_matrices m_custom_matrices {}; + stereo_render_mode_options m_stereo_mode = stereo_render_mode_options::disabled; + bool m_in_emulation = false; + + static stereo_matrices m_live_matrices; + static const std::unordered_map m_matrices; +}; diff --git a/rpcs3/Crypto/unpkg.h b/rpcs3/Crypto/unpkg.h index 0b7971c07e..621d811207 100644 --- a/rpcs3/Crypto/unpkg.h +++ b/rpcs3/Crypto/unpkg.h @@ -126,7 +126,7 @@ struct PKGEntry struct PKGMetaData { private: - static std::string to_hex_string(u8 buf[], usz size) + static std::string to_hex_string(const u8* buf, usz size) { std::stringstream sstream; for (usz i = 0; i < size; i++) @@ -135,7 +135,7 @@ private: } return sstream.str(); } - static std::string to_hex_string(u8 buf[], usz size, usz dotpos) + static std::string to_hex_string(const u8* buf, usz size, usz dotpos) { std::string result = to_hex_string(buf, size); if (result.size() > dotpos) diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 0de2d08839..77c8348335 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -77,6 +77,7 @@ target_sources(rpcs3_emu PRIVATE ../../Utilities/sema.cpp ../../Utilities/simple_ringbuf.cpp ../../Utilities/stack_trace.cpp + ../../Utilities/stereo_config.cpp ../../Utilities/StrFmt.cpp ../../Utilities/Thread.cpp ../../Utilities/version.cpp diff --git a/rpcs3/Emu/RSX/GL/GLOverlays.cpp b/rpcs3/Emu/RSX/GL/GLOverlays.cpp index a758804e4f..2bf7100ceb 100644 --- a/rpcs3/Emu/RSX/GL/GLOverlays.cpp +++ b/rpcs3/Emu/RSX/GL/GLOverlays.cpp @@ -1,6 +1,7 @@ #include "GLOverlays.h" #include "Utilities/StrUtil.h" +#include "Utilities/stereo_config.h" #include "../Program/RSXOverlay.h" #include "Emu/Cell/timers.hpp" @@ -510,7 +511,7 @@ namespace gl } void video_out_calibration_pass::run(gl::command_context& cmd, const areau& viewport, const rsx::simple_array& source, f32 gamma, bool limited_rgb, - bool stereo_enabled, stereo_render_mode_options stereo_mode, gl::filter input_filter) + bool stereo_enabled, gl::filter input_filter) { if (m_input_filter != input_filter) { @@ -519,10 +520,16 @@ namespace gl m_sampler.set_parameteri(GL_TEXTURE_MAG_FILTER, static_cast(m_input_filter)); } + static stereo_config stereo_cfg = stereo_config(true); + stereo_cfg.update_from_config(stereo_enabled); + const auto& matrices = stereo_cfg.matrices(); + program_handle.uniforms["gamma"] = gamma; program_handle.uniforms["limit_range"] = limited_rgb + 0; - program_handle.uniforms["stereo_display_mode"] = stereo_enabled ? static_cast(stereo_mode) : 0; - program_handle.uniforms["stereo_image_count"] = (source[1] == GL_NONE? 1 : 2); + program_handle.uniforms["stereo_display_mode"] = static_cast(stereo_cfg.stereo_mode()); + program_handle.uniforms["stereo_image_count"] = (source[1] == GL_NONE ? 1 : 2); + program_handle.uniforms["left_anaglyph_matrix"] = matrices.left; + program_handle.uniforms["right_anaglyph_matrix"] = matrices.right; saved_sampler_state saved(GL_TEMP_IMAGE_SLOT(0), m_sampler); cmd->bind_texture(GL_TEMP_IMAGE_SLOT(0), GL_TEXTURE_2D, source[0]); diff --git a/rpcs3/Emu/RSX/GL/GLOverlays.h b/rpcs3/Emu/RSX/GL/GLOverlays.h index 8ccfd67305..c10a8ca394 100644 --- a/rpcs3/Emu/RSX/GL/GLOverlays.h +++ b/rpcs3/Emu/RSX/GL/GLOverlays.h @@ -98,7 +98,7 @@ namespace gl video_out_calibration_pass(); void run(gl::command_context& cmd, const areau& viewport, const rsx::simple_array& source, f32 gamma, bool limited_rgb, - bool stereo_enabled, stereo_render_mode_options stereo_mode, gl::filter input_filter); + bool stereo_enabled, gl::filter input_filter); }; struct rp_ssbo_to_generic_texture final : public overlay_pass diff --git a/rpcs3/Emu/RSX/GL/GLPresent.cpp b/rpcs3/Emu/RSX/GL/GLPresent.cpp index 2aa11868ee..230d16694e 100644 --- a/rpcs3/Emu/RSX/GL/GLPresent.cpp +++ b/rpcs3/Emu/RSX/GL/GLPresent.cpp @@ -436,7 +436,7 @@ void GLGSRender::flip(const rsx::display_flip_info_t& info) } gl::screen.bind(); - m_video_output_pass.run(cmd, areau(aspect_ratio), images.map(FN(x ? x->id() : GL_NONE)), gamma, limited_range, avconfig.stereo_enabled, g_cfg.video.stereo_render_mode, filter); + m_video_output_pass.run(cmd, areau(aspect_ratio), images.map(FN(x ? x->id() : GL_NONE)), gamma, limited_range, avconfig.stereo_enabled, filter); } } diff --git a/rpcs3/Emu/RSX/GL/glutils/program.h b/rpcs3/Emu/RSX/GL/glutils/program.h index 0568bc5547..ea55107c1f 100644 --- a/rpcs3/Emu/RSX/GL/glutils/program.h +++ b/rpcs3/Emu/RSX/GL/glutils/program.h @@ -104,6 +104,7 @@ namespace gl void operator = (const color4f& rhs) const { glProgramUniform4f(m_program.id(), location(), rhs.r, rhs.g, rhs.b, rhs.a); } void operator = (const areaf& rhs) const { glProgramUniform4f(m_program.id(), location(), rhs.x1, rhs.y1, rhs.x2, rhs.y2); } void operator = (const areai& rhs) const { glProgramUniform4i(m_program.id(), location(), rhs.x1, rhs.y1, rhs.x2, rhs.y2); } + void operator = (const mat3f& rhs) const { glProgramUniformMatrix3fv(m_program.id(), location(), 1, GL_FALSE, &rhs[0].rgb[0]); } void operator = (const std::span& rhs) const { glProgramUniform1iv(m_program.id(), location(), ::size32(rhs), rhs.data()); } void operator = (const std::span& rhs) const { glProgramUniformHandleui64vARB(m_program.id(), location(), ::size32(rhs), rhs.data()); } }; diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/VideoOutCalibrationPass.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/VideoOutCalibrationPass.glsl index f33dbd6aa5..8ebc8ae787 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/VideoOutCalibrationPass.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/VideoOutCalibrationPass.glsl @@ -19,6 +19,7 @@ layout(location=0) out vec4 ocol; #define STEREO_MODE_ANAGLYPH_MAGENTA_CYAN 7 #define STEREO_MODE_ANAGLYPH_TRIOSCOPIC 8 #define STEREO_MODE_ANAGLYPH_AMBER_BLUE 9 +#define STEREO_MODE_ANAGLYPH_CUSTOM 10 #define TRUE 1 #define FALSE 0 @@ -37,26 +38,28 @@ layout(push_constant) uniform static_data int limit_range; int stereo_display_mode; int stereo_image_count; + mat3 left_anaglyph_matrix; + mat3 right_anaglyph_matrix; }; #else uniform float gamma; uniform int limit_range; uniform int stereo_display_mode; uniform int stereo_image_count; +uniform mat3 left_anaglyph_matrix; +uniform mat3 right_anaglyph_matrix; #endif +vec3 applyMatrix(vec3 left, vec3 right) +{ + vec3 outColor = left_anaglyph_matrix * left + right_anaglyph_matrix * right; + return clamp(outColor, 0.0, 1.0); +} + vec4 anaglyph(const in vec4 left, const in vec4 right) { - switch (stereo_display_mode) - { - case STEREO_MODE_ANAGLYPH_RED_GREEN: return vec4(left.r, right.g, 0.f, 1.f); - case STEREO_MODE_ANAGLYPH_RED_BLUE: return vec4(left.r, 0.f, right.b, 1.f); - case STEREO_MODE_ANAGLYPH_RED_CYAN: return vec4(left.r, right.g, right.b, 1.f); - case STEREO_MODE_ANAGLYPH_MAGENTA_CYAN: return vec4(left.r, right.g, (left.b + right.b) / 2.f, 1.f); - case STEREO_MODE_ANAGLYPH_TRIOSCOPIC: return vec4(right.r, left.g, right.b, 1.f); - case STEREO_MODE_ANAGLYPH_AMBER_BLUE: return vec4(left.r, left.g, (right.r + right.g + right.b) / 3.f, 1.f); - default: return texture(fs0, tc0); - } + vec3 color = applyMatrix(left.rgb, right.rgb); + return vec4(color, 1.0); } vec4 anaglyph_single_image() @@ -92,6 +95,7 @@ vec4 read_source() case STEREO_MODE_ANAGLYPH_MAGENTA_CYAN: case STEREO_MODE_ANAGLYPH_TRIOSCOPIC: case STEREO_MODE_ANAGLYPH_AMBER_BLUE: + case STEREO_MODE_ANAGLYPH_CUSTOM: return anaglyph_single_image(); case STEREO_MODE_SIDE_BY_SIDE: return (tc0.x < 0.5) @@ -120,6 +124,7 @@ vec4 read_source() case STEREO_MODE_ANAGLYPH_MAGENTA_CYAN: case STEREO_MODE_ANAGLYPH_TRIOSCOPIC: case STEREO_MODE_ANAGLYPH_AMBER_BLUE: + case STEREO_MODE_ANAGLYPH_CUSTOM: return anaglyph_stereo_image(); case STEREO_MODE_SIDE_BY_SIDE: return (tc0.x < 0.5) diff --git a/rpcs3/Emu/RSX/VK/VKOverlays.cpp b/rpcs3/Emu/RSX/VK/VKOverlays.cpp index 684cf8fedb..f879a62ddf 100644 --- a/rpcs3/Emu/RSX/VK/VKOverlays.cpp +++ b/rpcs3/Emu/RSX/VK/VKOverlays.cpp @@ -14,6 +14,7 @@ #include "../Program/RSXOverlay.h" #include "util/fnv_hash.hpp" +#include "Utilities/stereo_config.h" #include "Emu/Cell/timers.hpp" @@ -911,18 +912,28 @@ namespace vk void video_out_calibration_pass::update_uniforms(vk::command_buffer& cmd, vk::glsl::program* program) { - vkCmdPushConstants(cmd, program->layout(), VK_SHADER_STAGE_FRAGMENT_BIT, 0, 16, config.data); + vkCmdPushConstants(cmd, program->layout(), VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(config.data), config.data); } void video_out_calibration_pass::run(vk::command_buffer& cmd, const areau& viewport, vk::framebuffer* target, const rsx::simple_array& src, f32 gamma, bool limited_rgb, - bool stereo_enabled, stereo_render_mode_options stereo_mode, VkRenderPass render_pass) + bool stereo_enabled, VkRenderPass render_pass) { + static stereo_config stereo_cfg = stereo_config(true); + stereo_cfg.update_from_config(stereo_enabled); + const auto& matrices = stereo_cfg.matrices(); + config.gamma = gamma; - config.limit_range = limited_rgb? 1 : 0; - config.stereo_display_mode = stereo_enabled ? static_cast(stereo_mode) : 0; + config.limit_range = limited_rgb ? 1 : 0; + config.stereo_display_mode = static_cast(stereo_cfg.stereo_mode()); config.stereo_image_count = std::min(::size32(src), 2u); + for (u32 i = 0; i < 3; i++) + { + std::memcpy(config.left_anaglyph_matrix[i].rgba, matrices.left[i].rgb, sizeof(matrices.left[i].rgb)); + std::memcpy(config.right_anaglyph_matrix[i].rgba, matrices.right[i].rgb, sizeof(matrices.right[i].rgb)); + } + std::vector views; views.reserve(2); diff --git a/rpcs3/Emu/RSX/VK/VKOverlays.h b/rpcs3/Emu/RSX/VK/VKOverlays.h index de2c218ebe..cfae31f9df 100644 --- a/rpcs3/Emu/RSX/VK/VKOverlays.h +++ b/rpcs3/Emu/RSX/VK/VKOverlays.h @@ -209,9 +209,18 @@ namespace vk int limit_range; int stereo_display_mode; int stereo_image_count; + color4_base left_anaglyph_matrix[3]; + color4_base right_anaglyph_matrix[3]; }; - float data[4]; + float data[( + sizeof(gamma) + + sizeof(limit_range) + + sizeof(stereo_display_mode) + + sizeof(stereo_image_count) + + sizeof(left_anaglyph_matrix) + + sizeof(right_anaglyph_matrix) + ) / sizeof(float)]; } config = {}; @@ -223,7 +232,7 @@ namespace vk void run(vk::command_buffer& cmd, const areau& viewport, vk::framebuffer* target, const rsx::simple_array& src, f32 gamma, bool limited_rgb, - bool stereo_enabled, stereo_render_mode_options stereo_mode, VkRenderPass render_pass); + bool stereo_enabled, VkRenderPass render_pass); }; // TODO: Replace with a proper manager diff --git a/rpcs3/Emu/RSX/VK/VKPresent.cpp b/rpcs3/Emu/RSX/VK/VKPresent.cpp index 432e8c7e87..b5ba9c4607 100644 --- a/rpcs3/Emu/RSX/VK/VKPresent.cpp +++ b/rpcs3/Emu/RSX/VK/VKPresent.cpp @@ -839,7 +839,7 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info) vk::get_overlay_pass()->run( *m_current_command_buffer, areau(aspect_ratio), direct_fbo, calibration_src, - avconfig.gamma, !use_full_rgb_range_output, avconfig.stereo_enabled, g_cfg.video.stereo_render_mode, single_target_pass); + avconfig.gamma, !use_full_rgb_range_output, avconfig.stereo_enabled, single_target_pass); direct_fbo->release(); } diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 1272896f75..e10c12610a 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -234,6 +234,15 @@ struct cfg_root : cfg::node } shader_preloading_dialog{ this }; + struct anaglyph_matrices : cfg::node + { + anaglyph_matrices(cfg::node* _this) : cfg::node(_this, "Custom Anaglyph Matrices") {} + + cfg::node_map_entry left{ this, "Matrix Left" }; + cfg::node_map_entry right{ this, "Matrix Right" }; + + } custom_anaglyph_matrices{ this }; + } video{ this }; struct node_audio : cfg::node diff --git a/rpcs3/Emu/system_config_types.cpp b/rpcs3/Emu/system_config_types.cpp index 436028db90..ccb029d94d 100644 --- a/rpcs3/Emu/system_config_types.cpp +++ b/rpcs3/Emu/system_config_types.cpp @@ -681,6 +681,7 @@ void fmt_class_string::format(std::string& out, u64 case stereo_render_mode_options::anaglyph_magenta_cyan: return "Anaglyph Magenta-Cyan"; case stereo_render_mode_options::anaglyph_trioscopic: return "Anaglyph Trioscopic"; case stereo_render_mode_options::anaglyph_amber_blue: return "Anaglyph Amber-Blue"; + case stereo_render_mode_options::anaglyph_custom: return "Anaglyph Custom"; } return unknown; diff --git a/rpcs3/Emu/system_config_types.h b/rpcs3/Emu/system_config_types.h index 95cfe72a2a..49c529caa7 100644 --- a/rpcs3/Emu/system_config_types.h +++ b/rpcs3/Emu/system_config_types.h @@ -361,6 +361,7 @@ enum class stereo_render_mode_options anaglyph_magenta_cyan, anaglyph_trioscopic, anaglyph_amber_blue, + anaglyph_custom, }; enum class xfloat_accuracy diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 9a8bc5a76c..31ef781d93 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -59,6 +59,7 @@ + @@ -574,6 +575,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 8534c79285..17ed08a3f6 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1435,6 +1435,9 @@ Loader + + Utilities + @@ -2881,6 +2884,9 @@ Loader + + Utilities + diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 8bf5e5bc4f..61f05473b0 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -838,6 +838,7 @@ + @@ -1197,6 +1198,7 @@ "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index 184069c388..e755ee4a67 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -1311,6 +1311,9 @@ Gui\game list + + Gui\settings + @@ -1562,6 +1565,9 @@ Gui\utils + + Gui\settings + diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 849e162a86..160aa4fb05 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -1,5 +1,6 @@ add_library(rpcs3_ui STATIC about_dialog.cpp + anaglyph_settings_dialog.cpp auto_pause_settings_dialog.cpp basic_mouse_settings_dialog.cpp breakpoint_handler.cpp diff --git a/rpcs3/rpcs3qt/anaglyph_settings_dialog.cpp b/rpcs3/rpcs3qt/anaglyph_settings_dialog.cpp new file mode 100644 index 0000000000..14c4fea62b --- /dev/null +++ b/rpcs3/rpcs3qt/anaglyph_settings_dialog.cpp @@ -0,0 +1,293 @@ +#include "stdafx.h" +#include "anaglyph_settings_dialog.h" + +#include +#include +#include +#include + +color_wedge_widget::color_wedge_widget(QWidget* parent) + : QWidget(parent) +{ + setObjectName("color_wedge_widget"); + setMinimumSize(300, 300); +} + +color_wedge_widget::~color_wedge_widget() +{ + stereo_config::s_live_preview_enabled = false; +} + +void color_wedge_widget::set_option(stereo_render_mode_options option) +{ + m_stereo_config.set_stereo_mode(option); + update(); +} + +void color_wedge_widget::set_custom_matrices(const stereo_config::stereo_matrices& matrices) +{ + m_stereo_config.set_custom_matrices(matrices); + update(); +} + +QVector3D color_wedge_widget::apply_matrix(const QVector3D& left, const QVector3D& right, const stereo_config::stereo_matrices& m) +{ + const mat3f& ml = m.left; + const mat3f& mr = m.right; + + QVector3D out; + out.setX(ml[0].x * left.x() + ml[1].x * left.y() + ml[2].x * left.z() + + mr[0].x * right.x() + mr[1].x * right.y() + mr[2].x * right.z()); + + out.setY(ml[0].y * left.x() + ml[1].y * left.y() + ml[2].y * left.z() + + mr[0].y * right.x() + mr[1].y * right.y() + mr[2].y * right.z()); + + out.setZ(ml[0].z * left.x() + ml[1].z * left.y() + ml[2].z * left.z() + + mr[0].z * right.x() + mr[1].z * right.y() + mr[2].z * right.z()); + + out.setX(std::clamp(out.x(), 0.0f, 1.0f)); + out.setY(std::clamp(out.y(), 0.0f, 1.0f)); + out.setZ(std::clamp(out.z(), 0.0f, 1.0f)); + + return out; +} + +QVector3D color_wedge_widget::apply_anaglyph_matrix(const QVector3D& left, const QVector3D& right) +{ + if (m_stereo_config.stereo_mode() == stereo_render_mode_options::disabled) + { + return left; + } + + return apply_matrix(left, right, m_stereo_config.matrices()); +} + +void color_wedge_widget::paintEvent(QPaintEvent* /*event*/) +{ + const int w = width(); + const int h = height(); + + if (m_img.width() != w || m_img.height() != h) + { + m_img = QImage(w, h, QImage::Format_RGB32); + } + + QVector3D out; + + // Loop over pixels + for (int y = 0; y < h; ++y) + { + const float v = float(y) / (h - 1); + + for (int x = 0; x < w; ++x) + { + const float u = float(x) / (w - 1); + + const QVector3D left(u, v, 1.0f - u); + const QVector3D right = left; + + const QVector3D out = apply_anaglyph_matrix(left, right); + + m_img.setPixel(x, y, qRgb(int(out.x() * 255), int(out.y() * 255), int(out.z() * 255))); + } + } + + // Paint the image + QPainter p(this); + p.drawImage(0, 0, m_img); +} + +anaglyph_settings_dialog::anaglyph_settings_dialog(QWidget* parent, std::shared_ptr emu_settings) + : QDialog(parent) + , m_emu_settings(std::move(emu_settings)) +{ + setWindowTitle(tr("Anaglyph Settings")); + setObjectName("anaglyph_settings_dialog"); + setAttribute(Qt::WA_DeleteOnClose); + + stereo_render_mode_options current_mode = stereo_render_mode_options::disabled; + if (u64 result {}; cfg::try_to_enum_value(&result, &fmt_class_string::format, m_emu_settings->GetSetting(emu_settings_type::StereoRenderMode))) + { + const auto mode = static_cast(static_cast>(result)); + switch (mode) + { + case stereo_render_mode_options::anaglyph_red_green: + case stereo_render_mode_options::anaglyph_red_blue: + case stereo_render_mode_options::anaglyph_red_cyan: + case stereo_render_mode_options::anaglyph_magenta_cyan: + case stereo_render_mode_options::anaglyph_trioscopic: + case stereo_render_mode_options::anaglyph_amber_blue: + case stereo_render_mode_options::anaglyph_custom: + current_mode = mode; + break; + default: + break; + } + } + + stereo_config::stereo_matrices custom_matrices {}; + stereo_config::convert_matrix(custom_matrices.left, m_emu_settings->GetMapSetting(emu_settings_type::CustomAnaglyphMatrixLeft)); + stereo_config::convert_matrix(custom_matrices.right, m_emu_settings->GetMapSetting(emu_settings_type::CustomAnaglyphMatrixRight)); + + m_widget_reference = new color_wedge_widget(this); + m_widget_reference->set_option(stereo_render_mode_options::disabled); + + m_widget = new color_wedge_widget(this); + m_widget->set_option(current_mode); + m_widget->set_custom_matrices(custom_matrices); + + QPushButton* apply_button = new QPushButton(tr("Apply Custom Matrices")); + connect(apply_button, &QAbstractButton::clicked, this, [this]() + { + m_emu_settings->SetMapSetting(emu_settings_type::CustomAnaglyphMatrixLeft, m_widget->get_stereo_config().get_custom_matrix(true)); + m_emu_settings->SetMapSetting(emu_settings_type::CustomAnaglyphMatrixRight, m_widget->get_stereo_config().get_custom_matrix(false)); + }); + apply_button->setEnabled(current_mode == stereo_render_mode_options::anaglyph_custom); + + QCheckBox* live_preview_cb = new QCheckBox(tr("Live Preview")); + connect(live_preview_cb, &QCheckBox::checkStateChanged, this, [this](Qt::CheckState state) + { + stereo_config::s_live_preview_enabled = state == Qt::CheckState::Checked; + }); + + QComboBox* combo = new QComboBox(this); + combo->addItem(tr("Disabled"), static_cast(stereo_render_mode_options::disabled)); + combo->addItem(tr("Red-Green"), static_cast(stereo_render_mode_options::anaglyph_red_green)); + combo->addItem(tr("Red-Blue"), static_cast(stereo_render_mode_options::anaglyph_red_blue)); + combo->addItem(tr("Red-Cyan"), static_cast(stereo_render_mode_options::anaglyph_red_cyan)); + combo->addItem(tr("Magenta-Cyan"), static_cast(stereo_render_mode_options::anaglyph_magenta_cyan)); + combo->addItem(tr("Trioscopic"), static_cast(stereo_render_mode_options::anaglyph_trioscopic)); + combo->addItem(tr("Amber-Blue"), static_cast(stereo_render_mode_options::anaglyph_amber_blue)); + combo->addItem(tr("Custom"), static_cast(stereo_render_mode_options::anaglyph_custom)); + combo->setCurrentIndex(combo->findData(static_cast(current_mode))); + + connect(combo, &QComboBox::currentIndexChanged, this, [this, combo, apply_button](int /*index*/) + { + apply_button->setEnabled(static_cast(combo->currentData().toInt()) == stereo_render_mode_options::anaglyph_custom); + m_widget->set_option(static_cast(combo->currentData().toInt())); + update_matrices(); + }); + + m_matrix_left = create_matrix_table(); + m_matrix_right = create_matrix_table(); + + update_matrices(); + + const auto group_box = [](const QString& title, QWidget* widget) + { + QHBoxLayout* layout = new QHBoxLayout(); + layout->addWidget(widget); + + QGroupBox* gb = new QGroupBox(title); + gb->setLayout(layout); + return gb; + }; + + QHBoxLayout* lay_h_1 = new QHBoxLayout(); + lay_h_1->addWidget(combo); + lay_h_1->addWidget(apply_button); + lay_h_1->addWidget(live_preview_cb); + lay_h_1->addSpacerItem(new QSpacerItem(50, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding)); + + QHBoxLayout* lay_h_2 = new QHBoxLayout(); + lay_h_2->addWidget(group_box(tr("Left matrix"), m_matrix_left)); + lay_h_2->addWidget(group_box(tr("Right matrix"), m_matrix_right)); + + QHBoxLayout* lay_h_3 = new QHBoxLayout(); + lay_h_3->addWidget(group_box(tr("Reference"), m_widget_reference)); + lay_h_3->addWidget(group_box(tr("Anaglyph"), m_widget)); + + QVBoxLayout* lay = new QVBoxLayout(); + lay->addLayout(lay_h_1); + lay->addLayout(lay_h_2); + lay->addLayout(lay_h_3); + + setLayout(lay); + adjustSize(); +} + +QTableWidget* anaglyph_settings_dialog::create_matrix_table() +{ + QTableWidget* table = new QTableWidget(3, 3, this); + table->setVerticalHeaderLabels({tr("R in"), tr("G in"), tr("B in")}); + table->setHorizontalHeaderLabels({tr("R out"), tr("G out"), tr("B out")}); + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + table->setItem(r, c, new QTableWidgetItem("0.0")); + } + } + connect(table, &QTableWidget::cellChanged, this, [this]() + { + apply_custom_matrix(); + }); + + table->resizeColumnsToContents(); + table->resizeRowsToContents(); + return table; +} + +void anaglyph_settings_dialog::read_matrix(mat3f& matrix, QTableWidget* table) const +{ + if (!table) return; + + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + bool ok = false; + const float val = table->item(r, c)->text().toFloat(&ok); + matrix[r].rgb[c] = ok ? val : 0.0f; + } + } +} + +void anaglyph_settings_dialog::update_matrix(QTableWidget* table, const mat3f& matrix, bool is_custom) +{ + if (!table) return; + + for (int r = 0; r < 3; ++r) + { + for (int c = 0; c < 3; ++c) + { + if (QTableWidgetItem* item = table->item(r, c)) + { + Qt::ItemFlags flags = item->flags(); + flags.setFlag(Qt::ItemIsEditable, is_custom); + + item->setFlags(flags); + item->setText(QString::number(matrix[r].rgb[c])); + } + } + } +} + +void anaglyph_settings_dialog::update_matrices() +{ + if (!m_widget) return; + + const stereo_config& stereo_cfg = m_widget->get_stereo_config(); + const stereo_config::stereo_matrices& matrices = stereo_cfg.matrices(); + + stereo_config::set_live_matrices(matrices); + + const bool is_custom = stereo_cfg.stereo_mode() == stereo_render_mode_options::anaglyph_custom; + + m_updating = true; + update_matrix(m_matrix_left, matrices.left, is_custom); + update_matrix(m_matrix_right, matrices.right, is_custom); + m_updating = false; +} + +void anaglyph_settings_dialog::apply_custom_matrix() +{ + if (!m_widget || m_updating) return; + + stereo_config::stereo_matrices m {}; + read_matrix(m.left, m_matrix_left); + read_matrix(m.right, m_matrix_right); + m_widget->set_custom_matrices(m); + + stereo_config::set_live_matrices(m); +} diff --git a/rpcs3/rpcs3qt/anaglyph_settings_dialog.h b/rpcs3/rpcs3qt/anaglyph_settings_dialog.h new file mode 100644 index 0000000000..7947d6dcaa --- /dev/null +++ b/rpcs3/rpcs3qt/anaglyph_settings_dialog.h @@ -0,0 +1,54 @@ +#pragma once + +#include "emu_settings.h" +#include "Utilities/stereo_config.h" + +#include +#include +#include + +class color_wedge_widget : public QWidget +{ +public: + color_wedge_widget(QWidget* parent = nullptr); + virtual ~color_wedge_widget(); + + void set_option(stereo_render_mode_options option); + void set_custom_matrices(const stereo_config::stereo_matrices& matrices); + + const stereo_config& get_stereo_config() const { return m_stereo_config; } + +protected: + QVector3D apply_matrix(const QVector3D& left, const QVector3D& right, const stereo_config::stereo_matrices& m); + + QVector3D apply_anaglyph_matrix(const QVector3D& left, const QVector3D& right); + + void paintEvent(QPaintEvent* event) override; + + stereo_config m_stereo_config = stereo_config(false); + QImage m_img; +}; + +class anaglyph_settings_dialog : public QDialog +{ +public: + anaglyph_settings_dialog(QWidget* parent, std::shared_ptr emu_settings); + +private: + std::shared_ptr m_emu_settings; + color_wedge_widget* m_widget_reference = nullptr; + color_wedge_widget* m_widget = nullptr; + QTableWidget* m_matrix_left = nullptr; + QTableWidget* m_matrix_right = nullptr; + bool m_updating = false; + + QTableWidget* create_matrix_table(); + + void read_matrix(mat3f& matrix, QTableWidget* table) const; + + void update_matrix(QTableWidget* table, const mat3f& matrix, bool is_custom); + + void update_matrices(); + + void apply_custom_matrix(); +}; diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index c9a4cd3a56..6e90adc8c4 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -254,8 +254,10 @@ bool emu_settings::ValidateSettings(bool cleanup) if (cfg_node) { - // Ignore every node in Log subsection - if (level == 0 && cfg_node->get_name() == "Log") + // Ignore every node in map subsections + if (cfg_node->get_type() == cfg::type::log || + cfg_node->get_type() == cfg::type::map || + cfg_node->get_type() == cfg::type::node_map) { continue; } @@ -1503,6 +1505,7 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ case stereo_render_mode_options::anaglyph_magenta_cyan: return tr("Anaglyph Magenta-Cyan", "3D Display Mode"); case stereo_render_mode_options::anaglyph_trioscopic: return tr("Anaglyph Green-Magenta (Trioscopic)", "3D Display Mode"); case stereo_render_mode_options::anaglyph_amber_blue: return tr("Anaglyph Amber-Blue (ColorCode 3D)", "3D Display Mode"); + case stereo_render_mode_options::anaglyph_custom: return tr("Anaglyph Custom", "3D Display Mode"); } break; case emu_settings_type::MidiDevices: diff --git a/rpcs3/rpcs3qt/emu_settings_type.cpp b/rpcs3/rpcs3qt/emu_settings_type.cpp index afcd6949a3..6a9ff966b5 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.cpp +++ b/rpcs3/rpcs3qt/emu_settings_type.cpp @@ -144,6 +144,10 @@ const std::map settings_location = { emu_settings_type::ShaderLoadBgDarkening, get_cfg_location(local_cfg.video.shader_preloading_dialog.darkening_strength) }, { emu_settings_type::ShaderLoadBgBlur, get_cfg_location(local_cfg.video.shader_preloading_dialog.blur_strength) }, + // Anaglyph matrix + { emu_settings_type::CustomAnaglyphMatrixLeft, get_cfg_location(local_cfg.video.custom_anaglyph_matrices.left) }, + { emu_settings_type::CustomAnaglyphMatrixRight, get_cfg_location(local_cfg.video.custom_anaglyph_matrices.right) }, + // Audio { emu_settings_type::AudioRenderer, get_cfg_location(local_cfg.audio.renderer) }, { emu_settings_type::DumpToFile, get_cfg_location(local_cfg.audio.dump_to_file) }, diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index 313c92a640..d6105beb1f 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -112,6 +112,10 @@ enum class emu_settings_type RecordWithOverlays, DisableHWTexelRemapping, + // Anaglyph Matrix + CustomAnaglyphMatrixLeft, + CustomAnaglyphMatrixRight, + // Performance Overlay PerfOverlayEnabled, PerfOverlayFramerateGraphEnabled, diff --git a/rpcs3/rpcs3qt/gl_gs_frame.h b/rpcs3/rpcs3qt/gl_gs_frame.h index d1129f8e3a..223ac3cfd1 100644 --- a/rpcs3/rpcs3qt/gl_gs_frame.h +++ b/rpcs3/rpcs3qt/gl_gs_frame.h @@ -6,8 +6,8 @@ struct GLContext { - QSurface *surface = nullptr; - QOpenGLContext *handle = nullptr; + QSurface* surface = nullptr; + QOpenGLContext* handle = nullptr; bool owner = false; }; @@ -15,7 +15,7 @@ class gl_gs_frame : public gs_frame { private: QSurfaceFormat m_format; - GLContext *m_primary_context = nullptr; + GLContext* m_primary_context = nullptr; public: explicit gl_gs_frame(QScreen* screen, const QRect& geometry, const QIcon& appIcon, std::shared_ptr gui_settings, bool force_fullscreen); diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 19e5565cc2..fabc1f024e 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -24,6 +24,7 @@ #include "render_creator.h" #include "microphone_creator.h" #include "log_level_dialog.h" +#include "anaglyph_settings_dialog.h" #include "Emu/NP/rpcn_countries.h" #include "Emu/GameInfo.h" @@ -45,7 +46,7 @@ LOG_CHANNEL(cfg_log, "CFG"); -std::pair get_data(const QComboBox* box, int index) +static std::pair get_data(const QComboBox* box, int index) { if (!box) return {}; @@ -57,7 +58,7 @@ std::pair get_data(const QComboBox* box, int index) return { var_list[0].toString(), var_list[1].toInt() }; } -int find_item(const QComboBox* box, int value) +static int find_item(const QComboBox* box, int value) { for (int i = 0; box && i < box->count(); i++) { @@ -70,7 +71,7 @@ int find_item(const QComboBox* box, int value) return -1; } -void remove_item(QComboBox* box, int data_value, int def_value) +static void remove_item(QComboBox* box, int data_value, int def_value) { if (!box) return; @@ -593,9 +594,15 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std ui->stereoRenderMode->setEnabled(stereo_allowed && stereo_enabled); ui->stereoRenderEnabled->setEnabled(stereo_allowed); ui->gb_screen_size->setEnabled(stereo_allowed && stereo_enabled); + ui->gb_anaglyph_settings->setEnabled(stereo_allowed && stereo_enabled); }; connect(ui->resBox, &QComboBox::currentIndexChanged, this, [enable_3D_modes](int){ enable_3D_modes(); }); connect(ui->stereoRenderEnabled, &QCheckBox::checkStateChanged, this, [enable_3D_modes](Qt::CheckState){ enable_3D_modes(); }); + connect(ui->pb_anaglyph_settings, &QAbstractButton::clicked, [this]() + { + anaglyph_settings_dialog* dlg = new anaglyph_settings_dialog(this, m_emu_settings); + dlg->open(); + }); enable_3D_modes(); } else @@ -603,6 +610,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std ui->stereoRenderMode->setCurrentIndex(find_item(ui->stereoRenderMode, static_cast(g_cfg.video.stereo_render_mode.def))); ui->stereoRenderEnabled->setChecked(false); ui->gb_stereo->setEnabled(false); + ui->gb_anaglyph_settings->setEnabled(false); } // Checkboxes: main options diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index 82232fbeb8..a8f17f0310 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -557,6 +557,22 @@ + + + + Anaglyph Settings + + + + + + Configure + + + + + + From 3c2815e89c9368e5dae88df9cfe79f54380ae1b2 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Fri, 8 May 2026 23:40:01 +0200 Subject: [PATCH 082/283] Add option to derive MAC from PSID --- rpcs3/Emu/NP/np_handler.cpp | 9 +++++++++ rpcs3/Emu/system_config.h | 1 + rpcs3/rpcs3qt/emu_settings_type.cpp | 1 + rpcs3/rpcs3qt/emu_settings_type.h | 1 + rpcs3/rpcs3qt/settings_dialog.cpp | 9 +++++++-- rpcs3/rpcs3qt/settings_dialog.ui | 7 +++++++ rpcs3/rpcs3qt/tooltips.h | 1 + 7 files changed, 27 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index 3eb2bef0ed..0d628e299e 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -612,6 +612,15 @@ namespace np bool np_handler::discover_ether_address() { + if (g_cfg.net.derive_mac_from_psid) + { + const u128 psid = g_cfg.sys.console_psid; + memcpy(ether_address.data(), &psid, 6); + ether_address[0] &= 0xFE; + ether_address[0] |= 0x02; + return true; + } + #if defined(__FreeBSD__) || defined(__APPLE__) ifaddrs* ifap; diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index e10c12610a..08004f5a46 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -332,6 +332,7 @@ struct cfg_root : cfg::node cfg::string dns{this, "DNS address", "8.8.8.8"}; cfg::string swap_list{this, "IP swap list", ""}; cfg::_bool upnp_enabled{this, "UPNP Enabled", false}; + cfg::_bool derive_mac_from_psid{this, "Derive MAC from PSID", false}; cfg::_enum psn_status{this, "PSN status", np_psn_status::disabled}; cfg::string country{this, "PSN Country", "us"}; diff --git a/rpcs3/rpcs3qt/emu_settings_type.cpp b/rpcs3/rpcs3qt/emu_settings_type.cpp index 6a9ff966b5..0307f764d5 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.cpp +++ b/rpcs3/rpcs3qt/emu_settings_type.cpp @@ -219,6 +219,7 @@ const std::map settings_location = { emu_settings_type::PSNStatus, get_cfg_location(local_cfg.net.psn_status) }, { emu_settings_type::BindAddress, get_cfg_location(local_cfg.net.bind_address) }, { emu_settings_type::EnableUpnp, get_cfg_location(local_cfg.net.upnp_enabled) }, + { emu_settings_type::DeriveMacFromPsid, get_cfg_location(local_cfg.net.derive_mac_from_psid) }, { emu_settings_type::PSNCountry, get_cfg_location(local_cfg.net.country) }, { emu_settings_type::EnableClans, get_cfg_location(local_cfg.net.clans_enabled) }, diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index d6105beb1f..fbc3996e03 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -210,6 +210,7 @@ enum class emu_settings_type PSNStatus, BindAddress, EnableUpnp, + DeriveMacFromPsid, PSNCountry, EnableClans, diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index fabc1f024e..dddb803c4f 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -1432,14 +1432,19 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceCheckBox(ui->enable_upnp, emu_settings_type::EnableUpnp); SubscribeTooltip(ui->enable_upnp, tooltips.settings.enable_upnp); + m_emu_settings->EnhanceCheckBox(ui->derive_mac_from_psid, emu_settings_type::DeriveMacFromPsid); + SubscribeTooltip(ui->derive_mac_from_psid, tooltips.settings.derive_mac_from_psid); + // Comboboxes connect(ui->netStatusBox, &QComboBox::currentIndexChanged, [this](int index) { if (index < 0) return; const auto [text, value] = get_data(ui->netStatusBox, index); - ui->gb_edit_dns->setEnabled(static_cast(value) != np_internet_status::disabled); - ui->enable_upnp->setEnabled(static_cast(value) != np_internet_status::disabled); + const bool internet_enabled = static_cast(value) != np_internet_status::disabled; + ui->gb_edit_dns->setEnabled(internet_enabled); + ui->enable_upnp->setEnabled(internet_enabled); + ui->derive_mac_from_psid->setEnabled(internet_enabled); if (static_cast(value) == np_internet_status::disabled) { diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index a8f17f0310..d5024f4bef 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -2305,6 +2305,13 @@ + + + + Derive ethernet address from PSID + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 9e8bac8c49..dba2bd5c1d 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -265,6 +265,7 @@ public: const QString dns_swap = tr("DNS Swap List.\nOnly available in custom configurations."); const QString bind = tr("Interface IP Address to bind to.\nOnly available in custom configurations."); const QString enable_upnp = tr("Enable UPNP.\nThis will automatically forward ports bound on 0.0.0.0 if your router has UPNP enabled."); + const QString derive_mac_from_psid = tr("Derive the MAC address from the PSID."); const QString psn_country = tr("Changes the RPCN country."); const QString enable_clans = tr("Enable connection to the Clans server.\nOnly affects games supporting the Clans feature."); From 6850e2e5a8524180fb7cb014ff3703ed2bb7b689 Mon Sep 17 00:00:00 2001 From: AurisDSP <235008503+AurisDSP@users.noreply.github.com> Date: Sat, 9 May 2026 07:27:01 -0400 Subject: [PATCH 083/283] Apple Silicon: consolidate JIT W^X handling with RAII guards Address review feedback on #18701 (cc @elad335): combine the SPU worker fix from #18701, the SPRX Loader fix from #18703, and three additional similar W^X leaks discovered while auditing the codebase for the same pattern. Use Allman-style braces to match RPCS3 coding style. Background: On AArch64 Apple Silicon, MAP_JIT pages enforce W^X per-thread. pthread_jit_write_protect_np(false) enables write mode and pthread_jit_write_protect_np(true) restores execute mode. When code takes an early return or throws between these calls, the thread is left in write mode, which can cause segfaults on subsequent code fetches or inconsistent state at thread teardown. Fixes applied (all gated on __APPLE__): 1. Emu/Cell/SPUCommonRecompiler.cpp - SPU cache worker thread Add RAII guard so execute mode is restored on worker exit. 2. Emu/System.cpp - SPRX Loader thread Enter write mode (was missing entirely) so ppu_initialize() and ppu_precompile() can write to MAP_JIT pages, and pair with an RAII guard. Reproducer: Red Dead Redemption (BLUS30418) crashes ~12s into boot at 0x300010000 without this fix. 3. Emu/Cell/SPULLVMRecompiler.cpp - SPU LLVM compile path The compile function enters write mode, then has an early "return nullptr" path on rebuild_ubertrampoline failure that skipped the explicit restore. Add RAII guard so execute mode is restored on every exit path. The existing explicit restore before the cache-flush asm directives is preserved. 4. Emu/Cell/PPUThread.cpp - PPU LLVM worker thread (operator()) Worker entered write mode but never restored it on operator() return. Add RAII guard. 5. Emu/Cell/PPUThread.cpp - ppu_initialize() main path This scope alternates write/execute mode and contains an early "return compiled_new" at the empty-jits check plus a final return that both leak write mode. Add RAII guard so execute mode is always restored on exit. Intermediate explicit transitions for the symbol-resolver invocation are preserved. No behavioral change on x86_64 or non-Apple ARM64 (all changes are inside #ifdef __APPLE__ / #if defined(__APPLE__)). Supersedes #18703. --- rpcs3/Emu/Cell/PPUThread.cpp | 24 ++++++++++++++++++++++++ rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 13 +++++++++++++ rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 12 ++++++++++++ rpcs3/Emu/System.cpp | 18 ++++++++++++++++++ 4 files changed, 67 insertions(+) diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index f5d91cc519..c43ac5d73c 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -5289,7 +5289,19 @@ bool ppu_initialize(const ppu_module& info, bool check_only, u64 file_s thread_ctrl::scoped_priority low_prio(-1); #ifdef __APPLE__ + // Apple Silicon W^X: PPU LLVM worker enables write mode for + // JIT memory. Pair it with an RAII guard so execute mode + // is restored on every exit path (return, exception, etc.) + // to keep per-thread state consistent at teardown. pthread_jit_write_protect_np(false); + + struct jit_write_guard + { + ~jit_write_guard() + { + pthread_jit_write_protect_np(true); + } + } _jit_guard; #endif for (u32 i = work_cv++; i < workload.size(); i = work_cv++, g_progr_pdone++) { @@ -5421,7 +5433,19 @@ bool ppu_initialize(const ppu_module& info, bool check_only, u64 file_s // Jit can be null if the loop doesn't ever enter. #ifdef __APPLE__ + // Apple Silicon W^X: this scope toggles write/execute mode multiple + // times below. Use an RAII guard so execute mode is always restored + // on every exit path, including the early "return compiled_new" at + // the empty-jits check and the normal return at function end. pthread_jit_write_protect_np(false); + + struct jit_write_guard + { + ~jit_write_guard() + { + pthread_jit_write_protect_np(true); + } + } _jit_guard; #endif // Try to patch all single and unregistered BLRs with the same function (TODO: Maybe generalize it into PIC code detection and patching) ppu_intrp_func_t BLR_func = nullptr; diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index 8b4bd15e26..dffca21cae 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -857,7 +857,20 @@ void spu_cache::initialize(bool build_existing_cache) named_thread_group workers("SPU Worker ", worker_count, [&]() -> uint { #ifdef __APPLE__ + // Apple Silicon W^X: enable JIT write mode for this worker and + // pair it with an RAII guard so execute mode is restored on + // every exit path (return, exception, etc.). Leaving a worker + // in write mode at teardown can leave per-thread state + // inconsistent on AArch64. pthread_jit_write_protect_np(false); + + struct jit_write_guard + { + ~jit_write_guard() + { + pthread_jit_write_protect_np(true); + } + } _jit_guard; #endif // Set low priority thread_ctrl::scoped_priority low_prio(-1); diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 5b63ca80cc..927d7ac187 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -3463,7 +3463,19 @@ public: } #if defined(__APPLE__) + // Apple Silicon W^X: enter write mode for JIT memory and pair + // it with an RAII guard so execute mode is restored on every + // exit path (the early "return nullptr" below would otherwise + // leave the thread in write mode permanently). pthread_jit_write_protect_np(false); + + struct jit_write_guard + { + ~jit_write_guard() + { + pthread_jit_write_protect_np(true); + } + } _jit_guard; #endif if (g_cfg.core.spu_debug) diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 199aad38ee..29016a408a 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -1835,6 +1835,24 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, g_fxo->init("SPRX Loader"sv, [this, dir_queue, is_fast = m_precompilation_option.is_fast]() mutable { +#ifdef __APPLE__ + // Apple Silicon W^X: this thread invokes ppu_initialize() + // and ppu_precompile(), which write into MAP_JIT pages. + // Without enabling write mode here, these writes segfault + // before the game can boot (reproducible: RDR BLUS30418 + // crashes ~12s into boot at 0x300010000). Pair the enable + // with an RAII guard so execute mode is restored on every + // exit path (return, exception, etc.). + pthread_jit_write_protect_np(false); + + struct jit_write_guard + { + ~jit_write_guard() + { + pthread_jit_write_protect_np(true); + } + } _jit_guard; +#endif std::vector*> mod_list; if (auto& _main = *ensure(g_fxo->try_get>()); !_main.path.empty()) From b34eba2fb3ca6878a62ac7cbe67aaefd6cadbe0c Mon Sep 17 00:00:00 2001 From: schm1dtmac Date: Sat, 9 May 2026 13:17:32 +0100 Subject: [PATCH 084/283] Revert PPUThread changes Causes GOW3 segfaults at the end of PPU linking --- rpcs3/Emu/Cell/PPUThread.cpp | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index c43ac5d73c..f5d91cc519 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -5289,19 +5289,7 @@ bool ppu_initialize(const ppu_module& info, bool check_only, u64 file_s thread_ctrl::scoped_priority low_prio(-1); #ifdef __APPLE__ - // Apple Silicon W^X: PPU LLVM worker enables write mode for - // JIT memory. Pair it with an RAII guard so execute mode - // is restored on every exit path (return, exception, etc.) - // to keep per-thread state consistent at teardown. pthread_jit_write_protect_np(false); - - struct jit_write_guard - { - ~jit_write_guard() - { - pthread_jit_write_protect_np(true); - } - } _jit_guard; #endif for (u32 i = work_cv++; i < workload.size(); i = work_cv++, g_progr_pdone++) { @@ -5433,19 +5421,7 @@ bool ppu_initialize(const ppu_module& info, bool check_only, u64 file_s // Jit can be null if the loop doesn't ever enter. #ifdef __APPLE__ - // Apple Silicon W^X: this scope toggles write/execute mode multiple - // times below. Use an RAII guard so execute mode is always restored - // on every exit path, including the early "return compiled_new" at - // the empty-jits check and the normal return at function end. pthread_jit_write_protect_np(false); - - struct jit_write_guard - { - ~jit_write_guard() - { - pthread_jit_write_protect_np(true); - } - } _jit_guard; #endif // Try to patch all single and unregistered BLRs with the same function (TODO: Maybe generalize it into PIC code detection and patching) ppu_intrp_func_t BLR_func = nullptr; From 29b4577fdb1e9d168097ad6149c2e1772f936cdc Mon Sep 17 00:00:00 2001 From: brian218 Date: Sun, 10 May 2026 12:36:35 +0800 Subject: [PATCH 085/283] =?UTF-8?q?USIO:=20Implemented=20BanaPassport=20(?= =?UTF-8?q?=E3=83=90=E3=83=8A=E3=83=91=E3=82=B9=E3=83=9D=E3=83=BC=E3=83=88?= =?UTF-8?q?)=20card=20reader=20emulation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rpcs3/Emu/Io/usio.cpp | 232 +++++++++++++++++++++++++++++++++++-- rpcs3/Emu/Io/usio.h | 4 + rpcs3/Emu/Io/usio_config.h | 2 + 3 files changed, 231 insertions(+), 7 deletions(-) diff --git a/rpcs3/Emu/Io/usio.cpp b/rpcs3/Emu/Io/usio.cpp index 21dc241d81..f707ae8e6a 100644 --- a/rpcs3/Emu/Io/usio.cpp +++ b/rpcs3/Emu/Io/usio.cpp @@ -32,6 +32,7 @@ void fmt_class_string::format(std::string& out, u64 arg) case usio_btn::tekken_button3: return "Tekken Button 3"; case usio_btn::tekken_button4: return "Tekken Button 4"; case usio_btn::tekken_button5: return "Tekken Button 5"; + case usio_btn::card_tapping: return "Card Tapping"; case usio_btn::count: return "Count"; } @@ -42,6 +43,7 @@ void fmt_class_string::format(std::string& out, u64 arg) struct usio_memory { std::vector backup_memory; + std::array, g_cfg_usio.players.size()> card_data{}; usio_memory() = default; usio_memory(const usio_memory&) = delete; @@ -175,6 +177,16 @@ void usb_device_usio::load_backup() } usio_backup_file.read(memory.backup_memory.data(), file_size); + + for (usz i = 0; i < memory.card_data.size(); i++) + { + if (fs::file usio_card_file; + usio_card_file.open(fmt::format("%s/caches/usio_card_p%d.bin", rpcs3::utils::get_hdd1_dir(), i + 1), fs::read) && + usio_card_file.size() == memory.card_data[i].size()) + { + usio_card_file.read(memory.card_data[i].data(), memory.card_data[i].size()); + } + } } void usb_device_usio::save_backup() @@ -261,6 +273,10 @@ void usb_device_usio::translate_input_taiko() if (pressed) std::memcpy(input_buf.data() + 34 + offset, &c_hit, sizeof(u16)); break; + case usio_btn::card_tapping: + if (pressed) + tap_card(player); + break; default: break; } @@ -271,6 +287,8 @@ void usb_device_usio::translate_input_taiko() digital_input |= 0x80; }; + for (usz i = 0; i < m_io_status.size(); i++) + m_io_status[i].card_tapped = false; for (usz i = 0; i < g_cfg_usio.players.size(); i++) translate_from_pad(i, i); @@ -384,6 +402,10 @@ void usb_device_usio::translate_input_tekken() if (pressed) input |= 0x80000000ULL << shift; break; + case usio_btn::card_tapping: + if (pressed) + tap_card(player); + break; default: break; } @@ -398,6 +420,8 @@ void usb_device_usio::translate_input_tekken() } }; + for (usz i = 0; i < m_io_status.size(); i++) + m_io_status[i].card_tapped = false; for (usz i = 0; i < g_cfg_usio.players.size(); i++) translate_from_pad(i, i); @@ -414,6 +438,194 @@ void usb_device_usio::translate_input_tekken() response = std::move(input_buf); } +void usb_device_usio::emulate_card_reader(std::vector& buf, u16 reg) +{ + static std::array, 2> pending_response = {}; + usz reader_index = 0; + + const auto calculate_checksum = [](bool check, std::vector& data) -> bool + { + if (data.size() < 0x06) + return false; + + const usz data_end = data.size() - 2; + u8 sum = data[3] + data[4]; + + for (usz i = 5; i < data_end; i++) + sum -= data[i]; + + if (check) + return *reinterpret_cast*>(&data[data_end]) == sum; + + *reinterpret_cast*>(&data[data_end]) = sum; + return true; + }; + + switch (reg) + { + case 0x0080: + case 0x0090: + { + reader_index = reg == 0x0080 ? 0 : 1; + buf = {0x02, 0x03, 0x00, 0x00, 0xFF, 0x0F, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x10, 0x00}; + *reinterpret_cast*>(buf.data() + 2) = ::narrow(pending_response[reader_index].size()); + break; + } + case 0x7000: + case 0x7800: + { + reader_index = reg == 0x7000 ? 0 : 1; + buf = std::move(pending_response[reader_index]); + pending_response[reader_index].clear(); // Ensure its empty state after being moved + break; + } + case 0x7400: + case 0x7C00: + { + if (!calculate_checksum(true, buf)) + break; + reader_index = reg == 0x7400 ? 0 : 1; + const auto& status = ::at32(m_io_status, reader_index); + const usz card_player = reader_index * 2 + status.card_index; + const u8 payload_length = buf[3]; + const u8 command = buf[4]; + const u8* const payload = &buf[6]; + switch (command) + { + case 0xE8: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x0D, 0xF3, 0xD5, 0x07, 0xDC, 0xF4, 0x3F, 0x11, 0x4D, 0x85, 0x61, 0xF1, 0x26, 0x6A, 0x87, 0xC9, 0x00}; + break; + } + case 0xEE: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x0A, 0xF6, 0xD5, 0x07, 0xFF, 0x3F, 0x0E, 0xF1, 0xFF, 0x3F, 0x0E, 0xF1, 0xAA, 0x00}; + break; + } + case 0xF1: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x03, 0xFD, 0xD5, 0x41, 0x00, 0xEA, 0x00}; + break; + } + case 0xF2: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x02, 0xFE, 0xD5, 0x33, 0xF8, 0x00}; + break; + } + case 0xF7: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x03, 0xFD, 0xD5, 0x4B, 0x00, 0xE0, 0x00}; + break; + } + case 0xFA: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x02, 0xFE, 0xD5, 0x33, 0xF8, 0x00}; + break; + } + case 0xFB: + { + if (payload_length >= 5) + { + if (*reinterpret_cast*>(&payload[0]) == 0x0140) + { + if (payload[3] < 4) + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x13, 0xED, 0xD5, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA, 0x00}; + std::memcpy(pending_response[reader_index].data() + 8, g_fxo->get().card_data[card_player].data() + payload[3] * 0x10, 0x10); + } + else + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x03, 0xFD, 0xD5, 0x41, 0x13, 0xD7, 0x00}; + } + } + else + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x03, 0xFD, 0xD5, 0x09, 0x00, 0x22, 0x00}; + } + } + break; + } + case 0xFC: + { + if (payload_length >= 2) + { + switch (payload[0]) + { + case 0x52: + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x04, 0xFC, 0xD5, 0x53, 0x01, 0x00, 0xD7, 0x00}; + break; + case 0x0E: + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x02, 0xFE, 0xD5, 0x0F, 0x1C, 0x00}; + break; + case 0x4A: + if (status.card_tapped) + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x0C, 0xF4, 0xD5, 0x4B, 0x01, 0x01, 0x00, 0x04, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0xCE, 0x00}; + std::memcpy(pending_response[reader_index].data() + 0x13, g_fxo->get().card_data[card_player].data(), 4); + } + else + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x03, 0xFD, 0xD5, 0x4B, 0x00, 0xE0, 0x00}; + } + break; + case 0x32: + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x02, 0xFE, 0xD5, 0x33, 0xF8, 0x00}; + break; + default: + break; + } + } + break; + } + case 0xFD: + { + if (payload_length >= 2) + { + switch (payload[0]) + { + case 0x18: + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x02, 0xFE, 0xD5, 0x19, 0x12, 0x00}; + break; + case 0x12: + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x02, 0xFE, 0xD5, 0x13, 0x18, 0x00}; + break; + default: + break; + } + } + break; + } + case 0xFE: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x05, 0xFB, 0xD5, 0x0D, 0x00, 0x06, 0x00, 0x18, 0x00}; + break; + } + case 0xFF: + { + pending_response[reader_index] = {0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00}; + break; + } + default: + { + usio_log.trace("Unhandled card reader command: 0x%02X", command); + break; + } + } + calculate_checksum(false, pending_response[reader_index]); + break; + } + default: + break; + } +} + +void usb_device_usio::tap_card(usz player) +{ + auto& status = ::at32(m_io_status, player / 2); + status.card_tapped = true; + status.card_index = player % 2; +} + void usb_device_usio::usio_write(u8 channel, u16 reg, std::vector& data) { const auto get_u16 = [&](std::string_view usio_func) -> u16 @@ -461,6 +673,16 @@ void usb_device_usio::usio_write(u8 channel, u16 reg, std::vector& data) usio_log.trace("SetHopperRequest(Hopper: %d, Limit: 0x%04X)", (reg - 0x4A) / 0x10, get_u16("SetHopperLimit")); break; } + case 0x0080: + case 0x008D: + case 0x0090: + case 0x009D: + case 0x7400: + case 0x7C00: + { + emulate_card_reader(data, reg); + break; + } default: { usio_log.trace("Unhandled channel 0 register write(reg: 0x%04X, size: 0x%04X, data: %s)", reg, data.size(), fmt::buf_to_hexstring(data.data(), data.size())); @@ -502,15 +724,11 @@ void usb_device_usio::usio_read(u8 channel, u16 reg, u16 size) break; } case 0x0080: - { - // Card reader check - 1 - response = {0x02, 0x03, 0x06, 0x00, 0xFF, 0x0F, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x10, 0x00}; - break; - } + case 0x0090: case 0x7000: + case 0x7800: { - // Card reader check - 2 - // No data returned + emulate_card_reader(response, reg); break; } case 0x1000: diff --git a/rpcs3/Emu/Io/usio.h b/rpcs3/Emu/Io/usio.h index 7a83e7a8ca..d88032ed5a 100644 --- a/rpcs3/Emu/Io/usio.h +++ b/rpcs3/Emu/Io/usio.h @@ -20,6 +20,8 @@ private: void save_backup(); void translate_input_taiko(); void translate_input_tekken(); + void emulate_card_reader(std::vector& buf, u16 reg); + void tap_card(usz player); void usio_write(u8 channel, u16 reg, std::vector& data); void usio_read(u8 channel, u16 reg, u16 size); void usio_init(u8 channel, u16 reg, u16 size); @@ -34,7 +36,9 @@ private: bool test_on = false; bool test_key_pressed = false; bool coin_key_pressed = false; + bool card_tapped = false; le_t coin_counter = 0; + usz card_index = 0; }; std::array m_io_status; diff --git a/rpcs3/Emu/Io/usio_config.h b/rpcs3/Emu/Io/usio_config.h index 4c5fe6f017..0a0ecaa454 100644 --- a/rpcs3/Emu/Io/usio_config.h +++ b/rpcs3/Emu/Io/usio_config.h @@ -21,6 +21,7 @@ enum class usio_btn tekken_button3, tekken_button4, tekken_button5, + card_tapping, count }; @@ -46,6 +47,7 @@ struct cfg_usio final : public emulated_pad_config cfg_pad_btn tekken_button3{this, "Tekken Button 3", usio_btn::tekken_button3, pad_button::cross}; cfg_pad_btn tekken_button4{this, "Tekken Button 4", usio_btn::tekken_button4, pad_button::circle}; cfg_pad_btn tekken_button5{this, "Tekken Button 5", usio_btn::tekken_button5, pad_button::R1}; + cfg_pad_btn card_tapping{this, "Card Tapping", usio_btn::card_tapping, pad_button::L1}; }; struct cfg_usios final : public emulated_pads_config From c0b358003f813e28d7902cd65251c3506847619a Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 10 May 2026 16:27:18 +0300 Subject: [PATCH 086/283] Add basic guidance on AI usage for contributors --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index ac6e3cd594..8ccf4f12ce 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,16 @@ If you want to contribute as a developer, please take a look at the following pa You should also contact any of the developers in the forums or in the Discord server to learn more about the current state of the emulator. +### AI Use + +Use of AI tools for research and reverse engineering purposes is permitted. However, contributors are expected to fully own and understand all code they submit. Any communication with the team — including code, code comments, and GitHub comments — must come from the human contributor, not an AI agent acting autonomously. + +We have unfortunately seen a rise in untested and unverified AI-generated slop being submitted to this project. This wastes maintainer time and, in worse cases, such changes get merged and break functionality for all users. Repeated violations will result in a ban from the repository. Please be respectful of everyone's time. + +**Pull requests opened by AI agents or automated tools must include a disclosure in the PR description** stating the scope of AI involvement — which parts were AI-generated and what human testing or review was performed prior to submission. PRs that omit this disclosure may be closed without review. + +If you are unsure about your work, open a discussion issue to talk it through with the team, or reach out to a maintainer on [Discord](https://discord.gg/RPCS3). + ## Building See [BUILDING.md](BUILDING.md) for more information about how to setup an environment to build RPCS3. From cd7cb1cc11fdcae484fc6b2d447be96e045fed1b Mon Sep 17 00:00:00 2001 From: Arsh Kumar Singh Date: Sun, 10 May 2026 20:55:00 +0530 Subject: [PATCH 087/283] Qt: include territory in language menu labels (#18704) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes the GUI language dropdown to show territory variants when translation files include country codes. **Before:** "Portuguese" for both `pt_BR` and `pt_PT` **After:** "Portuguese (Brazil)" and "Portuguese (Portugal)" Also fixes the same ambiguity for Chinese and any other language with country variants. ## Implementation Uses `QLocale::territoryToString()` alongside the existing `QLocale::languageToString()` to construct display labels. When no territory exists (e.g., `en`, `ja`), the label is unchanged. ## Edge cases - No-territory locales (`en`, `ja`): unchanged — `QLocale::territoryToString(AnyTerritory)` returns empty, guard preserves plain language name - Territory names are locale-translated (not hardcoded English) — they follow the current UI language, same as `languageToString()` - `zh_CN` → "Chinese (China)" / `zh_TW` → "Chinese (Taiwan)" (territory-based, unlike the PS3 system language dropdown which uses Simplified/Traditional script names — this is appropriate for the GUI translator selector context) ## Test Plan - [ ] Language menu shows "Portuguese (Brazil)" when `rpcs3_pt_BR.qm` is present - [ ] Language menu shows "Chinese (China)" when `rpcs3_zh_CN.qm` is present - [ ] Plain languages (`en`, `ja`) continue to show without parens ---
Review note: territory strings in UI locale Territory names render in the current UI language (not forced English). This matches `QLocale::languageToString()` behavior and is consistent with how the rest of the menu renders. If hardcoded English labels are preferred, that can be added after review.
Fixes #18215 --- rpcs3/rpcs3qt/main_window.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 97081aca4a..28f76a63bc 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -2298,11 +2298,19 @@ void main_window::UpdateLanguageActions(const QStringList& language_codes, const { const QLocale locale = QLocale(code); const QString locale_name = QLocale::languageToString(locale.language()); + const QString territory = QLocale::territoryToString(locale.territory()); + + const bool is_unique = std::count_if(language_codes.cbegin(), language_codes.cend(), [&locale_name](const QString& code) + { + return locale_name == QLocale::languageToString(QLocale(code).language()); + }) == 1; + + const QString display_name = (!is_unique && !territory.isEmpty()) ? QString("%1 (%2)").arg(locale_name, territory) : locale_name; // create new action - QAction* act = new QAction(locale_name, this); + QAction* act = new QAction(display_name, this); act->setData(code); - act->setToolTip(locale_name); + act->setToolTip(display_name); act->setCheckable(true); act->setChecked(code == language_code); From 6b5a2f781aae6f87007b4cf7a12375b84882d0ea Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Sun, 10 May 2026 18:59:15 +0200 Subject: [PATCH 088/283] ISO: Fix missing read of remaining chunk of data on next extent, if present (#18706) --- rpcs3/Loader/ISO.cpp | 61 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/rpcs3/Loader/ISO.cpp b/rpcs3/Loader/ISO.cpp index cfd83863ef..4b7ee5141f 100644 --- a/rpcs3/Loader/ISO.cpp +++ b/rpcs3/Loader/ISO.cpp @@ -623,13 +623,14 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) // ' ' ' ' // | first sec | inner sec(s) | last sec | - const u64 max_size = std::min(size, local_extent_remaining(offset)); + u64 max_size = std::min(size, local_extent_remaining(offset)); if (max_size == 0) { return 0; } + const u64 total_size = this->size(); const u64 archive_first_offset = file_offset(offset); const u64 archive_last_offset = archive_first_offset + max_size - 1; iso_sector first_sec, last_sec; @@ -678,10 +679,19 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) { if (total_read != first_sec.size_aligned) { - iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, total_read, first_sec.size_aligned); + iso_log.error("read_at: %s: Error reading from file - O: %llu (%llu), S: %llu/%llu/%llu (%llu), TR: %llu", m_meta.name, + offset, first_sec.address_aligned, first_sec.size_aligned, max_size, size, total_size, total_read); + return 0; } + // If present, read the remaining chunk of data on next extent + if (size > max_size && (offset + max_size) < total_size) + { + iso_log.warning("read_at: %s: Extent limit reached reading from file (%llu/%llu)", m_meta.name, max_size, size); + max_size += read_at(offset + max_size, &reinterpret_cast(buffer)[max_size], size - max_size); + } + return max_size; } @@ -739,12 +749,20 @@ u64 iso_file_encrypted::read_at(u64 offset, void* buffer, u64 size) if (total_read != first_sec.size_aligned + last_sec.size_aligned + (sector_count - 2) * ISO_SECTOR_SIZE) { - iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, + iso_log.error("read_at: %s: Error reading from file - O: %llu (%llu), S: %llu/%llu/%llu (%llu), TR: %llu/%llu", m_meta.name, + offset, first_sec.address_aligned, last_sec.size_aligned, max_size, size, total_size, total_read, ISO_SECTOR_SIZE + ISO_SECTOR_SIZE + (sector_count - 2) * ISO_SECTOR_SIZE); return 0; } + // If present, read the remaining chunk of data on next extent + if (size > max_size && (offset + max_size) < total_size) + { + iso_log.warning("read_at: %s: Extent limit reached reading from file (%llu/%llu)", m_meta.name, max_size, size); + max_size += read_at(offset + max_size, &reinterpret_cast(buffer)[max_size], size - max_size); + } + return max_size; } @@ -1250,13 +1268,14 @@ u64 iso_file::read(void* buffer, u64 size) u64 iso_file::read_at(u64 offset, void* buffer, u64 size) { - const u64 max_size = std::min(size, local_extent_remaining(offset)); + u64 max_size = std::min(size, local_extent_remaining(offset)); if (max_size == 0) { return 0; } + const u64 total_size = this->size(); const u64 archive_first_offset = file_offset(offset); // If it's not a raw device @@ -1266,10 +1285,19 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) if (total_read != max_size) { - iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, total_read, max_size); + iso_log.error("read_at: %s: Error reading from file - O: %llu (%llu), S: %llu/%llu (%llu), TR: %llu", m_meta.name, + offset, archive_first_offset, max_size, size, total_size, total_read); + return 0; } + // If present, read the remaining chunk of data on next extent + if (size > max_size && (offset + max_size) < total_size) + { + iso_log.warning("read_at: %s: Extent limit reached reading from file (%llu/%llu)", m_meta.name, max_size, size); + max_size += read_at(offset + max_size, &reinterpret_cast(buffer)[max_size], size - max_size); + } + return max_size; } @@ -1320,10 +1348,19 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) { if (total_read != ISO_SECTOR_SIZE) { - iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, total_read, ISO_SECTOR_SIZE); + iso_log.error("read_at: %s: Error reading from file - O: %llu (%llu), S: %llu/%llu/%llu (%llu), TR: %llu", m_meta.name, + offset, first_sec.lba_address, ISO_SECTOR_SIZE, max_size, size, total_size, total_read); + return 0; } + // If present, read the remaining chunk of data on next extent + if (size > max_size && (offset + max_size) < total_size) + { + iso_log.warning("read_at: %s: Extent limit reached reading from file (%llu/%llu)", m_meta.name, max_size, size); + max_size += read_at(offset + max_size, &reinterpret_cast(buffer)[max_size], size - max_size); + } + return max_size; } @@ -1357,12 +1394,20 @@ u64 iso_file::read_at(u64 offset, void* buffer, u64 size) if (total_read != ISO_SECTOR_SIZE + ISO_SECTOR_SIZE + (sector_count - 2) * ISO_SECTOR_SIZE) { - iso_log.error("read_at: %s: Error reading from file (%llu/%llu)", m_meta.name, - total_read, ISO_SECTOR_SIZE + ISO_SECTOR_SIZE + (sector_count - 2) * ISO_SECTOR_SIZE); + iso_log.error("read_at: %s: Error reading from file - O: %llu (%llu), S: %llu/%llu/%llu (%llu), TR: %llu/%llu", m_meta.name, + offset, first_sec.lba_address, ISO_SECTOR_SIZE, max_size, size, total_size, + total_read, ISO_SECTOR_SIZE + ISO_SECTOR_SIZE + (sector_count - 2) * ISO_SECTOR_SIZE); return 0; } + // If present, read the remaining chunk of data on next extent + if (size > max_size && (offset + max_size) < total_size) + { + iso_log.warning("read_at: %s: Extent limit reached reading from file (%llu/%llu)", m_meta.name, max_size, size); + max_size += read_at(offset + max_size, &reinterpret_cast(buffer)[max_size], size - max_size); + } + return max_size; } From c534da86c3fc1d032be517c27f8ccaa6dd471178 Mon Sep 17 00:00:00 2001 From: Edwin Jarvis Date: Mon, 11 May 2026 15:26:07 +0800 Subject: [PATCH 089/283] gui: Remove close button from Game List dock widget Users frequently close the game list accidentally and then can't figure out how to restore it. Remove the DockWidgetClosable feature so the close button is not shown. The game list visibility can still be toggled via View > View Game List menu action. Fixes #18518 --- rpcs3/rpcs3qt/main_window.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 28f76a63bc..a78acc7964 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -3568,6 +3568,7 @@ void main_window::CreateDockWindows() m_mw->setContextMenuPolicy(Qt::PreventContextMenu); m_game_list_frame = new game_list_frame(m_gui_settings, m_emu_settings, m_persistent_settings, m_mw); + m_game_list_frame->setFeatures(m_game_list_frame->features() & ~QDockWidget::DockWidgetClosable); m_debugger_frame = new debugger_frame(m_gui_settings, m_mw); m_log_frame = new log_frame(m_gui_settings, m_mw); From 6fa7efbcc3be129fb4c3c1a92ffa4057cd527314 Mon Sep 17 00:00:00 2001 From: Edwin Jarvis Date: Mon, 11 May 2026 15:23:37 +0800 Subject: [PATCH 090/283] cellMusic: Fix shuffle always producing the same order The shuffle in step_track() used std::default_random_engine with a default (fixed) seed, causing the playlist to be 'shuffled' into the same deterministic order every time. Use std::random_device to seed the engine so each shuffle produces a genuinely random order. Fixes #18672 --- rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp b/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp index ae578d2329..42f4a0fb6f 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusicSelectionContext.cpp @@ -362,7 +362,8 @@ u32 music_selection_context::step_track(bool next) { // We reached the first or last track again. Let's shuffle! cellMusicSelectionContext.notice("step_track: Shuffling playlist..."); - auto engine = std::default_random_engine{}; + std::random_device rd; + auto engine = std::default_random_engine{rd()}; std::shuffle(std::begin(playlist), std::end(playlist), engine); } } From fb1c1eeaefe61aabad521e4ca73afe65e428a285 Mon Sep 17 00:00:00 2001 From: schm1dtmac Date: Mon, 11 May 2026 18:00:45 +0100 Subject: [PATCH 091/283] [System] Fix restart-loops upon restart-then-quit (#18723) I observed under macOS (although the bug seems platform agnostic to me) that when restarting a game, then subsequently quitting it, I'd get stuck in a 'restart loop' where the game would restart upon completely shutting down, and this would repeat every time I tried to close the game until I gave up & force quit RPCS3. Seems like for whatever reason, the use of std::move didn't actually guarantee that after_kill_callback got nuked if at all, so all I've done is take a hammer to the fucker and manually set it to nullptr after being called. Co-authored-by: Elad <18193363+elad335@users.noreply.github.com> --- rpcs3/Emu/System.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 29016a408a..d0a9e15ebc 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -4068,8 +4068,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s if (after_kill_callback) { // Make after_kill_callback empty before call - const auto callback = std::move(after_kill_callback); - callback(); + std::exchange(ensure(after_kill_callback), nullptr)(); } }); })); @@ -4125,7 +4124,7 @@ game_boot_result Emulator::Restart(bool graceful, bool reset_path) else { // Execute and empty the callback - ::as_rvalue(std::move(Emu.after_kill_callback))(); + std::exchange(ensure(Emu.after_kill_callback), nullptr)(); } return game_boot_result::no_errors; From 021f16f775fadaf5d9f2ed0584972b0bd0e4b6db Mon Sep 17 00:00:00 2001 From: Phil Coulson Date: Tue, 12 May 2026 22:02:01 +0800 Subject: [PATCH 092/283] rsx: Fix swapped width/height in NV309E_SET_FORMAT decoder --- rpcs3/Emu/RSX/rsx_decode.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/rsx_decode.h b/rpcs3/Emu/RSX/rsx_decode.h index bdb99f199f..cbd36f73d3 100644 --- a/rpcs3/Emu/RSX/rsx_decode.h +++ b/rpcs3/Emu/RSX/rsx_decode.h @@ -3775,12 +3775,12 @@ struct registers_decoder u8 sw_height_log2() const { - return bf_decoder<16, 8>(value); + return bf_decoder<24, 8>(value); } u8 sw_width_log2() const { - return bf_decoder<24, 8>(value); + return bf_decoder<16, 8>(value); } }; From 2387af0854a9bbdb39e8da61828c9fe33de04cab Mon Sep 17 00:00:00 2001 From: capriots <29807355+capriots@users.noreply.github.com> Date: Tue, 12 May 2026 20:10:08 +0200 Subject: [PATCH 093/283] cellDmux: fix state check --- rpcs3/Emu/Cell/Modules/cellDmux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/Cell/Modules/cellDmux.cpp b/rpcs3/Emu/Cell/Modules/cellDmux.cpp index 7c2fe6f1a9..20365033fb 100644 --- a/rpcs3/Emu/Cell/Modules/cellDmux.cpp +++ b/rpcs3/Emu/Cell/Modules/cellDmux.cpp @@ -997,7 +997,7 @@ error_code cellDmuxResetEs(ppu_thread& ppu, vm::ptr esHandle) return ret; } - if (dmux_status & DMUX_STOPPED) + if (!(dmux_status & DMUX_STOPPED)) { return CELL_DMUX_ERROR_SEQ; } From a9be80da6ffdb8ac9bda45d3dcdfd1ed006c7c4d Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 10 May 2026 18:05:47 +0300 Subject: [PATCH 094/283] rsx/vk: Implement software Z-buffer comparison - Useful for scenarios where desktop precision is too high --- rpcs3/Emu/RSX/NV47/HW/nv4097.cpp | 9 ++++++++ rpcs3/Emu/RSX/NV47/HW/nv4097.h | 2 ++ rpcs3/Emu/RSX/Program/GLSLCommon.cpp | 7 ++++++ rpcs3/Emu/RSX/Program/GLSLCommon.h | 6 +++-- .../GLSLSnippets/RSXProg/RSXROPEpilogue.glsl | 16 +++++++++++++ rpcs3/Emu/RSX/Program/GLSLTypes.h | 1 + rpcs3/Emu/RSX/RSXThread.cpp | 17 +++++++++++++- rpcs3/Emu/RSX/VK/VKDraw.cpp | 9 ++++++++ rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp | 23 ++++++++++++++++++- rpcs3/Emu/RSX/VK/VKFragmentProgram.h | 1 + rpcs3/Emu/RSX/gcm_enums.h | 23 ++++++++++--------- rpcs3/Emu/RSX/rsx_methods.cpp | 2 +- 12 files changed, 100 insertions(+), 16 deletions(-) diff --git a/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp b/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp index 3cc40efed1..9387f4f207 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp +++ b/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp @@ -272,6 +272,15 @@ namespace rsx } } + void set_depth_func(context* ctx, u32 reg, u32 arg) + { + if (arg != REGS(ctx)->latch) + { + set_surface_options_dirty_bit(ctx, reg, arg); + RSX(ctx)->m_graphics_state |= rsx::pipeline_state::fragment_program_state_dirty; + } + } + void set_color_mask(context* ctx, u32 reg, u32 arg) { if (arg == REGS(ctx)->latch) diff --git a/rpcs3/Emu/RSX/NV47/HW/nv4097.h b/rpcs3/Emu/RSX/NV47/HW/nv4097.h index a5a434e47d..aced1244e9 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv4097.h +++ b/rpcs3/Emu/RSX/NV47/HW/nv4097.h @@ -89,6 +89,8 @@ namespace rsx void set_aa_control(context* ctx, u32 reg, u32 arg); + void set_depth_func(context* ctx, u32 reg, u32 arg); + #define RSX(ctx) ctx->rsxthr #define REGS(ctx) (&rsx::method_registers) diff --git a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp index febb5c93bd..7749b24ac8 100644 --- a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp +++ b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp @@ -200,6 +200,8 @@ namespace glsl { "ALPHA_TEST_FUNC_LENGTH ", rsx::ROP_control_bits::ALPHA_FUNC_NUM_BITS }, { "MSAA_SAMPLE_CTRL_OFFSET ", rsx::ROP_control_bits::MSAA_SAMPLE_CTRL_OFFSET }, { "MSAA_SAMPLE_CTRL_LENGTH ", rsx::ROP_control_bits::MSAA_SAMPLE_CTRL_NUM_BITS }, + { "FRAG_DEPTH_24_BIT ", rsx::ROP_control_bits::FRAG_DEPTH_24_BIT }, + { "FRAG_DEPTH_FLOAT_BIT ", rsx::ROP_control_bits::FRAG_DEPTH_FLOAT_BIT }, { "ROP_CMD_MASK ", rsx::ROP_control_bits::ROP_CMD_MASK } }); @@ -240,6 +242,11 @@ namespace glsl { enabled_options.push_back("_ENABLE_POLYGON_STIPPLE"); } + + if (props.emulate_depth_compare) + { + enabled_options.push_back("_ENABLE_DEPTH_COMPARE"); + } } // Import common header diff --git a/rpcs3/Emu/RSX/Program/GLSLCommon.h b/rpcs3/Emu/RSX/Program/GLSLCommon.h index 81b5043fc9..b71dc8c80d 100644 --- a/rpcs3/Emu/RSX/Program/GLSLCommon.h +++ b/rpcs3/Emu/RSX/Program/GLSLCommon.h @@ -21,10 +21,12 @@ namespace rsx // Auxilliary config INT_FRAMEBUFFER_BIT = 16, MSAA_WRITE_ENABLE_BIT = 17, + FRAG_DEPTH_24_BIT = 18, + FRAG_DEPTH_FLOAT_BIT = 19, // Data - ALPHA_FUNC_OFFSET = 18, - MSAA_SAMPLE_CTRL_OFFSET = 21, + ALPHA_FUNC_OFFSET = 20, + MSAA_SAMPLE_CTRL_OFFSET = 23, // Data lengths ALPHA_FUNC_NUM_BITS = 3, diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPEpilogue.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPEpilogue.glsl index 240186cd29..6009c342e5 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPEpilogue.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPEpilogue.glsl @@ -38,6 +38,22 @@ R"( } #endif +#ifdef _ENABLE_DEPTH_COMPARE + float dstDepth = texelFetch(frag_depth, ivec2(gl_FragCoord.xy), 0).r; + float srcDepth = gl_FragCoord.z; + float scale = _test_bit(rop_control, FRAG_DEPTH_24_BIT) ? float(0xffffffu) : float(0xffffu); + + // Quantize and compare + if (abs(srcDepth - dstDepth) < (1.f / scale)) + { + gl_FragDepth = dstDepth; + } + else + { + gl_FragDepth = srcDepth; + } +#endif + #ifdef _ENABLE_PROGRAMMABLE_BLENDING switch (framebufferCount) { diff --git a/rpcs3/Emu/RSX/Program/GLSLTypes.h b/rpcs3/Emu/RSX/Program/GLSLTypes.h index 685d87fafd..468f69b10e 100644 --- a/rpcs3/Emu/RSX/Program/GLSLTypes.h +++ b/rpcs3/Emu/RSX/Program/GLSLTypes.h @@ -37,6 +37,7 @@ namespace glsl bool require_linear_to_srgb : 1; bool require_fog_read : 1; bool emulate_shadow_compare : 1; + bool emulate_depth_compare : 1; bool low_precision_tests : 1; bool disable_early_discard : 1; bool supports_native_fp16 : 1; diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index a97fa70258..fa54fe1080 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -1681,6 +1681,15 @@ namespace rsx m_frame_stats.framebuffer_stats.add(layout.width, layout.height, aa_mode); } + // Flags + if (g_cfg.video.emulate_depth_compare && + m_framebuffer_layout.zeta_address && + method_registers.depth_func() == rsx::comparison_function::equal) + { + current_fragment_program.ctrl |= RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE; + current_fragment_program.ctrl &= ~RSX_SHADER_CONTROL_DISABLE_EARLY_Z; + } + // Check if anything has changed bool really_changed = false; @@ -2153,6 +2162,12 @@ namespace rsx current_fragment_program.ctrl |= RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE; } } + + if (m_framebuffer_layout.zeta_address && + method_registers.depth_func() == rsx::comparison_function::equal) + { + current_fragment_program.ctrl |= RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE; + } } else if (m_ctx->register_state->point_sprite_enabled() && m_ctx->register_state->current_draw_clause.primitive == primitive_type::points) @@ -2321,7 +2336,7 @@ namespace rsx { m_graphics_state |= rsx::zeta_address_is_cyclic; - if (!(current_fragment_program.ctrl & (CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT | RSX_SHADER_CONTROL_META_USES_DISCARD)) && + if (!(current_fragment_program.ctrl & (CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT | RSX_SHADER_CONTROL_META_USES_DISCARD | RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE)) && m_framebuffer_layout.zeta_write_enabled) { current_fragment_program.ctrl |= RSX_SHADER_CONTROL_DISABLE_EARLY_Z; diff --git a/rpcs3/Emu/RSX/VK/VKDraw.cpp b/rpcs3/Emu/RSX/VK/VKDraw.cpp index f7dffc3029..43294b798e 100644 --- a/rpcs3/Emu/RSX/VK/VKDraw.cpp +++ b/rpcs3/Emu/RSX/VK/VKDraw.cpp @@ -768,6 +768,15 @@ bool VKGSRender::bind_texture_env() m_vs_binding_table->vtex_location[i]); } + if (current_fragment_program.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE) + { + auto ds = ensure(m_rtts.m_bound_depth_stencil.second); + vk::insert_texture_barrier(*m_current_command_buffer, ds, VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT, true); + + auto view = ds->get_view(rsx::default_remap_vector, VK_IMAGE_ASPECT_DEPTH_BIT); + m_program->bind_uniform({ *view, vk::null_sampler() }, vk::glsl::binding_set_index_fragment, m_fs_binding_table->frag_depth_input_location); + } + return out_of_memory; } diff --git a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp index 853f9f8683..7f7168322a 100644 --- a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp @@ -91,6 +91,11 @@ void VKFragmentDecompilerThread::prepareBindingTable() } } } + + if (m_prog.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE) + { + vk_prog->binding_table.frag_depth_input_location = location++; + } } void VKFragmentDecompilerThread::insertHeader(std::stringstream & OS) @@ -240,6 +245,20 @@ void VKFragmentDecompilerThread::insertConstants(std::stringstream & OS) } } + if (m_prog.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE) + { + OS << "layout(set=" << vk::glsl::binding_set_index_fragment << ", binding=" << vk_prog->binding_table.frag_depth_input_location << ") uniform sampler2D frag_depth;\n"; + + auto in = vk::glsl::program_input::make( + glsl::glsl_fragment_program, + "frag_depth", + vk::glsl::input_type_texture, + vk::glsl::binding_set_index_fragment, + vk_prog->binding_table.frag_depth_input_location + ); + inputs.push_back(in); + } + // Draw params are always provided by vertex program. Instead of pointer chasing, they're provided as varyings. if (!(m_prog.ctrl & RSX_SHADER_CONTROL_INTERPRETER_MODEL)) { @@ -341,6 +360,7 @@ void VKFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS) m_shader_props.require_shadowProj_ops = properties.shadow_sampler_mask != 0 && properties.has_texShadowProj; m_shader_props.require_alpha_kill = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL); m_shader_props.require_color_format_convert = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT); + m_shader_props.emulate_depth_compare = !!(m_prog.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE); // Declare global constants if (m_shader_props.require_fog_read) @@ -465,7 +485,8 @@ void VKFragmentDecompilerThread::insertMainEnd(std::stringstream & OS) OS << "void main()\n"; OS << "{\n"; - if (m_prog.ctrl & RSX_SHADER_CONTROL_ALPHA_TEST) + if ((m_prog.ctrl & RSX_SHADER_CONTROL_ALPHA_TEST) || + (m_prog.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE)) { OS << " const uint rop_control = fs_contexts[_fs_context_offset].rop_control;\n" diff --git a/rpcs3/Emu/RSX/VK/VKFragmentProgram.h b/rpcs3/Emu/RSX/VK/VKFragmentProgram.h index 50469aeca4..0412561340 100644 --- a/rpcs3/Emu/RSX/VK/VKFragmentProgram.h +++ b/rpcs3/Emu/RSX/VK/VKFragmentProgram.h @@ -75,6 +75,7 @@ public: u32 polygon_stipple_params_location = umax; // Polygon stipple settings u32 ftex_location[16]; // Texture locations array u32 ftex_stencil_location[16]; // Texture stencil mirror array + u32 frag_depth_input_location = umax; // Fragment depth compare } binding_table; diff --git a/rpcs3/Emu/RSX/gcm_enums.h b/rpcs3/Emu/RSX/gcm_enums.h index 542151e340..cd27a76a78 100644 --- a/rpcs3/Emu/RSX/gcm_enums.h +++ b/rpcs3/Emu/RSX/gcm_enums.h @@ -454,21 +454,22 @@ namespace gcm RSX_SHADER_CONTROL_UNKNOWN1 = 0x8000, // seemingly set when srgb packer is used?? // Custom - RSX_SHADER_CONTROL_ATTRIBUTE_INTERPOLATION = 0x0010000, // Rasterizing triangles and not lines or points - RSX_SHADER_CONTROL_INSTANCED_CONSTANTS = 0x0020000, // Support instance ID offsets when loading constants - RSX_SHADER_CONTROL_INTERPRETER_MODEL = 0x0040000, // Compile internals expecting interpreter + RSX_SHADER_CONTROL_ATTRIBUTE_INTERPOLATION = 0x00010000, // Rasterizing triangles and not lines or points + RSX_SHADER_CONTROL_INSTANCED_CONSTANTS = 0x00020000, // Support instance ID offsets when loading constants + RSX_SHADER_CONTROL_INTERPRETER_MODEL = 0x00040000, // Compile internals expecting interpreter - RSX_SHADER_CONTROL_8BIT_FRAMEBUFFER = 0x0080000, // Quantize outputs to 8-bit FBO - RSX_SHADER_CONTROL_SRGB_FRAMEBUFFER = 0x0100000, // Outputs are SRGB. We could reuse UNKNOWN1 but we just keep the namespaces separate. + RSX_SHADER_CONTROL_8BIT_FRAMEBUFFER = 0x00080000, // Quantize outputs to 8-bit FBO + RSX_SHADER_CONTROL_SRGB_FRAMEBUFFER = 0x00100000, // Outputs are SRGB. We could reuse UNKNOWN1 but we just keep the namespaces separate. - RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL = 0x0200000, // Uses alpha kill on texture input - RSX_SHADER_CONTROL_ALPHA_TEST = 0x0400000, // Uses alpha test on the outputs - RSX_SHADER_CONTROL_POLYGON_STIPPLE = 0x0800000, // Uses polygon stipple for dithered rendering - RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE = 0x1000000, // Alpha to coverage + RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL = 0x00200000, // Uses alpha kill on texture input + RSX_SHADER_CONTROL_ALPHA_TEST = 0x00400000, // Uses alpha test on the outputs + RSX_SHADER_CONTROL_POLYGON_STIPPLE = 0x00800000, // Uses polygon stipple for dithered rendering + RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE = 0x01000000, // Alpha to coverage - RSX_SHADER_CONTROL_DISABLE_EARLY_Z = 0x2000000, // Do not allow early-Z optimizations on this shader + RSX_SHADER_CONTROL_DISABLE_EARLY_Z = 0x02000000, // Do not allow early-Z optimizations on this shader - RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT = 0x4000000, // Allow format conversions (BX2, SNORM, SRGB, RENORM) + RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT = 0x04000000, // Allow format conversions (BX2, SNORM, SRGB, RENORM) + RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE = 0x08000000, // Emulate depth comparisons // Meta RSX_SHADER_CONTROL_META_USES_DISCARD = (RSX_SHADER_CONTROL_USES_KIL | RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL | RSX_SHADER_CONTROL_ALPHA_TEST | RSX_SHADER_CONTROL_POLYGON_STIPPLE | RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE) diff --git a/rpcs3/Emu/RSX/rsx_methods.cpp b/rpcs3/Emu/RSX/rsx_methods.cpp index 03bbd1e52a..2c2552e088 100644 --- a/rpcs3/Emu/RSX/rsx_methods.cpp +++ b/rpcs3/Emu/RSX/rsx_methods.cpp @@ -1686,7 +1686,7 @@ namespace rsx bind(NV4097_SET_ZPASS_PIXEL_COUNT_ENABLE, nv4097::set_zcull_pixel_count_enable); bind(NV4097_CLEAR_ZCULL_SURFACE, nv4097::clear_zcull); bind(NV4097_SET_DEPTH_TEST_ENABLE, nv4097::set_surface_options_dirty_bit); - bind(NV4097_SET_DEPTH_FUNC, nv4097::set_surface_options_dirty_bit); + bind(NV4097_SET_DEPTH_FUNC, nv4097::set_depth_func); bind(NV4097_SET_DEPTH_MASK, nv4097::set_surface_options_dirty_bit); bind(NV4097_SET_COLOR_MASK, nv4097::set_color_mask); bind(NV4097_SET_COLOR_MASK_MRT, nv4097::set_surface_options_dirty_bit); From 1d71854c7f48dc55ea4681dd62693f57dbd8e66a Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 10 May 2026 19:12:45 +0300 Subject: [PATCH 095/283] rsx/qt: Add depth compare emulation to the advanced tab --- rpcs3/Emu/RSX/RSXThread.cpp | 3 ++- rpcs3/Emu/system_config.h | 1 + rpcs3/rpcs3qt/emu_settings_type.cpp | 1 + rpcs3/rpcs3qt/emu_settings_type.h | 1 + rpcs3/rpcs3qt/settings_dialog.cpp | 3 +++ rpcs3/rpcs3qt/settings_dialog.ui | 7 +++++++ rpcs3/rpcs3qt/tooltips.h | 1 + 7 files changed, 16 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index fa54fe1080..ddb9e0848d 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -2163,7 +2163,8 @@ namespace rsx } } - if (m_framebuffer_layout.zeta_address && + if (g_cfg.video.emulate_depth_compare && + m_framebuffer_layout.zeta_address && method_registers.depth_func() == rsx::comparison_function::equal) { current_fragment_program.ctrl |= RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE; diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 08004f5a46..ab0f3fffd8 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -147,6 +147,7 @@ struct cfg_root : cfg::node cfg::_bool disable_video_output{ this, "Disable Video Output", false, true }; cfg::_bool disable_vertex_cache{ this, "Disable Vertex Cache", false }; cfg::_bool disable_FIFO_reordering{ this, "Disable FIFO Reordering", false }; + cfg::_bool emulate_depth_compare{ this, "Emulate Special Depth Comparison", false }; cfg::_bool frame_skip_enabled{ this, "Enable Frame Skip", false, true }; cfg::_bool force_cpu_blit_processing{ this, "Force CPU Blit", false, true }; // Debugging option cfg::_bool disable_on_disk_shader_cache{ this, "Disable On-Disk Shader Cache", false }; diff --git a/rpcs3/rpcs3qt/emu_settings_type.cpp b/rpcs3/rpcs3qt/emu_settings_type.cpp index 0307f764d5..ff9340aff6 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.cpp +++ b/rpcs3/rpcs3qt/emu_settings_type.cpp @@ -84,6 +84,7 @@ const std::map settings_location = { emu_settings_type::DisableOcclusionQueries, get_cfg_location(local_cfg.video.disable_zcull_queries) }, { emu_settings_type::DisableVideoOutput, get_cfg_location(local_cfg.video.disable_video_output) }, { emu_settings_type::DisableFIFOReordering, get_cfg_location(local_cfg.video.disable_FIFO_reordering) }, + { emu_settings_type::EmulateDepthCompare, get_cfg_location(local_cfg.video.emulate_depth_compare) }, { emu_settings_type::StereoRenderEnabled, get_cfg_location(local_cfg.video.stereo_enabled) }, { emu_settings_type::StereoRenderMode, get_cfg_location(local_cfg.video.stereo_render_mode) }, { emu_settings_type::ScreenSize, get_cfg_location(local_cfg.video.screen_size) }, diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index fbc3996e03..c22f111c76 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -79,6 +79,7 @@ enum class emu_settings_type DisableOcclusionQueries, DisableVideoOutput, DisableFIFOReordering, + EmulateDepthCompare, StrictTextureFlushing, ShaderPrecisionQuality, StereoRenderEnabled, diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index dddb803c4f..6194a85fdb 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -2457,6 +2457,9 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceCheckBox(ui->disableVertexCache, emu_settings_type::DisableVertexCache); SubscribeTooltip(ui->disableVertexCache, tooltips.settings.disable_vertex_cache); + m_emu_settings->EnhanceCheckBox(ui->emulateDepthCompare, emu_settings_type::EmulateDepthCompare); + SubscribeTooltip(ui->emulateDepthCompare, tooltips.settings.emulate_depth_compare); + m_emu_settings->EnhanceCheckBox(ui->forceHwMSAAResolve, emu_settings_type::ForceHwMSAAResolve); SubscribeTooltip(ui->forceHwMSAAResolve, tooltips.settings.force_hw_MSAA); diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index d5024f4bef..8e674722c0 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -2727,6 +2727,13 @@
+ + + + Emulate Special Depth Comparison + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index dba2bd5c1d..760bfc1509 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -42,6 +42,7 @@ public: const QString disable_vertex_cache = tr("Disables the vertex cache.\nMight resolve missing or flickering graphics output.\nMay degrade performance."); const QString disable_async_host_mm = tr("Force host memory management calls to be inlined instead of handled asynchronously.\nThis can cause severe performance degradation and stuttering in some games.\nThis option is only needed by developers to debug problems with texture cache memory protection."); const QString disable_spin_optimization = tr("Disable SPU GETLLAR spin optimization.\nThis can cause severe performance degradation and stuttering in many games.\nThis option is only needed for a select number of games."); + const QString emulate_depth_compare = tr("Emulate depth comparison operations where desktop hardware behavior differs from PS3, usually EQUAL comparison modes.\nFixes excessive shadow flickering and Z-fighting in some games.\nThis is most obvious in some games where the Z prepass depth format is different from the rasterization depth format, a scenario that will never work out correctly on compliant desktop hardware."); const QString enable_spu_events_busy_loop = tr("Enable SPU RdEventStat spin.\nThis increases CPU usage, this setting is beneficial for high-threaded CPUs (12+) with select number of games."); const QString zcull_operation_mode = tr("Changes ZCULL report synchronization behaviour. Experiment to find the best option for your game. Approximate mode is recommended for most games.\n· Precise is the most accurate to PS3 behaviour. Required for accurate visuals in some titles such as Demon's Souls and The Darkness.\n· Approximate is a much faster way to generate occlusion data which may not always match what the PS3 would generate. Works well with most PS3 games.\n· Relaxed changes the synchronization method completely and can greatly improve performance in some games or completely break others."); const QString max_spurs_threads = tr("Limits the maximum number of SPURS threads in each thread group.\nMay improve performance in some cases, especially on systems with limited number of hardware threads.\nLimiting the number of threads is likely to cause crashes; it's recommended to keep this at the default value."); From 0064ecb85b9de883daecaf9ebaddff8cd38a3c74 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 10 May 2026 20:17:21 +0300 Subject: [PATCH 096/283] gl: Implement emulated Z comparison support --- rpcs3/Emu/RSX/GL/GLDraw.cpp | 9 +++++++++ rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp | 6 ++++++ rpcs3/Emu/RSX/GL/GLProgramBuffer.h | 7 +++++++ rpcs3/Emu/RSX/GL/GLTextureCache.h | 8 ++------ rpcs3/Emu/RSX/GL/glutils/barriers.h | 16 ++++++++++++++++ rpcs3/GLGSRender.vcxproj | 1 + rpcs3/GLGSRender.vcxproj.filters | 3 +++ 7 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 rpcs3/Emu/RSX/GL/glutils/barriers.h diff --git a/rpcs3/Emu/RSX/GL/GLDraw.cpp b/rpcs3/Emu/RSX/GL/GLDraw.cpp index c57eba811e..57a9b08a17 100644 --- a/rpcs3/Emu/RSX/GL/GLDraw.cpp +++ b/rpcs3/Emu/RSX/GL/GLDraw.cpp @@ -543,6 +543,15 @@ void GLGSRender::bind_texture_env() cmd->bind_texture(GL_VERTEX_TEXTURES_START + i, GL_TEXTURE_2D, GL_NONE); } } + + if (current_fragment_program.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE) + { + ensure(m_rtts.m_bound_depth_stencil.first, "Invalid FBO setup"); + gl::insert_texture_barrier(); + + auto view = m_rtts.m_bound_depth_stencil.second->get_view(rsx::default_remap_vector, gl::image_aspect::depth); + view->bind(cmd, GL_TEMP_IMAGE_SLOT(0)); + } } void GLGSRender::bind_interpreter_texture_env() diff --git a/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp b/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp index 5271390af9..efac22b3ed 100644 --- a/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp @@ -167,6 +167,11 @@ void GLFragmentDecompilerThread::insertConstants(std::stringstream & OS) } } + if (m_prog.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE) + { + OS << "uniform sampler2D frag_depth;\n"; + } + OS << "\n"; if (!properties.constant_offsets.empty()) @@ -238,6 +243,7 @@ void GLFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS) m_shader_props.require_shadowProj_ops = properties.shadow_sampler_mask != 0 && properties.has_texShadowProj; m_shader_props.require_alpha_kill = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL); m_shader_props.require_color_format_convert = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT); + m_shader_props.emulate_depth_compare = !!(m_prog.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE); glsl::insert_glsl_legacy_function(OS, m_shader_props); } diff --git a/rpcs3/Emu/RSX/GL/GLProgramBuffer.h b/rpcs3/Emu/RSX/GL/GLProgramBuffer.h index 8dd34caacd..615e2e4b6d 100644 --- a/rpcs3/Emu/RSX/GL/GLProgramBuffer.h +++ b/rpcs3/Emu/RSX/GL/GLProgramBuffer.h @@ -101,6 +101,13 @@ struct GLTraits // Bind locations 0 and 1 to the stream buffers program->uniforms[0] = GL_STREAM_BUFFER_START + 0; program->uniforms[1] = GL_STREAM_BUFFER_START + 1; + + // Optional inputs + int location = 0; + if (program->uniforms.has_location("frag_depth", &location)) + { + program->uniforms[location] = GL_TEMP_IMAGE_SLOT(0); + } }; auto pipeline = compiler->compile(flags, post_create_func, post_link_func, callback); diff --git a/rpcs3/Emu/RSX/GL/GLTextureCache.h b/rpcs3/Emu/RSX/GL/GLTextureCache.h index 93c0ba2f5c..48a4ba98a0 100644 --- a/rpcs3/Emu/RSX/GL/GLTextureCache.h +++ b/rpcs3/Emu/RSX/GL/GLTextureCache.h @@ -2,6 +2,7 @@ #include "Emu/RSX/GL/GLTexture.h" #include "GLRenderTargets.h" +#include "glutils/barriers.h" #include "glutils/blitter.h" #include "glutils/sync.hpp" @@ -804,12 +805,7 @@ namespace gl void insert_texture_barrier(gl::command_context&, gl::texture*, bool) override { - auto &caps = gl::get_driver_caps(); - - if (caps.ARB_texture_barrier_supported) - glTextureBarrier(); - else if (caps.NV_texture_barrier_supported) - glTextureBarrierNV(); + gl::insert_texture_barrier(); } bool render_target_format_is_compatible(gl::texture* tex, u32 gcm_format) override diff --git a/rpcs3/Emu/RSX/GL/glutils/barriers.h b/rpcs3/Emu/RSX/GL/glutils/barriers.h new file mode 100644 index 0000000000..920e4c7088 --- /dev/null +++ b/rpcs3/Emu/RSX/GL/glutils/barriers.h @@ -0,0 +1,16 @@ +#pragma once + +#include "common.h" + +namespace gl +{ + static inline void insert_texture_barrier() + { + auto& caps = gl::get_driver_caps(); + + if (caps.ARB_texture_barrier_supported) + glTextureBarrier(); + else if (caps.NV_texture_barrier_supported) + glTextureBarrierNV(); + } +} diff --git a/rpcs3/GLGSRender.vcxproj b/rpcs3/GLGSRender.vcxproj index dbaffc5d00..8d6ba02dd9 100644 --- a/rpcs3/GLGSRender.vcxproj +++ b/rpcs3/GLGSRender.vcxproj @@ -61,6 +61,7 @@ + diff --git a/rpcs3/GLGSRender.vcxproj.filters b/rpcs3/GLGSRender.vcxproj.filters index 4866b14643..c4fe752d10 100644 --- a/rpcs3/GLGSRender.vcxproj.filters +++ b/rpcs3/GLGSRender.vcxproj.filters @@ -122,6 +122,9 @@ + + glutils +
From 6da3021158b98413f2e506edb7f39d3291ab03b4 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 11 May 2026 01:01:02 +0300 Subject: [PATCH 097/283] rsx: Make emulated Z operations compatible with MSAA --- rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp | 7 ++- rpcs3/Emu/RSX/GL/GLRenderTargets.cpp | 3 +- rpcs3/Emu/RSX/NV47/HW/nv4097.cpp | 9 --- rpcs3/Emu/RSX/NV47/HW/nv4097.h | 2 - rpcs3/Emu/RSX/Program/GLSLCommon.cpp | 5 ++ .../GLSLSnippets/RSXProg/RSXROPEpilogue.glsl | 4 ++ rpcs3/Emu/RSX/Program/GLSLTypes.h | 1 + rpcs3/Emu/RSX/RSXThread.cpp | 58 ++++++++++++++----- rpcs3/Emu/RSX/RSXThread.h | 8 ++- rpcs3/Emu/RSX/VK/VKDraw.cpp | 12 +++- rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp | 7 ++- rpcs3/Emu/RSX/gcm_enums.h | 40 +++++++------ rpcs3/Emu/RSX/rsx_methods.cpp | 2 +- 13 files changed, 104 insertions(+), 54 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp b/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp index efac22b3ed..ab63cb2ea1 100644 --- a/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp @@ -169,7 +169,11 @@ void GLFragmentDecompilerThread::insertConstants(std::stringstream & OS) if (m_prog.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE) { - OS << "uniform sampler2D frag_depth;\n"; + const auto frag_depth_type = (m_prog.ctrl & RSX_SHADER_CONTROL_MULTISAMPLED_ZBUFFER) + ? "sampler2DMS" + : "sampler2D"; + + OS << "uniform " << frag_depth_type << " frag_depth;\n"; } OS << "\n"; @@ -244,6 +248,7 @@ void GLFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS) m_shader_props.require_alpha_kill = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL); m_shader_props.require_color_format_convert = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT); m_shader_props.emulate_depth_compare = !!(m_prog.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE); + m_shader_props.depth_buffer_multisampled = !!(m_prog.ctrl & RSX_SHADER_CONTROL_MULTISAMPLED_ZBUFFER); glsl::insert_glsl_legacy_function(OS, m_shader_props); } diff --git a/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp b/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp index 34c2ca72d3..1a59f0438b 100644 --- a/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp +++ b/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp @@ -116,7 +116,8 @@ void GLGSRender::init_buffers(rsx::framebuffer_creation_context context, bool /* rsx::rtt_config_dirty | rsx::rtt_config_contested | rsx::rtt_config_valid | - rsx::rtt_cache_state_dirty); + rsx::rtt_cache_state_dirty | + rsx::pipeline_config_dirty); get_framebuffer_layout(context, m_framebuffer_layout); if (!m_graphics_state.test(rsx::rtt_config_valid)) diff --git a/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp b/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp index 9387f4f207..3cc40efed1 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp +++ b/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp @@ -272,15 +272,6 @@ namespace rsx } } - void set_depth_func(context* ctx, u32 reg, u32 arg) - { - if (arg != REGS(ctx)->latch) - { - set_surface_options_dirty_bit(ctx, reg, arg); - RSX(ctx)->m_graphics_state |= rsx::pipeline_state::fragment_program_state_dirty; - } - } - void set_color_mask(context* ctx, u32 reg, u32 arg) { if (arg == REGS(ctx)->latch) diff --git a/rpcs3/Emu/RSX/NV47/HW/nv4097.h b/rpcs3/Emu/RSX/NV47/HW/nv4097.h index aced1244e9..a5a434e47d 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv4097.h +++ b/rpcs3/Emu/RSX/NV47/HW/nv4097.h @@ -89,8 +89,6 @@ namespace rsx void set_aa_control(context* ctx, u32 reg, u32 arg); - void set_depth_func(context* ctx, u32 reg, u32 arg); - #define RSX(ctx) ctx->rsxthr #define REGS(ctx) (&rsx::method_registers) diff --git a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp index 7749b24ac8..4c6d8625fa 100644 --- a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp +++ b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp @@ -247,6 +247,11 @@ namespace glsl { enabled_options.push_back("_ENABLE_DEPTH_COMPARE"); } + + if (props.depth_buffer_multisampled) + { + enabled_options.push_back("_ENABLE_DEPTH_BUFFER_MULTISAMPLED"); + } } // Import common header diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPEpilogue.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPEpilogue.glsl index 6009c342e5..af2df8115e 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPEpilogue.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXROPEpilogue.glsl @@ -39,7 +39,11 @@ R"( #endif #ifdef _ENABLE_DEPTH_COMPARE +#ifdef _ENABLE_DEPTH_BUFFER_MULTISAMPLED + float dstDepth = texelFetch(frag_depth, ivec2(gl_FragCoord.xy), gl_SampleID).r; +#else float dstDepth = texelFetch(frag_depth, ivec2(gl_FragCoord.xy), 0).r; +#endif float srcDepth = gl_FragCoord.z; float scale = _test_bit(rop_control, FRAG_DEPTH_24_BIT) ? float(0xffffffu) : float(0xffffu); diff --git a/rpcs3/Emu/RSX/Program/GLSLTypes.h b/rpcs3/Emu/RSX/Program/GLSLTypes.h index 468f69b10e..a470ad5e3b 100644 --- a/rpcs3/Emu/RSX/Program/GLSLTypes.h +++ b/rpcs3/Emu/RSX/Program/GLSLTypes.h @@ -38,6 +38,7 @@ namespace glsl bool require_fog_read : 1; bool emulate_shadow_compare : 1; bool emulate_depth_compare : 1; + bool depth_buffer_multisampled : 1; bool low_precision_tests : 1; bool disable_early_discard : 1; bool supports_native_fp16 : 1; diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index ddb9e0848d..cfd8a58dc1 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -1681,15 +1681,6 @@ namespace rsx m_frame_stats.framebuffer_stats.add(layout.width, layout.height, aa_mode); } - // Flags - if (g_cfg.video.emulate_depth_compare && - m_framebuffer_layout.zeta_address && - method_registers.depth_func() == rsx::comparison_function::equal) - { - current_fragment_program.ctrl |= RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE; - current_fragment_program.ctrl &= ~RSX_SHADER_CONTROL_DISABLE_EARLY_Z; - } - // Check if anything has changed bool really_changed = false; @@ -1975,6 +1966,36 @@ namespace rsx return true; } + rsx::flags32_t thread::get_fragment_program_export_config() + { + if (!g_cfg.video.emulate_depth_compare) [[ likely ]] + { + return 0; + } + + if (m_ctx->register_state->current_draw_clause.classify_mode() != primitive_class::polygon) + { + return 0; + } + + u32 expected_ctrl = 0; + + if (m_framebuffer_layout.zeta_address && + m_ctx->register_state->depth_test_enabled() && + m_ctx->register_state->depth_func() == rsx::comparison_function::equal) + { + expected_ctrl |= RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE; + + if (backend_config.supports_hw_msaa && + m_ctx->register_state->surface_antialias() != rsx::surface_antialiasing::center_1_sample) + { + expected_ctrl |= RSX_SHADER_CONTROL_MULTISAMPLED_ZBUFFER; + } + } + + return expected_ctrl; + } + void thread::prefetch_fragment_program() { if (!m_graphics_state.test(rsx::pipeline_state::fragment_program_ucode_dirty)) @@ -2069,6 +2090,18 @@ namespace rsx { m_program_cache_hint.invalidate(m_graphics_state.load()); + constexpr u32 fs_export_config_mask = (RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE | RSX_SHADER_CONTROL_MULTISAMPLED_ZBUFFER); + if (u32 export_ctrl = get_fragment_program_export_config(); + (current_fragment_program.ctrl & fs_export_config_mask) != export_ctrl) + { + // Update control bits for immediate consumers + current_fragment_program.ctrl &= ~fs_export_config_mask; + current_fragment_program.ctrl |= export_ctrl; + + // Signal backend to reload pipeline + m_graphics_state.set(rsx::pipeline_state::fragment_state_dirty); + } + prefetch_vertex_program(); prefetch_fragment_program(); } @@ -2163,12 +2196,7 @@ namespace rsx } } - if (g_cfg.video.emulate_depth_compare && - m_framebuffer_layout.zeta_address && - method_registers.depth_func() == rsx::comparison_function::equal) - { - current_fragment_program.ctrl |= RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE; - } + current_fragment_program.ctrl |= get_fragment_program_export_config(); } else if (m_ctx->register_state->point_sprite_enabled() && m_ctx->register_state->current_draw_clause.primitive == primitive_type::points) diff --git a/rpcs3/Emu/RSX/RSXThread.h b/rpcs3/Emu/RSX/RSXThread.h index 2438fc0f57..30ff3bd346 100644 --- a/rpcs3/Emu/RSX/RSXThread.h +++ b/rpcs3/Emu/RSX/RSXThread.h @@ -280,11 +280,13 @@ namespace rsx // Prefetch and analyze the currently active vertex program ucode void prefetch_vertex_program(); + // Update fragment program export configuration. Can invalidate the current program. + rsx::flags32_t get_fragment_program_export_config(); + + // Gets the current vertex program and associated state. Can invalidate the bound progam. void get_current_vertex_program(const std::array, rsx::limits::vertex_textures_count>& sampler_descriptors); - /** - * Gets current fragment program and associated fragment state - */ + // Gets current fragment program and associated fragment state. Can invalidate the bound program. void get_current_fragment_program(const std::array, rsx::limits::fragment_textures_count>& sampler_descriptors); public: diff --git a/rpcs3/Emu/RSX/VK/VKDraw.cpp b/rpcs3/Emu/RSX/VK/VKDraw.cpp index 43294b798e..8a6789213b 100644 --- a/rpcs3/Emu/RSX/VK/VKDraw.cpp +++ b/rpcs3/Emu/RSX/VK/VKDraw.cpp @@ -607,6 +607,16 @@ void VKGSRender::load_texture_env() m_samplers_dirty.store(false); + if (current_fragment_program.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE) + { + // Transition our FBO to a loop-friendly format. + // We can also convert it into an input attachment, but for now this is easier. + auto ds = ensure(m_rtts.m_bound_depth_stencil.second, "Invalid FS export configuration."); + ds->texture_barrier(*m_current_command_buffer); + + check_for_cyclic_refs = true; + } + if (check_for_cyclic_refs) { // Regenerate renderpass key @@ -771,8 +781,6 @@ bool VKGSRender::bind_texture_env() if (current_fragment_program.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE) { auto ds = ensure(m_rtts.m_bound_depth_stencil.second); - vk::insert_texture_barrier(*m_current_command_buffer, ds, VK_IMAGE_LAYOUT_ATTACHMENT_FEEDBACK_LOOP_OPTIMAL_EXT, true); - auto view = ds->get_view(rsx::default_remap_vector, VK_IMAGE_ASPECT_DEPTH_BIT); m_program->bind_uniform({ *view, vk::null_sampler() }, vk::glsl::binding_set_index_fragment, m_fs_binding_table->frag_depth_input_location); } diff --git a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp index 7f7168322a..fda0690d33 100644 --- a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp @@ -247,7 +247,11 @@ void VKFragmentDecompilerThread::insertConstants(std::stringstream & OS) if (m_prog.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE) { - OS << "layout(set=" << vk::glsl::binding_set_index_fragment << ", binding=" << vk_prog->binding_table.frag_depth_input_location << ") uniform sampler2D frag_depth;\n"; + const auto frag_depth_type = (m_prog.ctrl & RSX_SHADER_CONTROL_MULTISAMPLED_ZBUFFER) + ? "sampler2DMS" + : "sampler2D"; + + OS << "layout(set=" << vk::glsl::binding_set_index_fragment << ", binding=" << vk_prog->binding_table.frag_depth_input_location << ") uniform " << frag_depth_type << " frag_depth;\n"; auto in = vk::glsl::program_input::make( glsl::glsl_fragment_program, @@ -361,6 +365,7 @@ void VKFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS) m_shader_props.require_alpha_kill = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL); m_shader_props.require_color_format_convert = !!(m_prog.ctrl & RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT); m_shader_props.emulate_depth_compare = !!(m_prog.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE); + m_shader_props.depth_buffer_multisampled = !!(m_prog.ctrl & RSX_SHADER_CONTROL_MULTISAMPLED_ZBUFFER); // Declare global constants if (m_shader_props.require_fog_read) diff --git a/rpcs3/Emu/RSX/gcm_enums.h b/rpcs3/Emu/RSX/gcm_enums.h index cd27a76a78..aaae097684 100644 --- a/rpcs3/Emu/RSX/gcm_enums.h +++ b/rpcs3/Emu/RSX/gcm_enums.h @@ -443,33 +443,35 @@ namespace gcm enum { - CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT = 0xe, ///< shader program exports the depth of the shaded fragment - CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS = 0x40, ///< shader program exports 32 bits registers values (instead of 16 bits ones) + CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT = 0x0000000e, ///< shader program exports the depth of the shaded fragment + CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS = 0x00000040, ///< shader program exports 32 bits registers values (instead of 16 bits ones) // Other known flags - RSX_SHADER_CONTROL_USED_REGS_MASK = 0xf, - RSX_SHADER_CONTROL_USED_TEMP_REGS_MASK = 0xff << 24, - RSX_SHADER_CONTROL_USES_KIL = 0x80, // program uses KIL op - RSX_SHADER_CONTROL_UNKNOWN0 = 0x400, // seemingly always set - RSX_SHADER_CONTROL_UNKNOWN1 = 0x8000, // seemingly set when srgb packer is used?? + RSX_SHADER_CONTROL_USED_REGS_MASK = 0x0000000f, + RSX_SHADER_CONTROL_USED_TEMP_REGS_MASK = 0xff000000, + + RSX_SHADER_CONTROL_USES_KIL = 0x00000080, // program uses KIL op + RSX_SHADER_CONTROL_UNKNOWN0 = 0x00000400, // seemingly always set + RSX_SHADER_CONTROL_UNKNOWN1 = 0x00008000, // seemingly set when srgb packer is used?? // Custom - RSX_SHADER_CONTROL_ATTRIBUTE_INTERPOLATION = 0x00010000, // Rasterizing triangles and not lines or points - RSX_SHADER_CONTROL_INSTANCED_CONSTANTS = 0x00020000, // Support instance ID offsets when loading constants - RSX_SHADER_CONTROL_INTERPRETER_MODEL = 0x00040000, // Compile internals expecting interpreter + RSX_SHADER_CONTROL_ATTRIBUTE_INTERPOLATION = 0x00010000, // Rasterizing triangles and not lines or points + RSX_SHADER_CONTROL_INSTANCED_CONSTANTS = 0x00020000, // Support instance ID offsets when loading constants + RSX_SHADER_CONTROL_INTERPRETER_MODEL = 0x00040000, // Compile internals expecting interpreter - RSX_SHADER_CONTROL_8BIT_FRAMEBUFFER = 0x00080000, // Quantize outputs to 8-bit FBO - RSX_SHADER_CONTROL_SRGB_FRAMEBUFFER = 0x00100000, // Outputs are SRGB. We could reuse UNKNOWN1 but we just keep the namespaces separate. + RSX_SHADER_CONTROL_8BIT_FRAMEBUFFER = 0x00080000, // Quantize outputs to 8-bit FBO + RSX_SHADER_CONTROL_SRGB_FRAMEBUFFER = 0x00100000, // Outputs are SRGB. We could reuse UNKNOWN1 but we just keep the namespaces separate. - RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL = 0x00200000, // Uses alpha kill on texture input - RSX_SHADER_CONTROL_ALPHA_TEST = 0x00400000, // Uses alpha test on the outputs - RSX_SHADER_CONTROL_POLYGON_STIPPLE = 0x00800000, // Uses polygon stipple for dithered rendering - RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE = 0x01000000, // Alpha to coverage + RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL = 0x00200000, // Uses alpha kill on texture input + RSX_SHADER_CONTROL_ALPHA_TEST = 0x00400000, // Uses alpha test on the outputs + RSX_SHADER_CONTROL_POLYGON_STIPPLE = 0x00800000, // Uses polygon stipple for dithered rendering + RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE = 0x01000000, // Alpha to coverage - RSX_SHADER_CONTROL_DISABLE_EARLY_Z = 0x02000000, // Do not allow early-Z optimizations on this shader + RSX_SHADER_CONTROL_DISABLE_EARLY_Z = 0x02000000, // Do not allow early-Z optimizations on this shader - RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT = 0x04000000, // Allow format conversions (BX2, SNORM, SRGB, RENORM) - RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE = 0x08000000, // Emulate depth comparisons + RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT = 0x04000000, // Allow format conversions (BX2, SNORM, SRGB, RENORM) + RSX_SHADER_CONTROL_EMULATE_DEPTH_COMPARE = 0x08000000, // Emulate depth comparisons + RSX_SHADER_CONTROL_MULTISAMPLED_ZBUFFER = 0x10000000, // Z buffer is multisampled. Only affects depth comparison behavior at this time. // Meta RSX_SHADER_CONTROL_META_USES_DISCARD = (RSX_SHADER_CONTROL_USES_KIL | RSX_SHADER_CONTROL_TEXTURE_ALPHA_KILL | RSX_SHADER_CONTROL_ALPHA_TEST | RSX_SHADER_CONTROL_POLYGON_STIPPLE | RSX_SHADER_CONTROL_ALPHA_TO_COVERAGE) diff --git a/rpcs3/Emu/RSX/rsx_methods.cpp b/rpcs3/Emu/RSX/rsx_methods.cpp index 2c2552e088..03bbd1e52a 100644 --- a/rpcs3/Emu/RSX/rsx_methods.cpp +++ b/rpcs3/Emu/RSX/rsx_methods.cpp @@ -1686,7 +1686,7 @@ namespace rsx bind(NV4097_SET_ZPASS_PIXEL_COUNT_ENABLE, nv4097::set_zcull_pixel_count_enable); bind(NV4097_CLEAR_ZCULL_SURFACE, nv4097::clear_zcull); bind(NV4097_SET_DEPTH_TEST_ENABLE, nv4097::set_surface_options_dirty_bit); - bind(NV4097_SET_DEPTH_FUNC, nv4097::set_depth_func); + bind(NV4097_SET_DEPTH_FUNC, nv4097::set_surface_options_dirty_bit); bind(NV4097_SET_DEPTH_MASK, nv4097::set_surface_options_dirty_bit); bind(NV4097_SET_COLOR_MASK, nv4097::set_color_mask); bind(NV4097_SET_COLOR_MASK_MRT, nv4097::set_surface_options_dirty_bit); From a4712a283f149fa70bcddea0719eaeb60d92e054 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 12 May 2026 02:37:32 +0300 Subject: [PATCH 098/283] C++ things --- Utilities/stereo_config.cpp | 1 - rpcs3/Emu/RSX/GL/glutils/barriers.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Utilities/stereo_config.cpp b/Utilities/stereo_config.cpp index 01b1fdfab9..186b8ef5e3 100644 --- a/Utilities/stereo_config.cpp +++ b/Utilities/stereo_config.cpp @@ -96,7 +96,6 @@ void stereo_config::update_from_config(bool stereo_enabled) if (m_stereo_mode == stereo_render_mode_options::anaglyph_custom) { - stereo_config::stereo_matrices custom_matrices {}; stereo_config::convert_matrix(m_custom_matrices.left, g_cfg.video.custom_anaglyph_matrices.left.get_map()); stereo_config::convert_matrix(m_custom_matrices.right, g_cfg.video.custom_anaglyph_matrices.right.get_map()); } diff --git a/rpcs3/Emu/RSX/GL/glutils/barriers.h b/rpcs3/Emu/RSX/GL/glutils/barriers.h index 920e4c7088..5e703ae313 100644 --- a/rpcs3/Emu/RSX/GL/glutils/barriers.h +++ b/rpcs3/Emu/RSX/GL/glutils/barriers.h @@ -6,7 +6,7 @@ namespace gl { static inline void insert_texture_barrier() { - auto& caps = gl::get_driver_caps(); + const auto& caps = gl::get_driver_caps(); if (caps.ARB_texture_barrier_supported) glTextureBarrier(); From e52b6ecbbfc98cef705dffe6c5909dcd9f344a53 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 12 May 2026 03:15:09 +0300 Subject: [PATCH 099/283] rsx: Typo fix for pipeline reload hint - Fragment state just reloads some constant buffers. - Fragment program state recalculates the program. - This is overkill for a variant reload, but that can be optimized later. --- rpcs3/Emu/RSX/RSXThread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index cfd8a58dc1..ce54444ccd 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -2099,7 +2099,7 @@ namespace rsx current_fragment_program.ctrl |= export_ctrl; // Signal backend to reload pipeline - m_graphics_state.set(rsx::pipeline_state::fragment_state_dirty); + m_graphics_state.set(rsx::pipeline_state::fragment_program_state_dirty); } prefetch_vertex_program(); From 320e8d634ae0a0dc0917e9483d12e5156c655160 Mon Sep 17 00:00:00 2001 From: Malcolm Date: Tue, 12 May 2026 22:43:04 -0400 Subject: [PATCH 100/283] SPU LLVM: Workaround bad LLVM codegen for FCGT on AARCH64 - LLVM was emitting a nasty sequence for select instead of just using BSL, so let's try using inline assembly. --- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 927d7ac187..848129fa20 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -7205,7 +7205,22 @@ public: const auto ai = eval(bitcast(a)); const auto bi = eval(bitcast(b)); +// Awful workaround to some awful LLVM codegen via inline assembly +// Once it is solved upstream we should remove it - Whatcookie +// https://github.com/llvm/llvm-project/issues/197360 +#if defined(ARCH_ARM64) + const auto select_bsl = [&](auto mask, auto t, auto f) + { + const auto asm_type = llvm::FunctionType::get(get_type(), {get_type(), get_type(), get_type()}, false); + const auto bsl_asm = llvm::InlineAsm::get(asm_type, "bsl $0.16b, $1.16b, $2.16b", "=w,w,w,0", false); + + return value(m_ir->CreateCall(asm_type, bsl_asm, {eval(sext(t)).value, eval(sext(f)).value, eval(sext(mask)).value})); + }; + + return eval(sext(fcmp_uno(a != b)) & select_bsl((ai & bi) >= 0, ai > bi, ai < bi)); +#else return eval(sext(fcmp_uno(a != b) & select((ai & bi) >= 0, ai > bi, ai < bi))); +#endif }; set_vr(op.rt, fcgt(get_vr(op.ra), get_vr(op.rb))); From 33016742f193fe2bf9c1cc634423672d8e2aa459 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Mon, 20 Apr 2026 12:09:43 +0300 Subject: [PATCH 101/283] SPU: Fix CPU usage of spu_channel on BE configuration --- rpcs3/Emu/Cell/SPUThread.cpp | 11 +++++++---- rpcs3/Emu/Cell/SPUThread.h | 9 ++++++--- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index 855d95a44e..8315164ea9 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -7200,11 +7200,13 @@ s64 spu_channel::pop_wait(cpu_thread& spu, bool pop) lv2_obj::notify_all(); - const u32 wait_on_val = static_cast(((pop ? bit_occupy : 0) | bit_wait) >> 32); + old = (pop ? bit_occupy : 0) | bit_wait; while (true) { - thread_ctrl::wait_on(utils::bless>(&data)[1], wait_on_val); + const usz is_le = std::endian::native == std::endian::little ? 1 : 0; + thread_ctrl::wait_on(utils::bless>(&data)[is_le], read_from_ptr(reinterpret_cast(&old), is_le * 4)); + old = data; if (!(old & bit_wait)) @@ -7248,7 +7250,7 @@ bool spu_channel::push_wait(cpu_thread& spu, u32 value, bool push) { if (data & bit_count) [[unlikely]] { - jostling_value.release(push ? (bit_occupy | value) : static_cast(data)); + jostling_value.release(push ? value : static_cast(data)); data |= (push ? bit_occupy : 0) | bit_wait; } else if (push) @@ -7288,7 +7290,8 @@ bool spu_channel::push_wait(cpu_thread& spu, u32 value, bool push) return !data.bit_test_reset(off_wait); } - thread_ctrl::wait_on(utils::bless>(&data)[1], u32(state >> 32)); + const usz is_le = std::endian::native == std::endian::little ? 1 : 0; + thread_ctrl::wait_on(utils::bless>(&data)[is_le], u32(state >> 32)); state = data; } } diff --git a/rpcs3/Emu/Cell/SPUThread.h b/rpcs3/Emu/Cell/SPUThread.h index eb4e936c3b..30455e57cd 100644 --- a/rpcs3/Emu/Cell/SPUThread.h +++ b/rpcs3/Emu/Cell/SPUThread.h @@ -269,7 +269,8 @@ public: { if (!postpone_notify) { - utils::bless>(&data)[1].notify_one(); + const usz is_le = std::endian::native == std::endian::little ? 1 : 0; + utils::bless>(&data)[is_le].notify_one(); } } @@ -280,7 +281,8 @@ public: void notify() { - utils::bless>(&data)[1].notify_one(); + const usz is_le = std::endian::native == std::endian::little ? 1 : 0; + utils::bless>(&data)[is_le].notify_one(); } // Returns true on success @@ -334,7 +336,8 @@ public: if (old & bit_wait) { - utils::bless>(&data)[1].notify_one(); + const usz is_le = std::endian::native == std::endian::little ? 1 : 0; + utils::bless>(&data)[is_le].notify_one(); } return static_cast(old); From ee436307cfbbd2188b14ff0e375dcdb1871b65ca Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Wed, 22 Apr 2026 19:14:34 +0300 Subject: [PATCH 102/283] SPU Update --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 51 +++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index dffca21cae..a955e09480 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -4953,6 +4953,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s struct reduced_statistics_t : stats_t { + atomic_t secret_compatible = 0; }; // Pattern structures @@ -6420,6 +6421,23 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } } + bool is_secret = true; + + for (u32 i = 0; i < s_reg_max; i++) + { + if (::at32(reduced_loop->loop_dicts, i) || ::at32(reduced_loop->loop_writes, i)) + { + if (auto reg_it = reduced_loop->find_reg(i)) + { + if (reg_it->regs.test(s_reg_max)) + { + is_secret = false; + } + } + } + } + + reduced_loop->is_secret = is_secret; reduced_loop_all.emplace(reduced_loop->loop_pc, *reduced_loop); reduced_loop->discard(); } @@ -7134,6 +7152,23 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } } + bool is_secret = true; + + for (u32 i = 0; i < s_reg_max; i++) + { + if (::at32(reduced_loop->loop_dicts, i) || ::at32(reduced_loop->loop_writes, i)) + { + if (auto reg_it = reduced_loop->find_reg(i)) + { + if (reg_it->regs.test(s_reg_max)) + { + is_secret = false; + } + } + } + } + + reduced_loop->is_secret = is_secret; reduced_loop_all.emplace(reduced_loop->loop_pc, *reduced_loop); reduced_loop->discard(); } @@ -8624,6 +8659,10 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (const auto& [loop_pc, pattern] : reduced_loop_all) { + auto& stats = g_fxo->get(); + + stats.all++; + if (!pattern.active || pattern.loop_pc == SPU_LS_SIZE) { continue; @@ -8631,20 +8670,22 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (inst_attr attr = m_inst_attrs[(loop_pc - entry_point) / 4]; attr == inst_attr::none) { + stats.single++; + add_pattern(inst_attr::reduced_loop, loop_pc - result.entry_point, 0, std::make_shared(pattern)); std::string regs = "{"; - for (const auto& [reg_num, reg] : pattern.regs) + for (u32 i = 0; i < s_reg_max; i++) { - if (reg.is_loop_dictator(reg_num)) + if (::at32(pattern.loop_dicts, i)) { if (regs.size() != 1) { regs += ","; } - fmt::append(regs, " r%u", reg_num); + fmt::append(regs, " r%u", i); } } @@ -8683,10 +8724,10 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s regs += " }"; - spu_log.success("Reduced Loop Pattern Detected! (REGS: %s, DICT: r%d, ARG: %s, Incr: %s (%s), CMP/Size: %s/%u, loop_pc=0x%x, 0x%x-%s)", regs, pattern.cond_val_register_idx + spu_log.success("Reduced Loop Pattern Detected! (REGS: %s, DICT: r%d, ARG: %s, Incr: %s (%s), CMP/Size: %s/%u, loop_pc=0x%x, 0x%x-%s) [All=%u/%u, Secret=%s/%u]", regs, pattern.cond_val_register_idx , pattern.cond_val_is_immediate ? fmt::format("0x%x", pattern.cond_val_min) : fmt::format("r%d", pattern.cond_val_register_argument_idx) , pattern.cond_val_incr_is_immediate ? fmt::format("%d", static_cast(pattern.cond_val_incr)) : fmt::format("r%d", pattern.cond_val_incr), pattern.cond_val_incr_before_cond ? "BEFORE" : "AFTER" - , pattern.cond_val_compare, std::popcount(pattern.cond_val_mask), loop_pc, entry_point, func_hash); + , pattern.cond_val_compare, std::popcount(pattern.cond_val_mask), loop_pc, entry_point, func_hash, +stats.single, +stats.all, pattern.is_secret ? "true" : "false", stats.secret_compatible.add_fetch(pattern.is_secret ? 1 : 0)); } } From a545fe92a090865d2433a999e516d388acfdda96 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Sun, 3 May 2026 08:41:51 +0300 Subject: [PATCH 103/283] SPU: Log Mega SPU programs properly --- rpcs3/Emu/System.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index d0a9e15ebc..2b7cd75ddf 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -3890,6 +3890,11 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s tty_buffer.resize(tty_read_fd.read_at(m_tty_file_init_pos, tty_buffer.data(), tty_buffer.size())); tty_read_fd.close(); + if (!tty_buffer.empty() && std::isspace(tty_buffer.back())) + { + tty_buffer.resize(tty_buffer.find_last_not_of(" \f\n\r\t\v"sv) + 1); + } + if (!tty_buffer.empty()) { // Mark start and end very clearly with RPCS3 put in it @@ -3923,6 +3928,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s std::string_view to_log = not_logged; to_log = to_log.substr(0, 0x8000); to_log = to_log.substr(0, utils::add_saturate(to_log.rfind("\n========== SPU BLOCK"sv), 1)); + to_log = to_log.substr(0, utils::add_saturate(to_log.find_last_of("\n"sv), 1)); to_remove = to_log.size(); std::string new_log(to_log); @@ -3977,6 +3983,11 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s iter = index + 1; } + if (!new_log.empty() && std::isspace(new_log.back())) + { + new_log.resize(new_log.find_last_not_of(" \f\n\r\t\v"sv) + 1); + } + // Cannot log it all at once due to technical reasons, split it to 8MB at maximum of whole functions // Assume the block prefix exists because it is created by RPCS3 (or log it in an ugly manner if it does not exist) sys_log.notice("Logging spu.log #%u:\n\n%s\n", part_ctr, new_log); From c860aa2107dd1c4753cd1f56e77446f209833482 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Thu, 14 May 2026 07:52:16 +0300 Subject: [PATCH 104/283] SPU Analyzer: Fix initiate_patterns function --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index a955e09480..f17380f467 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -5184,7 +5184,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // Check loop connector block (must jump to block-next or to loop-start) u32 targets_count = 0; - for (u32 target : get_block_targets(first_pred_of_loop)) + for (u32 target : get_block_targets(!invalid ? first_pred_of_loop : bpc)) { valid = true; targets_count++; From 53d76db753dbde4836f0f6f3799f74ab27b4a9b8 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Thu, 14 May 2026 13:21:23 +0300 Subject: [PATCH 105/283] SPU: More UB fixes --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 16 ++++++++++------ rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 6 +++--- rpcs3/Emu/Cell/SPURecompiler.h | 14 +++++++------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index f17380f467..b721ebc188 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -1222,7 +1222,7 @@ void spu_cache::initialize(bool build_existing_cache) } // Initialize global cache instance - if (g_cfg.core.spu_cache && cache) + if (g_cfg.core.spu_cache && !spu_precompilation_enabled && cache) { g_fxo->get() = std::move(cache); } @@ -1282,8 +1282,8 @@ spu_runtime::spu_runtime() fs::remove_all(m_cache_path + "llvm/", false); } - fs::file(m_cache_path + "spu.log", fs::rewrite); - fs::file(m_cache_path + "spu-ir.log", fs::rewrite); + fs::write_file(m_cache_path + "spu.log", fs::rewrite); + fs::write_file(m_cache_path + "spu-ir.log", fs::rewrite); } } @@ -3215,10 +3215,13 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s jt_abs.clear(); } + // If this fails, this is a TODO to compare them in another way ensure(jt_abs.size() != jt_rel.size()); } - if (jt_abs.size() >= jt_rel.size()) + const bool abs_domainates = jt_abs.size() > jt_rel.size(); + + if (abs_domainates) { const u32 new_size = (start - lsa) / 4 + ::size32(jt_abs); @@ -3236,8 +3239,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s m_targets.emplace(pos, std::move(jt_abs)); } - - if (jt_rel.size() >= jt_abs.size()) + else { const u32 new_size = (start - lsa) / 4 + ::size32(jt_rel); @@ -8766,6 +8768,8 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s void spu_recompiler_base::dump(const spu_program& result, std::string& out, u32 block_min, u32 block_max) { + block_max = std::min(block_max, SPU_LS_SIZE); + SPUDisAsm dis_asm(cpu_disasm_mode::dump, reinterpret_cast(result.data.data()), result.lower_bound); std::string hash; diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 848129fa20..1bf37086ad 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -2637,7 +2637,7 @@ public: for (u32 iteration_emit = 0; is_reduced_loop; m_pos += 4) { - if (m_pos != baddr && m_block_info[m_pos / 4] && m_reduced_loop_info->loop_end < m_pos) + if (m_pos != baddr && m_pos != SPU_LS_SIZE && m_block_info[m_pos / 4] && m_reduced_loop_info->loop_end < m_pos) { fmt::throw_exception("LLVM: Reduced Loop Pattern: Exit(1) too early at 0x%x", m_pos); } @@ -3217,7 +3217,7 @@ public: for (u32 target : m_bbs[cur].targets) { - if (!m_block_info[target / 4]) + if (target == SPU_LS_SIZE || !m_block_info[target / 4]) { continue; } @@ -9066,7 +9066,7 @@ public: for (u32 target : tfound->second) { - if (m_block_info[target / 4]) + if (target != SPU_LS_SIZE && m_block_info[target / 4]) { targets.emplace(target, nullptr); } diff --git a/rpcs3/Emu/Cell/SPURecompiler.h b/rpcs3/Emu/Cell/SPURecompiler.h index fc74bcec90..f1ce3a5982 100644 --- a/rpcs3/Emu/Cell/SPURecompiler.h +++ b/rpcs3/Emu/Cell/SPURecompiler.h @@ -714,14 +714,14 @@ protected: u64 m_hash_start; // Bit indicating start of the block - std::bitset<0x10000> m_block_info; + std::bitset m_block_info; // GPR modified by the instruction (-1 = not set) - std::array m_regmod; + std::array m_regmod; - std::bitset<0x10000> m_use_ra; - std::bitset<0x10000> m_use_rb; - std::bitset<0x10000> m_use_rc; + std::bitset m_use_ra; + std::bitset m_use_rb; + std::bitset m_use_rc; // List of possible targets for the instruction (entry shouldn't exist for simple instructions) std::unordered_map, value_hash> m_targets; @@ -730,10 +730,10 @@ protected: std::unordered_map, value_hash> m_preds; // List of function entry points and return points (set after BRSL, BRASL, BISL, BISLED) - std::bitset<0x10000> m_entry_info; + std::bitset m_entry_info; // Set after return points and disjoint chunks - std::bitset<0x10000> m_ret_info; + std::bitset m_ret_info; // Basic block information struct block_info From 6f50f0854fe7882e9fff17a1f76f9590cf46d497 Mon Sep 17 00:00:00 2001 From: Gustavo Graziano Date: Thu, 14 May 2026 09:05:39 -0300 Subject: [PATCH 106/283] I made some improvements to my theme to make it better (#18732) --- ...dows 11 (Dark Mode) by GustavoGraziano.qss | 23 ++++++++++++++----- ...ows 11 (Light Mode) by GustavoGraziano.qss | 23 ++++++++++++++----- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/bin/GuiConfigs/Windows 11 (Dark Mode) by GustavoGraziano.qss b/bin/GuiConfigs/Windows 11 (Dark Mode) by GustavoGraziano.qss index c93a1bb11e..1dd9f75b23 100644 --- a/bin/GuiConfigs/Windows 11 (Dark Mode) by GustavoGraziano.qss +++ b/bin/GuiConfigs/Windows 11 (Dark Mode) by GustavoGraziano.qss @@ -349,24 +349,24 @@ QPushButton:disabled { } -QDialog QDialogButtonBox QPushButton { +QDialog QDialogButtonBox QPushButton:default { color: #000000; background-color: #4CC2FF; border-color: #5AC7FF; } -QDialog QDialogButtonBox QPushButton:hover { +QDialog QDialogButtonBox QPushButton:default:hover { background-color: #48B2E9; border-color: #56B8EB; } -QDialog QDialogButtonBox QPushButton:pressed { +QDialog QDialogButtonBox QPushButton:default:pressed { color: #22526A; background-color: #45A4D5; border-color: #45A4D5; } -QDialog QDialogButtonBox QPushButton:disabled { +QDialog QDialogButtonBox QPushButton:default:disabled { color: #ABABAB; background-color: #4C4C4C; border-color: #4C4C4C; @@ -614,12 +614,13 @@ QComboBox QAbstractItemView { QComboBox QAbstractItemView::item { color: #FFFFFF; padding: 4px 6px; + margin: 2px 0px; background-color: transparent; border-radius: 4px; } QComboBox QAbstractItemView::item:hover { - background-color: #343434; + background-color: #383838; } QComboBox QAbstractItemView::item:selected { @@ -646,7 +647,7 @@ QDoubleSpinBox::up-button, QDoubleSpinBox::down-button { border-radius: 4px; width: 8px; height: 8px; - padding: 3px 4px; + padding: 3px 4px; } QSpinBox::up-button:hover, QSpinBox::down-button:hover, @@ -713,6 +714,16 @@ QSlider::handle:horizontal:disabled { image: url("GuiConfigs/dark/slider-handle-disabled.svg"); } +QSlider::sub-page:horizontal { + background-color: #4CC2FF; + border-radius: 2px; + height: 4px; +} + +QSlider::sub-page:horizontal:disabled { + background-color: #767A7D; +} + /* LINE EDIT */ QLineEdit { diff --git a/bin/GuiConfigs/Windows 11 (Light Mode) by GustavoGraziano.qss b/bin/GuiConfigs/Windows 11 (Light Mode) by GustavoGraziano.qss index f279bd0570..f1ce57d2fe 100644 --- a/bin/GuiConfigs/Windows 11 (Light Mode) by GustavoGraziano.qss +++ b/bin/GuiConfigs/Windows 11 (Light Mode) by GustavoGraziano.qss @@ -349,24 +349,24 @@ QPushButton:disabled { } -QDialog QDialogButtonBox QPushButton { +QDialog QDialogButtonBox QPushButton:default { color: #FFFFFF; background-color: #0067C0; border-color: #1473C5; } -QDialog QDialogButtonBox QPushButton:hover { +QDialog QDialogButtonBox QPushButton:default:hover { background-color: #1A76C6; border-color: #2C80CA; } -QDialog QDialogButtonBox QPushButton:pressed { +QDialog QDialogButtonBox QPushButton:default:pressed { color: #C2DAEF; background-color: #3284CB; border-color: #3284CB; } -QDialog QDialogButtonBox QPushButton:disabled { +QDialog QDialogButtonBox QPushButton:default:disabled { color: #FFFFFF; background-color: #C5C5C5; border-color: #C5C5C5; @@ -614,12 +614,13 @@ QComboBox QAbstractItemView { QComboBox QAbstractItemView::item { color: #1B1B1B; padding: 4px 6px; + margin: 2px 0px; background-color: transparent; border-radius: 4px; } QComboBox QAbstractItemView::item:hover { - background-color: #F3F3F3; + background-color: #F0F0F0; } QComboBox QAbstractItemView::item:selected { @@ -646,7 +647,7 @@ QDoubleSpinBox::up-button, QDoubleSpinBox::down-button { border-radius: 4px; width: 8px; height: 8px; - padding: 3px 4px; + padding: 3px 4px; } QSpinBox::up-button:hover, QSpinBox::down-button:hover, @@ -713,6 +714,16 @@ QSlider::handle:horizontal:disabled { image: url("GuiConfigs/light/slider-handle-disabled.svg"); } +QSlider::sub-page:horizontal { + background-color: #1A76C6; + border-radius: 2px; + height: 4px; +} + +QSlider::sub-page:horizontal:disabled { + background-color: #868788; +} + /* LINE EDIT */ QLineEdit { From c5ee48f54a3e051535cf41db8dcd53f924d33537 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Wed, 13 May 2026 23:27:39 +0200 Subject: [PATCH 107/283] Harden bitset access --- rpcs3/Emu/Cell/Modules/cellPamf.cpp | 9 +- rpcs3/Emu/Cell/Modules/cellSsl.cpp | 19 +-- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 116 ++++++++-------- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 18 +-- rpcs3/Emu/Cell/SPURecompiler.h | 60 ++++----- rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp | 15 +-- rpcs3/Emu/RSX/Common/bitfield.hpp | 7 +- rpcs3/Emu/RSX/Program/ProgramStateCache.cpp | 6 +- rpcs3/Emu/RSX/Program/ProgramStateCache.h | 2 +- rpcs3/Emu/RSX/Program/RSXVertexProgram.h | 3 +- rpcs3/Emu/RSX/RSXFIFO.cpp | 3 +- rpcs3/rpcs3qt/log_viewer.cpp | 2 +- rpcs3/rpcs3qt/log_viewer.h | 4 +- rpcs3/util/types.hpp | 139 ++++++++++++++++++++ 14 files changed, 264 insertions(+), 139 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellPamf.cpp b/rpcs3/Emu/Cell/Modules/cellPamf.cpp index 4bb383c2d2..42ce5b331c 100644 --- a/rpcs3/Emu/Cell/Modules/cellPamf.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPamf.cpp @@ -2,7 +2,6 @@ #include "Emu/System.h" #include "Emu/Cell/PPUModule.h" -#include #include "cellPamf.h" LOG_CHANNEL(cellPamf); @@ -500,7 +499,7 @@ error_code pamfVerify(vm::cptr pAddr, u64 fileSize, vm::ptrseq_info.grouping_periods.groups.streams; - std::bitset<16> channels_used[6]{}; + std::array, 6> channels_used{}; u32 end_of_streams_addr = 0; u32 next_ep_table_addr = 0; @@ -516,14 +515,16 @@ error_code pamfVerify(vm::cptr pAddr, u64 fileSize, vm::ptr& used_channels = ::at32(channels_used, *type); + // Every channel may only be used once per type - if (channels_used[*type].test(*ch)) + if (used_channels.test(*ch)) { return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid channel" }; } // Mark channel as used - channels_used[*type].set(*ch); + used_channels.set(*ch, true); const u32 ep_offset = streams[stream_idx].ep_offset; const u32 ep_num = streams[stream_idx].ep_num; diff --git a/rpcs3/Emu/Cell/Modules/cellSsl.cpp b/rpcs3/Emu/Cell/Modules/cellSsl.cpp index d7f32a5636..7c0aa6d9aa 100644 --- a/rpcs3/Emu/Cell/Modules/cellSsl.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSsl.cpp @@ -1,6 +1,5 @@ #include "stdafx.h" -#include #include #include "cellSsl.h" @@ -92,7 +91,7 @@ std::string getCert(const std::string& certPath, const int certID, const bool is newID = certID - 1; } - std::string filePath = fmt::format("%sCA%02d.cer", certPath, newID); + const std::string filePath = fmt::format("%sCA%02d.cer", certPath, newID); if (!fs::exists(filePath)) { @@ -106,7 +105,7 @@ error_code cellSslCertificateLoader(u64 flag, vm::ptr buffer, u32 size, vm { cellSsl.trace("cellSslCertificateLoader(flag=%llu, buffer=*0x%x, size=%zu, required=*0x%x)", flag, buffer, size, required); - const std::bitset<58> flagBits(flag); + const bit_set<58> flagBits(flag); const std::string certPath = vfs::get("/dev_flash/data/cert/"); if (required) @@ -114,10 +113,11 @@ error_code cellSslCertificateLoader(u64 flag, vm::ptr buffer, u32 size, vm *required = 0; for (uint i = 1; i <= flagBits.size(); i++) { - if (!flagBits[i-1]) + if (!flagBits.test(i - 1)) continue; + // If we're loading cert 6 (the baltimore cert), then we need set that we're loading the 'normal' set of certs. - *required += ::size32(getCert(certPath, i, flagBits[BaltimoreCert-1])); + *required += ::size32(getCert(certPath, i, flagBits.test(BaltimoreCert - 1))); } } else @@ -125,14 +125,15 @@ error_code cellSslCertificateLoader(u64 flag, vm::ptr buffer, u32 size, vm std::string final; for (uint i = 1; i <= flagBits.size(); i++) { - if (!flagBits[i-1]) + if (!flagBits.test(i - 1)) continue; + // If we're loading cert 6 (the baltimore cert), then we need set that we're loading the 'normal' set of certs. - final.append(getCert(certPath, i, flagBits[BaltimoreCert-1])); + final.append(getCert(certPath, i, flagBits.test(BaltimoreCert - 1))); } - memset(buffer.get_ptr(), '\0', size - 1); - memcpy(buffer.get_ptr(), final.c_str(), final.size()); + std::memset(buffer.get_ptr(), '\0', size - 1); + std::memcpy(buffer.get_ptr(), final.c_str(), final.size()); } return CELL_OK; diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index b721ebc188..44a3cfc314 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -2905,7 +2905,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // Check for redundancy if (!m_block_info[target / 4]) { - m_block_info[target / 4] = true; + m_block_info.set(target / 4, true); workload.push_back(target); } @@ -3080,7 +3080,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } else { - m_entry_info[target / 4] = true; + m_entry_info.set(target / 4, true); add_block(target); } } @@ -3091,8 +3091,8 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (!is_no_return && sl && g_cfg.core.spu_block_size != spu_block_size_type::safe) { - m_ret_info[pos / 4 + 1] = true; - m_entry_info[pos / 4 + 1] = true; + m_ret_info.set(pos / 4 + 1, true); + m_entry_info.set(pos / 4 + 1, true); m_targets[pos].push_back(pos + 4); add_block(pos + 4); } @@ -3307,8 +3307,8 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } else { - m_ret_info[pos / 4 + 1] = true; - m_entry_info[pos / 4 + 1] = true; + m_ret_info.set(pos / 4 + 1, true); + m_entry_info.set(pos / 4 + 1, true); m_targets[pos].push_back(pos + 4); add_block(pos + 4); } @@ -3375,15 +3375,15 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (!is_no_return && g_cfg.core.spu_block_size != spu_block_size_type::safe) { - m_ret_info[pos / 4 + 1] = true; - m_entry_info[pos / 4 + 1] = true; + m_ret_info.set(pos / 4 + 1, true); + m_entry_info.set(pos / 4 + 1, true); m_targets[pos].push_back(pos + 4); add_block(pos + 4); } if (!is_no_return && g_cfg.core.spu_block_size == spu_block_size_type::giga && !sync) { - m_entry_info[target / 4] = true; + m_entry_info.set(target / 4, true); add_block(target); } else @@ -3409,7 +3409,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (g_cfg.core.spu_block_size == spu_block_size_type::giga && !sync) { - m_entry_info[target / 4] = true; + m_entry_info.set(target / 4, true); } else { @@ -3714,7 +3714,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // Bit array used to deduplicate workload list workload.push_back(pair.first); - m_bits[pair.first / 4] = true; + m_bits.set(pair.first / 4, true); for (usz i = 0; !reachable && i < workload.size(); i++) { @@ -3746,7 +3746,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (new_pred >= lsa && new_pred < limit && !m_bits[new_pred / 4]) { workload.push_back(new_pred); - m_bits[new_pred / 4] = true; + m_bits.set(new_pred / 4, true); } } } @@ -3769,7 +3769,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 pred : workload) { - m_bits[pred / 4] = false; + m_bits.set(pred / 4, false); } if (!reachable && pair.first < limit) @@ -3828,9 +3828,9 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (addr < lsa || addr >= limit || !result.data[(addr - lsa) / 4]) { - m_block_info[addr / 4] = false; - m_entry_info[addr / 4] = false; - m_ret_info[addr / 4] = false; + m_block_info.set(addr / 4, false); + m_entry_info.set(addr / 4, false); + m_ret_info.set(addr / 4, false); m_preds.erase(addr); } } @@ -3856,7 +3856,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (it->second.empty() && !m_entry_info[it->first / 4]) { // If not an entry point, remove the block completely - m_block_info[it->first / 4] = false; + m_block_info.set(it->first / 4, false); it = m_preds.erase(it); continue; } @@ -3964,7 +3964,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (type == spu_itype::STQD && op.ra == s_reg_sp && !block.reg_mod[op.rt] && !block.reg_use[op.rt]) { // Register saved onto the stack before use - block.reg_save_dom[op.rt] = true; + block.reg_save_dom.set(op.rt, true); reg_save = op.rt; } @@ -3994,7 +3994,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (reg_save != reg && block.reg_save_dom[reg]) { // Register is still used after saving; probably not eligible for optimization - block.reg_save_dom[reg] = false; + block.reg_save_dom.set(reg, false); } } } @@ -4065,7 +4065,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (!is_tail) { block.reg_mod.set(i); - block.reg_mod_xf[i] = false; + block.reg_mod_xf.set(i, false); } } } @@ -4106,8 +4106,8 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { const u32 addr = emp.first->first; spu_log.error("[0x%05x] Fixed first function at 0x%05x", entry_point, addr); - m_entry_info[addr / 4] = true; - m_ret_info[addr / 4] = false; + m_entry_info.set(addr / 4, true); + m_ret_info.set(addr / 4, false); } } @@ -4146,9 +4146,9 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (!m_entry_info[target / 4] || m_ret_info[target / 4]) { // Create new function entry (likely a tail call) - m_entry_info[target / 4] = true; + m_entry_info.set(target / 4, true); - m_ret_info[target / 4] = false; + m_ret_info.set(target / 4, false); m_funcs.try_emplace(target); @@ -4246,10 +4246,10 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 entry : new_entries) { - m_entry_info[entry / 4] = true; + m_entry_info.set(entry / 4, true); // Acknowledge artificial (reversible) chunk entry point - m_ret_info[entry / 4] = true; + m_ret_info.set(entry / 4, true); } for (auto& bb : m_bbs) @@ -4435,7 +4435,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (orig < 0x40000) { auto& src = ::at32(m_bbs, orig); - bb.reg_const[i] = src.reg_const[i]; + bb.reg_const.set(i, src.reg_const[i]); bb.reg_val32[i] = src.reg_val32[i]; } @@ -4474,25 +4474,25 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { case spu_itype::IL: { - bb.reg_const[op.rt] = true; + bb.reg_const.set(op.rt, true); bb.reg_val32[op.rt] = op.si16; break; } case spu_itype::ILA: { - bb.reg_const[op.rt] = true; + bb.reg_const.set(op.rt, true); bb.reg_val32[op.rt] = op.i18; break; } case spu_itype::ILHU: { - bb.reg_const[op.rt] = true; + bb.reg_const.set(op.rt, true); bb.reg_val32[op.rt] = op.i16 << 16; break; } case spu_itype::ILH: { - bb.reg_const[op.rt] = true; + bb.reg_const.set(op.rt, true); bb.reg_val32[op.rt] = op.i16 << 16 | op.i16; break; } @@ -4503,37 +4503,37 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } case spu_itype::ORI: { - bb.reg_const[op.rt] = bb.reg_const[op.ra]; + bb.reg_const.set(op.rt, bb.reg_const[op.ra]); bb.reg_val32[op.rt] = bb.reg_val32[op.ra] | op.si10; break; } case spu_itype::OR: { - bb.reg_const[op.rt] = bb.reg_const[op.ra] && bb.reg_const[op.rb]; + bb.reg_const.set(op.rt, bb.reg_const[op.ra] && bb.reg_const[op.rb]); bb.reg_val32[op.rt] = bb.reg_val32[op.ra] | bb.reg_val32[op.rb]; break; } case spu_itype::AI: { - bb.reg_const[op.rt] = bb.reg_const[op.ra]; + bb.reg_const.set(op.rt, bb.reg_const[op.ra]); bb.reg_val32[op.rt] = bb.reg_val32[op.ra] + op.si10; break; } case spu_itype::A: { - bb.reg_const[op.rt] = bb.reg_const[op.ra] && bb.reg_const[op.rb]; + bb.reg_const.set(op.rt, bb.reg_const[op.ra] && bb.reg_const[op.rb]); bb.reg_val32[op.rt] = bb.reg_val32[op.ra] + bb.reg_val32[op.rb]; break; } case spu_itype::SFI: { - bb.reg_const[op.rt] = bb.reg_const[op.ra]; + bb.reg_const.set(op.rt, bb.reg_const[op.ra]); bb.reg_val32[op.rt] = op.si10 - bb.reg_val32[op.ra]; break; } case spu_itype::SF: { - bb.reg_const[op.rt] = bb.reg_const[op.ra] && bb.reg_const[op.rb]; + bb.reg_const.set(op.rt, bb.reg_const[op.ra] && bb.reg_const[op.rb]); bb.reg_val32[op.rt] = bb.reg_val32[op.rb] - bb.reg_val32[op.ra]; break; } @@ -4566,14 +4566,14 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } // Clear const - bb.reg_const[op.rt] = false; + bb.reg_const.set(op.rt, false); break; } default: { // Clear const if reg is modified here if (u8 reg = m_regmod[ia / 4]; reg < s_reg_max) - bb.reg_const[reg] = false; + bb.reg_const.set(reg, false); break; } } @@ -6103,17 +6103,17 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s u32 ra = s_reg_max, rb = s_reg_max, rc = s_reg_max; - if (::at32(m_use_ra, pos / 4)) + if (m_use_ra.test(pos / 4)) { ra = op.ra; } - if (::at32(m_use_rb, pos / 4)) + if (m_use_rb.test(pos / 4)) { rb = op.rb; } - if (::at32(m_use_rc, pos / 4)) + if (m_use_rc.test(pos / 4)) { rc = op.rc; } @@ -6373,8 +6373,8 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } std::array reg_use{}; - std::bitset reg_maybe_float{}; - std::bitset reg_mod{}; + bit_set reg_maybe_float{}; + bit_set reg_mod{}; for (auto it = m_bbs.find(reduced_loop->loop_pc); it != m_bbs.end() && it->first <= bpc; it++) { @@ -6398,7 +6398,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 i = 0; i < s_reg_max; i++) { - if (!::at32(reduced_loop->loop_dicts, i)) + if (!reduced_loop->loop_dicts.test(i)) { if (reg_use[i] && reg_mod[i]) { @@ -6662,7 +6662,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 i = 0; i < reg->regs.size() && reduced_loop->active; i++) { - if (::at32(reg->regs, i)) + if (reg->regs.test(i)) { if (0) if (i == op_rt || reg->modified == 0) { @@ -6708,11 +6708,11 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s auto reg_org = reduced_loop->find_reg(i); u32 reg_index = i; - if (reg_org && !cond_val_incr_before_cond && reg_org->modified == 0 && reg_org->regs.count() - 1u <= 1u && !::at32(reg_org->regs, i)) + if (reg_org && !cond_val_incr_before_cond && reg_org->modified == 0 && reg_org->regs.count() - 1u <= 1u && !reg_org->regs.test(i)) { for (u32 j = 0; j <= s_reg_127; j++) { - if (::at32(reg_org->regs, j)) + if (reg_org->regs.test(j)) { if (const auto reg_found = reduced_loop->find_reg(j)) { @@ -6781,7 +6781,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s break; } - if (reg_index != i && ::at32(reg->regs, reg_index)) + if (reg_index != i && reg->regs.test(reg_index)) { // Unimplemented break_reduced_loop_pattern(30, reduced_loop->discard()); @@ -7104,8 +7104,8 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } std::array reg_use{}; - std::bitset reg_maybe_float{}; - std::bitset reg_mod{}; + bit_set reg_maybe_float{}; + bit_set reg_mod{}; for (auto it = m_bbs.find(reduced_loop->loop_pc); it != m_bbs.end() && it->first <= bpc; it++) { @@ -7129,7 +7129,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 i = 0; i < s_reg_max; i++) { - if (!::at32(reduced_loop->loop_dicts, i)) + if (!reduced_loop->loop_dicts.test(i)) { if (reg_use[i] && reg_mod[i]) { @@ -8412,17 +8412,17 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s u32 ra = s_reg_max, rb = s_reg_max, rc = s_reg_max; - if (::at32(m_use_ra, pos / 4)) + if (m_use_ra.test(pos / 4)) { ra = op.ra; } - if (::at32(m_use_rb, pos / 4)) + if (m_use_rb.test(pos / 4)) { rb = op.rb; } - if (::at32(m_use_rc, pos / 4)) + if (m_use_rc.test(pos / 4)) { rc = op.rc; } @@ -8693,7 +8693,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 i = 0; i < s_reg_max; i++) { - if (::at32(pattern.loop_writes, i)) + if (pattern.loop_writes.test(i)) { if (regs.size() != 1) { @@ -8703,7 +8703,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s fmt::append(regs, " r%u-w", i); } - if (::at32(pattern.loop_args, i)) + if (pattern.loop_args.test(i)) { if (regs.size() != 1) { @@ -8713,7 +8713,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s fmt::append(regs, " r%u-r", i); } - if (::at32(pattern.loop_may_update, i)) + if (pattern.loop_may_update.test(i)) { if (regs.size() != 1) { diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 1bf37086ad..bd0928f965 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -7148,8 +7148,8 @@ public: { const value_t ab[2]{a, b}; - std::bitset<2> safe_int_compare(0); - std::bitset<2> safe_finite_compare(0); + bit_set<2> safe_int_compare(0); + bit_set<2> safe_finite_compare(0); for (u32 i = 0; i < 2; i++) { @@ -7192,12 +7192,12 @@ public: return eval(sext(bitcast(a) > bitcast(b))); } - if (safe_finite_compare.test(1)) + if (safe_finite_compare.test(1u)) { return eval(sext(fcmp_uno(clamp_negative_smax(a) > b))); } - if (safe_finite_compare.test(0)) + if (safe_finite_compare.test(0u)) { return eval(sext(fcmp_ord(a > clamp_smax(b)))); } @@ -7247,7 +7247,7 @@ public: const value_t ab[2]{a, b}; - std::bitset<2> safe_int_compare(0); + bit_set<2> safe_int_compare(0); for (u32 i = 0; i < 2; i++) { @@ -7521,8 +7521,8 @@ public: const value_t ab[2]{a, b}; - std::bitset<2> safe_float_compare(0); - std::bitset<2> safe_int_compare(0); + bit_set<2> safe_float_compare(0); + bit_set<2> safe_int_compare(0); for (u32 i = 0; i < 2; i++) { @@ -7595,8 +7595,8 @@ public: const value_t ab[2]{a, b}; - std::bitset<2> safe_float_compare(0); - std::bitset<2> safe_int_compare(0); + bit_set<2> safe_float_compare(0); + bit_set<2> safe_int_compare(0); for (u32 i = 0; i < 2; i++) { diff --git a/rpcs3/Emu/Cell/SPURecompiler.h b/rpcs3/Emu/Cell/SPURecompiler.h index f1ce3a5982..0dda3a3229 100644 --- a/rpcs3/Emu/Cell/SPURecompiler.h +++ b/rpcs3/Emu/Cell/SPURecompiler.h @@ -6,22 +6,10 @@ #include "SPUThread.h" #include "SPUAnalyser.h" #include -#include #include #include #include -// std::bitset -template - requires requires(std::remove_cvref_t& x, T&& y) { x.count(); x.test(y); x.flip(y); } -[[nodiscard]] constexpr bool at32(CT&& container, T&& index, std::source_location src_loc = std::source_location::current()) -{ - const usz csv = container.size(); - if (csv <= std::forward(index)) [[unlikely]] - fmt::raw_range_error(src_loc, format_object_simplified(index), csv); - return container[std::forward(index)]; -} - // Helper class class spu_cache { @@ -358,15 +346,15 @@ public: } } - std::bitset loop_args; - std::bitset loop_dicts; - std::bitset loop_writes; - std::bitset loop_may_update; - std::bitset gpr_not_nans; + bit_set loop_args; + bit_set loop_dicts; + bit_set loop_writes; + bit_set loop_may_update; + bit_set gpr_not_nans; struct origin_t { - std::bitset regs{}; + bit_set regs{}; u32 modified = 0; spu_itype_t mod1_type = spu_itype::UNK; spu_itype_t mod2_type = spu_itype::UNK; @@ -434,7 +422,7 @@ public: return true; } - return regs.count() == 1 && ::at32(regs, reg_val); + return regs.count() == 1 && regs.test(reg_val); } bool is_loop_dictator(u32 reg_val, bool test_predictable = false, bool should_predictable = true) const @@ -444,7 +432,7 @@ public: return false; } - if (regs.count() >= 1 && ::at32(regs, reg_val)) + if (regs.count() >= 1 && regs.test(reg_val)) { if (!test_predictable) { @@ -503,7 +491,7 @@ public: return false; } - if (regs.count() - (::at32(regs, reg_val) ? 1 : 0)) + if (regs.count() - (regs.test(reg_val) ? 1 : 0)) { return false; } @@ -686,7 +674,7 @@ public: bool is_gpr_not_NaN_hint(u32 i) const noexcept { - return ::at32(gpr_not_nans, i); + return gpr_not_nans.test(i); } origin_t get_reg(u32 reg_val) noexcept @@ -714,14 +702,14 @@ protected: u64 m_hash_start; // Bit indicating start of the block - std::bitset m_block_info; + bit_set m_block_info; // GPR modified by the instruction (-1 = not set) std::array m_regmod; - std::bitset m_use_ra; - std::bitset m_use_rb; - std::bitset m_use_rc; + bit_set m_use_ra; + bit_set m_use_rb; + bit_set m_use_rc; // List of possible targets for the instruction (entry shouldn't exist for simple instructions) std::unordered_map, value_hash> m_targets; @@ -730,10 +718,10 @@ protected: std::unordered_map, value_hash> m_preds; // List of function entry points and return points (set after BRSL, BRASL, BISL, BISLED) - std::bitset m_entry_info; + bit_set m_entry_info; // Set after return points and disjoint chunks - std::bitset m_ret_info; + bit_set m_ret_info; // Basic block information struct block_info @@ -751,28 +739,28 @@ protected: term_type terminator; // Bit mask of the registers modified in the block - std::bitset reg_mod{}; + bit_set reg_mod{}; // Set if last modifying instruction produces xfloat - std::bitset reg_mod_xf{}; + bit_set reg_mod_xf{}; // Set if the initial register value in this block may be xfloat - std::bitset reg_maybe_xf{}; + bit_set reg_maybe_xf{}; // Set if register is used in floating pont instruction - std::bitset reg_maybe_float{}; + bit_set reg_maybe_float{}; // Set if register is used as shuffle mask - std::bitset reg_maybe_shuffle_mask{}; + bit_set reg_maybe_shuffle_mask{}; // Number of times registers are used (before modified) std::array reg_use{}; // Bit mask of the trivial (u32 x 4) constant value resulting in this block - std::bitset reg_const{}; + bit_set reg_const{}; // Bit mask of register saved onto the stack before use - std::bitset reg_save_dom{}; + bit_set reg_save_dom{}; // Address of the function u32 func = 0x40000; @@ -850,7 +838,7 @@ protected: private: // For private use - std::bitset<0x10000> m_bits; + bit_set<0x10000> m_bits; // For private use std::vector workload; diff --git a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp index 29a1fa3501..ebcbd74f8b 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp @@ -9,7 +9,6 @@ #include "sys_rsxaudio.h" #include -#include #include #ifdef __linux__ @@ -1651,13 +1650,13 @@ void rsxaudio_backend_thread::set_mute_state(avport_bit muted_avports) u8 rsxaudio_backend_thread::gen_mute_state(avport_bit avports) { - std::bitset mute_state{0}; + bit_set mute_state{0}; - if (avports.hdmi_0) mute_state[static_cast(RsxaudioAvportIdx::HDMI_0)] = true; - if (avports.hdmi_1) mute_state[static_cast(RsxaudioAvportIdx::HDMI_1)] = true; - if (avports.avmulti) mute_state[static_cast(RsxaudioAvportIdx::AVMULTI)] = true; - if (avports.spdif_0) mute_state[static_cast(RsxaudioAvportIdx::SPDIF_0)] = true; - if (avports.spdif_1) mute_state[static_cast(RsxaudioAvportIdx::SPDIF_1)] = true; + if (avports.hdmi_0) mute_state.set(static_cast(RsxaudioAvportIdx::HDMI_0), true); + if (avports.hdmi_1) mute_state.set(static_cast(RsxaudioAvportIdx::HDMI_1), true); + if (avports.avmulti) mute_state.set(static_cast(RsxaudioAvportIdx::AVMULTI), true); + if (avports.spdif_0) mute_state.set(static_cast(RsxaudioAvportIdx::SPDIF_0), true); + if (avports.spdif_1) mute_state.set(static_cast(RsxaudioAvportIdx::SPDIF_1), true); return static_cast(mute_state.to_ulong()); } @@ -1832,7 +1831,7 @@ u32 rsxaudio_backend_thread::write_data_callback(u32 bytes, void* buf) return val; }); - const std::bitset mute_state{cb_cfg.mute_state}; + const bit_set mute_state{cb_cfg.mute_state}; if (cb_cfg.ready && !mute_state[static_cast(cb_cfg.avport_idx)] && Emu.IsRunning()) { diff --git a/rpcs3/Emu/RSX/Common/bitfield.hpp b/rpcs3/Emu/RSX/Common/bitfield.hpp index af02792fc7..4ffc07a537 100644 --- a/rpcs3/Emu/RSX/Common/bitfield.hpp +++ b/rpcs3/Emu/RSX/Common/bitfield.hpp @@ -2,12 +2,11 @@ #include "util/atomic.hpp" #include -#include namespace rsx { template - void unpack_bitset(const std::bitset& block, u64* values) + void unpack_bitset(const bit_set& block, u64* values) { for (int bit = 0, n = -1, shift = 0; bit < N; ++bit, ++shift) { @@ -25,11 +24,11 @@ namespace rsx } template - void pack_bitset(std::bitset& block, u64* values) + void pack_bitset(bit_set& block, u64* values) { for (int n = 0, shift = 0; shift < N; ++n, shift += 64) { - std::bitset tmp = values[n]; + bit_set tmp = values[n]; tmp <<= shift; block |= tmp; } diff --git a/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp b/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp index f1c2d49097..221e24ea75 100644 --- a/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp +++ b/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp @@ -140,7 +140,7 @@ vertex_program_utils::vertex_program_metadata vertex_program_utils::analyse_vert //u32 last_instruction_address = 0; //u32 first_instruction_address = entry; - std::bitset instructions_to_patch; + bit_set instructions_to_patch; std::pair instruction_range{ umax, 0 }; bool has_branch_instruction = false; std::stack call_stack; @@ -178,7 +178,7 @@ vertex_program_utils::vertex_program_metadata vertex_program_utils::analyse_vert d3.HEX = instruction._u32[3]; // Touch current instruction - result.instruction_mask[current_instruction] = true; + result.instruction_mask.set(current_instruction, true); instruction_range.first = std::min(current_instruction, instruction_range.first); instruction_range.second = std::max(current_instruction, instruction_range.second); @@ -258,7 +258,7 @@ vertex_program_utils::vertex_program_metadata vertex_program_utils::analyse_vert case RSX_SCA_OPCODE_CLB: { // Need to patch the jump address to be consistent wherever the program is located - instructions_to_patch[current_instruction] = true; + instructions_to_patch.set(current_instruction, true); has_branch_instruction = true; d0.HEX = instruction._u32[0]; diff --git a/rpcs3/Emu/RSX/Program/ProgramStateCache.h b/rpcs3/Emu/RSX/Program/ProgramStateCache.h index 76e168d20a..e1bdc88523 100644 --- a/rpcs3/Emu/RSX/Program/ProgramStateCache.h +++ b/rpcs3/Emu/RSX/Program/ProgramStateCache.h @@ -24,7 +24,7 @@ namespace program_hash_util { struct vertex_program_metadata { - std::bitset instruction_mask; + bit_set instruction_mask; u32 ucode_length; u32 referenced_textures_mask; u16 referenced_inputs_mask; diff --git a/rpcs3/Emu/RSX/Program/RSXVertexProgram.h b/rpcs3/Emu/RSX/Program/RSXVertexProgram.h index 1277250b56..c3834e4f93 100644 --- a/rpcs3/Emu/RSX/Program/RSXVertexProgram.h +++ b/rpcs3/Emu/RSX/Program/RSXVertexProgram.h @@ -3,7 +3,6 @@ #include "program_util.h" #include -#include #include enum vp_reg_type @@ -227,7 +226,7 @@ struct RSXVertexProgram u32 output_mask = 0; u32 base_address = 0; u32 entry = 0; - std::bitset instruction_mask; + bit_set instruction_mask; std::set jump_table; rsx::texture_dimension_extended get_texture_dimension(u8 id) const diff --git a/rpcs3/Emu/RSX/RSXFIFO.cpp b/rpcs3/Emu/RSX/RSXFIFO.cpp index 8ac6a436a2..de0663363a 100644 --- a/rpcs3/Emu/RSX/RSXFIFO.cpp +++ b/rpcs3/Emu/RSX/RSXFIFO.cpp @@ -12,7 +12,6 @@ #include "util/asm.hpp" #include -#include using spu_rdata_t = std::byte[128]; @@ -689,7 +688,7 @@ namespace rsx } // Check for flow control - if (std::bitset<2> jump_type; jump_type + if (bit_set<2> jump_type; jump_type .set(0, (cmd & RSX_METHOD_OLD_JUMP_CMD_MASK) == RSX_METHOD_OLD_JUMP_CMD) .set(1, (cmd & RSX_METHOD_NEW_JUMP_CMD_MASK) == RSX_METHOD_NEW_JUMP_CMD) .any()) diff --git a/rpcs3/rpcs3qt/log_viewer.cpp b/rpcs3/rpcs3qt/log_viewer.cpp index 623a74b48a..c19c8ef0d7 100644 --- a/rpcs3/rpcs3qt/log_viewer.cpp +++ b/rpcs3/rpcs3qt/log_viewer.cpp @@ -40,7 +40,7 @@ log_viewer::log_viewer(std::shared_ptr gui_settings) m_path_last = m_gui_settings->GetValue(gui::fd_log_viewer).toString(); m_show_timestamps = m_gui_settings->GetValue(gui::lv_show_timestamps).toBool(); m_show_threads = m_gui_settings->GetValue(gui::lv_show_threads).toBool(); - m_log_levels = std::bitset<32>(m_gui_settings->GetValue(gui::lv_log_levels).toUInt()); + m_log_levels = bit_set<32>(m_gui_settings->GetValue(gui::lv_log_levels).toUInt()); m_log_text = new QPlainTextEdit(this); m_log_text->setReadOnly(true); diff --git a/rpcs3/rpcs3qt/log_viewer.h b/rpcs3/rpcs3qt/log_viewer.h index d6ba2ff2a4..443c89eb78 100644 --- a/rpcs3/rpcs3qt/log_viewer.h +++ b/rpcs3/rpcs3qt/log_viewer.h @@ -1,11 +1,11 @@ #pragma once #include "find_dialog.h" +#include "util/types.hpp" #include #include -#include #include class LogHighlighter; @@ -37,7 +37,7 @@ private: QPlainTextEdit* m_log_text; LogHighlighter* m_log_highlighter; std::unique_ptr m_find_dialog; - std::bitset<32> m_log_levels = std::bitset<32>(0b11111111u); + bit_set<32> m_log_levels = bit_set<32>(0b11111111u); bool m_show_timestamps = true; bool m_show_threads = true; bool m_last_actions_only = false; diff --git a/rpcs3/util/types.hpp b/rpcs3/util/types.hpp index a1a07bac69..46a819c78c 100644 --- a/rpcs3/util/types.hpp +++ b/rpcs3/util/types.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -1064,6 +1065,144 @@ template requires requires (CT&& x, T&& y) { x.count(y return found->second; } +// Exception friendly std::bitset +template +struct bit_set +{ +public: + constexpr bit_set() noexcept : m_bitset() {} + constexpr bit_set(usz val) noexcept : m_bitset(val) {} + + [[nodiscard]] bool test(usz pos, std::source_location src_loc = std::source_location::current()) const + { + if (pos >= Bits) [[unlikely]] + fmt::raw_range_error(src_loc, format_object_simplified(pos), Bits); + + return m_bitset[pos]; + } + + bit_set& set(usz pos, bool val = true, std::source_location src_loc = std::source_location::current()) + { + if (pos >= Bits) [[unlikely]] + fmt::raw_range_error(src_loc, format_object_simplified(pos), Bits); + + m_bitset[pos] = val; + return *this; + } + + bit_set& reset(usz pos, std::source_location src_loc = std::source_location::current()) + { + if (pos >= Bits) [[unlikely]] + fmt::raw_range_error(src_loc, format_object_simplified(pos), Bits); + + m_bitset[pos] = false; + return *this; + } + + constexpr bit_set& reset() noexcept + { + m_bitset.reset(); + return *this; + } + + [[nodiscard]] constexpr unsigned long to_ulong() const noexcept requires(Bits <= 32) + { + return m_bitset.to_ulong(); + } + + [[nodiscard]] constexpr unsigned long long to_ullong() const noexcept requires(Bits <= 64) + { + return m_bitset.to_ullong(); + } + + [[nodiscard]] constexpr bool any() const noexcept + { + return m_bitset.any(); + } + + [[nodiscard]] constexpr bool none() const noexcept + { + return m_bitset.none(); + } + + [[nodiscard]] constexpr bool all() const noexcept + { + return m_bitset.all(); + } + + [[nodiscard]] constexpr usz count() const noexcept + { + return m_bitset.count(); + } + + [[nodiscard]] constexpr usz size() const noexcept + { + return Bits; + } + + // Helps us getting the source location when using the [] operator + struct location_index + { + template requires std::convertible_to + location_index(T&& pos, std::source_location src_loc = std::source_location::current()) + : index(static_cast(std::forward(pos))), loc(src_loc) + {} + + usz index; + std::source_location loc; + }; + + [[nodiscard]] constexpr bool operator[](location_index index) const + { + return test(index.index, index.loc); + } + + constexpr bit_set& operator&=(const bit_set& r) noexcept + { + m_bitset &= r.m_bitset; + return *this; + } + + constexpr bit_set& operator|=(const bit_set& r) noexcept + { + m_bitset |= r.m_bitset; + return *this; + } + + constexpr bit_set& operator^=(const bit_set& r) noexcept + { + m_bitset ^= r.m_bitset; + return *this; + } + + constexpr bit_set& operator<<=(usz pos) noexcept + { + m_bitset <<= pos; + return *this; + } + + constexpr bit_set& operator>>=(usz pos) noexcept + { + m_bitset >>= pos; + return *this; + } + + [[nodiscard]] constexpr bit_set operator<<(const usz pos) const noexcept + { + return m_bitset << pos; + } + + [[nodiscard]] constexpr bit_set operator>>(const usz pos) const noexcept + { + return m_bitset >> pos; + } + +private: + constexpr bit_set(std::bitset&& set) noexcept : m_bitset(set) {} + + std::bitset m_bitset; +}; + // Simplified hash algorithm. May be used in std::unordered_(map|set). template struct value_hash From f5c420994d275153c851a0340d0dff8425c4d25d Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 14 May 2026 03:24:54 +0200 Subject: [PATCH 108/283] Add unsafe bit_set access for obvious cases --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 84 ++++++++++++-------------- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 36 +++++------ rpcs3/Emu/RSX/RSXFIFO.cpp | 6 +- rpcs3/util/types.hpp | 17 ++++++ 4 files changed, 78 insertions(+), 65 deletions(-) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index 44a3cfc314..d9702f287f 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -2868,10 +2868,6 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s u32 lsa = entry_point; u32 limit = SPU_LS_SIZE; - if (g_cfg.core.spu_block_size == spu_block_size_type::giga) - { - } - // Weak constant propagation context (for guessing branch targets) std::array, 128> vflags{}; @@ -4005,7 +4001,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // Expand MFC_Cmd reg use for (u8 reg : {s_reg_mfc_lsa, s_reg_mfc_tag, s_reg_mfc_size}) { - if (!block.reg_mod[reg]) + if (!block.reg_mod.test_unsafe(reg)) block.reg_use[reg]++; } } @@ -4013,11 +4009,11 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // Register reg modification if (u8 reg = m_regmod[ia / 4]; reg < s_reg_max) { - block.reg_mod.set(reg); - block.reg_mod_xf.set(reg, type & spu_itype::xfloat); + block.reg_mod.set_unsafe(reg); + block.reg_mod_xf.set_unsafe(reg, type & spu_itype::xfloat); if (type == spu_itype::SELB && (block.reg_mod_xf[op.ra] || block.reg_mod_xf[op.rb])) - block.reg_mod_xf.set(reg); + block.reg_mod_xf.set_unsafe(reg); // Possible post-dominating register load if (type == spu_itype::LQD && op.ra == s_reg_sp) @@ -4059,13 +4055,13 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { if (i == s_reg_lr || (i >= 2 && i < s_reg_80) || i > s_reg_127) { - if (!block.reg_mod[i]) + if (!block.reg_mod.test_unsafe(i)) block.reg_use[i]++; if (!is_tail) { - block.reg_mod.set(i); - block.reg_mod_xf.set(i, false); + block.reg_mod.set_unsafe(i); + block.reg_mod_xf.set_unsafe(i, false); } } } @@ -4347,7 +4343,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { if (tb.chunk == block.chunk && tb.reg_origin[i] + 1) { - const u32 expected = block.reg_mod[i] ? addr : block.reg_origin[i]; + const u32 expected = block.reg_mod.test_unsafe(i) ? addr : block.reg_origin[i]; if (tb.reg_origin[i] == 0x80000000) { @@ -4364,7 +4360,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (g_cfg.core.spu_block_size == spu_block_size_type::giga && tb.func == block.func && tb.reg_origin_abs[i] + 2) { - const u32 expected = block.reg_mod[i] ? addr : block.reg_origin_abs[i]; + const u32 expected = block.reg_mod.test_unsafe(i) ? addr : block.reg_origin_abs[i]; if (tb.reg_origin_abs[i] == 0x80000000) { @@ -4435,11 +4431,11 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (orig < 0x40000) { auto& src = ::at32(m_bbs, orig); - bb.reg_const.set(i, src.reg_const[i]); + bb.reg_const.set_unsafe(i, src.reg_const.test_unsafe(i)); bb.reg_val32[i] = src.reg_val32[i]; } - if (!bb.reg_save_dom[i] && bb.reg_use[i] && (orig == SPU_LS_SIZE || orig + 2 == 0)) + if (!bb.reg_save_dom.test_unsafe(i) && bb.reg_use[i] && (orig == SPU_LS_SIZE || orig + 2 == 0)) { // Destroy offset if external reg value is used func.reg_save_off[i] = -1; @@ -4573,7 +4569,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { // Clear const if reg is modified here if (u8 reg = m_regmod[ia / 4]; reg < s_reg_max) - bb.reg_const.set(reg, false); + bb.reg_const.set_unsafe(reg, false); break; } } @@ -4581,7 +4577,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // $SP is modified if (m_regmod[ia / 4] == s_reg_sp) { - if (bb.reg_const[s_reg_sp]) + if (bb.reg_const.test_unsafe(s_reg_sp)) { // Making $SP a constant is a funny thing too. bb.stack_sub = 0x80000000; @@ -4799,7 +4795,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // Check $80..$127 (should be restored or unmodified) for (u32 i = s_reg_80; is_ok && i <= s_reg_127; i++) { - if (u32 orig = bb.reg_mod[i] ? addr : bb.reg_origin_abs[i]; orig < SPU_LS_SIZE) + if (u32 orig = bb.reg_mod.test_unsafe(i) ? addr : bb.reg_origin_abs[i]; orig < SPU_LS_SIZE) { auto& src = ::at32(m_bbs, orig); @@ -6380,7 +6376,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { for (u32 i = 0; i < s_reg_max; i++) { - if (!reg_mod[i]) + if (!reg_mod.test_unsafe(i)) { reg_use[i] += it->second.reg_use[i]; } @@ -6400,26 +6396,26 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { if (!reduced_loop->loop_dicts.test(i)) { - if (reg_use[i] && reg_mod[i]) + if (reg_use[i] && reg_mod.test_unsafe(i)) { reduced_loop->is_constant_expression = false; - reduced_loop->loop_writes.set(i); - reduced_loop->loop_may_update.reset(i); + reduced_loop->loop_writes.set_unsafe(i); + reduced_loop->loop_may_update.reset_unsafe(i); } else if (reg_use[i]) { - reduced_loop->loop_args.set(i); + reduced_loop->loop_args.set_unsafe(i); - if (reg_use[i] >= 3 && reg_maybe_float[i]) + if (reg_use[i] >= 3 && reg_maybe_float.test_unsafe(i)) { - reduced_loop->gpr_not_nans.set(i); + reduced_loop->gpr_not_nans.set_unsafe(i); } } } else { // Cleanup - reduced_loop->loop_may_update.reset(i); + reduced_loop->loop_may_update.reset_unsafe(i); } } @@ -6427,11 +6423,11 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 i = 0; i < s_reg_max; i++) { - if (::at32(reduced_loop->loop_dicts, i) || ::at32(reduced_loop->loop_writes, i)) + if (reduced_loop->loop_dicts.test_unsafe(i) || reduced_loop->loop_writes.test_unsafe(i)) { - if (auto reg_it = reduced_loop->find_reg(i)) + if (const auto reg_it = reduced_loop->find_reg(i)) { - if (reg_it->regs.test(s_reg_max)) + if (reg_it->regs.test_unsafe(s_reg_max)) { is_secret = false; } @@ -7111,7 +7107,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { for (u32 i = 0; i < s_reg_max; i++) { - if (!reg_mod[i]) + if (!reg_mod.test_unsafe(i)) { reg_use[i] += it->second.reg_use[i]; } @@ -7131,26 +7127,26 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { if (!reduced_loop->loop_dicts.test(i)) { - if (reg_use[i] && reg_mod[i]) + if (reg_use[i] && reg_mod.test_unsafe(i)) { reduced_loop->is_constant_expression = false; - reduced_loop->loop_writes.set(i); - reduced_loop->loop_may_update.reset(i); + reduced_loop->loop_writes.set_unsafe(i); + reduced_loop->loop_may_update.reset_unsafe(i); } else if (reg_use[i]) { - reduced_loop->loop_args.set(i); + reduced_loop->loop_args.set_unsafe(i); - if (reg_use[i] >= 3 && reg_maybe_float[i]) + if (reg_use[i] >= 3 && reg_maybe_float.test_unsafe(i)) { - reduced_loop->gpr_not_nans.set(i); + reduced_loop->gpr_not_nans.set_unsafe(i); } } } else { // Cleanup - reduced_loop->loop_may_update.reset(i); + reduced_loop->loop_may_update.reset_unsafe(i); } } @@ -7158,11 +7154,11 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 i = 0; i < s_reg_max; i++) { - if (::at32(reduced_loop->loop_dicts, i) || ::at32(reduced_loop->loop_writes, i)) + if (reduced_loop->loop_dicts.test_unsafe(i) || reduced_loop->loop_writes.test_unsafe(i)) { - if (auto reg_it = reduced_loop->find_reg(i)) + if (const auto reg_it = reduced_loop->find_reg(i)) { - if (reg_it->regs.test(s_reg_max)) + if (reg_it->regs.test_unsafe(s_reg_max)) { is_secret = false; } @@ -8680,7 +8676,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 i = 0; i < s_reg_max; i++) { - if (::at32(pattern.loop_dicts, i)) + if (pattern.loop_dicts.test_unsafe(i)) { if (regs.size() != 1) { @@ -8693,7 +8689,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s for (u32 i = 0; i < s_reg_max; i++) { - if (pattern.loop_writes.test(i)) + if (pattern.loop_writes.test_unsafe(i)) { if (regs.size() != 1) { @@ -8703,7 +8699,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s fmt::append(regs, " r%u-w", i); } - if (pattern.loop_args.test(i)) + if (pattern.loop_args.test_unsafe(i)) { if (regs.size() != 1) { @@ -8713,7 +8709,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s fmt::append(regs, " r%u-r", i); } - if (pattern.loop_may_update.test(i)) + if (pattern.loop_may_update.test_unsafe(i)) { if (regs.size() != 1) { diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index bd0928f965..93f9ea8793 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -2175,7 +2175,7 @@ public: if (src > 0x40000) { // Use the xfloat hint to create 256-bit (4x double) PHI - llvm::Type* type = g_cfg.core.spu_xfloat_accuracy == xfloat_accuracy::accurate && bb.reg_maybe_xf[i] ? get_type() : get_reg_type(i); + llvm::Type* type = g_cfg.core.spu_xfloat_accuracy == xfloat_accuracy::accurate && bb.reg_maybe_xf.test_unsafe(i) ? get_type() : get_reg_type(i); const auto _phi = m_ir->CreatePHI(type, ::size32(bb.preds), fmt::format("phi0x%05x_r%u", baddr, i)); m_block->phi[i] = _phi; @@ -2581,7 +2581,7 @@ public: { for (u32 i = 0; i < s_reg_max; i++) { - llvm::Type* type = g_cfg.core.spu_xfloat_accuracy == xfloat_accuracy::accurate && bb.reg_maybe_xf[i] ? get_type() : get_reg_type(i); + llvm::Type* type = g_cfg.core.spu_xfloat_accuracy == xfloat_accuracy::accurate && bb.reg_maybe_xf.test_unsafe(i) ? get_type() : get_reg_type(i); if (i < m_reduced_loop_info->loop_dicts.size() && (m_reduced_loop_info->loop_dicts.test(i) || m_reduced_loop_info->loop_writes.test(i))) { @@ -7155,8 +7155,8 @@ public: { if (auto [ok, data] = get_const_vector(ab[i].value, m_pos, __LINE__ + i); ok) { - safe_int_compare.set(i); - safe_finite_compare.set(i); + safe_int_compare.set_unsafe(i); + safe_finite_compare.set_unsafe(i); for (u32 j = 0; j < 4; j++) { @@ -7170,8 +7170,8 @@ public: // Note: Technically this optimization is accurate for any positive value, but due to the fact that // we don't produce "extended range" values the same way as real hardware, it's not safe to apply // this optimization for values outside of the range of x86 floating point hardware. - safe_int_compare.reset(i); - if ((value & 0x7fffffffu) >= 0x7f7ffffeu) safe_finite_compare.reset(i); + safe_int_compare.reset_unsafe(i); + if ((value & 0x7fffffffu) >= 0x7f7ffffeu) safe_finite_compare.reset_unsafe(i); } } } @@ -7179,12 +7179,12 @@ public: if (m_reduced_loop_info && m_reduced_loop_info->is_gpr_not_NaN_hint(op.ra)) { - safe_finite_compare.set(0); + safe_finite_compare.set_unsafe(0); } if (m_reduced_loop_info && m_reduced_loop_info->is_gpr_not_NaN_hint(op.rb)) { - safe_finite_compare.set(1); + safe_finite_compare.set_unsafe(1); } if (safe_int_compare.any()) @@ -7253,7 +7253,7 @@ public: { if (auto [ok, data] = get_const_vector(ab[i].value, m_pos, __LINE__ + i); ok) { - safe_int_compare.set(i); + safe_int_compare.set_unsafe(i); for (u32 j = 0; j < 4; j++) { @@ -7263,7 +7263,7 @@ public: if ((value & 0x7fffffffu) >= 0x7f7fffffu || !exponent) { // See above - safe_int_compare.reset(i); + safe_int_compare.reset_unsafe(i); } } } @@ -7528,8 +7528,8 @@ public: { if (auto [ok, data] = get_const_vector(ab[i].value, m_pos, __LINE__ + i); ok) { - safe_float_compare.set(i); - safe_int_compare.set(i); + safe_float_compare.set_unsafe(i); + safe_int_compare.set_unsafe(i); for (u32 j = 0; j < 4; j++) { @@ -7539,13 +7539,13 @@ public: // unsafe if nan if (exponent == 255) { - safe_float_compare.reset(i); + safe_float_compare.reset_unsafe(i); } // unsafe if denormal or 0 if (!exponent) { - safe_int_compare.reset(i); + safe_int_compare.reset_unsafe(i); } } } @@ -7602,8 +7602,8 @@ public: { if (auto [ok, data] = get_const_vector(ab[i].value, m_pos, __LINE__ + i); ok) { - safe_float_compare.set(i); - safe_int_compare.set(i); + safe_float_compare.set_unsafe(i); + safe_int_compare.set_unsafe(i); for (u32 j = 0; j < 4; j++) { @@ -7613,13 +7613,13 @@ public: // unsafe if nan if (exponent == 255) { - safe_float_compare.reset(i); + safe_float_compare.reset_unsafe(i); } // unsafe if denormal or 0 if (!exponent) { - safe_int_compare.reset(i); + safe_int_compare.reset_unsafe(i); } } } diff --git a/rpcs3/Emu/RSX/RSXFIFO.cpp b/rpcs3/Emu/RSX/RSXFIFO.cpp index de0663363a..76241ac2de 100644 --- a/rpcs3/Emu/RSX/RSXFIFO.cpp +++ b/rpcs3/Emu/RSX/RSXFIFO.cpp @@ -689,11 +689,11 @@ namespace rsx // Check for flow control if (bit_set<2> jump_type; jump_type - .set(0, (cmd & RSX_METHOD_OLD_JUMP_CMD_MASK) == RSX_METHOD_OLD_JUMP_CMD) - .set(1, (cmd & RSX_METHOD_NEW_JUMP_CMD_MASK) == RSX_METHOD_NEW_JUMP_CMD) + .set_unsafe(0, (cmd & RSX_METHOD_OLD_JUMP_CMD_MASK) == RSX_METHOD_OLD_JUMP_CMD) + .set_unsafe(1, (cmd & RSX_METHOD_NEW_JUMP_CMD_MASK) == RSX_METHOD_NEW_JUMP_CMD) .any()) { - const u32 offs = cmd & (jump_type.test(0) ? RSX_METHOD_OLD_JUMP_OFFSET_MASK : RSX_METHOD_NEW_JUMP_OFFSET_MASK); + const u32 offs = cmd & (jump_type.test_unsafe(0) ? RSX_METHOD_OLD_JUMP_OFFSET_MASK : RSX_METHOD_NEW_JUMP_OFFSET_MASK); if (offs == fifo_ctrl->get_pos()) { //Jump to self. Often preceded by NOP diff --git a/rpcs3/util/types.hpp b/rpcs3/util/types.hpp index 46a819c78c..cd73f925d0 100644 --- a/rpcs3/util/types.hpp +++ b/rpcs3/util/types.hpp @@ -1081,6 +1081,11 @@ public: return m_bitset[pos]; } + [[nodiscard]] bool test_unsafe(usz pos) const + { + return m_bitset[pos]; + } + bit_set& set(usz pos, bool val = true, std::source_location src_loc = std::source_location::current()) { if (pos >= Bits) [[unlikely]] @@ -1090,6 +1095,12 @@ public: return *this; } + bit_set& set_unsafe(usz pos, bool val = true) + { + m_bitset[pos] = val; + return *this; + } + bit_set& reset(usz pos, std::source_location src_loc = std::source_location::current()) { if (pos >= Bits) [[unlikely]] @@ -1099,6 +1110,12 @@ public: return *this; } + bit_set& reset_unsafe(usz pos) + { + m_bitset.reset(pos); + return *this; + } + constexpr bit_set& reset() noexcept { m_bitset.reset(); From 46364856aedb39ef77d8b9c78508402eccde5538 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 14 May 2026 08:41:05 +0200 Subject: [PATCH 109/283] Remove unused variable --- rpcs3/rpcs3qt/anaglyph_settings_dialog.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/rpcs3/rpcs3qt/anaglyph_settings_dialog.cpp b/rpcs3/rpcs3qt/anaglyph_settings_dialog.cpp index 14c4fea62b..dd24bdc72f 100644 --- a/rpcs3/rpcs3qt/anaglyph_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/anaglyph_settings_dialog.cpp @@ -72,8 +72,6 @@ void color_wedge_widget::paintEvent(QPaintEvent* /*event*/) m_img = QImage(w, h, QImage::Format_RGB32); } - QVector3D out; - // Loop over pixels for (int y = 0; y < h; ++y) { From 4cc0e4c7fc110530562a0a89ec8797e26c21bbb5 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 14 May 2026 09:00:04 +0200 Subject: [PATCH 110/283] spu_recompiler: Initialize members --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 6 ++-- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 38 +++++++++++++------------- rpcs3/Emu/Cell/SPURecompiler.h | 12 ++++---- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index d9702f287f..aa4fd72487 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -4430,7 +4430,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (orig < 0x40000) { - auto& src = ::at32(m_bbs, orig); + const auto& src = ::at32(m_bbs, orig); bb.reg_const.set_unsafe(i, src.reg_const.test_unsafe(i)); bb.reg_val32[i] = src.reg_val32[i]; } @@ -4773,7 +4773,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s // Check $LR (alternative return registers are currently not supported) if (u32 lr_orig = bb.reg_mod[s_reg_lr] ? addr : bb.reg_origin_abs[s_reg_lr]; lr_orig < SPU_LS_SIZE) { - auto& src = ::at32(m_bbs, lr_orig); + const auto& src = ::at32(m_bbs, lr_orig); if (src.reg_load_mod[s_reg_lr] != func.reg_save_off[s_reg_lr]) { @@ -4797,7 +4797,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { if (u32 orig = bb.reg_mod.test_unsafe(i) ? addr : bb.reg_origin_abs[i]; orig < SPU_LS_SIZE) { - auto& src = ::at32(m_bbs, orig); + const auto& src = ::at32(m_bbs, orig); if (src.reg_load_mod[i] != func.reg_save_off[i]) { diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 93f9ea8793..3ca51e1a43 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -76,10 +76,10 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator u32 m_op_const_mask = -1; // Current function chunk entry point - u32 m_entry; + u32 m_entry = 0; // Main entry point offset - u32 m_base; + u32 m_base = 0; // Module name std::string m_hash; @@ -91,26 +91,26 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator u32 m_next_op = 0; // Current function (chunk) - llvm::Function* m_function; + llvm::Function* m_function{}; - llvm::Value* m_thread; - llvm::Value* m_lsptr; - llvm::Value* m_interp_op; - llvm::Value* m_interp_pc; - llvm::Value* m_interp_table; - llvm::Value* m_interp_7f0; - llvm::Value* m_interp_regs; + llvm::Value* m_thread{}; + llvm::Value* m_lsptr{}; + llvm::Value* m_interp_op{}; + llvm::Value* m_interp_pc{}; + llvm::Value* m_interp_table{}; + llvm::Value* m_interp_7f0{}; + llvm::Value* m_interp_regs{}; // Helpers - llvm::Value* m_base_pc; - llvm::Value* m_interp_pc_next; - llvm::BasicBlock* m_interp_bblock; + llvm::Value* m_base_pc{}; + llvm::Value* m_interp_pc_next{}; + llvm::BasicBlock* m_interp_bblock{}; // i8*, contains constant vm::g_base_addr value - llvm::Value* m_memptr; + llvm::Value* m_memptr{}; // Pointers to registers in the thread context - std::array m_reg_addr; + std::array m_reg_addr{}; // Global variable (function table) llvm::GlobalVariable* m_function_table{}; @@ -130,10 +130,10 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator // Chunk for external tail call (dispatch) llvm::Function* m_dispatch{}; - llvm::MDNode* m_md_unlikely; - llvm::MDNode* m_md_likely; - llvm::MDNode* m_md_spu_memory_domain; - llvm::MDNode* m_md_spu_context_domain; + llvm::MDNode* m_md_unlikely{}; + llvm::MDNode* m_md_likely{}; + llvm::MDNode* m_md_spu_memory_domain{}; + llvm::MDNode* m_md_spu_context_domain{}; struct block_info { diff --git a/rpcs3/Emu/Cell/SPURecompiler.h b/rpcs3/Emu/Cell/SPURecompiler.h index 0dda3a3229..08cdc42b02 100644 --- a/rpcs3/Emu/Cell/SPURecompiler.h +++ b/rpcs3/Emu/Cell/SPURecompiler.h @@ -361,7 +361,7 @@ public: spu_itype_t mod3_type = spu_itype::UNK; u32 IMM = 0; -private: + private: // Internal, please access using fixed order spu_itype_t access_type(u32 i) const { @@ -380,7 +380,7 @@ private: return spu_itype::UNK; } -public: + public: spu_itype_t reverse1_type() { @@ -697,15 +697,15 @@ public: protected: spu_runtime* m_spurt{}; - u32 m_pos; - u32 m_size; - u64 m_hash_start; + u32 m_pos = 0; + u32 m_size = 0; + u64 m_hash_start = 0; // Bit indicating start of the block bit_set m_block_info; // GPR modified by the instruction (-1 = not set) - std::array m_regmod; + std::array m_regmod {}; bit_set m_use_ra; bit_set m_use_rb; From 53180b81419c2a6f3d5e6ca6dbe3bcaa19ace5a2 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 14 May 2026 10:35:21 +0200 Subject: [PATCH 111/283] Add bit_set unit test --- rpcs3/CMakeLists.txt | 1 + rpcs3/tests/rpcs3_test.vcxproj | 1 + rpcs3/tests/test_bit_set.cpp | 199 +++++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+) create mode 100644 rpcs3/tests/test_bit_set.cpp diff --git a/rpcs3/CMakeLists.txt b/rpcs3/CMakeLists.txt index ba65a16eaf..6178f9c7d1 100644 --- a/rpcs3/CMakeLists.txt +++ b/rpcs3/CMakeLists.txt @@ -182,6 +182,7 @@ if(BUILD_RPCS3_TESTS) target_sources(rpcs3_test PRIVATE tests/test.cpp + tests/test_bit_set.cpp tests/test_fmt.cpp tests/test_pair.cpp tests/test_tuple.cpp diff --git a/rpcs3/tests/rpcs3_test.vcxproj b/rpcs3/tests/rpcs3_test.vcxproj index a60b150469..886a4d7752 100644 --- a/rpcs3/tests/rpcs3_test.vcxproj +++ b/rpcs3/tests/rpcs3_test.vcxproj @@ -92,6 +92,7 @@ + true diff --git a/rpcs3/tests/test_bit_set.cpp b/rpcs3/tests/test_bit_set.cpp new file mode 100644 index 0000000000..d2058b4877 --- /dev/null +++ b/rpcs3/tests/test_bit_set.cpp @@ -0,0 +1,199 @@ +#include + +#include "util/types.hpp" + +namespace utils +{ + TEST(BitSetTest, DefaultConstructedIsEmpty) + { + bit_set<8> bits; + + EXPECT_TRUE(bits.none()); + EXPECT_FALSE(bits.any()); + EXPECT_FALSE(bits.all()); + EXPECT_EQ(bits.count(), 0u); + EXPECT_EQ(bits.size(), 8u); + EXPECT_EQ(bits.to_ulong(), 0u); + EXPECT_EQ(bits.to_ullong(), 0u); + } + + TEST(BitSetTest, ConstructFromValue) + { + bit_set<8> bits(0b10101010); + + EXPECT_TRUE(bits.test(1)); + EXPECT_TRUE(bits.test(3)); + EXPECT_TRUE(bits.test(5)); + EXPECT_TRUE(bits.test(7)); + + EXPECT_FALSE(bits.test(0)); + EXPECT_FALSE(bits.test(2)); + EXPECT_FALSE(bits.test(4)); + EXPECT_FALSE(bits.test(6)); + + EXPECT_EQ(bits.count(), 4u); + } + + TEST(BitSetTest, SetAndResetBit) + { + bit_set<16> bits; + + bits.set(3); + + EXPECT_TRUE(bits.test(3)); + EXPECT_EQ(bits.count(), 1u); + + bits.reset(3); + + EXPECT_FALSE(bits.test(3)); + EXPECT_EQ(bits.count(), 0u); + } + + TEST(BitSetTest, SetWithExplicitValue) + { + bit_set<8> bits; + + bits.set(2, true); + EXPECT_TRUE(bits.test(2)); + + bits.set(2, false); + EXPECT_FALSE(bits.test(2)); + } + + TEST(BitSetTest, ResetAllBits) + { + bit_set<8> bits(0xFF); + + EXPECT_TRUE(bits.all()); + + bits.reset(); + + EXPECT_TRUE(bits.none()); + EXPECT_EQ(bits.count(), 0u); + } + + TEST(BitSetTest, OperatorBracketUsesTest) + { + bit_set<8> bits; + + bits.set(5); + + EXPECT_TRUE(bits[5]); + EXPECT_FALSE(bits[4]); + } + + TEST(BitSetTest, BitwiseAnd) + { + bit_set<8> a(0b11110000); + bit_set<8> b(0b10101010); + + a &= b; + + EXPECT_EQ(a.to_ulong(), 0b10100000u); + } + + TEST(BitSetTest, BitwiseOr) + { + bit_set<8> a(0b11110000); + bit_set<8> b(0b00001111); + + a |= b; + + EXPECT_EQ(a.to_ulong(), 0b11111111u); + } + + TEST(BitSetTest, BitwiseXor) + { + bit_set<8> a(0b11110000); + bit_set<8> b(0b10101010); + + a ^= b; + + EXPECT_EQ(a.to_ulong(), 0b01011010u); + } + + TEST(BitSetTest, LeftShift) + { + bit_set<8> bits(0b00001111); + + bits <<= 2; + + EXPECT_EQ(bits.to_ulong(), 0b00111100u); + } + + TEST(BitSetTest, RightShift) + { + bit_set<8> bits(0b11110000); + + bits >>= 2; + + EXPECT_EQ(bits.to_ulong(), 0b00111100u); + } + + TEST(BitSetTest, LeftShiftOperator) + { + bit_set<8> bits(0b00001111); + + const auto shifted = bits << 1; + + EXPECT_EQ(shifted.to_ulong(), 0b00011110u); + + // Original unchanged + EXPECT_EQ(bits.to_ulong(), 0b00001111u); + } + + TEST(BitSetTest, RightShiftOperator) + { + bit_set<8> bits(0b11110000); + + const auto shifted = bits >> 1; + + EXPECT_EQ(shifted.to_ulong(), 0b01111000u); + + EXPECT_EQ(bits.to_ulong(), 0b11110000u); + } + + TEST(BitSetTest, AnyNoneAll) + { + bit_set<4> bits; + + EXPECT_TRUE(bits.none()); + EXPECT_FALSE(bits.any()); + EXPECT_FALSE(bits.all()); + + bits.set(0); + EXPECT_TRUE(bits.any()); + EXPECT_FALSE(bits.none()); + EXPECT_FALSE(bits.all()); + + bits.set(1).set(2).set(3); + EXPECT_TRUE(bits.all()); + } + + TEST(BitSetTest, UnsafeAccess) + { + bit_set<8> bits; + + bits.set_unsafe(6); + + EXPECT_TRUE(bits.test_unsafe(6)); + + bits.reset_unsafe(6); + + EXPECT_FALSE(bits.test_unsafe(6)); + } + + TEST(BitSetTest, ToULong) + { + bit_set<32> bits(12345); + + EXPECT_EQ(bits.to_ulong(), 12345ul); + } + + TEST(BitSetTest, ToULLong) + { + bit_set<64> bits(123456789ull); + + EXPECT_EQ(bits.to_ullong(), 123456789ull); + } +} From 5f00b87a44e09ef37127cb6833ecc2d8fc43b322 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 14 May 2026 13:48:10 +0200 Subject: [PATCH 112/283] Move bit_set to new file --- rpcs3/Emu/Cell/Modules/cellPamf.cpp | 1 + rpcs3/Emu/Cell/Modules/cellSsl.cpp | 1 + rpcs3/Emu/Cell/SPURecompiler.h | 1 + rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp | 1 + rpcs3/Emu/RSX/Common/bitfield.hpp | 1 + rpcs3/Emu/RSX/Program/RSXVertexProgram.h | 1 + rpcs3/emucore.vcxproj | 2 + rpcs3/emucore.vcxproj.filters | 57 ++++++++ rpcs3/rpcs3qt/log_viewer.h | 2 +- rpcs3/tests/test_bit_set.cpp | 2 +- rpcs3/util/bit_set.hpp | 159 +++++++++++++++++++++++ rpcs3/util/types.hpp | 156 ---------------------- 12 files changed, 226 insertions(+), 158 deletions(-) create mode 100644 rpcs3/util/bit_set.hpp diff --git a/rpcs3/Emu/Cell/Modules/cellPamf.cpp b/rpcs3/Emu/Cell/Modules/cellPamf.cpp index 42ce5b331c..314b646a1f 100644 --- a/rpcs3/Emu/Cell/Modules/cellPamf.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPamf.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "Emu/System.h" #include "Emu/Cell/PPUModule.h" +#include "util/bit_set.hpp" #include "cellPamf.h" diff --git a/rpcs3/Emu/Cell/Modules/cellSsl.cpp b/rpcs3/Emu/Cell/Modules/cellSsl.cpp index 7c0aa6d9aa..7de808bd25 100644 --- a/rpcs3/Emu/Cell/Modules/cellSsl.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSsl.cpp @@ -7,6 +7,7 @@ #include "Utilities/File.h" #include "Emu/VFS.h" #include "Emu/IdManager.h" +#include "util/bit_set.hpp" #include "cellRtc.h" diff --git a/rpcs3/Emu/Cell/SPURecompiler.h b/rpcs3/Emu/Cell/SPURecompiler.h index 08cdc42b02..307cd74a91 100644 --- a/rpcs3/Emu/Cell/SPURecompiler.h +++ b/rpcs3/Emu/Cell/SPURecompiler.h @@ -3,6 +3,7 @@ #include "Utilities/File.h" #include "Utilities/lockless.h" #include "Utilities/address_range.h" +#include "util/bit_set.hpp" #include "SPUThread.h" #include "SPUAnalyser.h" #include diff --git a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp index ebcbd74f8b..c5204b295c 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp @@ -4,6 +4,7 @@ #include "Emu/System.h" #include "Emu/system_config.h" #include "Emu//Audio/audio_utils.h" +#include "util/bit_set.hpp" #include "util/video_provider.h" #include "sys_rsxaudio.h" diff --git a/rpcs3/Emu/RSX/Common/bitfield.hpp b/rpcs3/Emu/RSX/Common/bitfield.hpp index 4ffc07a537..f0e7f704b3 100644 --- a/rpcs3/Emu/RSX/Common/bitfield.hpp +++ b/rpcs3/Emu/RSX/Common/bitfield.hpp @@ -1,6 +1,7 @@ #pragma once #include "util/atomic.hpp" +#include "util/bit_set.hpp" #include namespace rsx diff --git a/rpcs3/Emu/RSX/Program/RSXVertexProgram.h b/rpcs3/Emu/RSX/Program/RSXVertexProgram.h index c3834e4f93..26fbd98731 100644 --- a/rpcs3/Emu/RSX/Program/RSXVertexProgram.h +++ b/rpcs3/Emu/RSX/Program/RSXVertexProgram.h @@ -1,6 +1,7 @@ #pragma once #include "program_util.h" +#include "util/bit_set.hpp" #include #include diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 31ef781d93..6adcd8d917 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -780,9 +780,11 @@ + + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 17ed08a3f6..80c6359e69 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1438,6 +1438,33 @@ Utilities + + Emu\Cell\Modules + + + Emu\Cell\Modules + + + Emu\Cell\Modules + + + Emu\Cell\Modules + + + Emu\Cell\Modules + + + Emu\Cell\Modules + + + Emu\Cell\Modules + + + Emu\Cell\Modules + + + Loader + @@ -2887,6 +2914,36 @@ Utilities + + Utilities + + + Emu\Cell\Modules + + + Emu\Cell\Modules + + + Emu\Cell\Modules + + + Emu\Cell\Modules + + + Emu\Cell\Modules + + + Emu\Cell\Modules + + + Emu\Cell\Modules + + + Emu\Cell\Modules + + + Loader + diff --git a/rpcs3/rpcs3qt/log_viewer.h b/rpcs3/rpcs3qt/log_viewer.h index 443c89eb78..fef2d3198f 100644 --- a/rpcs3/rpcs3qt/log_viewer.h +++ b/rpcs3/rpcs3qt/log_viewer.h @@ -1,7 +1,7 @@ #pragma once #include "find_dialog.h" -#include "util/types.hpp" +#include "util/bit_set.hpp" #include #include diff --git a/rpcs3/tests/test_bit_set.cpp b/rpcs3/tests/test_bit_set.cpp index d2058b4877..5122c28089 100644 --- a/rpcs3/tests/test_bit_set.cpp +++ b/rpcs3/tests/test_bit_set.cpp @@ -1,6 +1,6 @@ #include -#include "util/types.hpp" +#include "util/bit_set.hpp" namespace utils { diff --git a/rpcs3/util/bit_set.hpp b/rpcs3/util/bit_set.hpp new file mode 100644 index 0000000000..2fa3921630 --- /dev/null +++ b/rpcs3/util/bit_set.hpp @@ -0,0 +1,159 @@ +#pragma once + +#include "types.hpp" +#include + +// Exception friendly std::bitset +template +struct bit_set +{ +public: + constexpr bit_set() noexcept : m_bitset() {} + constexpr bit_set(usz val) noexcept : m_bitset(val) {} + + [[nodiscard]] bool test(usz pos, std::source_location src_loc = std::source_location::current()) const + { + if (pos >= Bits) [[unlikely]] + fmt::raw_range_error(src_loc, format_object_simplified(pos), Bits); + + return m_bitset[pos]; + } + + [[nodiscard]] bool test_unsafe(usz pos) const + { + return m_bitset[pos]; + } + + bit_set& set(usz pos, bool val = true, std::source_location src_loc = std::source_location::current()) + { + if (pos >= Bits) [[unlikely]] + fmt::raw_range_error(src_loc, format_object_simplified(pos), Bits); + + m_bitset[pos] = val; + return *this; + } + + bit_set& set_unsafe(usz pos, bool val = true) + { + m_bitset[pos] = val; + return *this; + } + + bit_set& reset(usz pos, std::source_location src_loc = std::source_location::current()) + { + if (pos >= Bits) [[unlikely]] + fmt::raw_range_error(src_loc, format_object_simplified(pos), Bits); + + m_bitset[pos] = false; + return *this; + } + + bit_set& reset_unsafe(usz pos) + { + m_bitset.reset(pos); + return *this; + } + + constexpr bit_set& reset() noexcept + { + m_bitset.reset(); + return *this; + } + + [[nodiscard]] constexpr unsigned long to_ulong() const noexcept requires(Bits <= 32) + { + return m_bitset.to_ulong(); + } + + [[nodiscard]] constexpr unsigned long long to_ullong() const noexcept requires(Bits <= 64) + { + return m_bitset.to_ullong(); + } + + [[nodiscard]] constexpr bool any() const noexcept + { + return m_bitset.any(); + } + + [[nodiscard]] constexpr bool none() const noexcept + { + return m_bitset.none(); + } + + [[nodiscard]] constexpr bool all() const noexcept + { + return m_bitset.all(); + } + + [[nodiscard]] constexpr usz count() const noexcept + { + return m_bitset.count(); + } + + [[nodiscard]] constexpr usz size() const noexcept + { + return Bits; + } + + // Helps us getting the source location when using the [] operator + struct location_index + { + template requires std::convertible_to + location_index(T&& pos, std::source_location src_loc = std::source_location::current()) + : index(static_cast(std::forward(pos))), loc(src_loc) + {} + + usz index; + std::source_location loc; + }; + + [[nodiscard]] constexpr bool operator[](location_index index) const + { + return test(index.index, index.loc); + } + + constexpr bit_set& operator&=(const bit_set& r) noexcept + { + m_bitset &= r.m_bitset; + return *this; + } + + constexpr bit_set& operator|=(const bit_set& r) noexcept + { + m_bitset |= r.m_bitset; + return *this; + } + + constexpr bit_set& operator^=(const bit_set& r) noexcept + { + m_bitset ^= r.m_bitset; + return *this; + } + + constexpr bit_set& operator<<=(usz pos) noexcept + { + m_bitset <<= pos; + return *this; + } + + constexpr bit_set& operator>>=(usz pos) noexcept + { + m_bitset >>= pos; + return *this; + } + + [[nodiscard]] constexpr bit_set operator<<(const usz pos) const noexcept + { + return m_bitset << pos; + } + + [[nodiscard]] constexpr bit_set operator>>(const usz pos) const noexcept + { + return m_bitset >> pos; + } + +private: + constexpr bit_set(std::bitset&& set) noexcept : m_bitset(set) {} + + std::bitset m_bitset; +}; diff --git a/rpcs3/util/types.hpp b/rpcs3/util/types.hpp index cd73f925d0..a1a07bac69 100644 --- a/rpcs3/util/types.hpp +++ b/rpcs3/util/types.hpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -1065,161 +1064,6 @@ template requires requires (CT&& x, T&& y) { x.count(y return found->second; } -// Exception friendly std::bitset -template -struct bit_set -{ -public: - constexpr bit_set() noexcept : m_bitset() {} - constexpr bit_set(usz val) noexcept : m_bitset(val) {} - - [[nodiscard]] bool test(usz pos, std::source_location src_loc = std::source_location::current()) const - { - if (pos >= Bits) [[unlikely]] - fmt::raw_range_error(src_loc, format_object_simplified(pos), Bits); - - return m_bitset[pos]; - } - - [[nodiscard]] bool test_unsafe(usz pos) const - { - return m_bitset[pos]; - } - - bit_set& set(usz pos, bool val = true, std::source_location src_loc = std::source_location::current()) - { - if (pos >= Bits) [[unlikely]] - fmt::raw_range_error(src_loc, format_object_simplified(pos), Bits); - - m_bitset[pos] = val; - return *this; - } - - bit_set& set_unsafe(usz pos, bool val = true) - { - m_bitset[pos] = val; - return *this; - } - - bit_set& reset(usz pos, std::source_location src_loc = std::source_location::current()) - { - if (pos >= Bits) [[unlikely]] - fmt::raw_range_error(src_loc, format_object_simplified(pos), Bits); - - m_bitset[pos] = false; - return *this; - } - - bit_set& reset_unsafe(usz pos) - { - m_bitset.reset(pos); - return *this; - } - - constexpr bit_set& reset() noexcept - { - m_bitset.reset(); - return *this; - } - - [[nodiscard]] constexpr unsigned long to_ulong() const noexcept requires(Bits <= 32) - { - return m_bitset.to_ulong(); - } - - [[nodiscard]] constexpr unsigned long long to_ullong() const noexcept requires(Bits <= 64) - { - return m_bitset.to_ullong(); - } - - [[nodiscard]] constexpr bool any() const noexcept - { - return m_bitset.any(); - } - - [[nodiscard]] constexpr bool none() const noexcept - { - return m_bitset.none(); - } - - [[nodiscard]] constexpr bool all() const noexcept - { - return m_bitset.all(); - } - - [[nodiscard]] constexpr usz count() const noexcept - { - return m_bitset.count(); - } - - [[nodiscard]] constexpr usz size() const noexcept - { - return Bits; - } - - // Helps us getting the source location when using the [] operator - struct location_index - { - template requires std::convertible_to - location_index(T&& pos, std::source_location src_loc = std::source_location::current()) - : index(static_cast(std::forward(pos))), loc(src_loc) - {} - - usz index; - std::source_location loc; - }; - - [[nodiscard]] constexpr bool operator[](location_index index) const - { - return test(index.index, index.loc); - } - - constexpr bit_set& operator&=(const bit_set& r) noexcept - { - m_bitset &= r.m_bitset; - return *this; - } - - constexpr bit_set& operator|=(const bit_set& r) noexcept - { - m_bitset |= r.m_bitset; - return *this; - } - - constexpr bit_set& operator^=(const bit_set& r) noexcept - { - m_bitset ^= r.m_bitset; - return *this; - } - - constexpr bit_set& operator<<=(usz pos) noexcept - { - m_bitset <<= pos; - return *this; - } - - constexpr bit_set& operator>>=(usz pos) noexcept - { - m_bitset >>= pos; - return *this; - } - - [[nodiscard]] constexpr bit_set operator<<(const usz pos) const noexcept - { - return m_bitset << pos; - } - - [[nodiscard]] constexpr bit_set operator>>(const usz pos) const noexcept - { - return m_bitset >> pos; - } - -private: - constexpr bit_set(std::bitset&& set) noexcept : m_bitset(set) {} - - std::bitset m_bitset; -}; - // Simplified hash algorithm. May be used in std::unordered_(map|set). template struct value_hash From fdce82fc24651555eaee5f824b0f2a944ff30b84 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Fri, 15 May 2026 00:02:45 +0200 Subject: [PATCH 113/283] input: Use smaller external structs for button and stick access outside of the pad_thread This needs less memory and hides unwanted members from client code. --- rpcs3/Emu/Cell/Modules/cellGem.cpp | 4 ++-- rpcs3/Emu/Cell/Modules/cellPad.cpp | 4 ++-- rpcs3/Emu/Io/GameTablet.cpp | 2 +- rpcs3/Emu/Io/emulated_pad_config.h | 4 ++-- rpcs3/Emu/Io/pad_types.h | 18 ++++++++++++++++-- rpcs3/Emu/RSX/Overlays/overlays.cpp | 4 ++-- rpcs3/Input/pad_thread.cpp | 8 ++++---- 7 files changed, 29 insertions(+), 15 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index f9f5ea4100..24d0ef60d9 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -2183,7 +2183,7 @@ static void ds3_input_to_ext(u32 gem_num, gem_config::gem_controller& controller ext.status = controller.ext_status; - for (const AnalogStick& stick : pad->m_sticks_external) + for (const AnalogStickExternal& stick : pad->m_sticks_external) { switch (stick.m_offset) { @@ -2195,7 +2195,7 @@ static void ds3_input_to_ext(u32 gem_num, gem_config::gem_controller& controller } } - for (const Button& button : pad->m_buttons_external) + for (const ButtonExternal& button : pad->m_buttons_external) { if (!button.m_pressed) continue; diff --git a/rpcs3/Emu/Cell/Modules/cellPad.cpp b/rpcs3/Emu/Cell/Modules/cellPad.cpp index 9c8e05f74f..b2fbe187f9 100644 --- a/rpcs3/Emu/Cell/Modules/cellPad.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPad.cpp @@ -418,7 +418,7 @@ void pad_get_data(u32 port_no, CellPadData* data, bool get_periph_data = false) } }; - for (const Button& button : pad->m_buttons_external) + for (const ButtonExternal& button : pad->m_buttons_external) { // here we check btns, and set pad accordingly, // if something changed, set btnChanged @@ -497,7 +497,7 @@ void pad_get_data(u32 port_no, CellPadData* data, bool get_periph_data = false) } } - for (const AnalogStick& stick : pad->m_sticks_external) + for (const AnalogStickExternal& stick : pad->m_sticks_external) { switch (stick.m_offset) { diff --git a/rpcs3/Emu/Io/GameTablet.cpp b/rpcs3/Emu/Io/GameTablet.cpp index 1d0fcf24fe..f72c108ae1 100644 --- a/rpcs3/Emu/Io/GameTablet.cpp +++ b/rpcs3/Emu/Io/GameTablet.cpp @@ -200,7 +200,7 @@ void usb_device_gametablet::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endp const auto& pad = ::at32(pads, m_controller_index); if (pad->is_connected() && !pad->is_copilot()) { - for (Button& button : pad->m_buttons_external) + for (ButtonExternal& button : pad->m_buttons_external) { if (!button.m_pressed) { diff --git a/rpcs3/Emu/Io/emulated_pad_config.h b/rpcs3/Emu/Io/emulated_pad_config.h index b9bf68a223..eb54966583 100644 --- a/rpcs3/Emu/Io/emulated_pad_config.h +++ b/rpcs3/Emu/Io/emulated_pad_config.h @@ -93,7 +93,7 @@ public: if (!pad || pad->is_copilot()) return; - for (const Button& button : pad->m_buttons_external) + for (const ButtonExternal& button : pad->m_buttons_external) { if (button.m_pressed || !press_only) { @@ -104,7 +104,7 @@ public: } } - for (const AnalogStick& stick : pad->m_sticks_external) + for (const AnalogStickExternal& stick : pad->m_sticks_external) { if (handle_input(func, stick.m_offset, get_axis_keycode(stick.m_offset, stick.m_value), stick.m_value, true, true)) { diff --git a/rpcs3/Emu/Io/pad_types.h b/rpcs3/Emu/Io/pad_types.h index d2b1535a1a..685d5ba59f 100644 --- a/rpcs3/Emu/Io/pad_types.h +++ b/rpcs3/Emu/Io/pad_types.h @@ -421,6 +421,14 @@ struct Button } }; +struct ButtonExternal +{ + u32 m_offset = 0; + u32 m_outKeyCode = 0; + u16 m_value = 0; + bool m_pressed = false; +}; + struct AnalogStick { u32 m_offset = 0; @@ -442,6 +450,12 @@ struct AnalogStick {} }; +struct AnalogStickExternal +{ + u32 m_offset = 0; + u16 m_value = 128; +}; + struct AnalogSensor { u32 m_offset = 0; @@ -519,8 +533,8 @@ struct Pad std::array m_sensors{}; std::array m_vibrate_motors{}; - std::vector