diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index 05a806f345..2e6434e24b 100644 --- a/.github/workflows/rpcs3.yml +++ b/.github/workflows/rpcs3.yml @@ -33,23 +33,23 @@ jobs: matrix: include: - os: ubuntu-24.04 - docker_img: "rpcs3/rpcs3-ci-jammy:1.8" + docker_img: "rpcs3/rpcs3-ci-jammy:1.9" 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.8" + docker_img: "rpcs3/rpcs3-ci-jammy:1.9" build_sh: "/rpcs3/.ci/build-linux.sh" compiler: gcc - os: ubuntu-24.04-arm - docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.8" + docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.9" 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.8" + docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:1.9" build_sh: "/rpcs3/.ci/build-linux-aarch64.sh" compiler: gcc name: RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }} diff --git a/3rdparty/OpenAL/openal-soft b/3rdparty/OpenAL/openal-soft index 75c0059630..c41d64c6a3 160000 --- a/3rdparty/OpenAL/openal-soft +++ b/3rdparty/OpenAL/openal-soft @@ -1 +1 @@ -Subproject commit 75c00596307bf05ba7bbc8c7022836bf52f17477 +Subproject commit c41d64c6a35f6174bf4a27010aeac52a8d3bb2c6 diff --git a/3rdparty/libsdl-org/SDL b/3rdparty/libsdl-org/SDL index a962f40bbb..683181b47c 160000 --- a/3rdparty/libsdl-org/SDL +++ b/3rdparty/libsdl-org/SDL @@ -1 +1 @@ -Subproject commit a962f40bbba175e9716557a25d5d7965f134a3d3 +Subproject commit 683181b47cfabd293e3ea409f838915b8297a4fd diff --git a/3rdparty/zlib/zlib b/3rdparty/zlib/zlib index 51b7f2abda..da607da739 160000 --- a/3rdparty/zlib/zlib +++ b/3rdparty/zlib/zlib @@ -1 +1 @@ -Subproject commit 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf +Subproject commit da607da739fa6047df13e66a2af6b8bec7c2a498 diff --git a/Utilities/File.cpp b/Utilities/File.cpp index d2adb22c61..490605c792 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -117,6 +117,7 @@ static fs::error to_error(DWORD e) case ERROR_NEGATIVE_SEEK: return fs::error::inval; case ERROR_DIRECTORY: return fs::error::inval; case ERROR_INVALID_NAME: return fs::error::inval; + case ERROR_INVALID_FUNCTION: return fs::error::inval; case ERROR_SHARING_VIOLATION: return fs::error::acces; case ERROR_DIR_NOT_EMPTY: return fs::error::notempty; case ERROR_NOT_READY: return fs::error::noent; @@ -584,7 +585,7 @@ namespace fs if (!GetFileInformationByHandleEx(m_handle, FileIdInfo, &info, sizeof(info))) { // Try GetFileInformationByHandle as a fallback - BY_HANDLE_FILE_INFORMATION info2; + BY_HANDLE_FILE_INFORMATION info2{}; ensure(GetFileInformationByHandle(m_handle, &info2)); info = {}; @@ -1658,11 +1659,40 @@ fs::file::file(const std::string& path, bs_t mode) // 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; + BY_HANDLE_FILE_INFORMATION info{}; if (!GetFileInformationByHandle(handle, &info)) { + const DWORD last_error = GetLastError(); CloseHandle(handle); - g_tls_error = to_error(GetLastError()); + + if (last_error == ERROR_INVALID_FUNCTION) + { + g_tls_error = fs::error::isdir; + return; + } + + g_tls_error = to_error(last_error); + return; + } + + if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + CloseHandle(handle); + g_tls_error = fs::error::isdir; + return; + } + + if (info.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) + { + CloseHandle(handle); + g_tls_error = fs::error::acces; + return; + } + + if ((mode & fs::write) && (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) + { + CloseHandle(handle); + g_tls_error = fs::error::readonly; return; } @@ -2605,7 +2635,7 @@ bool fs::pending_file::commit(bool overwrite) while (file_handle != INVALID_HANDLE_VALUE) { // Get file ID (used to check for hardlinks) - BY_HANDLE_FILE_INFORMATION file_info; + BY_HANDLE_FILE_INFORMATION file_info{}; if (!GetFileInformationByHandle(file_handle, &file_info) || file_info.nNumberOfLinks == 1) { diff --git a/Utilities/StrUtil.h b/Utilities/StrUtil.h index d274cc074d..66c351d60f 100644 --- a/Utilities/StrUtil.h +++ b/Utilities/StrUtil.h @@ -13,13 +13,13 @@ std::string wchar_to_utf8(std::wstring_view src); std::string utf16_to_utf8(std::u16string_view src); std::u16string utf8_to_utf16(std::string_view src); -// Copy null-terminated string from a std::string or a char array to a char array with truncation -template +// Copy null-terminated string from a std::basic_string or a char array to a char array with truncation +template requires requires (D& d, T& t) { std::declval() = &d[0]; } inline void strcpy_trunc(D&& dst, const T& src) { const usz count = std::size(src) >= std::size(dst) ? std::max(std::size(dst), 1) - 1 : std::size(src); - std::memcpy(std::data(dst), std::data(src), count); - std::memset(std::data(dst) + count, 0, std::size(dst) - count); + std::copy_n(std::data(src), count, std::data(dst)); + std::fill_n(std::data(dst) + count, std::size(dst) - count, std::remove_cvref_t{}); } // Convert string to signed integer diff --git a/Utilities/geometry.h b/Utilities/geometry.h index faace6c77e..3ffbc04dd3 100644 --- a/Utilities/geometry.h +++ b/Utilities/geometry.h @@ -821,6 +821,14 @@ struct color4_base a *= rhs; } + void operator += (const color4_base& rhs) + { + r += rhs.r; + g += rhs.g; + b += rhs.b; + a += rhs.a; + } + constexpr color4_base operator * (const color4_base& rhs) const { return { r * rhs.r, g * rhs.g, b * rhs.b, a * rhs.a }; diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index afec56f7e1..e37e6ed0da 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -206,11 +206,7 @@ struct cpu_prof // Print only 7 hash characters out of 11 (which covers roughly 48 bits) if (type_id == 2) { - fmt::append(results, "\n\t[%s", fmt::base57(be_t{name})); - results.resize(results.size() - 4); - - // Print chunk address from lowest 16 bits - fmt::append(results, "...chunk-0x%05x]: %.4f%% (%u)", (name & 0xffff) * 4, _frac * 100., count); + fmt::append(results, "\n\t[%s]: %.4f%% (%u)", spu_block_hash{name}, _frac * 100., count); } else { diff --git a/rpcs3/Emu/Cell/Modules/sceNp.h b/rpcs3/Emu/Cell/Modules/sceNp.h index 88dd2d816b..1bc0a345db 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.h +++ b/rpcs3/Emu/Cell/Modules/sceNp.h @@ -1397,9 +1397,9 @@ struct SceNpBasicMessageDetails // Presence details of an user struct SceNpBasicPresenceDetails { - s8 title[SCE_NP_BASIC_PRESENCE_TITLE_SIZE_MAX]; - s8 status[SCE_NP_BASIC_PRESENCE_STATUS_SIZE_MAX]; - s8 comment[SCE_NP_BASIC_PRESENCE_COMMENT_SIZE_MAX]; + char title[SCE_NP_BASIC_PRESENCE_TITLE_SIZE_MAX]; + char status[SCE_NP_BASIC_PRESENCE_STATUS_SIZE_MAX]; + char comment[SCE_NP_BASIC_PRESENCE_COMMENT_SIZE_MAX]; u8 data[SCE_NP_BASIC_MAX_PRESENCE_SIZE]; be_t size; be_t state; @@ -1410,9 +1410,9 @@ struct SceNpBasicPresenceDetails2 { be_t struct_size; be_t state; - s8 title[SCE_NP_BASIC_PRESENCE_TITLE_SIZE_MAX]; - s8 status[SCE_NP_BASIC_PRESENCE_EXTENDED_STATUS_SIZE_MAX]; - s8 comment[SCE_NP_BASIC_PRESENCE_COMMENT_SIZE_MAX]; + char title[SCE_NP_BASIC_PRESENCE_TITLE_SIZE_MAX]; + char status[SCE_NP_BASIC_PRESENCE_EXTENDED_STATUS_SIZE_MAX]; + char comment[SCE_NP_BASIC_PRESENCE_COMMENT_SIZE_MAX]; u8 data[SCE_NP_BASIC_MAX_PRESENCE_SIZE]; be_t size; }; @@ -1420,9 +1420,9 @@ struct SceNpBasicPresenceDetails2 // Country/region code struct SceNpCountryCode { - s8 data[2]; - s8 term; - s8 padding[1]; + char data[2]; + char term; + char padding[1]; }; // Date information @@ -1451,8 +1451,8 @@ struct SceNpScoreGameInfo // Ranking comment structure struct SceNpScoreComment { - s8 data[SCE_NP_SCORE_COMMENT_MAXLEN]; - s8 term[1]; + char data[SCE_NP_SCORE_COMMENT_MAXLEN]; + char term[1]; }; // Ranking information structure @@ -1524,15 +1524,15 @@ struct SceNpScoreNpIdPcId // Basic clan information to be used in raking struct SceNpScoreClanBasicInfo { - s8 clanName[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1]; - s8 clanTag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1]; + char clanName[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1]; + char clanTag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1]; u8 reserved[10]; }; // Clan member information handled in ranking struct SceNpScoreClansMemberDescription { - s8 description[SCE_NP_CLANS_CLAN_DESCRIPTION_MAX_LENGTH + 1]; + char description[SCE_NP_CLANS_CLAN_DESCRIPTION_MAX_LENGTH + 1]; }; // Clan ranking information diff --git a/rpcs3/Emu/Cell/PPUDisAsm.cpp b/rpcs3/Emu/Cell/PPUDisAsm.cpp index 484688ee12..ebfdffb5bc 100644 --- a/rpcs3/Emu/Cell/PPUDisAsm.cpp +++ b/rpcs3/Emu/Cell/PPUDisAsm.cpp @@ -330,7 +330,7 @@ void comment_constant(std::string& last_opcode, u64 value, bool print_float = fa // Comment constant formation fmt::append(last_opcode, " #0x%xh", value); - if (print_float && ((value >> 31) <= 1u || (value >> 31) == 0x1'ffff'ffffu)) + if (print_float && ((value >> 31) <= 1u || (value >> 31) == 0x1'ffff'ffffu) && (value > 0x3fffff && (value << 32 >> 32) < 0xffc00000)) { const f32 float_val = std::bit_cast(static_cast(value)); diff --git a/rpcs3/Emu/Cell/SPUAnalyser.h b/rpcs3/Emu/Cell/SPUAnalyser.h index 103c655a9e..123a629bed 100644 --- a/rpcs3/Emu/Cell/SPUAnalyser.h +++ b/rpcs3/Emu/Cell/SPUAnalyser.h @@ -51,22 +51,22 @@ struct spu_itype RDCH, RCHCNT, - BR, // branch_tag first + BR, // branch_tag first, zregmod_tag (2) first BRA, BRNZ, BRZ, BRHNZ, BRHZ, - BRSL, - BRASL, IRET, BI, BISLED, - BISL, BIZ, BINZ, BIHZ, - BIHNZ, // branch_tag last + BIHNZ, // zregmod_tag (2) last + BRSL, + BRASL, + BISL, // branch_tag last ILH, // constant_tag_first ILHU, @@ -245,7 +245,7 @@ struct spu_itype // Test for branch instruction friend constexpr bool operator &(type value, branch_tag) { - return value >= BR && value <= BIHNZ; + return value >= BR && value <= BISL; } // Test for floating point instruction @@ -299,7 +299,7 @@ struct spu_itype // Test for non register-modifying instruction friend constexpr bool operator &(type value, zregmod_tag) { - return value >= HEQ && value <= STQR; + return (value >= HEQ && value <= STQR) || (value >= BR && value <= BIHNZ); } }; diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index 4d04b13666..1b6003036b 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -1176,108 +1176,6 @@ void spu_cache::initialize(bool build_existing_cache) if ((g_cfg.core.spu_decoder == spu_decoder_type::asmjit || g_cfg.core.spu_decoder == spu_decoder_type::llvm) && !func_list.empty()) { spu_log.success("SPU Runtime: Built %u functions.", func_list.size()); - - if (g_cfg.core.spu_debug) - { - std::string dump; - dump.reserve(10'000'000); - - std::map, spu_program*, span_less> sorted; - - for (auto&& f : func_list) - { - // Interpret as a byte string - std::span data = {reinterpret_cast(f.data.data()), f.data.size() * sizeof(u32)}; - - sorted[data] = &f; - } - - std::unordered_set depth_n; - - u32 n_max = 0; - - for (auto&& [bytes, f] : sorted) - { - { - sha1_context ctx; - u8 output[20]; - - sha1_starts(&ctx); - sha1_update(&ctx, bytes.data(), bytes.size()); - sha1_finish(&ctx, output); - fmt::append(dump, "\n\t[%s] ", fmt::base57(output)); - } - - u32 depth_m = 0; - - for (auto&& [data, f2] : sorted) - { - u32 depth = 0; - - if (f2 == f) - { - continue; - } - - for (u32 i = 0; i < bytes.size(); i++) - { - if (i < data.size() && data[i] == bytes[i]) - { - depth++; - } - else - { - break; - } - } - - depth_n.emplace(depth); - depth_m = std::max(depth, depth_m); - } - - fmt::append(dump, "c=%06d,d=%06d ", depth_n.size(), depth_m); - - bool sk = false; - - for (u32 i = 0; i < std::min(bytes.size(), std::max(256, depth_m)); i++) - { - if (depth_m == i) - { - dump += '|'; - sk = true; - } - - fmt::append(dump, "%02x", bytes[i]); - - if (i % 4 == 3) - { - if (sk) - { - sk = false; - } - else - { - dump += ' '; - } - - dump += ' '; - } - } - - fmt::append(dump, "\n\t%49s", ""); - - for (u32 i = 0; i < std::min(f->data.size(), std::max(64, utils::aligned_div(depth_m, 4))); i++) - { - fmt::append(dump, "%-10s", g_spu_iname.decode(std::bit_cast>(f->data[i]))); - } - - n_max = std::max(n_max, ::size32(depth_n)); - - depth_n.clear(); - } - - spu_log.notice("SPU Cache Dump (max_c=%d): %s", n_max, dump); - } } // Initialize global cache instance @@ -3705,6 +3603,11 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s default: { + if (type & spu_itype::zregmod) + { + break; + } + // Unconst const u32 op_rt = type & spu_itype::_quadrop ? +op.rt4 : +op.rt; m_regmod[pos / 4] = op_rt; @@ -7488,6 +7391,7 @@ void spu_recompiler_base::dump(const spu_program& result, std::string& out) SPUDisAsm dis_asm(cpu_disasm_mode::dump, reinterpret_cast(result.data.data()), result.lower_bound); std::string hash; + be_t hash_start{}; if (!result.data.empty()) { @@ -7498,6 +7402,7 @@ void spu_recompiler_base::dump(const spu_program& result, std::string& out) sha1_update(&ctx, reinterpret_cast(result.data.data()), result.data.size() * 4); sha1_finish(&ctx, output); fmt::append(hash, "%s", fmt::base57(output)); + std::memcpy(&hash_start, output, sizeof(hash_start)); } else { @@ -7510,7 +7415,7 @@ void spu_recompiler_base::dump(const spu_program& result, std::string& out) { if (m_block_info[bb.first / 4]) { - fmt::append(out, "A: [0x%05x] %s\n", bb.first, m_entry_info[bb.first / 4] ? (m_ret_info[bb.first / 4] ? "Chunk" : "Entry") : "Block"); + fmt::append(out, "A: [0x%05x] %s [%s]\n", bb.first, m_entry_info[bb.first / 4] ? (m_ret_info[bb.first / 4] ? "Chunk" : "Entry") : "Block", spu_block_hash{(hash_start & -65536) + bb.first / 4}); fmt::append(out, "\t F: 0x%05x\n", bb.second.func); diff --git a/rpcs3/Emu/Cell/SPUDisAsm.h b/rpcs3/Emu/Cell/SPUDisAsm.h index 0d5862025b..5b1f097393 100644 --- a/rpcs3/Emu/Cell/SPUDisAsm.h +++ b/rpcs3/Emu/Cell/SPUDisAsm.h @@ -903,8 +903,14 @@ public: if (auto [is_const, value] = try_get_const_equal_value_array(+op.ra); is_const) { + if (value % 0x200 != 0) + { + // si10 is overwritten - likely an analysis mistake + return; + } + // Comment constant formation - comment_constant(last_opcode, value | static_cast(op.si10)); + comment_constant(last_opcode, value | static_cast(op.si10), false); } } void ORHI(spu_opcode_t op) @@ -941,8 +947,14 @@ public: if (auto [is_const, value] = try_get_const_equal_value_array(op.ra); is_const) { + if (value % 0x200 != 0) + { + // si10 is overwritten - likely an analysis mistake + return; + } + // Comment constant formation - comment_constant(last_opcode, value + static_cast(op.si10)); + comment_constant(last_opcode, value + static_cast(op.si10), false); } } void AHI(spu_opcode_t op) @@ -963,8 +975,14 @@ public: if (auto [is_const, value] = try_get_const_equal_value_array(op.ra); is_const) { + if (value % 0x200 != 0) + { + // si10 is overwritten - likely an analysis mistake + return; + } + // Comment constant formation - comment_constant(last_opcode, value ^ static_cast(op.si10)); + comment_constant(last_opcode, value ^ static_cast(op.si10), false); } } void XORHI(spu_opcode_t op) diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index eb44289320..d42acd3560 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -2160,6 +2160,14 @@ public: } } + if (bb.preds.size() >= 2) + { + if (g_cfg.core.spu_prof || g_cfg.core.spu_debug) + { + m_ir->CreateStore(m_ir->getInt64((m_hash_start & -65536) | (baddr >> 2)), spu_ptr(&spu_thread::block_hash)); + } + } + // State check at the beginning of the chunk if (need_check || (bi == 0 && g_cfg.core.spu_block_size != spu_block_size_type::safe)) { @@ -2802,12 +2810,9 @@ public: std::string& llvm_log = function_log; raw_string_ostream out(llvm_log); - if (g_cfg.core.spu_debug) - { - fmt::append(llvm_log, "LLVM IR at 0x%x:\n", func.entry_point); - out << *_module; // print IR - out << "\n\n"; - } + fmt::append(llvm_log, "LLVM IR at 0x%x:\n", func.entry_point); + out << *_module; // print IR + out << "\n\n"; if (verifyModule(*_module, &out)) { @@ -3274,12 +3279,9 @@ public: std::string llvm_log; raw_string_ostream out(llvm_log); - if (g_cfg.core.spu_debug) - { - fmt::append(llvm_log, "LLVM IR (interpreter):\n"); - out << *_module; // print IR - out << "\n\n"; - } + fmt::append(llvm_log, "LLVM IR (interpreter):\n"); + out << *_module; // print IR + out << "\n\n"; if (verifyModule(*_module, &out)) { diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index 4442cfeea4..c113492820 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -164,7 +164,7 @@ void fmt_class_string::format(std::string& out, u64 arg) out.resize(out.size() - 4); // Print chunk address from lowest 16 bits - fmt::append(out, "...chunk-0x%05x", (arg & 0xffff) * 4); + fmt::append(out, "-0x%05x", (arg & 0xffff) * 4); } enum class spu_block_hash_short : u64{}; diff --git a/rpcs3/Emu/Cell/lv2/sys_cond.cpp b/rpcs3/Emu/Cell/lv2/sys_cond.cpp index 401ed3bd66..f66146ae01 100644 --- a/rpcs3/Emu/Cell/lv2/sys_cond.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_cond.cpp @@ -59,6 +59,7 @@ CellError lv2_cond::on_id_create() if (!mutex) { _mutex = static_cast>(ensure(idm::get_unlocked(mtx_id))); + mutex = static_cast(_mutex.get()); } // Defer function diff --git a/rpcs3/Emu/Cell/lv2/sys_config.cpp b/rpcs3/Emu/Cell/lv2/sys_config.cpp index 0179a6b7f2..54cb2d6ce8 100644 --- a/rpcs3/Emu/Cell/lv2/sys_config.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_config.cpp @@ -174,20 +174,28 @@ bool lv2_config_service_listener::check_service(const lv2_config_service& servic return true; } -bool lv2_config_service_listener::notify(const shared_ptr& event) -{ - service_events.emplace_back(event); - return event->notify(); -} - bool lv2_config_service_listener::notify(const shared_ptr& service) { - if (!check_service(*service)) - return false; + { + std::lock_guard lock(mutex_service_events); - // Create service event and notify queue! - const auto event = lv2_config_service_event::create(handle, service, *this); - return notify(event); + if (!check_service(*service)) + return false; + + // Create service event and notify queue! + const auto event = lv2_config_service_event::create(handle, service, *this); + service_events.emplace_back(event); + + if (!event->notify()) + { + // If we fail to deliver the event to the queue just clean the event up or it'll hold the listener alive forever + g_fxo->get().remove_service_event(event->id); + service_events.pop_back(); + return false; + } + } + + return true; } void lv2_config_service_listener::notify_all() @@ -267,7 +275,7 @@ void lv2_config_service_event::write(sys_config_service_event_t *dst) const { const auto registered = service->is_registered(); - dst->service_listener_handle = listener.get_id(); + dst->service_listener_handle = listener_id; dst->registered = registered; dst->service_id = service->id; dst->user_id = service->user_id; @@ -346,7 +354,7 @@ error_code sys_config_get_service_event(u32 config_hdl, u32 event_id, vm::ptrget().find_event(event_id); - if (!event) + if (!event || event->handle != cfg) { return CELL_ESRCH; } diff --git a/rpcs3/Emu/Cell/lv2/sys_config.h b/rpcs3/Emu/Cell/lv2/sys_config.h index 0804671141..aaf4c6f5d0 100644 --- a/rpcs3/Emu/Cell/lv2/sys_config.h +++ b/rpcs3/Emu/Cell/lv2/sys_config.h @@ -296,11 +296,10 @@ private: // The service listener owns the service events - service events will not be freed as long as their corresponding listener exists // This has been confirmed to be the case in realhw + shared_mutex mutex_service_events; std::vector> service_events; shared_ptr handle; - bool notify(const shared_ptr& event); - public: const sys_config_service_id service_id; const u64 min_verbosity; @@ -370,14 +369,14 @@ public: // This has been confirmed to be the case in realhw const shared_ptr handle; const shared_ptr service; - const lv2_config_service_listener& listener; + const u32 listener_id; // Constructors (should not be used directly) lv2_config_service_event(shared_ptr _handle, shared_ptr _service, const lv2_config_service_listener& _listener) noexcept : id(get_next_id()) , handle(std::move(_handle)) , service(std::move(_service)) - , listener(_listener) + , listener_id(_listener.get_id()) { } diff --git a/rpcs3/Emu/Cell/lv2/sys_dbg.cpp b/rpcs3/Emu/Cell/lv2/sys_dbg.cpp index 27fa51b148..eb04cff61b 100644 --- a/rpcs3/Emu/Cell/lv2/sys_dbg.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_dbg.cpp @@ -105,7 +105,7 @@ error_code sys_dbg_write_process_memory(s32 pid, u32 address, u32 size, vm::cptr i += op_size; } - if (!is_exec || i >= end) + if ((!is_exec || i >= end) && exec_update_size > 0) { // Commit executable data update // The read memory is also super ptr so memmove can work correctly on all implementations diff --git a/rpcs3/Emu/Cell/lv2/sys_event.cpp b/rpcs3/Emu/Cell/lv2/sys_event.cpp index 8b3de14bf7..c5fa15cf55 100644 --- a/rpcs3/Emu/Cell/lv2/sys_event.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_event.cpp @@ -170,8 +170,7 @@ CellError lv2_event_queue::send(lv2_event event, bool* notified_thread, lv2_even { if (auto cpu = get_current_cpu_thread()) { - cpu->state += cpu_flag::again; - cpu->state += cpu_flag::exit; + cpu->state += cpu_flag::again + cpu_flag::exit; } sys_event.warning("Ignored event!"); @@ -309,6 +308,15 @@ error_code sys_event_queue_destroy(ppu_thread& ppu, u32 equeue_id, s32 mode) return CELL_EBUSY; } + for (auto cpu = head; cpu; cpu = cpu->get_next_cpu()) + { + if (cpu->state & cpu_flag::again) + { + ppu.state += cpu_flag::again; + return CELL_EAGAIN; + } + } + if (!queue.events.empty()) { // Copy events for logging, does not empty @@ -321,17 +329,6 @@ error_code sys_event_queue_destroy(ppu_thread& ppu, u32 equeue_id, s32 mode) { qlock.unlock(); } - else - { - for (auto cpu = head; cpu; cpu = cpu->get_next_cpu()) - { - if (cpu->state & cpu_flag::again) - { - ppu.state += cpu_flag::again; - return CELL_EAGAIN; - } - } - } return {}; }); @@ -621,7 +618,7 @@ error_code sys_event_port_create(cpu_thread& cpu, vm::ptr eport_id, s32 por sys_event.warning("sys_event_port_create(eport_id=*0x%x, port_type=%d, name=0x%llx)", eport_id, port_type, name); - if (port_type != SYS_EVENT_PORT_LOCAL && port_type != 3) + if (port_type != SYS_EVENT_PORT_LOCAL && port_type != SYS_EVENT_PORT_IPC) { sys_event.error("sys_event_port_create(): unknown port type (%d)", port_type); return CELL_EINVAL; @@ -675,8 +672,9 @@ error_code sys_event_port_connect_local(cpu_thread& cpu, u32 eport_id, u32 equeu std::lock_guard lock(id_manager::g_mutex); const auto port = idm::check_unlocked(eport_id); + auto queue = idm::get_unlocked(equeue_id); - if (!port || !idm::check_unlocked(equeue_id)) + if (!port || !queue) { return CELL_ESRCH; } @@ -691,7 +689,7 @@ error_code sys_event_port_connect_local(cpu_thread& cpu, u32 eport_id, u32 equeu return CELL_EISCONN; } - port->queue = idm::get_unlocked(equeue_id); + port->queue = std::move(queue); return CELL_OK; } diff --git a/rpcs3/Emu/Cell/lv2/sys_event.h b/rpcs3/Emu/Cell/lv2/sys_event.h index 6c43798a30..8364361e6c 100644 --- a/rpcs3/Emu/Cell/lv2/sys_event.h +++ b/rpcs3/Emu/Cell/lv2/sys_event.h @@ -7,7 +7,6 @@ #include class cpu_thread; -class spu_thrread; // Event Queue Type enum : u32 diff --git a/rpcs3/Emu/Cell/lv2/sys_event_flag.cpp b/rpcs3/Emu/Cell/lv2/sys_event_flag.cpp index c28efaf711..89a6c42ac5 100644 --- a/rpcs3/Emu/Cell/lv2/sys_event_flag.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_event_flag.cpp @@ -19,6 +19,22 @@ lv2_event_flag::lv2_event_flag(utils::serial& ar) ar(pattern); } +// Always set result +struct sys_event_store_result +{ + vm::ptr ptr; + u64 val = 0; + + ~sys_event_store_result() noexcept + { + if (ptr) + { + cpu_thread::get_current()->check_state(); + *ptr = val; + } + } +}; + std::function lv2_event_flag::load(utils::serial& ar) { return load_func(make_shared(stx::exact_t(ar))); @@ -120,21 +136,7 @@ error_code sys_event_flag_wait(ppu_thread& ppu, u32 id, u64 bitptn, u32 mode, vm ppu.gpr[5] = mode; ppu.gpr[6] = 0; - // Always set result - struct store_result - { - vm::ptr ptr; - u64 val = 0; - - ~store_result() noexcept - { - if (ptr) - { - cpu_thread::get_current()->check_state(); - *ptr = val; - } - } - } store{result}; + sys_event_store_result store{result}; if (!lv2_event_flag::check_mode(mode)) { @@ -273,21 +275,7 @@ error_code sys_event_flag_trywait(ppu_thread& ppu, u32 id, u64 bitptn, u32 mode, sys_event_flag.trace("sys_event_flag_trywait(id=0x%x, bitptn=0x%llx, mode=0x%x, result=*0x%x)", id, bitptn, mode, result); - // Always set result - struct store_result - { - vm::ptr ptr; - u64 val = 0; - - ~store_result() noexcept - { - if (ptr) - { - cpu_thread::get_current()->check_state(); - *ptr = val; - } - } - } store{result}; + sys_event_store_result store{result}; if (!lv2_event_flag::check_mode(mode)) { @@ -556,8 +544,6 @@ error_code sys_event_flag_get(ppu_thread& ppu, u32 id, vm::ptr flags) return +flag.pattern; }); - ppu.check_state(); - if (!flag) { if (flags) *flags = 0; @@ -569,6 +555,8 @@ error_code sys_event_flag_get(ppu_thread& ppu, u32 id, vm::ptr flags) return CELL_EFAULT; } + ppu.check_state(); + *flags = flag.ret; return CELL_OK; } diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index 961d0d6ad3..172dbc60b5 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -382,10 +382,12 @@ lv2_fs_object::lv2_fs_object(utils::serial& ar, bool) u64 lv2_file::op_read(const fs::file& file, vm::ptr buf, u64 size, u64 opt_pos) { - if (u64 region = buf.addr() >> 28, region_end = (buf.addr() & 0xfff'ffff) + (size & 0xfff'ffff); region == region_end && ((region >> 28) == 0 || region >= 0xC)) + if (u64 region = buf.addr() >> 28, region_end = (buf.addr() + size) >> 28; + size < u32{umax} && region == region_end && (region == 0 || region == 0xD) && vm::check_addr(buf.addr(), vm::page_writable, static_cast(size))) { // Optimize reads from safe memory - return (opt_pos == umax ? file.read(buf.get_ptr(), size) : file.read_at(opt_pos, buf.get_ptr(), size)); + const auto buf_ptr = vm::get_super_ptr(buf.addr()); + return (opt_pos == umax ? file.read(buf_ptr, size) : file.read_at(opt_pos, buf_ptr, size)); } // Copy data from intermediate buffer (avoid passing vm pointer to a native API) @@ -412,6 +414,14 @@ u64 lv2_file::op_read(const fs::file& file, vm::ptr buf, u64 size, u64 opt u64 lv2_file::op_write(const fs::file& file, vm::cptr buf, u64 size) { + if (u64 region = buf.addr() >> 28, region_end = (buf.addr() + size) >> 28; + size < u32{umax} && region == region_end && (region == 0 || region == 0xD) && vm::check_addr(buf.addr(), vm::page_readable, static_cast(size))) + { + // Optimize writes from safe memory + const auto buf_ptr = vm::get_super_ptr(buf.addr()); + return file.write(buf_ptr, size); + } + // Copy data to intermediate buffer (avoid passing vm pointer to a native API) std::vector local_buf(std::min(size, 65536)); @@ -1391,7 +1401,8 @@ error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr path, vm::ptr fd) // Add additional entries for split file candidates (while ends with .66600) while (mp.mp != &g_mp_sys_dev_hdd1 && data.back().name.ends_with(".66600")) { - data.emplace_back(data.back()).name.resize(data.back().name.size() - 6); + fs::dir_entry copy = data.back(); + data.emplace_back(copy).name.resize(copy.name.size() - 6); } } @@ -2147,6 +2158,7 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr _arg, u32 sys_fs.notice("sys_fs_fcntl(0xc0000006): %s", vpath); // Check only mountpoint + vpath = vpath.substr(0, vpath.find_first_of('\0')); vpath = vpath.substr(0, vpath.find_first_of("/", 1)); // Some mountpoints seem to be handled specially @@ -2635,8 +2647,6 @@ error_code sys_fs_lseek(ppu_thread& ppu, u32 fd, s64 offset, s32 whence, vm::ptr error_code sys_fs_fdatasync(ppu_thread& ppu, u32 fd) { - lv2_obj::sleep(ppu); - sys_fs.trace("sys_fs_fdadasync(fd=%d)", fd); const auto file = idm::get_unlocked(fd); @@ -2661,8 +2671,6 @@ error_code sys_fs_fdatasync(ppu_thread& ppu, u32 fd) error_code sys_fs_fsync(ppu_thread& ppu, u32 fd) { - lv2_obj::sleep(ppu); - sys_fs.trace("sys_fs_fsync(fd=%d)", fd); const auto file = idm::get_unlocked(fd); @@ -2903,14 +2911,6 @@ error_code sys_fs_chmod(ppu_thread&, vm::cptr path, s32 mode) { // Try to locate split files - for (u32 i = 66601; i <= 66699; i++) - { - if (mp != &g_mp_sys_dev_hdd1 && !fs::get_stat(fmt::format("%s.%u", local_path, i), info) && !info.is_directory) - { - break; - } - } - if (fs::get_stat(local_path + ".66600", info) && !info.is_directory) { break; diff --git a/rpcs3/Emu/Cell/lv2/sys_lwcond.cpp b/rpcs3/Emu/Cell/lv2/sys_lwcond.cpp index 43bfb308ba..ebe4dfc07c 100644 --- a/rpcs3/Emu/Cell/lv2/sys_lwcond.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_lwcond.cpp @@ -487,6 +487,8 @@ error_code _sys_lwcond_queue_wait(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id { ensure(cond.unqueue(cond.sq, &ppu)); ppu.state += cpu_flag::again; + cond.lwmutex_waiters--; + mutex->lwcond_waiters--; return; } diff --git a/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp b/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp index d7b66adfd8..0723564a5e 100644 --- a/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp @@ -333,7 +333,7 @@ error_code sys_mmapper_allocate_shared_memory_ext(ppu_thread& ppu, u64 ipc_key, } } - if (flags & ~SYS_MEMORY_PAGE_SIZE_MASK) + if (flags & ~SYS_MEMORY_GRANULARITY_MASK) { return CELL_EINVAL; } @@ -401,6 +401,11 @@ error_code sys_mmapper_allocate_shared_memory_from_container_ext(ppu_thread& ppu sys_mmapper.todo("sys_mmapper_allocate_shared_memory_from_container_ext(ipc_key=0x%x, size=0x%x, flags=0x%x, cid=0x%x, entries=*0x%x, entry_count=0x%x, mem_id=*0x%x)", ipc_key, size, flags, cid, entries, entry_count, mem_id); + if (size == 0) + { + return CELL_EALIGN; + } + switch (flags & SYS_MEMORY_PAGE_SIZE_MASK) { case SYS_MEMORY_PAGE_SIZE_1M: @@ -546,8 +551,7 @@ error_code sys_mmapper_free_address(ppu_thread& ppu, u32 addr) // If a memory block is freed, remove it from page notification table. auto& pf_entries = g_fxo->get(); - std::lock_guard lock(pf_entries.mutex); - + std::unique_lock lock(pf_entries.mutex); auto ind_to_remove = pf_entries.entries.begin(); for (; ind_to_remove != pf_entries.entries.end(); ++ind_to_remove) { @@ -558,7 +562,11 @@ error_code sys_mmapper_free_address(ppu_thread& ppu, u32 addr) } if (ind_to_remove != pf_entries.entries.end()) { + u32 port_id = ind_to_remove->port_id; pf_entries.entries.erase(ind_to_remove); + lock.unlock(); + sys_event_port_disconnect(ppu, port_id); + sys_event_port_destroy(ppu, port_id); } return CELL_OK; @@ -826,7 +834,6 @@ error_code sys_mmapper_enable_page_fault_notification(ppu_thread& ppu, u32 start vm::var port_id(0); error_code res = sys_event_port_create(ppu, port_id, SYS_EVENT_PORT_LOCAL, SYS_MEMORY_PAGE_FAULT_EVENT_KEY); - sys_event_port_connect_local(ppu, *port_id, event_queue_id); if (res + 0u == CELL_EAGAIN) { @@ -834,6 +841,8 @@ error_code sys_mmapper_enable_page_fault_notification(ppu_thread& ppu, u32 start return CELL_EAGAIN; } + sys_event_port_connect_local(ppu, *port_id, event_queue_id); + auto& pf_entries = g_fxo->get(); std::unique_lock lock(pf_entries.mutex); diff --git a/rpcs3/Emu/Cell/lv2/sys_mutex.cpp b/rpcs3/Emu/Cell/lv2/sys_mutex.cpp index e6c96ffd64..9f436ae4d3 100644 --- a/rpcs3/Emu/Cell/lv2/sys_mutex.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_mutex.cpp @@ -85,7 +85,7 @@ error_code sys_mutex_create(ppu_thread& ppu, vm::ptr mutex_id, vm::ptr(_attr.pshared, _attr.ipc_key, _attr.flags, [&]() + if (auto error = lv2_obj::create(_attr.pshared, ipc_key, _attr.flags, [&]() { return make_shared( _attr.protocol, diff --git a/rpcs3/Emu/Cell/lv2/sys_mutex.h b/rpcs3/Emu/Cell/lv2/sys_mutex.h index f82f913399..c4fe04ce2a 100644 --- a/rpcs3/Emu/Cell/lv2/sys_mutex.h +++ b/rpcs3/Emu/Cell/lv2/sys_mutex.h @@ -173,7 +173,11 @@ struct lv2_mutex final : lv2_obj if (sq == data.sq) { - atomic_storage::release(control.raw().owner, res->id); + if (cpu_flag::again - res->state) + { + atomic_storage::release(control.raw().owner, res->id); + } + return false; } diff --git a/rpcs3/Emu/Cell/lv2/sys_net.cpp b/rpcs3/Emu/Cell/lv2/sys_net.cpp index 27731e44ca..a860d43b12 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net.cpp @@ -563,37 +563,34 @@ error_code sys_net_bnet_connect(ppu_thread& ppu, s32 s, vm::ptr(-SYS_NET_EINTR)) + if (state & cpu_flag::signal) { - return -SYS_NET_EINTR; + break; } - if (result) - { - if (result < 0) - { - return sys_net_error{result}; - } + ppu.state.wait(state); + } - return not_an_error(result); + if (ppu.gpr[3] == static_cast(-SYS_NET_EINTR)) + { + return -SYS_NET_EINTR; + } + + if (result) + { + if (result < 0) + { + return sys_net_error{result}; } + + return not_an_error(result); } return CELL_OK; @@ -1295,7 +1292,7 @@ error_code sys_net_bnet_poll(ppu_thread& ppu, vm::ptr fds, s32 n if (auto sock = idm::check_unlocked(fds_buf[i].fd)) { - signaled += sock->poll(fds_buf[i], _fds[i]); + sock->poll(fds_buf[i], _fds[i]); #ifdef _WIN32 connecting[i] = sock->is_connecting(); #endif @@ -1303,7 +1300,6 @@ error_code sys_net_bnet_poll(ppu_thread& ppu, vm::ptr fds, s32 n else { fds_buf[i].revents |= SYS_NET_POLLNVAL; - signaled++; } } @@ -1536,9 +1532,9 @@ error_code sys_net_bnet_select(ppu_thread& ppu, s32 nfds, vm::ptr select(bs_t selected, pollfd& native_pfd) = 0; error_code abort_socket(s32 flags); diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp index 2eb47d7b55..b422db814b 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp @@ -1147,14 +1147,14 @@ s32 lv2_socket_native::shutdown(s32 how) return -get_last_error(false); } -s32 lv2_socket_native::poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) +void lv2_socket_native::poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) { // Check for fake packet for dns interceptions auto& dnshook = g_fxo->get(); if (sn_pfd.events & SYS_NET_POLLIN && dnshook.is_dns(sn_pfd.fd) && dnshook.is_dns_queue(sn_pfd.fd)) { sn_pfd.revents |= SYS_NET_POLLIN; - return 1; + return; } if (sn_pfd.events & ~(SYS_NET_POLLIN | SYS_NET_POLLOUT | SYS_NET_POLLERR)) { @@ -1171,8 +1171,6 @@ s32 lv2_socket_native::poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) { native_pfd.events |= POLLOUT; } - - return 0; } std::tuple lv2_socket_native::select(bs_t selected, pollfd& native_pfd) diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h index 84f4218cbd..af9e6a57b7 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h @@ -50,7 +50,7 @@ public: std::optional sendto(s32 flags, const std::vector& buf, std::optional opt_sn_addr, bool is_lock = true) override; std::optional sendmsg(s32 flags, const sys_net_msghdr& msg, bool is_lock = true) override; - s32 poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; + void poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; std::tuple select(bs_t selected, pollfd& native_pfd) override; bool is_socket_connected(); diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.cpp index 67cefe3e3e..903752085b 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.cpp @@ -364,7 +364,7 @@ s32 lv2_socket_p2p::shutdown([[maybe_unused]] s32 how) return CELL_OK; } -s32 lv2_socket_p2p::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd) +void lv2_socket_p2p::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd) { std::lock_guard lock(mutex); ensure(vport); @@ -381,8 +381,6 @@ s32 lv2_socket_p2p::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native { sn_pfd.revents |= SYS_NET_POLLOUT; } - - return sn_pfd.revents ? 1 : 0; } std::tuple lv2_socket_p2p::select(bs_t selected, [[maybe_unused]] pollfd& native_pfd) diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.h index b8fadb3d53..ec6c1d8b31 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.h @@ -30,7 +30,7 @@ public: void close() override; s32 shutdown(s32 how) override; - s32 poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; + void poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; std::tuple select(bs_t selected, pollfd& native_pfd) override; void handle_new_data(sys_net_sockaddr_in_p2p p2p_addr, std::vector p2p_data); diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp index 771402809e..cb59557458 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp @@ -985,7 +985,7 @@ s32 lv2_socket_p2ps::shutdown([[maybe_unused]] s32 how) return CELL_OK; } -s32 lv2_socket_p2ps::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd) +void lv2_socket_p2ps::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd) { std::lock_guard lock(mutex); sys_net.trace("[P2PS] poll checking for 0x%X", sn_pfd.events); @@ -1002,14 +1002,7 @@ s32 lv2_socket_p2ps::poll(sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& nativ { sn_pfd.revents |= SYS_NET_POLLOUT; } - - if (sn_pfd.revents) - { - return 1; - } } - - return 0; } std::tuple lv2_socket_p2ps::select(bs_t selected, [[maybe_unused]] pollfd& native_pfd) diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.h index 8158138936..ac23528d57 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.h @@ -89,7 +89,7 @@ public: void close() override; s32 shutdown(s32 how) override; - s32 poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; + void poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; std::tuple select(bs_t selected, pollfd& native_pfd) override; private: diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.cpp index 6e74bd512f..39ae39e5b5 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.cpp @@ -134,10 +134,9 @@ s32 lv2_socket_raw::shutdown([[maybe_unused]] s32 how) return {}; } -s32 lv2_socket_raw::poll([[maybe_unused]] sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd) +void lv2_socket_raw::poll([[maybe_unused]] sys_net_pollfd& sn_pfd, [[maybe_unused]] pollfd& native_pfd) { LOG_ONCE(raw_poll, "lv2_socket_raw::poll"); - return {}; } std::tuple lv2_socket_raw::select([[maybe_unused]] bs_t selected, [[maybe_unused]] pollfd& native_pfd) diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.h index 01b7255884..a03339354f 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.h @@ -32,6 +32,6 @@ public: void close() override; s32 shutdown(s32 how) override; - s32 poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; + void poll(sys_net_pollfd& sn_pfd, pollfd& native_pfd) override; std::tuple select(bs_t selected, pollfd& native_pfd) override; }; diff --git a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp index e2463ca101..f26b650346 100644 --- a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp @@ -305,6 +305,7 @@ error_code sys_ppu_thread_detach(ppu_thread& ppu, u32 thread_id) { // Join and notify thread (it is detached from IDM now so it must be done explicitly now) *ptr = thread_state::finished; + return CELL_OK; } return result; diff --git a/rpcs3/Emu/Cell/lv2/sys_prx.cpp b/rpcs3/Emu/Cell/lv2/sys_prx.cpp index 046d19c48e..f20b6be6cb 100644 --- a/rpcs3/Emu/Cell/lv2/sys_prx.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_prx.cpp @@ -899,7 +899,7 @@ error_code _sys_prx_register_library(ppu_thread& ppu, vm::ptr library) { for (u32 lib_addr = prx.exports_start, index = 0; lib_addr < prx.exports_end; index++, lib_addr += vm::read8(lib_addr) ? vm::read8(lib_addr) : sizeof_lib) { - if (std::memcpy(vm::base(lib_addr), mem_copy.data(), sizeof_lib) == 0) + if (std::memcmp(vm::base(lib_addr), mem_copy.data(), sizeof_lib) == 0) { atomic_storage::release(prx.m_external_loaded_flags[index], true); return true; diff --git a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp index 2ba15b1146..29a1fa3501 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_rsxaudio.cpp @@ -46,14 +46,14 @@ namespace rsxaudio_ringbuf_reader static void set_timestamp(rsxaudio_shmem::ringbuf_t& ring_buf, u64 timestamp) { const s32 entry_idx_raw = (ring_buf.read_idx + ring_buf.rw_max_idx - (ring_buf.rw_max_idx > 2) - 1) % ring_buf.rw_max_idx; - const s32 entry_idx = std::clamp(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ); + const s32 entry_idx = std::clamp(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ - 1); ring_buf.entries[entry_idx].timestamp = convert_to_timebased_time(timestamp); } static std::tuple update_status(rsxaudio_shmem::ringbuf_t& ring_buf) { - const s32 read_idx = std::clamp(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ); + const s32 read_idx = std::clamp(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ - 1); if ((ring_buf.entries[read_idx].valid & 1) == 0U) { @@ -61,7 +61,7 @@ namespace rsxaudio_ringbuf_reader } const s32 entry_idx_raw = (ring_buf.read_idx + ring_buf.rw_max_idx - (ring_buf.rw_max_idx > 2)) % ring_buf.rw_max_idx; - const s32 entry_idx = std::clamp(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ); + const s32 entry_idx = std::clamp(entry_idx_raw, 0, SYS_RSXAUDIO_RINGBUF_SZ - 1); ring_buf.entries[read_idx].valid = 0; ring_buf.queue_notify_idx = (ring_buf.queue_notify_idx + 1) % ring_buf.queue_notify_step; @@ -72,7 +72,7 @@ namespace rsxaudio_ringbuf_reader static std::pair get_addr(const rsxaudio_shmem::ringbuf_t& ring_buf) { - const s32 read_idx = std::clamp(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ); + const s32 read_idx = std::clamp(ring_buf.read_idx, 0, SYS_RSXAUDIO_RINGBUF_SZ - 1); if (ring_buf.entries[read_idx].valid & 1) { @@ -1392,9 +1392,9 @@ void rsxaudio_backend_thread::operator()() return; } - static rsxaudio_state ra_state{}; - static emu_audio_cfg emu_cfg{}; - static bool backend_failed = false; + rsxaudio_state ra_state{}; + emu_audio_cfg emu_cfg{}; + bool backend_failed = false; for (;;) { @@ -2018,7 +2018,7 @@ void rsxaudio_periodic_tmr::cancel_timer_unlocked() { const u64 flag = 1; const auto wr_res = write(cancel_event, &flag, sizeof(flag)); - ensure(wr_res == sizeof(flag) || wr_res == -EAGAIN); + ensure(wr_res == sizeof(flag) || errno == EAGAIN); } #elif defined(BSD) || defined(__APPLE__) handle[TIMER_ID].flags = (handle[TIMER_ID].flags & ~EV_ENABLE) | EV_DISABLE; diff --git a/rpcs3/Emu/Cell/lv2/sys_rwlock.cpp b/rpcs3/Emu/Cell/lv2/sys_rwlock.cpp index c2abd40284..e60d4895cc 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rwlock.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_rwlock.cpp @@ -441,6 +441,8 @@ error_code sys_rwlock_wlock(ppu_thread& ppu, u32 rw_lock_id, u64 timeout) continue; } + ppu.state += cpu_flag::wait; + std::lock_guard lock(rwlock->mutex); if (!rwlock->unqueue(rwlock->wq, &ppu)) diff --git a/rpcs3/Emu/Cell/lv2/sys_semaphore.cpp b/rpcs3/Emu/Cell/lv2/sys_semaphore.cpp index 7440cf2def..b6ca578977 100644 --- a/rpcs3/Emu/Cell/lv2/sys_semaphore.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_semaphore.cpp @@ -72,7 +72,7 @@ error_code sys_semaphore_create(ppu_thread& ppu, vm::ptr sem_id, vm::ptr(ppu.test_stopped()); + ppu.check_state(); *sem_id = idm::last_id(); return CELL_OK; @@ -358,7 +358,7 @@ error_code sys_semaphore_get_value(ppu_thread& ppu, u32 sem_id, vm::ptr cou return CELL_EFAULT; } - static_cast(ppu.test_stopped()); + ppu.check_state(); *count = sema.ret; return CELL_OK; diff --git a/rpcs3/Emu/Cell/lv2/sys_spu.cpp b/rpcs3/Emu/Cell/lv2/sys_spu.cpp index 3665efcd0a..ff4294a24f 100644 --- a/rpcs3/Emu/Cell/lv2/sys_spu.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_spu.cpp @@ -437,7 +437,7 @@ struct spu_limits_t raw_spu_count += spu_thread::g_raw_spu_ctr; // physical_spus_count >= spu_limit returns EBUSY, not EINVAL! - if (spu_limit + raw_limit > 6 || raw_spu_count > raw_limit || physical_spus_count >= spu_limit || physical_spus_count > spu_limit || controllable_spu_count > spu_limit) + if (spu_limit + raw_limit > 6 || raw_spu_count > raw_limit || physical_spus_count >= spu_limit || controllable_spu_count > spu_limit) { return false; } diff --git a/rpcs3/Emu/Cell/lv2/sys_ss.cpp b/rpcs3/Emu/Cell/lv2/sys_ss.cpp index 2c4b1282fd..725adbcfc3 100644 --- a/rpcs3/Emu/Cell/lv2/sys_ss.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_ss.cpp @@ -24,7 +24,7 @@ struct lv2_update_manager // For example, 4.90 should be converted to 0x4900000000000 std::erase(version_str, '.'); - if (std::from_chars(version_str.data(), version_str.data() + version_str.size(), system_sw_version, 16).ec != std::errc{}) + if (std::from_chars(version_str.data(), version_str.data() + version_str.size(), system_sw_version, 16).ec == std::errc{}) system_sw_version <<= 40; else system_sw_version = 0; @@ -79,6 +79,7 @@ struct lv2_update_manager if (malloc_set.count(addr)) { + malloc_set.erase(addr); return vm::dealloc(addr, vm::main); } diff --git a/rpcs3/Emu/Cell/lv2/sys_uart.cpp b/rpcs3/Emu/Cell/lv2/sys_uart.cpp index 67e91b97a0..0049b60ffe 100644 --- a/rpcs3/Emu/Cell/lv2/sys_uart.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_uart.cpp @@ -1531,9 +1531,11 @@ private: } }(); + if (sce_idx == umax) + return PS3AV_STATUS_INVALID_VIDEO_PARAM; + const video_sce_param &sce_param = sce_param_arr[sce_idx]; - if (sce_idx == umax || - video_head_cfg.video_head > PS3AV_HEAD_B_ANALOG || + if (video_head_cfg.video_head > PS3AV_HEAD_B_ANALOG || video_head_cfg.video_order > 1 || video_head_cfg.video_format > 16 || video_head_cfg.video_out_format > 16 || diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp index e1c3caf9d7..dca61f3be8 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp @@ -1173,11 +1173,15 @@ error_code sys_usbd_get_device_list(ppu_thread& ppu, u32 handle, vm::ptr - u32 i_tocopy = std::min(max_devices, ::size32(usbh.handled_devices)); + const u32 i_tocopy = std::min(max_devices, ::size32(usbh.handled_devices)); + u32 index = 0; - for (u32 index = 0; index < i_tocopy; index++) + for (const auto& [_, device] : usbh.handled_devices) { - device_list[index] = usbh.handled_devices[index].first; + if (index == i_tocopy) + break; + + device_list[index++] = device.first; } return not_an_error(i_tocopy); @@ -1409,7 +1413,7 @@ error_code sys_usbd_receive_event(ppu_thread& ppu, u32 handle, vm::ptr arg1 if (is_stopped(state)) { - std::lock_guard lock(usbh.mutex); + std::lock_guard lock(usbh.mutex_sq); for (auto cpu = +usbh.sq; cpu; cpu = cpu->next_cpu) { @@ -1587,7 +1591,7 @@ error_code sys_usbd_get_transfer_status(ppu_thread& ppu, u32 handle, u32 id_tran std::lock_guard lock(usbh.mutex); - if (!usbh.is_init) + if (!usbh.is_init || id_transfer >= MAX_SYS_USBD_TRANSFERS) return CELL_EINVAL; const auto status = usbh.get_transfer_status(id_transfer); @@ -1607,7 +1611,7 @@ error_code sys_usbd_get_isochronous_transfer_status(ppu_thread& ppu, u32 handle, std::lock_guard lock(usbh.mutex); - if (!usbh.is_init) + if (!usbh.is_init || id_transfer >= MAX_SYS_USBD_TRANSFERS) return CELL_EINVAL; const auto status = usbh.get_isochronous_transfer_status(id_transfer); diff --git a/rpcs3/Emu/Cell/lv2/sys_vm.cpp b/rpcs3/Emu/Cell/lv2/sys_vm.cpp index 2a224d2339..49337cf223 100644 --- a/rpcs3/Emu/Cell/lv2/sys_vm.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_vm.cpp @@ -97,7 +97,7 @@ error_code sys_vm_memory_map(ppu_thread& ppu, u64 vsize, u64 psize, u32 cid, u64 // Look for unmapped space if (const auto area = vm::find_map(0x10000000, 0x10000000, 2 | (flag & SYS_MEMORY_PAGE_SIZE_MASK))) { - sys_vm.warning("sys_vm_memory_map(): Found VM 0x%x area (vsize=0x%x)", addr, vsize); + sys_vm.warning("sys_vm_memory_map(): Found VM 0x%x area (vsize=0x%x)", area->addr, vsize); // Alloc all memory (shall not fail) ensure(area->alloc(static_cast(vsize))); diff --git a/rpcs3/Emu/NP/pb_helpers.cpp b/rpcs3/Emu/NP/pb_helpers.cpp index 83170426ff..3debd0ba20 100644 --- a/rpcs3/Emu/NP/pb_helpers.cpp +++ b/rpcs3/Emu/NP/pb_helpers.cpp @@ -532,8 +532,8 @@ namespace np if (!mi.msg().empty()) { - sce_mi->msgLen = ::narrow(mi.msg().size()); - auto* ptr_msg_data = static_cast(edata.allocate(mi.msg().size(), sce_mi->msg)); + sce_mi->msgLen = ::size32(mi.msg()); + auto* ptr_msg_data = static_cast(edata.allocate(::size32(mi.msg()), sce_mi->msg)); memcpy(ptr_msg_data, mi.msg().data(), mi.msg().size()); } } @@ -575,8 +575,8 @@ namespace np if (!resp.opt().empty()) { - room_status->opt_len = ::narrow(resp.opt().size()); - u8* opt_data = static_cast(edata.allocate(resp.opt().size(), room_status->opt)); + room_status->opt_len = ::size32(resp.opt()); + u8* opt_data = static_cast(edata.allocate(::size32(resp.opt()), room_status->opt)); memcpy(opt_data, resp.opt().data(), resp.opt().size()); } @@ -604,8 +604,8 @@ namespace np cur_attr->id = attr.attr_id(); if (!attr.data().empty()) { - cur_attr->value.data.size = ::narrow(attr.data().size()); - u8* data_ptr = static_cast(edata.allocate(attr.data().size(), cur_attr->value.data.ptr)); + cur_attr->value.data.size = ::size32(attr.data()); + u8* data_ptr = static_cast(edata.allocate(::size32(attr.data()), cur_attr->value.data.ptr)); memcpy(data_ptr, attr.data().data(), attr.data().size()); } else diff --git a/rpcs3/Emu/RSX/Common/TextureUtils.cpp b/rpcs3/Emu/RSX/Common/TextureUtils.cpp index 7fe0431baf..937508035b 100644 --- a/rpcs3/Emu/RSX/Common/TextureUtils.cpp +++ b/rpcs3/Emu/RSX/Common/TextureUtils.cpp @@ -847,6 +847,17 @@ namespace rsx } } + bool texture_format_ex::hw_SNORM_possible() const + { + return (texel_remap_control & SEXT_MASK) == (get_host_format_snorm_mask(format()) << SEXT_OFFSET); + } + + bool texture_format_ex::hw_SRGB_possible() const + { + return encoded_remap == RSX_TEXTURE_REMAP_IDENTITY && + (texel_remap_control & GAMMA_CTRL_MASK) == GAMMA_RGB_MASK; + } + std::vector get_subresources_layout(const rsx::fragment_texture& texture) { return get_subresources_layout_impl(texture); @@ -1255,6 +1266,32 @@ namespace rsx fmt::throw_exception("Unknown format 0x%x", texture_format); } + /** + * Returns a channel mask in ARGB that can be SNORM-converted + * Some formats have a hardcoded constant in one lane which we cannot SNORM-interpret in hardware. + */ + u32 get_host_format_snorm_mask(u32 format) + { + switch (format) + { + case CELL_GCM_TEXTURE_B8: + case CELL_GCM_TEXTURE_R5G6B5: + case CELL_GCM_TEXTURE_R6G5B5: + case CELL_GCM_TEXTURE_D1R5G5B5: + case CELL_GCM_TEXTURE_D8R8G8B8: + case CELL_GCM_TEXTURE_COMPRESSED_B8R8_G8R8: + case CELL_GCM_TEXTURE_COMPRESSED_R8B8_R8G8: + // Hardcoded alpha formats + return 0b1110; + + case CELL_GCM_TEXTURE_X16: + // This one is a mess. X and Z are hardcoded. Not supported. + // Fall through instead of throw + default: + return 0b1111; + } + } + /** * A texture is stored as an array of blocks, where a block is a pixel for standard texture * but is a structure containing several pixels for compressed format diff --git a/rpcs3/Emu/RSX/Common/TextureUtils.h b/rpcs3/Emu/RSX/Common/TextureUtils.h index c32152fecd..31f43432dc 100644 --- a/rpcs3/Emu/RSX/Common/TextureUtils.h +++ b/rpcs3/Emu/RSX/Common/TextureUtils.h @@ -135,6 +135,12 @@ namespace rsx RSX_FORMAT_FEATURE_16BIT_CHANNELS = (1 << 3), // Complements RSX_FORMAT_FEATURE_SIGNED_COMPONENTS }; + enum host_format_features : u8 + { + RSX_HOST_FORMAT_FEATURE_SNORM = (1 << 0), + RSX_HOST_FORMAT_FEATURE_SRGB = (1 << 1), + }; + using enum format_features; struct texture_format_ex @@ -147,10 +153,28 @@ namespace rsx bool valid() const { return format_bits != 0; } u32 format() const { return format_bits & ~(CELL_GCM_TEXTURE_LN | CELL_GCM_TEXTURE_UN); } + bool hw_SNORM_possible() const; + bool hw_SRGB_possible() const; + + bool host_snorm_format_active() const { return host_features & RSX_HOST_FORMAT_FEATURE_SNORM; } + bool host_srgb_format_active() const { return host_features & RSX_HOST_FORMAT_FEATURE_SRGB; } + + operator bool() const { return valid(); } + + bool operator == (const texture_format_ex& that) const + { + return this->format_bits == that.format_bits && + this->features == that.features && + this->host_features == that.host_features && + this->encoded_remap == that.encoded_remap; + } + //private: u32 format_bits = 0; u32 features = 0; + u32 encoded_remap = 0; u32 texel_remap_control = 0; + u32 host_features = 0; }; // Sampled image descriptor @@ -293,6 +317,12 @@ namespace rsx */ rsx::flags32_t get_format_features(u32 texture_format); + /** + * Returns a channel mask in ARGB that can be SNORM-converted + * Some formats have a hardcoded constant in one lane which we cannot SNORM-interpret in hardware. + */ + u32 get_host_format_snorm_mask(u32 format); + /** * Returns number of texel rows encoded in one pitch-length line of bytes */ diff --git a/rpcs3/Emu/RSX/GL/GLDraw.cpp b/rpcs3/Emu/RSX/GL/GLDraw.cpp index 475507ee78..3de2585523 100644 --- a/rpcs3/Emu/RSX/GL/GLDraw.cpp +++ b/rpcs3/Emu/RSX/GL/GLDraw.cpp @@ -299,95 +299,155 @@ void GLGSRender::load_texture_env() for (u32 textures_ref = current_fp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i) { if (!(textures_ref & 1)) + { continue; + } if (!fs_sampler_state[i]) + { fs_sampler_state[i] = std::make_unique(); + } auto sampler_state = static_cast(fs_sampler_state[i].get()); const auto& tex = rsx::method_registers.fragment_textures[i]; const auto previous_format_class = sampler_state->format_class; - if (m_samplers_dirty || m_textures_dirty[i] || m_gl_texture_cache.test_if_descriptor_expired(cmd, m_rtts, sampler_state, tex)) + if (!m_samplers_dirty && + !m_textures_dirty[i] && + !m_gl_texture_cache.test_if_descriptor_expired(cmd, m_rtts, sampler_state, tex)) { - if (tex.enabled()) + continue; + } + + const bool is_sampler_dirty = m_textures_dirty[i]; + m_textures_dirty[i] = false; + + if (!tex.enabled()) + { + *sampler_state = {}; + continue; + } + + *sampler_state = m_gl_texture_cache.upload_texture(cmd, tex, m_rtts); + if (!sampler_state->validate()) + { + continue; + } + + if (!is_sampler_dirty) + { + if (sampler_state->format_class != previous_format_class) { - *sampler_state = m_gl_texture_cache.upload_texture(cmd, tex, m_rtts); - - if (sampler_state->validate()) - { - sampler_state->format_ex = tex.format_ex(); - - if (m_textures_dirty[i]) - { - m_fs_sampler_states[i].apply(tex, fs_sampler_state[i].get()); - } - else if (sampler_state->format_class != previous_format_class) - { - m_graphics_state |= rsx::fragment_program_state_dirty; - } - - const auto texture_format = sampler_state->format_ex.format(); - // Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking. - // If accurate graphics are desired, force a bitcast to COLOR as a workaround. - const bool is_depth_reconstructed = sampler_state->format_class != rsx::classify_format(texture_format) && - (texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8); - // SNORM conversion required in shader. Do not interpolate to avoid introducing discontinuities due to how negative numbers work - const bool is_snorm = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) != 0; - - if (is_depth_reconstructed || is_snorm) - { - // Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking. - m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MIN_FILTER, GL_NEAREST); - m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MAG_FILTER, GL_NEAREST); - } - } - } - else - { - *sampler_state = {}; + // Host details changed but RSX is not aware + m_graphics_state |= rsx::fragment_program_state_dirty; } - m_textures_dirty[i] = false; + if (sampler_state->format_ex) + { + // Nothing to change, use cached sampler + continue; + } + } + + sampler_state->format_ex = tex.format_ex(); + + if (sampler_state->format_ex.texel_remap_control && + sampler_state->image_handle && + sampler_state->upload_context == rsx::texture_upload_context::shader_read && + (current_fp_metadata.bx2_texture_reads_mask & (1u << i)) == 0 && + !g_cfg.video.disable_hardware_texel_remapping) [[ unlikely ]] + { + // Check if we need to override the view format + const auto gl_format = sampler_state->image_handle->view_format(); + GLenum format_override = gl_format; + rsx::flags32_t flags_to_erase = 0u; + rsx::flags32_t host_flags_to_set = 0u; + + if (sampler_state->format_ex.hw_SNORM_possible()) + { + format_override = gl::get_compatible_snorm_format(gl_format); + flags_to_erase = rsx::texture_control_bits::SEXT_MASK; + host_flags_to_set = rsx::RSX_HOST_FORMAT_FEATURE_SNORM; + } + else if (sampler_state->format_ex.hw_SRGB_possible()) + { + format_override = gl::get_compatible_srgb_format(gl_format); + flags_to_erase = rsx::texture_control_bits::GAMMA_CTRL_MASK; + host_flags_to_set = rsx::RSX_HOST_FORMAT_FEATURE_SRGB; + } + + if (format_override != GL_NONE && format_override != gl_format) + { + sampler_state->image_handle = sampler_state->image_handle->as(format_override); + sampler_state->format_ex.texel_remap_control &= (~flags_to_erase); + sampler_state->format_ex.host_features |= host_flags_to_set; + } + } + + m_fs_sampler_states[i].apply(tex, fs_sampler_state[i].get()); + + const auto texture_format = sampler_state->format_ex.format(); + // Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking. + // If accurate graphics are desired, force a bitcast to COLOR as a workaround. + const bool is_depth_reconstructed = sampler_state->format_class != rsx::classify_format(texture_format) && + (texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8); + // SNORM conversion required in shader. Do not interpolate to avoid introducing discontinuities due to how negative numbers work + const bool is_snorm = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) != 0; + + if (is_depth_reconstructed || is_snorm) + { + // Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking. + m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MIN_FILTER, GL_NEAREST); + m_fs_sampler_states[i].set_parameteri(GL_TEXTURE_MAG_FILTER, GL_NEAREST); } } for (u32 textures_ref = current_vp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i) { if (!(textures_ref & 1)) + { continue; + } if (!vs_sampler_state[i]) + { vs_sampler_state[i] = std::make_unique(); + } auto sampler_state = static_cast(vs_sampler_state[i].get()); const auto& tex = rsx::method_registers.vertex_textures[i]; const auto previous_format_class = sampler_state->format_class; - if (m_samplers_dirty || m_vertex_textures_dirty[i] || m_gl_texture_cache.test_if_descriptor_expired(cmd, m_rtts, sampler_state, tex)) + if (!m_samplers_dirty && + !m_vertex_textures_dirty[i] && + !m_gl_texture_cache.test_if_descriptor_expired(cmd, m_rtts, sampler_state, tex)) { - if (rsx::method_registers.vertex_textures[i].enabled()) - { - *sampler_state = m_gl_texture_cache.upload_texture(cmd, rsx::method_registers.vertex_textures[i], m_rtts); + continue; + } - if (sampler_state->validate()) - { - if (m_vertex_textures_dirty[i]) - { - m_vs_sampler_states[i].apply(tex, vs_sampler_state[i].get()); - } - else if (sampler_state->format_class != previous_format_class) - { - m_graphics_state |= rsx::vertex_program_state_dirty; - } - } - } - else - { - *sampler_state = {}; - } + const bool is_sampler_dirty = m_vertex_textures_dirty[i]; + m_vertex_textures_dirty[i] = false; - m_vertex_textures_dirty[i] = false; + if (!tex.enabled()) + { + *sampler_state = {}; + continue; + } + + *sampler_state = m_gl_texture_cache.upload_texture(cmd, rsx::method_registers.vertex_textures[i], m_rtts); + + if (!sampler_state->validate()) + { + continue; + } + + if (is_sampler_dirty) + { + m_vs_sampler_states[i].apply(tex, vs_sampler_state[i].get()); + } + else if (sampler_state->format_class != previous_format_class) + { + m_graphics_state |= rsx::vertex_program_state_dirty; } } @@ -402,7 +462,9 @@ void GLGSRender::bind_texture_env() 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* view = nullptr; auto sampler_state = static_cast(fs_sampler_state[i].get()); @@ -442,22 +504,26 @@ void GLGSRender::bind_texture_env() 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 (sampler_state->image_handle) [[likely]] + if (view = sampler_state->image_handle; !view) { - sampler_state->image_handle->bind(cmd, GL_VERTEX_TEXTURES_START + i); - } - else - { - m_gl_texture_cache.create_temporary_subresource(cmd, sampler_state->external_subresource_desc)->bind(cmd, GL_VERTEX_TEXTURES_START + i); + 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); diff --git a/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp b/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp index f8cc046569..5271390af9 100644 --- a/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/GL/GLFragmentProgram.cpp @@ -237,6 +237,7 @@ void GLFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS) m_shader_props.require_tex3D_ops = properties.has_tex3D; 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); glsl::insert_glsl_legacy_function(OS, m_shader_props); } diff --git a/rpcs3/Emu/RSX/GL/GLTexture.cpp b/rpcs3/Emu/RSX/GL/GLTexture.cpp index 9a439177f4..181e5058c8 100644 --- a/rpcs3/Emu/RSX/GL/GLTexture.cpp +++ b/rpcs3/Emu/RSX/GL/GLTexture.cpp @@ -266,6 +266,44 @@ namespace gl fmt::throw_exception("Unknown format 0x%x", texture_format); } + GLenum get_compatible_snorm_format(GLenum base_format) + { + switch (base_format) + { + case GL_R8: + return GL_R8_SNORM; + case GL_RG8: + return GL_RG8_SNORM; + case GL_RGBA8: + return GL_RGBA8_SNORM; + case GL_R16: + return GL_R16_SNORM; + case GL_RG16: + return GL_RG16_SNORM; + case GL_RGBA16: + return GL_RGBA16_SNORM; + default: + return GL_NONE; + } + } + + GLenum get_compatible_srgb_format(GLenum base_format) + { + switch (base_format) + { + case GL_RGBA8: + return GL_SRGB8_ALPHA8_EXT; + case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: + return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; + case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: + return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; + case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: + return GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + default: + return GL_NONE; + } + } + cs_shuffle_base* get_trivial_transform_job(const pixel_buffer_layout& pack_info) { if (!pack_info.swap_bytes) diff --git a/rpcs3/Emu/RSX/GL/GLTexture.h b/rpcs3/Emu/RSX/GL/GLTexture.h index dc6d90098a..10c26dc536 100644 --- a/rpcs3/Emu/RSX/GL/GLTexture.h +++ b/rpcs3/Emu/RSX/GL/GLTexture.h @@ -62,6 +62,8 @@ namespace gl std::tuple get_format_type(u32 texture_format); pixel_buffer_layout get_format_type(texture::internal_format format); std::array get_swizzle_remap(u32 texture_format); + GLenum get_compatible_snorm_format(GLenum base_format); + GLenum get_compatible_srgb_format(GLenum base_format); viewable_image* create_texture(u32 gcm_format, u16 width, u16 height, u16 depth, u16 mipmaps, rsx::texture_dimension_extended type); diff --git a/rpcs3/Emu/RSX/GL/glutils/image.cpp b/rpcs3/Emu/RSX/GL/glutils/image.cpp index bb6439cc05..7dbd7fc254 100644 --- a/rpcs3/Emu/RSX/GL/glutils/image.cpp +++ b/rpcs3/Emu/RSX/GL/glutils/image.cpp @@ -318,6 +318,34 @@ namespace gl } } + texture_view* texture_view::as(GLenum format) + { + if (format == this->m_view_format) + { + return this; + } + + auto self = m_root_view ? m_root_view : this; + if (auto found = self->m_subviews.find(format); + found != self->m_subviews.end()) + { + return found->second.get(); + } + + GLenum swizzle_argb[4] = + { + component_swizzle[3], + component_swizzle[0], + component_swizzle[1], + component_swizzle[2], + }; + + auto view = std::make_unique(m_image_data, m_target, format, swizzle_argb, m_aspect_flags); + auto ret = view.get(); + self->m_subviews.emplace(format, std::move(view)); + return ret; + } + void texture_view::bind(gl::command_context& cmd, GLuint layer) const { cmd->bind_texture(layer, m_target, m_id); diff --git a/rpcs3/Emu/RSX/GL/glutils/image.h b/rpcs3/Emu/RSX/GL/glutils/image.h index 6617caa54c..4112d833c7 100644 --- a/rpcs3/Emu/RSX/GL/glutils/image.h +++ b/rpcs3/Emu/RSX/GL/glutils/image.h @@ -353,7 +353,10 @@ namespace gl GLenum m_aspect_flags = 0; texture* m_image_data = nullptr; - GLenum component_swizzle[4] {}; + GLenum component_swizzle[4]{}; + + std::unordered_map> m_subviews; + texture_view* m_root_view = nullptr; texture_view() = default; @@ -395,6 +398,8 @@ namespace gl virtual ~texture_view(); + texture_view* as(GLenum format); + GLuint id() const { return m_id; diff --git a/rpcs3/Emu/RSX/GL/glutils/sampler.cpp b/rpcs3/Emu/RSX/GL/glutils/sampler.cpp index 580caf0dc2..387228983c 100644 --- a/rpcs3/Emu/RSX/GL/glutils/sampler.cpp +++ b/rpcs3/Emu/RSX/GL/glutils/sampler.cpp @@ -82,11 +82,34 @@ namespace gl { // NOTE: In OpenGL, the border texels are processed by the pipeline and will be swizzled by the texture view. // Therefore, we pass the raw value here, and the texture view will handle the rest for us. - const auto encoded_color = tex.border_color(); - if (get_parameteri(GL_TEXTURE_BORDER_COLOR) != encoded_color) + const bool sext_conv_required = (sampled_image->format_ex.texel_remap_control & rsx::SEXT_MASK) != 0; + const auto encoded_color = tex.border_color(sext_conv_required); + const auto host_features = sampled_image->format_ex.host_features; + + if (get_parameteri(GL_TEXTURE_BORDER_COLOR) != encoded_color || + get_parameteri(GL_TEXTURE_BORDER_VALUES_NV) != host_features) { m_propertiesi[GL_TEXTURE_BORDER_COLOR] = encoded_color; - const auto border_color = rsx::decode_border_color(encoded_color); + m_propertiesi[GL_TEXTURE_BORDER_VALUES_NV] = host_features; + + auto border_color = rsx::decode_border_color(encoded_color); + if (sampled_image->format_ex.host_snorm_format_active()) [[ unlikely ]] + { + // Hardware SNORM is active + // Convert the border color in host space (2N - 1) + // HW does the conversion in integer space as (x - 128) / 127 which introduces a biasing error. + const float bias_v = 128.f / 255.f; + const float scale_v = 255.f / 127.f; + + color4f scale{ 1.f }, bias{ 0.f }; + const auto snorm_mask = tex.argb_signed(); + if (snorm_mask & 1) { scale.a = scale_v; bias.a = -bias_v; } + if (snorm_mask & 2) { scale.r = scale_v; bias.r = -bias_v; } + if (snorm_mask & 4) { scale.g = scale_v; bias.g = -bias_v; } + if (snorm_mask & 8) { scale.b = scale_v; bias.b = -bias_v; } + border_color = (border_color + bias) * scale; + } + glSamplerParameterfv(sampler_handle, GL_TEXTURE_BORDER_COLOR, border_color.rgba); } } diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp index 7a6a5a6b47..368ac4dd39 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp @@ -12,6 +12,8 @@ extern atomic_t g_user_asked_for_recording; +atomic_t g_user_asked_for_fullscreen = false; + namespace rsx { namespace overlays @@ -80,7 +82,7 @@ namespace rsx if (btn != pad_button::cross) return page_navigation::stay; rsx_log.notice("User selected trophies in home menu"); - Emu.CallFromMainThread([trop_name = std::move(trop_name)]() + Emu.CallFromMainThread([trop_name]() { if (auto manager = g_fxo->try_get()) { @@ -110,6 +112,17 @@ namespace rsx return page_navigation::exit; }); + std::unique_ptr fullscreen = std::make_unique(get_localized_string(localized_string_id::HOME_MENU_TOGGLE_FULLSCREEN)); + add_item(fullscreen, [](pad_button btn) -> page_navigation + { + if (btn != pad_button::cross) + return page_navigation::stay; + + rsx_log.notice("User selected toggle fullscreen in home menu"); + g_user_asked_for_fullscreen = true; + return page_navigation::stay; // No need to exit + }); + add_page(std::make_shared(x, y, width, height, use_separators, this)); std::unique_ptr restart = std::make_unique(get_localized_string(localized_string_id::HOME_MENU_RESTART)); diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_page.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_page.cpp index 81c1ebecba..edfb932d26 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_page.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_page.cpp @@ -215,12 +215,24 @@ namespace rsx case pad_button::dpad_up: case pad_button::ls_up: { + if (!is_auto_repeat && get_selected_index() <= 0) + { + select_entry(get_elements_count() - 1); + break; + } + select_previous(); break; } case pad_button::dpad_down: case pad_button::ls_down: { + if (!is_auto_repeat && get_selected_index() >= (get_elements_count() - 1)) + { + select_entry(0); + break; + } + select_next(); break; } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_list_view.cpp b/rpcs3/Emu/RSX/Overlays/overlay_list_view.cpp index 093902dbe3..1b6f4e8fc6 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_list_view.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_list_view.cpp @@ -189,7 +189,12 @@ namespace rsx update_selection(); } - int list_view::get_selected_index() const + u16 list_view::get_elements_count() const + { + return m_elements_count; + } + + s32 list_view::get_selected_index() const { return m_selected_entry; } diff --git a/rpcs3/Emu/RSX/Overlays/overlay_list_view.hpp b/rpcs3/Emu/RSX/Overlays/overlay_list_view.hpp index c2378c5319..8f191dd404 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_list_view.hpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_list_view.hpp @@ -34,7 +34,8 @@ namespace rsx void add_entry(std::unique_ptr& entry); - int get_selected_index() const; + u16 get_elements_count() const; + s32 get_selected_index() const; bool get_cancel_only() const; const overlay_element* get_selected_entry() const; diff --git a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp index ebf73e935a..28cbaa61f0 100644 --- a/rpcs3/Emu/RSX/Program/GLSLCommon.cpp +++ b/rpcs3/Emu/RSX/Program/GLSLCommon.cpp @@ -408,6 +408,16 @@ namespace glsl enabled_options.push_back("_ENABLE_TEXTURE_ALPHA_KILL"); } + if (props.require_color_format_convert) + { + enabled_options.push_back("_ENABLE_FORMAT_CONVERSION"); + } + + if (props.require_depth_conversion) + { + enabled_options.push_back("_ENABLE_DEPTH_FORMAT_RECONSTRUCTION"); + } + program_common::define_glsl_switches(OS, enabled_options); enabled_options.clear(); diff --git a/rpcs3/Emu/RSX/Program/GLSLCommon.h b/rpcs3/Emu/RSX/Program/GLSLCommon.h index ae22464f12..81b5043fc9 100644 --- a/rpcs3/Emu/RSX/Program/GLSLCommon.h +++ b/rpcs3/Emu/RSX/Program/GLSLCommon.h @@ -4,52 +4,12 @@ #include "GLSLTypes.h" #include "ShaderParam.h" +#include "../color_utils.h" struct RSXFragmentProgram; namespace rsx { - // TODO: Move this somewhere else once more compilers are supported other than glsl - enum texture_control_bits - { - GAMMA_A = 0, - GAMMA_R, - GAMMA_G, - GAMMA_B, - ALPHAKILL, - RENORMALIZE, - EXPAND_A, - EXPAND_R, - EXPAND_G, - EXPAND_B, - SEXT_A, - SEXT_R, - SEXT_G, - SEXT_B, - DEPTH_FLOAT, - DEPTH_COMPARE_OP, - DEPTH_COMPARE_1, - DEPTH_COMPARE_2, - FILTERED_MAG, - FILTERED_MIN, - UNNORMALIZED_COORDS, - CLAMP_TEXCOORDS_BIT, - WRAP_S, - WRAP_T, - WRAP_R, - FF_SIGNED_BIT, - FF_BIASED_RENORM_BIT, - FF_GAMMA_BIT, - FF_16BIT_CHANNELS_BIT, - - GAMMA_CTRL_MASK = (1 << GAMMA_R) | (1 << GAMMA_G) | (1 << GAMMA_B) | (1 << GAMMA_A), - EXPAND_MASK = (1 << EXPAND_R) | (1 << EXPAND_G) | (1 << EXPAND_B) | (1 << EXPAND_A), - EXPAND_OFFSET = EXPAND_A, - SEXT_MASK = (1 << SEXT_R) | (1 << SEXT_G) | (1 << SEXT_B) | (1 << SEXT_A), - SEXT_OFFSET = SEXT_A, - FORMAT_FEATURES_OFFSET = FF_SIGNED_BIT, - }; - enum ROP_control_bits : u32 { // Commands. These trigger explicit action. diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl index 43dcf9e6eb..aaf4dc434a 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureOps.glsl @@ -46,8 +46,10 @@ R"( _texture_bx2_active = false; \ } while (false) #define TEX_FLAGS(index) ((TEX_PARAM(index).flags & ~(_texture_flag_erase)) | _texture_flag_override) -#else +#elif defined(_ENABLE_TEXTURE_ALPHA_KILL) || defined(_ENABLE_FORMAT_CONVERSION) || defined(_ENABLE_DEPTH_FORMAT_RECONSTRUCTION) #define TEX_FLAGS(index) (TEX_PARAM(index).flags) +#else + #define TEX_FLAGS(index) 0 #endif #define TEX_NAME(index) tex##index @@ -193,6 +195,8 @@ vec4 _texcoord_xform_shadow(const in vec4 coord4, const in sampler_info params) #endif // _EMULATE_SHADOW +#ifdef _ENABLE_FORMAT_CONVERSION + vec4 _sext_unorm8x4(const in vec4 x) { // TODO: Handle clamped sign-extension @@ -285,4 +289,27 @@ vec4 _process_texel(in vec4 rgba, const in uint control_bits) return rgba; } +#elif defined(_ENABLE_TEXTURE_ALPHA_KILL) + +vec4 _process_texel(in vec4 rgba, const in uint control_bits) +{ + if (_test_bit(control_bits, ALPHAKILL)) + { + // Alphakill + if (rgba.a < 0.000001) + { + _kill(); + return rgba; + } + } + + return rgba; +} + +#else + +#define _process_texel(rgba, control) rgba + +#endif // _ENABLE_FORMAT_CONVERSION + )" diff --git a/rpcs3/Emu/RSX/Program/GLSLTypes.h b/rpcs3/Emu/RSX/Program/GLSLTypes.h index 8f7cb6fa03..685d87fafd 100644 --- a/rpcs3/Emu/RSX/Program/GLSLTypes.h +++ b/rpcs3/Emu/RSX/Program/GLSLTypes.h @@ -60,5 +60,6 @@ namespace glsl bool require_tex3D_ops : 1; // Include 3D texture stuff (including cubemap) bool require_shadowProj_ops : 1; // Include shadow2DProj projection textures (1D is unsupported anyway) bool require_alpha_kill : 1; // Include alpha kill checking code + bool require_color_format_convert : 1; // Include colorspace conversion code }; }; diff --git a/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp b/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp index 6e1f93f33a..beaacf3860 100644 --- a/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp +++ b/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp @@ -651,7 +651,7 @@ fragment_program_utils::fragment_program_metadata fragment_program_utils::analys case RSX_FP_OPCODE_TXB: case RSX_FP_OPCODE_TXL: result.referenced_textures_mask |= (1 << d0.tex_num); - result.has_tex_bx2_conv |= !!d0.exp_tex; + result.bx2_texture_reads_mask |= ((d0.exp_tex ? 1u : 0u) << d0.tex_num); break; case RSX_FP_OPCODE_PK4: case RSX_FP_OPCODE_UP4: diff --git a/rpcs3/Emu/RSX/Program/ProgramStateCache.h b/rpcs3/Emu/RSX/Program/ProgramStateCache.h index ce5be4a425..76e168d20a 100644 --- a/rpcs3/Emu/RSX/Program/ProgramStateCache.h +++ b/rpcs3/Emu/RSX/Program/ProgramStateCache.h @@ -56,10 +56,10 @@ namespace program_hash_util u32 program_ucode_length; u32 program_constants_buffer_length; u16 referenced_textures_mask; + u16 bx2_texture_reads_mask; bool has_pack_instructions; bool has_branch_instructions; - bool has_tex_bx2_conv; bool is_nop_shader; // Does this affect Z-pass testing??? }; diff --git a/rpcs3/Emu/RSX/RSXTexture.cpp b/rpcs3/Emu/RSX/RSXTexture.cpp index 6f8eec0e3b..0d18cf0cd3 100644 --- a/rpcs3/Emu/RSX/RSXTexture.cpp +++ b/rpcs3/Emu/RSX/RSXTexture.cpp @@ -121,6 +121,7 @@ namespace rsx texture_format_ex result { format_bits }; result.features = format_features; result.texel_remap_control = format_convert; + result.encoded_remap = remap(); return result; } @@ -364,11 +365,15 @@ namespace rsx return dimension() != rsx::texture_dimension::dimension1d ? ((registers[NV4097_SET_TEXTURE_IMAGE_RECT + (m_index * 8)]) & 0xffff) : 1; } - u32 fragment_texture::border_color() const + u32 fragment_texture::border_color(bool apply_colorspace_remapping) const { const u32 raw = registers[NV4097_SET_TEXTURE_BORDER_COLOR + (m_index * 8)]; - const u32 sext = argb_signed(); + if (!apply_colorspace_remapping) [[ likely ]] + { + return raw; + } + const u32 sext = argb_signed(); if (!sext) [[ likely ]] { return raw; @@ -430,9 +435,9 @@ namespace rsx return (conv & mask) | (raw & ~mask); } - color4f fragment_texture::remapped_border_color() const + color4f fragment_texture::remapped_border_color(bool apply_colorspace_remapping) const { - color4f base_color = rsx::decode_border_color(border_color()); + color4f base_color = rsx::decode_border_color(border_color(apply_colorspace_remapping)); if (remap() == RSX_TEXTURE_REMAP_IDENTITY) { return base_color; @@ -571,14 +576,14 @@ namespace rsx return dimension() != rsx::texture_dimension::dimension1d ? ((registers[NV4097_SET_VERTEX_TEXTURE_IMAGE_RECT + (m_index * 8)]) & 0xffff) : 1; } - u32 vertex_texture::border_color() const + u32 vertex_texture::border_color(bool) const { return registers[NV4097_SET_VERTEX_TEXTURE_BORDER_COLOR + (m_index * 8)]; } - color4f vertex_texture::remapped_border_color() const + color4f vertex_texture::remapped_border_color(bool) const { - return rsx::decode_border_color(border_color()); + return rsx::decode_border_color(border_color(false)); } u16 vertex_texture::depth() const diff --git a/rpcs3/Emu/RSX/RSXTexture.h b/rpcs3/Emu/RSX/RSXTexture.h index 3b0ccdebce..3acb532c0f 100644 --- a/rpcs3/Emu/RSX/RSXTexture.h +++ b/rpcs3/Emu/RSX/RSXTexture.h @@ -80,8 +80,8 @@ namespace rsx u16 height() const; // Border Color - u32 border_color() const; - color4f remapped_border_color() const; + u32 border_color(bool apply_colorspace_remapping = false) const; + color4f remapped_border_color(bool apply_colorspace_remapping = false) const; u16 depth() const; u32 pitch() const; @@ -136,8 +136,8 @@ namespace rsx u16 height() const; // Border Color - u32 border_color() const; - color4f remapped_border_color() const; + u32 border_color(bool = false) const; + color4f remapped_border_color(bool = false) const; u16 depth() const; u32 pitch() const; diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 5bfc60ee2a..a8aa7cdf60 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -2315,6 +2315,7 @@ namespace rsx case CELL_GCM_TEXTURE_R5G6B5: case CELL_GCM_TEXTURE_R6G5B5: texture_control |= (1 << texture_control_bits::RENORMALIZE); + current_fragment_program.ctrl |= RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT; break; default: break; @@ -2326,8 +2327,15 @@ namespace rsx texture_control |= format_ex.texel_remap_control; texture_control |= format_ex.features << texture_control_bits::FORMAT_FEATURES_OFFSET; - if (current_fp_metadata.has_tex_bx2_conv) + if (format_ex.texel_remap_control) { + current_fragment_program.ctrl |= RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT; + } + + if (current_fp_metadata.bx2_texture_reads_mask) + { + current_fragment_program.ctrl |= RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT; + const u32 remap_hi = tex.decoded_remap().shuffle_mask_bits(0xFu); current_fragment_program.texture_params[i].remap &= ~(0xFu << 16u); current_fragment_program.texture_params[i].remap |= (remap_hi << 16u); diff --git a/rpcs3/Emu/RSX/VK/VKDraw.cpp b/rpcs3/Emu/RSX/VK/VKDraw.cpp index 7a9d6aae6e..634131b13b 100644 --- a/rpcs3/Emu/RSX/VK/VKDraw.cpp +++ b/rpcs3/Emu/RSX/VK/VKDraw.cpp @@ -263,7 +263,7 @@ void VKGSRender::load_texture_env() { // Load textures bool check_for_cyclic_refs = false; - auto check_surface_cache_sampler = [&](auto descriptor, const auto& tex) + auto check_surface_cache_sampler_valid = [&](auto descriptor, const auto& tex) { if (!m_texture_cache.test_if_descriptor_expired(*m_current_command_buffer, m_rtts, descriptor, tex)) { @@ -274,11 +274,11 @@ void VKGSRender::load_texture_env() return false; }; - auto get_border_color = [&](const rsx::Texture auto& tex) + auto get_border_color = [&](const rsx::Texture auto& tex, bool remap_colorspace) { return m_device->get_custom_border_color_support().require_border_color_remap - ? tex.remapped_border_color() - : rsx::decode_border_color(tex.border_color()); + ? tex.remapped_border_color(remap_colorspace) + : rsx::decode_border_color(tex.border_color(remap_colorspace)); }; std::lock_guard lock(m_sampler_mutex); @@ -286,242 +286,319 @@ void VKGSRender::load_texture_env() for (u32 textures_ref = current_fp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i) { if (!(textures_ref & 1)) + { continue; + } if (!fs_sampler_state[i]) + { fs_sampler_state[i] = std::make_unique(); + } auto sampler_state = static_cast(fs_sampler_state[i].get()); const auto& tex = rsx::method_registers.fragment_textures[i]; const auto previous_format_class = fs_sampler_state[i]->format_class; - if (m_samplers_dirty || m_textures_dirty[i] || !check_surface_cache_sampler(sampler_state, tex)) + if (!m_samplers_dirty && + !m_textures_dirty[i] && + check_surface_cache_sampler_valid(sampler_state, tex)) { - if (tex.enabled()) + continue; + } + + const bool is_sampler_dirty = m_textures_dirty[i]; + m_textures_dirty[i] = false; + + if (!tex.enabled()) + { + *sampler_state = {}; + continue; + } + + *sampler_state = m_texture_cache.upload_texture(*m_current_command_buffer, tex, m_rtts); + if (!sampler_state->validate()) + { + continue; + } + + if (sampler_state->is_cyclic_reference) + { + check_for_cyclic_refs |= true; + } + + if (!is_sampler_dirty) + { + if (sampler_state->format_class != previous_format_class) { - *sampler_state = m_texture_cache.upload_texture(*m_current_command_buffer, tex, m_rtts); + // Host details changed but RSX is not aware + m_graphics_state |= rsx::fragment_program_state_dirty; + } + + if (sampler_state->format_ex) + { + // Nothing to change, use cached sampler + continue; + } + } + + sampler_state->format_ex = tex.format_ex(); + + if (sampler_state->format_ex.texel_remap_control && + sampler_state->image_handle && + sampler_state->upload_context == rsx::texture_upload_context::shader_read && + (current_fp_metadata.bx2_texture_reads_mask & (1u << i)) == 0 && + !g_cfg.video.disable_hardware_texel_remapping) [[ unlikely ]] + { + // Check if we need to override the view format + const auto vk_format = sampler_state->image_handle->format(); + VkFormat format_override = vk_format;; + rsx::flags32_t flags_to_erase = 0u; + rsx::flags32_t host_flags_to_set = 0u; + + if (sampler_state->format_ex.hw_SNORM_possible()) + { + format_override = vk::get_compatible_snorm_format(vk_format); + flags_to_erase = rsx::texture_control_bits::SEXT_MASK; + host_flags_to_set = rsx::RSX_HOST_FORMAT_FEATURE_SNORM; + } + else if (sampler_state->format_ex.hw_SRGB_possible()) + { + format_override = vk::get_compatible_srgb_format(vk_format); + flags_to_erase = rsx::texture_control_bits::GAMMA_CTRL_MASK; + host_flags_to_set = rsx::RSX_HOST_FORMAT_FEATURE_SRGB; + } + + if (format_override != VK_FORMAT_UNDEFINED && format_override != vk_format) + { + sampler_state->image_handle = sampler_state->image_handle->as(format_override); + sampler_state->format_ex.texel_remap_control &= (~flags_to_erase); + sampler_state->format_ex.host_features |= host_flags_to_set; + } + } + + VkFilter mag_filter; + vk::minification_filter min_filter; + f32 min_lod = 0.f, max_lod = 0.f; + f32 lod_bias = 0.f; + + const u32 texture_format = sampler_state->format_ex.format(); + VkBool32 compare_enabled = VK_FALSE; + VkCompareOp depth_compare_mode = VK_COMPARE_OP_NEVER; + + if (texture_format >= CELL_GCM_TEXTURE_DEPTH24_D8 && texture_format <= CELL_GCM_TEXTURE_DEPTH16_FLOAT) + { + compare_enabled = VK_TRUE; + depth_compare_mode = vk::get_compare_func(tex.zfunc(), true); + } + + const f32 af_level = vk::max_aniso(tex.max_aniso()); + const auto wrap_s = vk::vk_wrap_mode(tex.wrap_s()); + const auto wrap_t = vk::vk_wrap_mode(tex.wrap_t()); + const auto wrap_r = vk::vk_wrap_mode(tex.wrap_r()); + + // NOTE: In vulkan, the border color can bypass the sample swizzle stage. + // Check the device properties to determine whether to pre-swizzle the colors or not. + const bool sext_conv_required = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) != 0; + vk::border_color_t border_color(VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK); + + if (rsx::is_border_clamped_texture(tex)) + { + auto color_value = get_border_color(tex, sext_conv_required); + if (sampler_state->format_ex.host_snorm_format_active()) + { + // Convert the border color in host space (2N - 1) + // HW does the conversion in integer space as (x - 128) / 127 which introduces a biasing error. + const float bias_v = 128.f / 255.f; + const float scale_v = 255.f / 127.f; + + color4f scale{ 1.f }, bias{ 0.f }; + const auto snorm_mask = tex.argb_signed(); + if (snorm_mask & 1) { scale.a = scale_v; bias.a = -bias_v; } + if (snorm_mask & 2) { scale.r = scale_v; bias.r = -bias_v; } + if (snorm_mask & 4) { scale.g = scale_v; bias.g = -bias_v; } + if (snorm_mask & 8) { scale.b = scale_v; bias.b = -bias_v; } + color_value = (color_value + bias) * scale; + } + + border_color = color_value; + } + + // Check if non-point filtering can even be used on this format + bool can_sample_linear; + if (sampler_state->format_class == RSX_FORMAT_CLASS_COLOR) [[likely]] + { + // Most PS3-like formats can be linearly filtered without problem + // Exclude textures that require SNORM conversion however + can_sample_linear = !sext_conv_required; + } + else if (sampler_state->format_class != rsx::classify_format(texture_format) && + (texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8)) + { + // Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking + can_sample_linear = false; + } + else + { + // Not all GPUs support linear filtering of depth formats + const auto vk_format = sampler_state->image_handle ? sampler_state->image_handle->image()->format() : + vk::get_compatible_sampler_format(m_device->get_formats_support(), sampler_state->external_subresource_desc.gcm_format); + + can_sample_linear = m_device->get_format_properties(vk_format).optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; + } + + const auto mipmap_count = tex.get_exact_mipmap_count(); + min_filter = vk::get_min_filter(tex.min_filter()); + + if (can_sample_linear) + { + mag_filter = vk::get_mag_filter(tex.mag_filter()); + } + else + { + mag_filter = VK_FILTER_NEAREST; + min_filter.filter = VK_FILTER_NEAREST; + } + + if (min_filter.sample_mipmaps && mipmap_count > 1) + { + f32 actual_mipmaps; + if (sampler_state->upload_context == rsx::texture_upload_context::shader_read) + { + actual_mipmaps = static_cast(mipmap_count); + } + else if (sampler_state->external_subresource_desc.op == rsx::deferred_request_command::mipmap_gather) + { + // Clamp min and max lod + actual_mipmaps = static_cast(sampler_state->external_subresource_desc.sections_to_copy.size()); } else { - *sampler_state = {}; + actual_mipmaps = 1.f; } - if (sampler_state->validate()) + if (actual_mipmaps > 1.f) { - sampler_state->format_ex = tex.format_ex(); + min_lod = tex.min_lod(); + max_lod = tex.max_lod(); + lod_bias = tex.bias(); - if (sampler_state->is_cyclic_reference) + min_lod = std::min(min_lod, actual_mipmaps - 1.f); + max_lod = std::min(max_lod, actual_mipmaps - 1.f); + + if (min_filter.mipmap_mode == VK_SAMPLER_MIPMAP_MODE_NEAREST) { - check_for_cyclic_refs |= true; - } - - if (!m_textures_dirty[i] && sampler_state->format_class != previous_format_class) - { - // Host details changed but RSX is not aware - m_graphics_state |= rsx::fragment_program_state_dirty; - } - - bool replace = !fs_sampler_handles[i]; - VkFilter mag_filter; - vk::minification_filter min_filter; - f32 min_lod = 0.f, max_lod = 0.f; - f32 lod_bias = 0.f; - - const u32 texture_format = sampler_state->format_ex.format(); - VkBool32 compare_enabled = VK_FALSE; - VkCompareOp depth_compare_mode = VK_COMPARE_OP_NEVER; - - if (texture_format >= CELL_GCM_TEXTURE_DEPTH24_D8 && texture_format <= CELL_GCM_TEXTURE_DEPTH16_FLOAT) - { - compare_enabled = VK_TRUE; - depth_compare_mode = vk::get_compare_func(tex.zfunc(), true); - } - - const f32 af_level = vk::max_aniso(tex.max_aniso()); - const auto wrap_s = vk::vk_wrap_mode(tex.wrap_s()); - const auto wrap_t = vk::vk_wrap_mode(tex.wrap_t()); - const auto wrap_r = vk::vk_wrap_mode(tex.wrap_r()); - - // NOTE: In vulkan, the border color can bypass the sample swizzle stage. - // Check the device properties to determine whether to pre-swizzle the colors or not. - const auto border_color = rsx::is_border_clamped_texture(tex) - ? vk::border_color_t(get_border_color(tex)) - : vk::border_color_t(VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK); - - // Check if non-point filtering can even be used on this format - bool can_sample_linear; - if (sampler_state->format_class == RSX_FORMAT_CLASS_COLOR) [[likely]] - { - // Most PS3-like formats can be linearly filtered without problem - // Exclude textures that require SNORM conversion however - can_sample_linear = (sampler_state->format_ex.texel_remap_control & rsx::texture_control_bits::SEXT_MASK) == 0; - } - else if (sampler_state->format_class != rsx::classify_format(texture_format) && - (texture_format == CELL_GCM_TEXTURE_A8R8G8B8 || texture_format == CELL_GCM_TEXTURE_D8R8G8B8)) - { - // Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking - can_sample_linear = false; - } - else - { - // Not all GPUs support linear filtering of depth formats - const auto vk_format = sampler_state->image_handle ? sampler_state->image_handle->image()->format() : - vk::get_compatible_sampler_format(m_device->get_formats_support(), sampler_state->external_subresource_desc.gcm_format); - - can_sample_linear = m_device->get_format_properties(vk_format).optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; - } - - const auto mipmap_count = tex.get_exact_mipmap_count(); - min_filter = vk::get_min_filter(tex.min_filter()); - - if (can_sample_linear) - { - mag_filter = vk::get_mag_filter(tex.mag_filter()); - } - else - { - mag_filter = VK_FILTER_NEAREST; - min_filter.filter = VK_FILTER_NEAREST; - } - - if (min_filter.sample_mipmaps && mipmap_count > 1) - { - f32 actual_mipmaps; - if (sampler_state->upload_context == rsx::texture_upload_context::shader_read) - { - actual_mipmaps = static_cast(mipmap_count); - } - else if (sampler_state->external_subresource_desc.op == rsx::deferred_request_command::mipmap_gather) - { - // Clamp min and max lod - actual_mipmaps = static_cast(sampler_state->external_subresource_desc.sections_to_copy.size()); - } - else - { - actual_mipmaps = 1.f; - } - - if (actual_mipmaps > 1.f) - { - min_lod = tex.min_lod(); - max_lod = tex.max_lod(); - lod_bias = tex.bias(); - - min_lod = std::min(min_lod, actual_mipmaps - 1.f); - max_lod = std::min(max_lod, actual_mipmaps - 1.f); - - if (min_filter.mipmap_mode == VK_SAMPLER_MIPMAP_MODE_NEAREST) - { - // Round to nearest 0.5 to work around some broken games - // Unlike openGL, sampler parameters cannot be dynamically changed on vulkan, leading to many permutations - lod_bias = std::floor(lod_bias * 2.f + 0.5f) * 0.5f; - } - } - else - { - min_lod = max_lod = lod_bias = 0.f; - min_filter.mipmap_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST; - } - } - - if (fs_sampler_handles[i] && m_textures_dirty[i]) - { - if (!fs_sampler_handles[i]->matches(wrap_s, wrap_t, wrap_r, false, lod_bias, af_level, min_lod, max_lod, - min_filter.filter, mag_filter, min_filter.mipmap_mode, border_color, compare_enabled, depth_compare_mode)) - { - replace = true; - } - } - - if (replace) - { - fs_sampler_handles[i] = vk::get_resource_manager()->get_sampler( - *m_device, - fs_sampler_handles[i], - wrap_s, wrap_t, wrap_r, - false, - lod_bias, af_level, min_lod, max_lod, - min_filter.filter, mag_filter, min_filter.mipmap_mode, - border_color, compare_enabled, depth_compare_mode); + // Round to nearest 0.5 to work around some broken games + // Unlike openGL, sampler parameters cannot be dynamically changed on vulkan, leading to many permutations + lod_bias = std::floor(lod_bias * 2.f + 0.5f) * 0.5f; } } - - m_textures_dirty[i] = false; + else + { + min_lod = max_lod = lod_bias = 0.f; + min_filter.mipmap_mode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + } } + + if (fs_sampler_handles[i] && + fs_sampler_handles[i]->matches(wrap_s, wrap_t, wrap_r, false, lod_bias, af_level, min_lod, max_lod, + min_filter.filter, mag_filter, min_filter.mipmap_mode, border_color, compare_enabled, depth_compare_mode)) + { + continue; + } + + fs_sampler_handles[i] = vk::get_resource_manager()->get_sampler( + *m_device, + fs_sampler_handles[i], + wrap_s, wrap_t, wrap_r, + false, + lod_bias, af_level, min_lod, max_lod, + min_filter.filter, mag_filter, min_filter.mipmap_mode, + border_color, compare_enabled, depth_compare_mode); } for (u32 textures_ref = current_vp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i) { if (!(textures_ref & 1)) + { continue; + } if (!vs_sampler_state[i]) + { vs_sampler_state[i] = std::make_unique(); + } auto sampler_state = static_cast(vs_sampler_state[i].get()); const auto& tex = rsx::method_registers.vertex_textures[i]; const auto previous_format_class = sampler_state->format_class; - if (m_samplers_dirty || m_vertex_textures_dirty[i] || !check_surface_cache_sampler(sampler_state, tex)) + if (!m_samplers_dirty && + !m_vertex_textures_dirty[i] && + check_surface_cache_sampler_valid(sampler_state, tex)) { - if (rsx::method_registers.vertex_textures[i].enabled()) - { - *sampler_state = m_texture_cache.upload_texture(*m_current_command_buffer, tex, m_rtts); - } - else - { - *sampler_state = {}; - } - - if (sampler_state->validate()) - { - if (sampler_state->is_cyclic_reference || sampler_state->external_subresource_desc.do_not_cache) - { - check_for_cyclic_refs |= true; - } - - if (!m_vertex_textures_dirty[i] && sampler_state->format_class != previous_format_class) - { - // Host details changed but RSX is not aware - m_graphics_state |= rsx::vertex_program_state_dirty; - } - - bool replace = !vs_sampler_handles[i]; - const VkBool32 unnormalized_coords = !!(tex.format() & CELL_GCM_TEXTURE_UN); - const auto min_lod = tex.min_lod(); - const auto max_lod = tex.max_lod(); - const auto wrap_s = vk::vk_wrap_mode(tex.wrap_s()); - const auto wrap_t = vk::vk_wrap_mode(tex.wrap_t()); - - // NOTE: In vulkan, the border color can bypass the sample swizzle stage. - // Check the device properties to determine whether to pre-swizzle the colors or not. - const auto border_color = is_border_clamped_texture(tex) - ? vk::border_color_t(get_border_color(tex)) - : vk::border_color_t(VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK); - - if (vs_sampler_handles[i]) - { - if (!vs_sampler_handles[i]->matches(wrap_s, wrap_t, VK_SAMPLER_ADDRESS_MODE_REPEAT, - unnormalized_coords, 0.f, 1.f, min_lod, max_lod, VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_MIPMAP_MODE_NEAREST, border_color)) - { - replace = true; - } - } - - if (replace) - { - vs_sampler_handles[i] = vk::get_resource_manager()->get_sampler( - *m_device, - vs_sampler_handles[i], - wrap_s, wrap_t, VK_SAMPLER_ADDRESS_MODE_REPEAT, - unnormalized_coords, - 0.f, 1.f, min_lod, max_lod, - VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_MIPMAP_MODE_NEAREST, border_color); - } - } - - m_vertex_textures_dirty[i] = false; + continue; } + + const bool is_sampler_dirty = m_vertex_textures_dirty[i]; + m_vertex_textures_dirty[i] = false; + + if (!rsx::method_registers.vertex_textures[i].enabled()) + { + *sampler_state = {}; + continue; + } + + *sampler_state = m_texture_cache.upload_texture(*m_current_command_buffer, tex, m_rtts); + if (!sampler_state->validate()) + { + continue; + } + + if (sampler_state->is_cyclic_reference || sampler_state->external_subresource_desc.do_not_cache) + { + check_for_cyclic_refs |= true; + } + + if (!is_sampler_dirty) + { + if (sampler_state->format_class != previous_format_class) + { + // Host details changed but RSX is not aware + m_graphics_state |= rsx::vertex_program_state_dirty; + } + + if (vs_sampler_handles[i]) + { + continue; + } + } + + const VkBool32 unnormalized_coords = !!(tex.format() & CELL_GCM_TEXTURE_UN); + const auto min_lod = tex.min_lod(); + const auto max_lod = tex.max_lod(); + const auto wrap_s = vk::vk_wrap_mode(tex.wrap_s()); + const auto wrap_t = vk::vk_wrap_mode(tex.wrap_t()); + + // NOTE: In vulkan, the border color can bypass the sample swizzle stage. + // Check the device properties to determine whether to pre-swizzle the colors or not. + const auto border_color = is_border_clamped_texture(tex) + ? vk::border_color_t(get_border_color(tex, false)) + : vk::border_color_t(VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK); + + if (vs_sampler_handles[i] && + vs_sampler_handles[i]->matches(wrap_s, wrap_t, VK_SAMPLER_ADDRESS_MODE_REPEAT, + unnormalized_coords, 0.f, 1.f, min_lod, max_lod, VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_MIPMAP_MODE_NEAREST, border_color)) + { + continue; + } + + vs_sampler_handles[i] = vk::get_resource_manager()->get_sampler( + *m_device, + vs_sampler_handles[i], + wrap_s, wrap_t, VK_SAMPLER_ADDRESS_MODE_REPEAT, + unnormalized_coords, + 0.f, 1.f, min_lod, max_lod, + VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_MIPMAP_MODE_NEAREST, border_color); } m_samplers_dirty.store(false); @@ -557,7 +634,9 @@ bool VKGSRender::bind_texture_env() for (u32 textures_ref = current_fp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i) { if (!(textures_ref & 1)) + { continue; + } vk::image_view* view = nullptr; auto sampler_state = static_cast(fs_sampler_state[i].get()); @@ -627,7 +706,9 @@ bool VKGSRender::bind_texture_env() for (u32 textures_ref = current_vp_metadata.referenced_textures_mask, i = 0; textures_ref; textures_ref >>= 1, ++i) { if (!(textures_ref & 1)) + { continue; + } if (!rsx::method_registers.vertex_textures[i].enabled()) { diff --git a/rpcs3/Emu/RSX/VK/VKFormats.cpp b/rpcs3/Emu/RSX/VK/VKFormats.cpp index f766a70e79..0110460a51 100644 --- a/rpcs3/Emu/RSX/VK/VKFormats.cpp +++ b/rpcs3/Emu/RSX/VK/VKFormats.cpp @@ -244,8 +244,16 @@ namespace vk { switch (rgb_format) { + // 8-bit + case VK_FORMAT_R8_UNORM: + return VK_FORMAT_R8_SRGB; + case VK_FORMAT_R8G8_UNORM: + return VK_FORMAT_R8G8_SRGB; + case VK_FORMAT_R8G8B8A8_UNORM: + return VK_FORMAT_R8G8B8A8_SRGB; case VK_FORMAT_B8G8R8A8_UNORM: return VK_FORMAT_B8G8R8A8_SRGB; + // DXT case VK_FORMAT_BC1_RGBA_UNORM_BLOCK: return VK_FORMAT_BC1_RGBA_SRGB_BLOCK; case VK_FORMAT_BC2_UNORM_BLOCK: @@ -253,7 +261,30 @@ namespace vk case VK_FORMAT_BC3_UNORM_BLOCK: return VK_FORMAT_BC3_SRGB_BLOCK; default: - return rgb_format; + return VK_FORMAT_UNDEFINED; + } + } + + VkFormat get_compatible_snorm_format(VkFormat rgb_format) + { + switch (rgb_format) + { + // 8-bit + case VK_FORMAT_R8_UNORM: + return VK_FORMAT_R8_SNORM; + case VK_FORMAT_R8G8_UNORM: + return VK_FORMAT_R8G8_SNORM; + case VK_FORMAT_R8G8B8A8_UNORM: + return VK_FORMAT_R8G8B8A8_SNORM; + case VK_FORMAT_B8G8R8A8_UNORM: + return VK_FORMAT_B8G8R8A8_SNORM; + // 16-bit + case VK_FORMAT_R16_UNORM: + return VK_FORMAT_R16_SNORM; + case VK_FORMAT_R16G16_UNORM: + return VK_FORMAT_R16G16_SNORM; + default: + return VK_FORMAT_UNDEFINED; } } diff --git a/rpcs3/Emu/RSX/VK/VKFormats.h b/rpcs3/Emu/RSX/VK/VKFormats.h index 85b52ca56b..fae25f92b7 100644 --- a/rpcs3/Emu/RSX/VK/VKFormats.h +++ b/rpcs3/Emu/RSX/VK/VKFormats.h @@ -19,6 +19,7 @@ namespace vk VkFormat get_compatible_depth_surface_format(const gpu_formats_support& support, rsx::surface_depth_format2 format); VkFormat get_compatible_sampler_format(const gpu_formats_support& support, u32 format); VkFormat get_compatible_srgb_format(VkFormat rgb_format); + VkFormat get_compatible_snorm_format(VkFormat rgb_format); u8 get_format_texel_width(VkFormat format); std::pair get_format_element_size(VkFormat format); std::pair get_format_convert_flags(VkFormat format); diff --git a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp index af2e60ec93..02709d9b5c 100644 --- a/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/VK/VKFragmentProgram.cpp @@ -336,6 +336,7 @@ void VKFragmentDecompilerThread::insertGlobalFunctions(std::stringstream &OS) m_shader_props.require_tex3D_ops = properties.has_tex3D; 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); // Declare global constants if (m_shader_props.require_fog_read) diff --git a/rpcs3/Emu/RSX/VK/VKFramebuffer.cpp b/rpcs3/Emu/RSX/VK/VKFramebuffer.cpp index d1fc5a4764..eb5f11ce03 100644 --- a/rpcs3/Emu/RSX/VK/VKFramebuffer.cpp +++ b/rpcs3/Emu/RSX/VK/VKFramebuffer.cpp @@ -45,7 +45,7 @@ namespace vk for (const auto& e : image_list) { const VkImageSubresourceRange subres = { e->aspect(), 0, 1, 0, 1 }; - image_views.push_back(std::make_unique(dev, e, VK_IMAGE_VIEW_TYPE_2D, vk::default_component_map, subres)); + image_views.push_back(std::make_unique(dev, e, e->format(), VK_IMAGE_VIEW_TYPE_2D, vk::default_component_map, subres)); } auto value = std::make_unique(dev, renderpass, width, height, std::move(image_views)); diff --git a/rpcs3/Emu/RSX/VK/VKTextureCache.cpp b/rpcs3/Emu/RSX/VK/VKTextureCache.cpp index d1b0e536ef..c9d9599b9e 100644 --- a/rpcs3/Emu/RSX/VK/VKTextureCache.cpp +++ b/rpcs3/Emu/RSX/VK/VKTextureCache.cpp @@ -1025,6 +1025,29 @@ namespace vk const VkFormat vk_format = get_compatible_sampler_format(m_formats_support, gcm_format); VkImageCreateFlags create_flags = is_cubemap ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0; VkSharingMode sharing_mode = (flags & texture_create_flags::shareable) ? VK_SHARING_MODE_CONCURRENT : VK_SHARING_MODE_EXCLUSIVE; + rsx::simple_array mutable_format_list; + + if (flags & texture_create_flags::mutable_format) + { + const VkFormat snorm_fmt = get_compatible_snorm_format(vk_format); + const VkFormat srgb_fmt = get_compatible_srgb_format(vk_format); + + if (snorm_fmt != VK_FORMAT_UNDEFINED) + { + mutable_format_list.push_back(snorm_fmt); + } + + if (srgb_fmt != VK_FORMAT_UNDEFINED) + { + mutable_format_list.push_back(srgb_fmt); + } + + if (!mutable_format_list.empty()) + { + create_flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + mutable_format_list.push_back(vk_format); + } + } if (auto found = find_cached_image(vk_format, width, height, depth, mipmaps, image_type, create_flags, usage_flags, sharing_mode)) { @@ -1037,9 +1060,16 @@ namespace vk create_flags |= VK_IMAGE_CREATE_SHAREABLE_RPCS3; } + VkFormatEx create_format = vk_format; + if (!mutable_format_list.empty()) + { + create_format.pViewFormats = mutable_format_list.data(); + create_format.viewFormatCount = mutable_format_list.size(); + } + image = new vk::viewable_image(*m_device, m_memory_types.device_local, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - image_type, vk_format, + image_type, create_format, width, height, depth, mipmaps, layer, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_TILING_OPTIMAL, usage_flags, create_flags, VMM_ALLOCATION_POOL_TEXTURE_CACHE, rsx::classify_format(gcm_format)); @@ -1123,6 +1153,12 @@ namespace vk const bool upload_async = rsx::get_current_renderer()->get_backend_config().supports_asynchronous_compute; rsx::flags32_t create_flags = 0; + if (context == rsx::texture_upload_context::shader_read && + !g_cfg.video.disable_hardware_texel_remapping) + { + create_flags |= texture_create_flags::mutable_format; + } + if (upload_async && ensure(g_fxo->try_get())->is_host_mode()) { create_flags |= texture_create_flags::do_not_reuse; diff --git a/rpcs3/Emu/RSX/VK/VKTextureCache.h b/rpcs3/Emu/RSX/VK/VKTextureCache.h index 786eb51270..85687c8c69 100644 --- a/rpcs3/Emu/RSX/VK/VKTextureCache.h +++ b/rpcs3/Emu/RSX/VK/VKTextureCache.h @@ -418,7 +418,8 @@ namespace vk { initialize_image_contents = 1, do_not_reuse = 2, - shareable = 4 + shareable = 4, + mutable_format = 8 }; void on_section_destroyed(cached_texture_section& tex) override; diff --git a/rpcs3/Emu/RSX/VK/vkutils/ex.h b/rpcs3/Emu/RSX/VK/vkutils/ex.h index 361ae1d32d..1a80ffbbf5 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/ex.h +++ b/rpcs3/Emu/RSX/VK/vkutils/ex.h @@ -39,6 +39,22 @@ namespace vk VkDescriptorBufferInfoEx() = default; VkDescriptorBufferInfoEx(const vk::buffer& buffer, u64 offset, u64 range); }; + + struct VkFormatEx + { + VkFormat baseFormat = VK_FORMAT_UNDEFINED; + VkFormat* pViewFormats = nullptr; + u32 viewFormatCount = 0u; + + VkFormatEx() = default; + + VkFormatEx(VkFormat format) + : baseFormat(format) + {} + + operator VkFormat() const { return baseFormat; } + bool is_mutable() const { return viewFormatCount != 0; } + }; } // Re-export diff --git a/rpcs3/Emu/RSX/VK/vkutils/image.cpp b/rpcs3/Emu/RSX/VK/vkutils/image.cpp index 5e4c8ce022..9197c5a936 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/image.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/image.cpp @@ -23,7 +23,7 @@ namespace vk break; case VK_IMAGE_TYPE_2D: longest_dim = std::max(info.extent.width, info.extent.height); - dim_limit = (info.flags == VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) ? gpu_limits.maxImageDimensionCube : gpu_limits.maxImageDimension2D; + dim_limit = (info.flags & VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) ? gpu_limits.maxImageDimensionCube : gpu_limits.maxImageDimension2D; break; case VK_IMAGE_TYPE_3D: longest_dim = std::max({ info.extent.width, info.extent.height, info.extent.depth }); @@ -47,7 +47,7 @@ namespace vk const memory_type_info& memory_type, u32 access_flags, VkImageType image_type, - VkFormat format, + const VkFormatEx& format, u32 width, u32 height, u32 depth, u32 mipmaps, u32 layers, VkSampleCountFlagBits samples, @@ -59,7 +59,6 @@ namespace vk rsx::format_class format_class) : m_device(dev) { - info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; info.imageType = image_type; info.format = format; info.extent = { width, height, depth }; @@ -84,6 +83,16 @@ namespace vk info.pQueueFamilyIndices = concurrency_queue_families.data(); } + VkImageFormatListCreateInfo format_list = { .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO }; + if (format.is_mutable()) + { + info.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; + + format_list.pViewFormats = format.pViewFormats; + format_list.viewFormatCount = format.viewFormatCount; + info.pNext = &format_list; + } + create_impl(dev, access_flags, memory_type, allocation_pool); m_storage_aspect = get_aspect_flags(format); @@ -100,6 +109,7 @@ namespace vk } m_format_class = format_class; + info.pNext = nullptr; } // TODO: Ctor that uses a provided memory heap @@ -333,10 +343,10 @@ namespace vk create_impl(); } - image_view::image_view(VkDevice dev, vk::image* resource, VkImageViewType view_type, const VkComponentMapping& mapping, const VkImageSubresourceRange& range) + image_view::image_view(VkDevice dev, vk::image* resource, VkFormat format, VkImageViewType view_type, const VkComponentMapping& mapping, const VkImageSubresourceRange& range) : m_device(dev), m_resource(resource) { - info.format = resource->info.format; + info.format = format == VK_FORMAT_UNDEFINED ? resource->format() : format; info.image = resource->value; info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; info.components = mapping; @@ -350,7 +360,7 @@ namespace vk info.viewType = VK_IMAGE_VIEW_TYPE_1D; break; case VK_IMAGE_TYPE_2D: - if (resource->info.flags == VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) + if (resource->info.flags & VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) info.viewType = VK_IMAGE_VIEW_TYPE_CUBE; else if (resource->info.arrayLayers == 1) info.viewType = VK_IMAGE_VIEW_TYPE_2D; @@ -379,6 +389,33 @@ namespace vk vkDestroyImageView(m_device, value, nullptr); } + image_view* image_view::as(VkFormat format) + { + if (this->format() == format) + { + return this; + } + + auto self = this->m_root_view + ? this->m_root_view + : this; + + if (auto found = self->m_subviews.find(format); + found != self->m_subviews.end()) + { + return found->second.get(); + } + + // Create a derived + auto view = std::make_unique(m_device, info.image, info.viewType, format, info.components, info.subresourceRange); + view->m_resource = self->m_resource; + view->m_root_view = self; + + auto ret = view.get(); + self->m_subviews.emplace(format, std::move(view)); + return ret; + } + u32 image_view::encoded_component_map() const { #if (VK_DISABLE_COMPONENT_SWIZZLE) @@ -479,7 +516,7 @@ namespace vk const VkImageSubresourceRange range = { aspect() & mask, 0, info.mipLevels, 0, info.arrayLayers }; ensure(range.aspectMask); - auto view = std::make_unique(*g_render_device, this, VK_IMAGE_VIEW_TYPE_MAX_ENUM, real_mapping, range); + auto view = std::make_unique(*g_render_device, this, format(), VK_IMAGE_VIEW_TYPE_MAX_ENUM, real_mapping, range); auto result = view.get(); views.emplace(storage_key, std::move(view)); return result; diff --git a/rpcs3/Emu/RSX/VK/vkutils/image.h b/rpcs3/Emu/RSX/VK/vkutils/image.h index fbe7abb5fd..a301db6a6d 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/image.h +++ b/rpcs3/Emu/RSX/VK/vkutils/image.h @@ -5,6 +5,7 @@ #include "commands.h" #include "device.h" +#include "ex.h" #include "memory.h" #include "unique_resource.h" @@ -46,14 +47,14 @@ namespace vk VkComponentMapping native_component_map = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; VkImageLayout current_layout = VK_IMAGE_LAYOUT_UNDEFINED; u32 current_queue_family = VK_QUEUE_FAMILY_IGNORED; - VkImageCreateInfo info = {}; + VkImageCreateInfo info = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO }; std::shared_ptr memory; image(const vk::render_device& dev, const memory_type_info& memory_type, u32 access_flags, VkImageType image_type, - VkFormat format, + const VkFormatEx& format, u32 width, u32 height, u32 depth, u32 mipmaps, u32 layers, VkSampleCountFlagBits samples, @@ -112,21 +113,29 @@ namespace vk image_view(VkDevice dev, VkImageViewCreateInfo create_info); image_view(VkDevice dev, vk::image* resource, + VkFormat format = VK_FORMAT_UNDEFINED, VkImageViewType view_type = VK_IMAGE_VIEW_TYPE_MAX_ENUM, const VkComponentMapping& mapping = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }, const VkImageSubresourceRange& range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); ~image_view(); + vk::image_view* as(VkFormat new_format); + u32 encoded_component_map() const; vk::image* image() const; image_view(const image_view&) = delete; image_view(image_view&&) = delete; + VkFormat format() const { return info.format; } + private: + std::unordered_map> m_subviews; + VkDevice m_device; vk::image* m_resource = nullptr; + vk::image_view* m_root_view = nullptr; void create_impl(); void set_debug_name(std::string_view name); @@ -141,7 +150,8 @@ namespace vk public: using image::image; - virtual image_view* get_view(const rsx::texture_channel_remap_t& remap, + virtual image_view* get_view( + const rsx::texture_channel_remap_t& remap, VkImageAspectFlags mask = VK_IMAGE_ASPECT_COLOR_BIT | VK_IMAGE_ASPECT_DEPTH_BIT); void set_native_component_layout(VkComponentMapping new_layout); diff --git a/rpcs3/Emu/RSX/color_utils.h b/rpcs3/Emu/RSX/color_utils.h index 40335c07e3..25aa6eb7e9 100644 --- a/rpcs3/Emu/RSX/color_utils.h +++ b/rpcs3/Emu/RSX/color_utils.h @@ -7,6 +7,52 @@ namespace rsx { + enum texture_control_bits : u32 + { + GAMMA_A = 0, + GAMMA_R, + GAMMA_G, + GAMMA_B, + ALPHAKILL, + RENORMALIZE, + EXPAND_A, + EXPAND_R, + EXPAND_G, + EXPAND_B, + SEXT_A, + SEXT_R, + SEXT_G, + SEXT_B, + DEPTH_FLOAT, + DEPTH_COMPARE_OP, + DEPTH_COMPARE_1, + DEPTH_COMPARE_2, + FILTERED_MAG, + FILTERED_MIN, + UNNORMALIZED_COORDS, + CLAMP_TEXCOORDS_BIT, + WRAP_S, + WRAP_T, + WRAP_R, + FF_SIGNED_BIT, + FF_BIASED_RENORM_BIT, + FF_GAMMA_BIT, + FF_16BIT_CHANNELS_BIT, + + // Meta + GAMMA_CTRL_MASK = (1 << GAMMA_R) | (1 << GAMMA_G) | (1 << GAMMA_B) | (1 << GAMMA_A), + GAMMA_RGB_MASK = (1 << GAMMA_R) | (1 << GAMMA_G) | (1 << GAMMA_B), + GAMMA_OFFSET = GAMMA_A, + + EXPAND_MASK = (1 << EXPAND_R) | (1 << EXPAND_G) | (1 << EXPAND_B) | (1 << EXPAND_A), + EXPAND_OFFSET = EXPAND_A, + + SEXT_MASK = (1 << SEXT_R) | (1 << SEXT_G) | (1 << SEXT_B) | (1 << SEXT_A), + SEXT_OFFSET = SEXT_A, + + FORMAT_FEATURES_OFFSET = FF_SIGNED_BIT, + }; + struct texture_channel_remap_t { u32 encoded = 0xDEAD; diff --git a/rpcs3/Emu/RSX/gcm_enums.h b/rpcs3/Emu/RSX/gcm_enums.h index 6662df1653..542151e340 100644 --- a/rpcs3/Emu/RSX/gcm_enums.h +++ b/rpcs3/Emu/RSX/gcm_enums.h @@ -468,6 +468,8 @@ namespace gcm RSX_SHADER_CONTROL_DISABLE_EARLY_Z = 0x2000000, // Do not allow early-Z optimizations on this shader + RSX_SHADER_CONTROL_TEXTURE_FORMAT_CONVERT = 0x4000000, // Allow format conversions (BX2, SNORM, SRGB, RENORM) + // 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/localized_string_id.h b/rpcs3/Emu/localized_string_id.h index 399b45c437..9ab9f57a9b 100644 --- a/rpcs3/Emu/localized_string_id.h +++ b/rpcs3/Emu/localized_string_id.h @@ -277,6 +277,7 @@ enum class localized_string_id HOME_MENU_RELOAD_SECOND_SAVESTATE, HOME_MENU_RELOAD_THIRD_SAVESTATE, HOME_MENU_RELOAD_FOURTH_SAVESTATE, + HOME_MENU_TOGGLE_FULLSCREEN, HOME_MENU_RECORDING, HOME_MENU_TROPHIES, HOME_MENU_TROPHY_LIST_TITLE, diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index aaa889ce1f..8ccf91db0e 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -176,6 +176,7 @@ struct cfg_root : cfg::node cfg::_bool disable_async_host_memory_manager{ this, "Disable Asynchronous Memory Manager", false, true }; cfg::_enum output_scaling{ this, "Output Scaling Mode", output_scaling_mode::bilinear, true }; cfg::_bool record_with_overlays{ this, "Record With Overlays", true, true }; + cfg::_bool disable_hardware_texel_remapping{ this, "Disable Hardware ColorSpace Remapping", false, true }; struct node_vk : cfg::node { diff --git a/rpcs3/Emu/title.h b/rpcs3/Emu/title.h index 7084231f32..783ad7abb4 100644 --- a/rpcs3/Emu/title.h +++ b/rpcs3/Emu/title.h @@ -11,7 +11,7 @@ namespace rpcs3 std::string title_id; std::string renderer; std::string vulkan_adapter; - double fps = .0; + double fps = 0.0; }; std::string get_formatted_title(const title_format_data& title_data); diff --git a/rpcs3/Input/mouse_gyro_handler.cpp b/rpcs3/Input/mouse_gyro_handler.cpp index 6f1c7cd637..e1b241f9cd 100644 --- a/rpcs3/Input/mouse_gyro_handler.cpp +++ b/rpcs3/Input/mouse_gyro_handler.cpp @@ -1,3 +1,4 @@ +#include "stdafx.h" #include "mouse_gyro_handler.h" #include @@ -7,53 +8,57 @@ #include +LOG_CHANNEL(gui_log, "GUI"); + void mouse_gyro_handler::clear() { - active = false; - reset = false; - gyro_x = DEFAULT_MOTION_X; - gyro_y = DEFAULT_MOTION_Y; - gyro_z = DEFAULT_MOTION_Z; + m_active = false; + m_reset = false; + m_gyro_x = DEFAULT_MOTION_X; + m_gyro_y = DEFAULT_MOTION_Y; + m_gyro_z = DEFAULT_MOTION_Z; } bool mouse_gyro_handler::toggle_enabled() { - enabled = !enabled; + m_enabled = !m_enabled; clear(); - return enabled; + return m_enabled; } void mouse_gyro_handler::set_gyro_active() { - active = true; + gui_log.notice("Mouse-based gyro activated"); + m_active = true; } void mouse_gyro_handler::set_gyro_reset() { - active = false; - reset = true; + gui_log.notice("Mouse-based gyro deactivated"); + m_active = false; + m_reset = true; } void mouse_gyro_handler::set_gyro_xz(s32 off_x, s32 off_y) { - if (!active) + if (!m_active) return; - gyro_x = static_cast(std::clamp(off_x, 0, DEFAULT_MOTION_X * 2 - 1)); - gyro_z = static_cast(std::clamp(off_y, 0, DEFAULT_MOTION_Z * 2 - 1)); + m_gyro_x = static_cast(std::clamp(off_x, 0, DEFAULT_MOTION_X * 2 - 1)); + m_gyro_z = static_cast(std::clamp(off_y, 0, DEFAULT_MOTION_Z * 2 - 1)); } void mouse_gyro_handler::set_gyro_y(s32 steps) { - if (!active) + if (!m_active) return; - gyro_y = static_cast(std::clamp(gyro_y + steps, 0, DEFAULT_MOTION_Y * 2 - 1)); + m_gyro_y = static_cast(std::clamp(m_gyro_y + steps, 0, DEFAULT_MOTION_Y * 2 - 1)); } void mouse_gyro_handler::handle_event(QEvent* ev, const QWindow& win) { - if (!enabled) + if (!m_enabled) return; // Mouse-based motion input. @@ -119,15 +124,12 @@ void mouse_gyro_handler::handle_event(QEvent* ev, const QWindow& win) void mouse_gyro_handler::apply_gyro(const std::shared_ptr& pad) { - if (!enabled) - return; - - if (!pad || !pad->is_connected()) + if (!m_enabled || !pad || !pad->is_connected()) return; // Inject mouse-based motion sensor values into pad sensors for gyro emulation. // The Qt frontend maps cursor offset and wheel input to absolute motion values while RMB is held. - if (reset) + if (m_reset) { // RMB released → reset motion pad->m_sensors[0].m_value = DEFAULT_MOTION_X; @@ -139,8 +141,8 @@ void mouse_gyro_handler::apply_gyro(const std::shared_ptr& pad) { // RMB held → accumulate motion // Axes have been chosen as tested in Sly 4 minigames. Top-down view motion uses X/Z axes. - pad->m_sensors[0].m_value = gyro_x; // Mouse X → Motion X - pad->m_sensors[1].m_value = gyro_y; // Mouse Wheel → Motion Y - pad->m_sensors[2].m_value = gyro_z; // Mouse Y → Motion Z + pad->m_sensors[0].m_value = m_gyro_x; // Mouse X → Motion X + pad->m_sensors[1].m_value = m_gyro_y; // Mouse Wheel → Motion Y + pad->m_sensors[2].m_value = m_gyro_z; // Mouse Y → Motion Z } } diff --git a/rpcs3/Input/mouse_gyro_handler.h b/rpcs3/Input/mouse_gyro_handler.h index 97a745d919..9c53f574e4 100644 --- a/rpcs3/Input/mouse_gyro_handler.h +++ b/rpcs3/Input/mouse_gyro_handler.h @@ -10,24 +10,23 @@ class QWindow; // Mouse-based motion sensor emulation state. class mouse_gyro_handler { -private: - atomic_t enabled = false; // Whether mouse-based gyro emulation mode has been enabled by using the associated hotkey - - atomic_t active = false; // Whether right mouse button is currently held (gyro active) - atomic_t reset = false; // One-shot reset request on right mouse button release - atomic_t gyro_x = DEFAULT_MOTION_X; // Accumulated from mouse X position relative to center - atomic_t gyro_y = DEFAULT_MOTION_Y; // Accumulated from mouse wheel delta - atomic_t gyro_z = DEFAULT_MOTION_Z; // Accumulated from mouse Y position relative to center - - void set_gyro_active(); - void set_gyro_reset(); - void set_gyro_xz(s32 off_x, s32 off_y); - void set_gyro_y(s32 steps); - public: void clear(); bool toggle_enabled(); void handle_event(QEvent* ev, const QWindow& win); void apply_gyro(const std::shared_ptr& pad); + +private: + atomic_t m_enabled = false; // Whether mouse-based gyro emulation mode has been enabled by using the associated hotkey + atomic_t m_active = false; // Whether right mouse button is currently held (gyro active) + atomic_t m_reset = false; // One-shot reset request on right mouse button release + atomic_t m_gyro_x = DEFAULT_MOTION_X; // Accumulated from mouse X position relative to center + atomic_t m_gyro_y = DEFAULT_MOTION_Y; // Accumulated from mouse wheel delta + atomic_t m_gyro_z = DEFAULT_MOTION_Z; // Accumulated from mouse Y position relative to center + + void set_gyro_active(); + void set_gyro_reset(); + void set_gyro_xz(s32 off_x, s32 off_y); + void set_gyro_y(s32 steps); }; diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index cc8ab57b96..6a5b6841b1 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -108,6 +108,7 @@ enum class emu_settings_type DisableAsyncHostMM, UseReBAR, RecordWithOverlays, + DisableHWTexelRemapping, // Performance Overlay PerfOverlayEnabled, @@ -312,6 +313,7 @@ inline static const std::map settings_location { emu_settings_type::ForceHwMSAAResolve, { "Video", "Force Hardware MSAA Resolve"}}, { emu_settings_type::DisableAsyncHostMM, { "Video", "Disable Asynchronous Memory Manager"}}, { emu_settings_type::RecordWithOverlays, { "Video", "Record With Overlays"}}, + { emu_settings_type::DisableHWTexelRemapping, { "Video", "Disable Hardware ColorSpace Remapping"}}, // Vulkan { emu_settings_type::VulkanAsyncTextureUploads, { "Video", "Vulkan", "Asynchronous Texture Streaming 2"}}, diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index 6557168cce..67a750316e 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -51,6 +51,7 @@ LOG_CHANNEL(screenshot_log, "SCREENSHOT"); LOG_CHANNEL(mark_log, "MARK"); LOG_CHANNEL(gui_log, "GUI"); +extern atomic_t g_user_asked_for_fullscreen; extern atomic_t g_user_asked_for_recording; extern atomic_t g_user_asked_for_screenshot; extern atomic_t g_user_asked_for_frame_capture; @@ -150,6 +151,9 @@ gs_frame::gs_frame(QScreen* screen, const QRect& geometry, const QIcon& appIcon, gs_frame::~gs_frame() { g_user_asked_for_screenshot = false; + g_user_asked_for_recording = false; + g_user_asked_for_frame_capture = false; + g_user_asked_for_fullscreen = false; pad::g_home_menu_requested = false; // Save active screen to gui settings @@ -201,12 +205,12 @@ void gs_frame::update_shortcuts() } } -void gs_frame::paintEvent(QPaintEvent *event) +void gs_frame::paintEvent(QPaintEvent* event) { Q_UNUSED(event) } -void gs_frame::showEvent(QShowEvent *event) +void gs_frame::showEvent(QShowEvent* event) { // We have to calculate new window positions, since the frame is only known once the window was created. // We will try to find the originally requested dimensions if possible by moving the frame. @@ -801,7 +805,7 @@ f64 gs_frame::client_display_rate() return rate; } -void gs_frame::flip(draw_context_t, bool /*skip_frame*/) +void gs_frame::flip(draw_context_t /*context*/, bool /*skip_frame*/) { static Timer fps_t; @@ -840,6 +844,14 @@ void gs_frame::flip(draw_context_t, bool /*skip_frame*/) toggle_recording(); }); } + + if (g_user_asked_for_fullscreen.exchange(false)) + { + Emu.CallFromMainThread([this]() + { + toggle_fullscreen(); + }); + } } bool gs_frame::can_consume_frame() const diff --git a/rpcs3/rpcs3qt/gs_frame.h b/rpcs3/rpcs3qt/gs_frame.h index a00773240e..5e63a2d9c1 100644 --- a/rpcs3/rpcs3qt/gs_frame.h +++ b/rpcs3/rpcs3qt/gs_frame.h @@ -85,8 +85,8 @@ public: protected: video_renderer m_renderer; - void paintEvent(QPaintEvent *event) override; - void showEvent(QShowEvent *event) override; + void paintEvent(QPaintEvent* event) override; + void showEvent(QShowEvent* event) override; void close() override; void reset() override; diff --git a/rpcs3/rpcs3qt/localized_emu.h b/rpcs3/rpcs3qt/localized_emu.h index 7d4453a1bd..d05d3e52ee 100644 --- a/rpcs3/rpcs3qt/localized_emu.h +++ b/rpcs3/rpcs3qt/localized_emu.h @@ -298,6 +298,7 @@ private: case localized_string_id::HOME_MENU_RELOAD_SECOND_SAVESTATE: return tr("Reload Second-To-Last Emulation State"); case localized_string_id::HOME_MENU_RELOAD_THIRD_SAVESTATE: return tr("Reload Third-To-Last Emulation State"); case localized_string_id::HOME_MENU_RELOAD_FOURTH_SAVESTATE: return tr("Reload Fourth-To-Last Emulation State"); + case localized_string_id::HOME_MENU_TOGGLE_FULLSCREEN: return tr("Toggle Fullscreen"); case localized_string_id::HOME_MENU_RECORDING: return tr("Start/Stop Recording"); case localized_string_id::HOME_MENU_TROPHIES: return tr("Trophies"); case localized_string_id::HOME_MENU_TROPHY_LIST_TITLE: return tr("Trophy Progress: %0").arg(std::forward(args)...); diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 81e85546e9..0c0fb63efd 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -1555,6 +1555,9 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceCheckBox(ui->enableSpuEventsBusyLoop, emu_settings_type::EnabledSPUEventsBusyLoop); SubscribeTooltip(ui->enableSpuEventsBusyLoop, tooltips.settings.enable_spu_events_busy_loop); + m_emu_settings->EnhanceCheckBox(ui->disableHardwareTexelRemapping, emu_settings_type::DisableHWTexelRemapping); + SubscribeTooltip(ui->disableHardwareTexelRemapping, tooltips.settings.disable_hw_texel_remapping); + // Comboboxes m_emu_settings->EnhanceComboBox(ui->maxSPURSThreads, emu_settings_type::MaxSPURSThreads, true); diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index 6a27f45be3..e394407e11 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -4316,6 +4316,13 @@ + + + + Disable Hardware ColorSpace Remapping + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index abdc60f3dd..e9c8ad436b 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -127,6 +127,7 @@ public: const QString accurate_ppu_128_loop = tr("When enabled, PPU atomic operations will operate on entire cache line data, as opposed to a single 64bit block of memory when disabled.\nNumerical values control whether or not to enable the accurate version based on the atomic operation's length."); const QString enable_performance_report = tr("Measure certain events and print a chart after the emulator is stopped. Don't enable if not asked to."); const QString num_ppu_threads = tr("Affects maximum amount of PPU threads running concurrently, the value of 1 has very low compatibility with games.\n2 is the default, if unsure do not modify this setting."); + const QString disable_hw_texel_remapping = tr("Disables use of hardware-native color-space remapping formats such as _sRGB and _SNORM suffixes.\nDisabling this option increases accuracy compared to PS3 but can also introduce some noise due to how the software emulation works."); // emulator diff --git a/rpcs3/stdafx.h b/rpcs3/stdafx.h index b1e768dc52..e2f04ca1b1 100644 --- a/rpcs3/stdafx.h +++ b/rpcs3/stdafx.h @@ -22,3 +22,7 @@ #include // IWYU pragma: export #include // IWYU pragma: export #include // IWYU pragma: export + +#if defined(__INTELLISENSE__) && !defined(LLVM_AVAILABLE) +#define LLVM_AVAILABLE +#endif