From a03a78dbd2ec96a9a6349cc52755234419e7bd24 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Wed, 1 Apr 2026 09:27:51 +0300 Subject: [PATCH 01/27] SPU: Verify SPU Reduced loop completability --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 7 +++++++ rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 19 +++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index 4e8c044113..d5791c30dd 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -6731,6 +6731,13 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s break; } + if (reg_index != i && ::at32(reg->regs, reg_index)) + { + // Unimplemented + break_reduced_loop_pattern(30, reduced_loop->discard()); + break; + } + u32 cond_val_incr = static_cast(reg_org->IMM); if (reg_org->mod1_type == spu_itype::AI || reg_org->mod1_type == spu_itype::AHI) diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index b13c27e376..9880c167e8 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -2542,9 +2542,24 @@ public: break; } } - } - //condition = m_ir->getInt1(0); + // TODO: Optimze so constant evalatuated cases will not be checked + const bool is_cond_need_runtime_verify = compare == ICmpInst::ICMP_NE && (!m_reduced_loop_info->cond_val_is_immediate || m_reduced_loop_info->cond_val_incr % 2 == 0); + + if (is_cond_need_runtime_verify) + { + // Verify that it is actually possible to finish the loop and it is not an infinite loop + + // First: create a mask of the bits that definitely do not change between iterations (0 results in umax which is accurate here) + const auto no_change_bits = m_ir->CreateAnd(m_ir->CreateNot(cond_val_incr), m_ir->CreateSub(cond_val_incr, m_ir->getIntN(type_bits, 1))); + + // Compare that when the mask applied to both the result and the original value is the same + const auto cond_verify = m_ir->CreateICmpEQ(m_ir->CreateAnd(loop_dictator_after_adjustment, no_change_bits), m_ir->CreateAnd(loop_argument, no_change_bits)); + + // Amend condition + condition = m_ir->CreateAnd(cond_verify, condition); + } + } m_ir->CreateCondBr(condition, optimization_block, block_optimization_next); }; From 555ace09556cba51cb3af96f4cf900cda0a155ae Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:15:29 +0300 Subject: [PATCH 02/27] rsx: Silence Unknown render mode error --- rpcs3/Emu/RSX/NV47/HW/nv4097.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp b/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp index 43c8a5d9f4..3cc40efed1 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp +++ b/rpcs3/Emu/RSX/NV47/HW/nv4097.cpp @@ -633,9 +633,17 @@ namespace rsx case 2: break; default: - rsx_log.error("Unknown render mode %d", mode); + { + struct logged_t + { + atomic_t logged_cause[256]{}; + }; + + const auto& is_error = ::at32(g_fxo->get().logged_cause, mode).try_inc(10); + (is_error ? rsx_log.error : rsx_log.trace)("Unknown render mode %d", mode); return; } + } const u32 offset = arg & 0xffffff; auto address_ptr = util::get_report_data_impl(ctx, offset); From ff4444b18edeb78e1fdb016fcb73f6b3edd357c6 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Wed, 1 Apr 2026 17:35:56 +0300 Subject: [PATCH 03/27] Silence cellNetCtlGetInfo --- rpcs3/Emu/Cell/Modules/cellNetCtl.cpp | 41 ++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/Cell/Modules/cellNetCtl.cpp b/rpcs3/Emu/Cell/Modules/cellNetCtl.cpp index 97375c4e6d..83b001cc52 100644 --- a/rpcs3/Emu/Cell/Modules/cellNetCtl.cpp +++ b/rpcs3/Emu/Cell/Modules/cellNetCtl.cpp @@ -192,7 +192,46 @@ error_code cellNetCtlDelHandler(s32 hid) error_code cellNetCtlGetInfo(s32 code, vm::ptr info) { - cellNetCtl.warning("cellNetCtlGetInfo(code=0x%x (%s), info=*0x%x)", code, InfoCodeToName(code), info); + bool log_it_once = false; + + switch (code) + { + case CELL_NET_CTL_INFO_ETHER_ADDR: + case CELL_NET_CTL_INFO_DEVICE: + case CELL_NET_CTL_INFO_MTU: + case CELL_NET_CTL_INFO_LINK_TYPE: + case CELL_NET_CTL_INFO_IP_CONFIG: + case CELL_NET_CTL_INFO_IP_ADDRESS: + case CELL_NET_CTL_INFO_NETMASK: + case CELL_NET_CTL_INFO_DEFAULT_ROUTE: + case CELL_NET_CTL_INFO_HTTP_PROXY_CONFIG: + case CELL_NET_CTL_INFO_UPNP_CONFIG: + { + log_it_once = true; + break; + } + default: + { + break; + } + } + + bool log_it = true; + + if (log_it_once && vm::check_addr(info.addr())) + { + struct logged_t + { + std::array, 256> logged_code{}; + }; + + if (g_fxo->get().logged_code[::narrow(code)].exchange(true)) + { + log_it = false; + } + } + + (log_it ? cellNetCtl.warning : cellNetCtl.trace)("cellNetCtlGetInfo(code=0x%x (%s), info=*0x%x)", code, InfoCodeToName(code), info); auto& nph = g_fxo->get>(); From 13de8233b08df42c4126bd79bd0e023647b0c690 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Thu, 2 Apr 2026 21:00:25 +0300 Subject: [PATCH 04/27] SPU Analyzer: Fix register origin for Reduced Loop --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 8 +++++++- rpcs3/Emu/Cell/SPURecompiler.h | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index d5791c30dd..1b7c37dc51 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -6194,7 +6194,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s { const auto arg = reduced_loop->find_reg(reg); - if (arg && reg != op_rt) + if (arg && arg->regs.count() != 0) { if (reg_first == reg) { @@ -6217,6 +6217,12 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } } + if (type & spu_itype::memory || type == spu_itype::RDCH || type == spu_itype::RCHCNT) + { + // Register external origin + org.add_register_origin(s_reg_max); + } + *ensure(reduced_loop->find_reg(op_rt)) = org; } diff --git a/rpcs3/Emu/Cell/SPURecompiler.h b/rpcs3/Emu/Cell/SPURecompiler.h index 54ddcb2f1e..13a868448a 100644 --- a/rpcs3/Emu/Cell/SPURecompiler.h +++ b/rpcs3/Emu/Cell/SPURecompiler.h @@ -364,7 +364,7 @@ public: struct origin_t { - std::bitset regs{}; + std::bitset regs{}; u32 modified = 0; spu_itype_t mod1_type = spu_itype::UNK; spu_itype_t mod2_type = spu_itype::UNK; From 02eb5492088564cb5f4ba01f422b40bb1978ab36 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Thu, 2 Apr 2026 21:32:58 +0300 Subject: [PATCH 05/27] SPU LLVM: Fix register updates in second block of Reduced Loop --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 112 ++++++++++++++++++----- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 121 ++++++++++++++++--------- rpcs3/Emu/Cell/SPURecompiler.h | 7 ++ 3 files changed, 173 insertions(+), 67 deletions(-) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index 1b7c37dc51..53dc0df200 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -6365,26 +6365,55 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } } + std::array reg_use{}; + std::bitset reg_maybe_float{}; + std::bitset reg_mod{}; + + for (auto it = m_bbs.find(reduced_loop->loop_pc); it != m_bbs.end() && it->first <= bpc; it++) + { + for (u32 i = 0; i < s_reg_max; i++) + { + if (!reg_mod[i]) + { + reg_use[i] += it->second.reg_use[i]; + } + } + + reg_maybe_float |= it->second.reg_maybe_float; + reg_mod |= it->second.reg_mod; + + // Note: update when sup_conds are implemented + if (it->first == bpc && it->first != reduced_loop->loop_pc) + { + reduced_loop->loop_may_update |= it->second.reg_mod; + } + } + for (u32 i = 0; i < s_reg_max; i++) { - const auto& b = ::at32(m_bbs, reduced_loop->loop_pc); - const auto& b2 = ::at32(m_bbs, bpc); - if (!::at32(reduced_loop->loop_dicts, i)) { - if (b.reg_use[i] || (!::at32(b.reg_mod, i) && b2.reg_use[i])) + if (reg_use[i] && reg_mod[i]) { - if ((b.reg_use[i] && ::at32(b.reg_mod, i)) || ::at32(b2.reg_mod, i)) + reduced_loop->is_constant_expression = false; + reduced_loop->loop_writes.set(i); + reduced_loop->loop_may_update.reset(i); + } + else if (reg_use[i]) + { + reduced_loop->loop_args.set(i); + + if (reg_use[i] >= 3 && reg_maybe_float[i]) { - reduced_loop->is_constant_expression = false; - reduced_loop->loop_writes.set(i); - } - else - { - reduced_loop->loop_args.set(i); + reduced_loop->gpr_not_nans.set(i); } } } + else + { + // Cleanup + reduced_loop->loop_may_update.reset(i); + } } reduced_loop_all.emplace(reduced_loop->loop_pc, *reduced_loop); @@ -7062,26 +7091,55 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } } + std::array reg_use{}; + std::bitset reg_maybe_float{}; + std::bitset reg_mod{}; + + for (auto it = m_bbs.find(reduced_loop->loop_pc); it != m_bbs.end() && it->first <= bpc; it++) + { + for (u32 i = 0; i < s_reg_max; i++) + { + if (!reg_mod[i]) + { + reg_use[i] += it->second.reg_use[i]; + } + } + + reg_maybe_float |= it->second.reg_maybe_float; + reg_mod |= it->second.reg_mod; + + // Note: update when sup_conds are implemented + if (it->first == bpc && it->first != reduced_loop->loop_pc) + { + reduced_loop->loop_may_update |= it->second.reg_mod; + } + } + for (u32 i = 0; i < s_reg_max; i++) { - const auto& b = ::at32(m_bbs, reduced_loop->loop_pc); - const auto& b2 = ::at32(m_bbs, bpc); - if (!::at32(reduced_loop->loop_dicts, i)) { - if (b.reg_use[i] || (!::at32(b.reg_mod, i) && b2.reg_use[i])) + if (reg_use[i] && reg_mod[i]) { - if ((b.reg_use[i] && ::at32(b.reg_mod, i)) || ::at32(b2.reg_mod, i)) + reduced_loop->is_constant_expression = false; + reduced_loop->loop_writes.set(i); + reduced_loop->loop_may_update.reset(i); + } + else if (reg_use[i]) + { + reduced_loop->loop_args.set(i); + + if (reg_use[i] >= 3 && reg_maybe_float[i]) { - reduced_loop->is_constant_expression = false; - reduced_loop->loop_writes.set(i); - } - else - { - reduced_loop->loop_args.set(i); + reduced_loop->gpr_not_nans.set(i); } } } + else + { + // Cleanup + reduced_loop->loop_may_update.reset(i); + } } reduced_loop_all.emplace(reduced_loop->loop_pc, *reduced_loop); @@ -8621,6 +8679,16 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s fmt::append(regs, " r%u-r", i); } + + if (::at32(pattern.loop_may_update, i)) + { + if (regs.size() != 1) + { + regs += ","; + } + + fmt::append(regs, " r%u-m", i); + } } regs += " }"; diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 9880c167e8..c47a25d9f0 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -60,6 +60,7 @@ const extern spu_decoder g_spu_iflag; #pragma GCC diagnostic pop #endif +#pragma optimize("", off) #ifdef ARCH_ARM64 #include "Emu/CPU/Backends/AArch64/AArch64JIT.h" #endif @@ -152,6 +153,9 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator // Current register values std::array reg{}; + // Opimization: restoring register state for registers that would be rewritten in other blocks + std::array reg_save_and_restore{}; + // PHI nodes created for this block (if any) std::array phi{}; @@ -177,11 +181,6 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator const usz first_id = store_context_first_id[i]; return counter != 1 && first_id != umax && counter < first_id; } - - bool is_gpr_not_NaN_hint(u32 i) const noexcept - { - return block_wide_reg_store_elimination && ::at32(bb->reg_maybe_float, i) && ::at32(bb->reg_use, i) >= 3 && !::at32(bb->reg_mod, i); - } }; struct function_info @@ -197,10 +196,13 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator }; // Current block - block_info* m_block; + block_info* m_block = nullptr; // Current function or chunk - function_info* m_finfo; + function_info* m_finfo = nullptr; + + // Reduced Loop Pattern information (if available) + reduced_loop_t* m_reduced_loop_info = nullptr; // All blocks in the current function chunk std::unordered_map> m_blocks; @@ -2280,7 +2282,7 @@ public: } const bool is_reduced_loop = m_inst_attrs[(baddr - start) / 4] == inst_attr::reduced_loop; - const auto reduced_loop_info = is_reduced_loop ? std::static_pointer_cast(ensure(m_patterns.at(baddr - start).info_ptr)) : nullptr; + m_reduced_loop_info = is_reduced_loop ? std::static_pointer_cast(ensure(m_patterns.at(baddr - start).info_ptr)).get() : nullptr; BasicBlock* block_optimization_phi_parent = nullptr; const auto block_optimization_inner = is_reduced_loop ? BasicBlock::Create(m_context, fmt::format("b-loop-it-0x%x", m_pos), m_function) : nullptr; @@ -2290,11 +2292,24 @@ public: std::array reduced_loop_phi_nodes{}; std::array reduced_loop_init_regs{}; - auto make_reduced_loop_condition = [&](llvm::BasicBlock* optimization_block, bool is_second_time, u32 reserve_iterations) + // Reserve additional iteration for rare case where GPR may not be rewritten after the iteration + // So that it would have to be rewritten by future code + // This avoids using additional PHI connectors + const u32 reserve_iterations = m_reduced_loop_info && m_reduced_loop_info->loop_may_update.count() != 0 ? 3 : 2; + + for (u32 i = 0; i < s_reg_max; i++) + { + if (m_reduced_loop_info && m_reduced_loop_info->loop_may_update.test(i)) + { + m_block->reg_save_and_restore[i] = m_block->reg[i]; + } + } + + auto make_reduced_loop_condition = [&](llvm::BasicBlock* optimization_block, bool is_second_time) { llvm::ICmpInst::Predicate compare{}; - switch (reduced_loop_info->cond_val_compare) + switch (m_reduced_loop_info->cond_val_compare) { case CMP_SLESS: compare = ICmpInst::ICMP_SLT; break; case CMP_SGREATER: compare = ICmpInst::ICMP_SGT; break; @@ -2323,11 +2338,11 @@ public: llvm::Value* loop_dictator_after_adjustment{}; spu_opcode_t reg_target{}; - reg_target.rt = static_cast(reduced_loop_info->cond_val_register_idx); + reg_target.rt = static_cast(m_reduced_loop_info->cond_val_register_idx); - if (reg_target.rt != reduced_loop_info->cond_val_register_idx) + if (reg_target.rt != m_reduced_loop_info->cond_val_register_idx) { - fmt::throw_exception("LLVM: Reduced Loop Pattern: Illegal condition register index: 0x%llx", reduced_loop_info->cond_val_register_idx); + fmt::throw_exception("LLVM: Reduced Loop Pattern: Illegal condition register index: 0x%llx", m_reduced_loop_info->cond_val_register_idx); } if (!m_block->reg[reg_target.rt]) @@ -2335,7 +2350,7 @@ public: m_block->reg[reg_target.rt] = reduced_loop_init_regs[reg_target.rt]; } - switch (reduced_loop_info->cond_val_mask) + switch (m_reduced_loop_info->cond_val_mask) { case u8{umax}: { @@ -2360,28 +2375,28 @@ public: } default: { - fmt::throw_exception("LLVM: Reduced Loop Pattern: Illegal condition bit mask: 0x%llx", reduced_loop_info->cond_val_mask); + fmt::throw_exception("LLVM: Reduced Loop Pattern: Illegal condition bit mask: 0x%llx", m_reduced_loop_info->cond_val_mask); } } - const u32 type_bits = std::popcount(reduced_loop_info->cond_val_mask); + const u32 type_bits = std::popcount(m_reduced_loop_info->cond_val_mask); llvm::Value* cond_val_incr = nullptr; - if (reduced_loop_info->cond_val_incr_is_immediate) + if (m_reduced_loop_info->cond_val_incr_is_immediate) { - cond_val_incr = m_ir->getIntN(type_bits, reduced_loop_info->cond_val_incr & reduced_loop_info->cond_val_mask); + cond_val_incr = m_ir->getIntN(type_bits, m_reduced_loop_info->cond_val_incr & m_reduced_loop_info->cond_val_mask); } else { spu_opcode_t reg_incr{}; - reg_incr.rt = static_cast(reduced_loop_info->cond_val_incr); + reg_incr.rt = static_cast(m_reduced_loop_info->cond_val_incr); - if (reg_incr.rt != reduced_loop_info->cond_val_incr) + if (reg_incr.rt != m_reduced_loop_info->cond_val_incr) { - fmt::throw_exception("LLVM: Reduced Loop Pattern: Illegal increment arguemnt register index: 0x%llx", reduced_loop_info->cond_val_incr); + fmt::throw_exception("LLVM: Reduced Loop Pattern: Illegal increment arguemnt register index: 0x%llx", m_reduced_loop_info->cond_val_incr); } - switch (reduced_loop_info->cond_val_mask) + switch (m_reduced_loop_info->cond_val_mask) { case u8{umax}: { @@ -2407,7 +2422,7 @@ public: } } - if (reduced_loop_info->cond_val_incr_before_cond && !reduced_loop_info->cond_val_incr_before_cond_taken_in_account) + if (m_reduced_loop_info->cond_val_incr_before_cond && !m_reduced_loop_info->cond_val_incr_before_cond_taken_in_account) { loop_dictator_after_adjustment = m_ir->CreateAdd(loop_dictator_before_adjustment, cond_val_incr); } @@ -2418,21 +2433,21 @@ public: llvm::Value* loop_argument = nullptr; - if (reduced_loop_info->cond_val_is_immediate) + if (m_reduced_loop_info->cond_val_is_immediate) { - loop_argument = m_ir->CreateTrunc(m_ir->getInt64(reduced_loop_info->cond_val_min & reduced_loop_info->cond_val_mask), loop_dictator_before_adjustment->getType()); + loop_argument = m_ir->CreateTrunc(m_ir->getInt64(m_reduced_loop_info->cond_val_min & m_reduced_loop_info->cond_val_mask), loop_dictator_before_adjustment->getType()); } else { spu_opcode_t reg_target2{}; - reg_target2.rt = static_cast(reduced_loop_info->cond_val_register_argument_idx); + reg_target2.rt = static_cast(m_reduced_loop_info->cond_val_register_argument_idx); - if (reg_target2.rt != reduced_loop_info->cond_val_register_argument_idx) + if (reg_target2.rt != m_reduced_loop_info->cond_val_register_argument_idx) { - fmt::throw_exception("LLVM: Reduced Loop Pattern: Illegal condition arguemnt register index: 0x%llx", reduced_loop_info->cond_val_register_argument_idx); + fmt::throw_exception("LLVM: Reduced Loop Pattern: Illegal condition arguemnt register index: 0x%llx", m_reduced_loop_info->cond_val_register_argument_idx); } - switch (reduced_loop_info->cond_val_mask) + switch (m_reduced_loop_info->cond_val_mask) { case u8{umax}: { @@ -2464,7 +2479,7 @@ public: { condition = m_ir->CreateICmp(compare, loop_dictator_after_adjustment, loop_argument); } - // else if ((reduced_loop_info->cond_val_compare == CMP_LGREATER || (reduced_loop_info->cond_val_compare == CMP_LGREATER_EQUAL && reduced_loop_info->cond_val_is_immediate && reduced_loop_info->cond_val_incr)) && cond_val_incr->getSExtValue() < 0) + // else if ((m_reduced_loop_info->cond_val_compare == CMP_LGREATER || (m_reduced_loop_info->cond_val_compare == CMP_LGREATER_EQUAL && m_reduced_loop_info->cond_val_is_immediate && m_reduced_loop_info->cond_val_incr)) && cond_val_incr->getSExtValue() < 0) // { // const auto cond_val_incr_multiplied = m_ir->CreateMul(cond_val_incr, reserve_iterations - 1); // condition = m_ir->CreateICmp(compare, select(m_ir->CreateICmpUGE(cond_val_incr_multiplied, loop_dictator_after_adjustment), m_ir->CreateAdd(loop_dictator_after_adjustment, cond_val_incr_multiplied), m_ir->getIntN(type_bits, 0)), loop_argument); @@ -2493,7 +2508,7 @@ public: { const bool is_last = !(count <= 20 && i < s_reg_max); - if (is_last || m_block->is_gpr_not_NaN_hint(i)) + if (is_last || m_reduced_loop_info->is_gpr_not_NaN_hint(i)) { count++; @@ -2570,7 +2585,7 @@ public: { llvm::Type* type = g_cfg.core.spu_xfloat_accuracy == xfloat_accuracy::accurate && bb.reg_maybe_xf[i] ? get_type() : get_reg_type(i); - if (i < reduced_loop_info->loop_dicts.size() && (reduced_loop_info->loop_dicts.test(i) || reduced_loop_info->loop_writes.test(i))) + if (i < m_reduced_loop_info->loop_dicts.size() && (m_reduced_loop_info->loop_dicts.test(i) || m_reduced_loop_info->loop_writes.test(i))) { // Connect registers which are used and then modified by the block auto value = m_block->reg[i]; @@ -2582,7 +2597,7 @@ public: reduced_loop_init_regs[i] = value; } - else if (i < reduced_loop_info->loop_dicts.size() && reduced_loop_info->loop_args.test(i)) + else if (i < m_reduced_loop_info->loop_dicts.size() && m_reduced_loop_info->loop_args.test(i)) { // Load registers used as arguments of the loop if (!m_block->reg[i]) @@ -2595,8 +2610,8 @@ public: const auto prev_insert_block = m_ir->GetInsertBlock(); block_optimization_phi_parent = prev_insert_block; - - make_reduced_loop_condition(block_optimization_inner, false, 2); + + make_reduced_loop_condition(block_optimization_inner, false); m_ir->SetInsertPoint(block_optimization_inner); for (u32 i = 0; i < s_reg_max; i++) @@ -2626,7 +2641,7 @@ public: for (u32 iteration_emit = 0; is_reduced_loop; m_pos += 4) { - if (m_pos != baddr && m_block_info[m_pos / 4] && reduced_loop_info->loop_end < m_pos) + if (m_pos != baddr && m_block_info[m_pos / 4] && m_reduced_loop_info->loop_end < m_pos) { fmt::throw_exception("LLVM: Reduced Loop Pattern: Exit(1) too early at 0x%x", m_pos); } @@ -2682,8 +2697,8 @@ public: } } - ensure(!!m_block->reg[reduced_loop_info->cond_val_register_idx]); - make_reduced_loop_condition(block_optimization_inner, true, 2); + ensure(!!m_block->reg[m_reduced_loop_info->cond_val_register_idx]); + make_reduced_loop_condition(block_optimization_inner, true); m_ir->SetInsertPoint(block_optimization_next); m_block->block_wide_reg_store_elimination = false; @@ -2778,6 +2793,22 @@ public: } } + for (u32 i = 0; i < s_reg_max; i++) + { + if (m_reduced_loop_info && m_reduced_loop_info->loop_may_update.test(i)) + { + m_block->reg[i] = m_block->reg_save_and_restore[i]; + + if (m_block->reg[i]) + { + m_block->reg[i] = m_ir->CreateAdd(bitcast(m_block->reg[i], get_type()), splat(1).eval(m_ir)); + m_block->reg[i] = m_ir->CreateAdd(bitcast(m_block->reg[i], get_type()), splat(0 - 1).eval(m_ir)); + } + } + } + + m_reduced_loop_info = nullptr; + // Emit instructions for (m_pos = baddr; m_pos >= start && m_pos < end && !m_ir->GetInsertBlock()->getTerminator(); m_pos += 4) { @@ -6993,7 +7024,7 @@ public: value_t clamp_smax(value_t v, u32 gpr = s_reg_max) { - if (m_block && gpr < s_reg_max && m_block->block_wide_reg_store_elimination && m_block->is_gpr_not_NaN_hint(gpr)) + if (m_reduced_loop_info && gpr < s_reg_max && m_reduced_loop_info->is_gpr_not_NaN_hint(gpr)) { return v; } @@ -7144,12 +7175,12 @@ public: } } - if (m_block && m_block->block_wide_reg_store_elimination && m_block->is_gpr_not_NaN_hint(op.ra)) + if (m_reduced_loop_info && m_reduced_loop_info->is_gpr_not_NaN_hint(op.ra)) { safe_finite_compare.set(0); } - if (m_block && m_block->block_wide_reg_store_elimination && m_block->is_gpr_not_NaN_hint(op.rb)) + if (m_reduced_loop_info && m_reduced_loop_info->is_gpr_not_NaN_hint(op.rb)) { safe_finite_compare.set(1); } @@ -7343,8 +7374,8 @@ public: } }); - const u32 a_notnan = m_block && m_block->block_wide_reg_store_elimination && m_block->is_gpr_not_NaN_hint(op.ra) ? 1 : 0; - const u32 b_notnan = m_block && m_block->block_wide_reg_store_elimination && m_block->is_gpr_not_NaN_hint(op.rb) ? 1 : 0; + const u32 a_notnan = m_reduced_loop_info && m_reduced_loop_info->is_gpr_not_NaN_hint(op.ra) ? 1 : 0; + const u32 b_notnan = m_reduced_loop_info && m_reduced_loop_info->is_gpr_not_NaN_hint(op.rb) ? 1 : 0; if (op.ra == op.rb && !m_interp_magn) { @@ -7780,8 +7811,8 @@ public: const auto [a, b, c] = get_vrs(op.ra, op.rb, op.rc); static const auto MT = match(); - const u32 a_notnan = m_block && m_block->block_wide_reg_store_elimination && m_block->is_gpr_not_NaN_hint(op.ra) ? 1 : 0; - const u32 b_notnan = m_block && m_block->block_wide_reg_store_elimination && m_block->is_gpr_not_NaN_hint(op.rb) ? 1 : 0; + const u32 a_notnan = m_reduced_loop_info && m_reduced_loop_info->is_gpr_not_NaN_hint(op.ra) ? 1 : 0; + const u32 b_notnan = m_reduced_loop_info && m_reduced_loop_info->is_gpr_not_NaN_hint(op.rb) ? 1 : 0; auto check_sqrt_pattern_for_float = [&](f32 float_value) -> bool { diff --git a/rpcs3/Emu/Cell/SPURecompiler.h b/rpcs3/Emu/Cell/SPURecompiler.h index 13a868448a..6c629571d9 100644 --- a/rpcs3/Emu/Cell/SPURecompiler.h +++ b/rpcs3/Emu/Cell/SPURecompiler.h @@ -361,6 +361,8 @@ public: std::bitset loop_args; std::bitset loop_dicts; std::bitset loop_writes; + std::bitset loop_may_update; + std::bitset gpr_not_nans; struct origin_t { @@ -680,6 +682,11 @@ public: return true; } + bool is_gpr_not_NaN_hint(u32 i) const noexcept + { + return ::at32(gpr_not_nans, i); + } + origin_t get_reg(u32 reg_val) noexcept { const auto org = find_reg(reg_val); From ceb2168375da5f4f65608568a59403129e40389e Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Fri, 3 Apr 2026 23:17:05 +0300 Subject: [PATCH 06/27] SPU LLVM: Remove debug code --- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index c47a25d9f0..85d391aceb 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -2798,12 +2798,6 @@ public: if (m_reduced_loop_info && m_reduced_loop_info->loop_may_update.test(i)) { m_block->reg[i] = m_block->reg_save_and_restore[i]; - - if (m_block->reg[i]) - { - m_block->reg[i] = m_ir->CreateAdd(bitcast(m_block->reg[i], get_type()), splat(1).eval(m_ir)); - m_block->reg[i] = m_ir->CreateAdd(bitcast(m_block->reg[i], get_type()), splat(0 - 1).eval(m_ir)); - } } } From aa841ac332195a58d58c556d7de182b5f7fce3e4 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Fri, 3 Apr 2026 23:23:00 +0300 Subject: [PATCH 07/27] LLVM: Add source location for bitcast error --- Utilities/JIT.h | 4 ++++ rpcs3/Emu/CPU/CPUTranslator.cpp | 4 ++-- rpcs3/Emu/CPU/CPUTranslator.h | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Utilities/JIT.h b/Utilities/JIT.h index 6dfa9e7cd0..86fc72ed55 100644 --- a/Utilities/JIT.h +++ b/Utilities/JIT.h @@ -493,6 +493,10 @@ inline FT build_function_asm(std::string_view name, F&& builder, ::jit_runtime* return reinterpret_cast(uptr(result)); } +#if defined(__INTELLISENSE__) && !defined(LLVM_AVAILABLE) +#define LLVM_AVAILABLE +#endif + #ifdef LLVM_AVAILABLE namespace llvm diff --git a/rpcs3/Emu/CPU/CPUTranslator.cpp b/rpcs3/Emu/CPU/CPUTranslator.cpp index 22413f62b8..6bd7924ea5 100644 --- a/rpcs3/Emu/CPU/CPUTranslator.cpp +++ b/rpcs3/Emu/CPU/CPUTranslator.cpp @@ -210,7 +210,7 @@ void cpu_translator::initialize(llvm::LLVMContext& context, llvm::ExecutionEngin #endif } -llvm::Value* cpu_translator::bitcast(llvm::Value* val, llvm::Type* type) const +llvm::Value* cpu_translator::bitcast(llvm::Value* val, llvm::Type* type, std::source_location src_loc) const { uint s1 = type->getScalarSizeInBits(); uint s2 = val->getType()->getScalarSizeInBits(); @@ -222,7 +222,7 @@ llvm::Value* cpu_translator::bitcast(llvm::Value* val, llvm::Type* type) const if (s1 != s2) { - fmt::throw_exception("cpu_translator::bitcast(): incompatible type sizes (%u vs %u)", s1, s2); + fmt::throw_exception("cpu_translator::bitcast(): incompatible type sizes (%u vs %u)\nCalled from: %s", s1, s2, src_loc); } if (val->getType() == type) diff --git a/rpcs3/Emu/CPU/CPUTranslator.h b/rpcs3/Emu/CPU/CPUTranslator.h index 738932808d..ab2aed8156 100644 --- a/rpcs3/Emu/CPU/CPUTranslator.h +++ b/rpcs3/Emu/CPU/CPUTranslator.h @@ -43,6 +43,7 @@ #include #include +#include // Helper function llvm::Value* peek_through_bitcasts(llvm::Value*); @@ -3239,7 +3240,7 @@ public: } // Bitcast with immediate constant folding - llvm::Value* bitcast(llvm::Value* val, llvm::Type* type) const; + llvm::Value* bitcast(llvm::Value* val, llvm::Type* type, std::source_location src_loc = std::source_location::current()) const; template llvm::Value* bitcast(llvm::Value* val) From 110ef818b3fbeb50b09f851c9aa80e2fa280a873 Mon Sep 17 00:00:00 2001 From: oltolm Date: Sat, 4 Apr 2026 13:57:50 +0200 Subject: [PATCH 08/27] PPU LLVM: fix compilation for PPU Debug option --- rpcs3/Emu/Cell/PPUTranslator.cpp | 10 +++++----- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/rpcs3/Emu/Cell/PPUTranslator.cpp b/rpcs3/Emu/Cell/PPUTranslator.cpp index b12cd9c55d..0205715328 100644 --- a/rpcs3/Emu/Cell/PPUTranslator.cpp +++ b/rpcs3/Emu/Cell/PPUTranslator.cpp @@ -340,7 +340,7 @@ Function* PPUTranslator::GetSymbolResolver(const ppu_module& info) const auto ftype = FunctionType::get(get_type(), { get_type(), // Exec base - m_ir->getPtrTy(), // PPU context + get_type(), // PPU context get_type(), // Segment address (for PRX) get_type(), // Memory base get_type(), // r0 @@ -386,7 +386,7 @@ Function* PPUTranslator::GetSymbolResolver(const ppu_module& info) const auto addr_array = new GlobalVariable(*m_module, addr_array_type, false, GlobalValue::PrivateLinkage, ConstantDataArray::get(m_context, vec_addrs)); // Create an array of function pointers - const auto func_table_type = ArrayType::get(m_ir->getPtrTy(), functions.size()); + const auto func_table_type = ArrayType::get(get_type(), functions.size()); const auto init_func_table = ConstantArray::get(func_table_type, functions); const auto func_table = new GlobalVariable(*m_module, func_table_type, false, GlobalVariable::PrivateLinkage, init_func_table); @@ -413,7 +413,7 @@ Function* PPUTranslator::GetSymbolResolver(const ppu_module& info) const auto func_pc = ZExt(m_ir->CreateLoad(ptr_inst->getResultElementType(), ptr_inst), get_type()); ptr_inst = dyn_cast(m_ir->CreateGEP(func_table->getValueType(), func_table, {m_ir->getInt64(0), index_value})); - assert(ptr_inst->getResultElementType() == m_ir->getPtrTy()); + assert(ptr_inst->getResultElementType() == get_type()); const auto faddr = m_ir->CreateLoad(ptr_inst->getResultElementType(), ptr_inst); const auto pos_32 = m_reloc ? m_ir->CreateAdd(func_pc, m_seg0) : func_pc; @@ -622,7 +622,7 @@ void PPUTranslator::CallFunction(u64 target, Value* indirect) const auto pos = m_ir->CreateShl(indirect, 1); const auto ptr = m_ir->CreatePtrAdd(m_exec, pos); const auto val = m_ir->CreateLoad(get_type(), ptr); - callee = FunctionCallee(type, m_ir->CreateIntToPtr(val, m_ir->getPtrTy())); + callee = FunctionCallee(type, m_ir->CreateIntToPtr(val, get_type())); // Load new segment address const auto seg_base_ptr = m_ir->CreatePtrAdd(m_exec, m_ir->getInt64(vm::g_exec_addr_seg_offset)); @@ -5414,7 +5414,7 @@ MDNode* PPUTranslator::CheckBranchProbability(u32 bo) void PPUTranslator::build_interpreter() { #define BUILD_VEC_INST(i) { \ - m_function = llvm::cast(m_module->getOrInsertFunction("op_" #i, get_type(), m_ir->getPtrTy()).getCallee()); \ + m_function = llvm::cast(m_module->getOrInsertFunction("op_" #i, get_type(), get_type()).getCallee()); \ std::fill(std::begin(m_globals), std::end(m_globals), nullptr); \ std::fill(std::begin(m_locals), std::end(m_locals), nullptr); \ IRBuilder<> irb(BasicBlock::Create(m_context, "__entry", m_function)); \ diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 85d391aceb..6837baaa97 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -3586,7 +3586,7 @@ public: // Create interpreter table const auto if_type = get_ftype(); - m_function_table = new GlobalVariable(*m_module, ArrayType::get(m_ir->getPtrTy(), 1ull << m_interp_magn), true, GlobalValue::InternalLinkage, nullptr); + m_function_table = new GlobalVariable(*m_module, ArrayType::get(get_type(), 1ull << m_interp_magn), true, GlobalValue::InternalLinkage, nullptr); init_luts(); @@ -3630,7 +3630,7 @@ public: m_ir->CreateStore(m_ir->CreateCall(get_intrinsic(Intrinsic::read_register), {rsp_name}), native_sp); // Decode (shift) and load function pointer - const auto first = m_ir->CreateLoad(m_ir->getPtrTy(), m_ir->CreateGEP(m_ir->getPtrTy(), m_interp_table, m_ir->CreateLShr(m_interp_op, 32u - m_interp_magn))); + const auto first = m_ir->CreateLoad(get_type(), m_ir->CreateGEP(get_type(), m_interp_table, m_ir->CreateLShr(m_interp_op, 32u - m_interp_magn))); const auto call0 = m_ir->CreateCall(if_type, first, {m_lsptr, m_thread, m_interp_pc, m_interp_op, m_interp_table, m_interp_7f0, m_interp_regs}); call0->setCallingConv(CallingConv::GHC); m_ir->CreateRetVoid(); @@ -3774,7 +3774,7 @@ public: const auto next_pc = itype & spu_itype::branch ? m_interp_pc : m_interp_pc_next; const auto be32_op = m_ir->CreateLoad(get_type(), _ptr(m_lsptr, m_ir->CreateZExt(next_pc, get_type()))); const auto next_op = m_ir->CreateCall(get_intrinsic(Intrinsic::bswap), {be32_op}); - const auto next_if = m_ir->CreateLoad(m_ir->getPtrTy(), m_ir->CreateGEP(m_ir->getPtrTy(), m_interp_table, m_ir->CreateLShr(next_op, 32u - m_interp_magn))); + const auto next_if = m_ir->CreateLoad(get_type(), m_ir->CreateGEP(get_type(), m_interp_table, m_ir->CreateLShr(next_op, 32u - m_interp_magn))); llvm::cast(next_if)->setVolatile(true); if (!(itype & spu_itype::branch)) @@ -3899,7 +3899,7 @@ public: } } - m_function_table->setInitializer(ConstantArray::get(ArrayType::get(m_ir->getPtrTy(), 1ull << m_interp_magn), iptrs)); + m_function_table->setInitializer(ConstantArray::get(ArrayType::get(get_type(), 1ull << m_interp_magn), iptrs)); m_function_table = nullptr; for (auto& f : *_module) From ec989781a345839423b34656beda5e7e62a98c3e Mon Sep 17 00:00:00 2001 From: Vishrut Sachan Date: Tue, 31 Mar 2026 10:28:03 +0530 Subject: [PATCH 09/27] game_list_table: Allow reordering game list columns --- rpcs3/rpcs3qt/game_list_table.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rpcs3/rpcs3qt/game_list_table.cpp b/rpcs3/rpcs3qt/game_list_table.cpp index 3d40467bb1..ec88561580 100644 --- a/rpcs3/rpcs3qt/game_list_table.cpp +++ b/rpcs3/rpcs3qt/game_list_table.cpp @@ -38,6 +38,7 @@ game_list_table::game_list_table(game_list_frame* frame, std::shared_ptrsetStretchLastSection(true); horizontalHeader()->setDefaultSectionSize(150); horizontalHeader()->setDefaultAlignment(Qt::AlignLeft); + horizontalHeader()->setSectionsMovable(true); setContextMenuPolicy(Qt::CustomContextMenu); setAlternatingRowColors(true); setColumnCount(static_cast(gui::game_list_columns::count)); @@ -72,6 +73,8 @@ void game_list_table::restore_layout(const QByteArray& state) // Nothing to do } + // Re-apply after restoreState() since it resets setSectionsMovable to false + horizontalHeader()->setSectionsMovable(true); // Make sure no columns are squished fix_narrow_columns(); From b22d0a0b4a1f319696f6c05a25471f2ba9230396 Mon Sep 17 00:00:00 2001 From: Sanjay Govind Date: Sat, 4 Apr 2026 13:51:32 +1300 Subject: [PATCH 10/27] Bump SDL to 3.4.4 --- 3rdparty/libsdl-org/SDL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/libsdl-org/SDL b/3rdparty/libsdl-org/SDL index 683181b47c..5848e584a1 160000 --- a/3rdparty/libsdl-org/SDL +++ b/3rdparty/libsdl-org/SDL @@ -1 +1 @@ -Subproject commit 683181b47cfabd293e3ea409f838915b8297a4fd +Subproject commit 5848e584a1b606de26e3dbd1c7e4ecbc34f807a6 From afdb57bee95169653c75f1fe32dac0908e2475d4 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 5 Apr 2026 10:12:38 +0200 Subject: [PATCH 11/27] Update FAudio to 26.04 --- 3rdparty/FAudio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/FAudio b/3rdparty/FAudio index dc034fc671..0372329dbb 160000 --- a/3rdparty/FAudio +++ b/3rdparty/FAudio @@ -1 +1 @@ -Subproject commit dc034fc671b07bbd14e8410d5dd6be6da38fdf6d +Subproject commit 0372329dbb56e7814d0dea7b6eafa7a613bd8042 From b8e6785789d0b13654be92de675b5d0585ff7bcd Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 5 Apr 2026 10:23:20 +0200 Subject: [PATCH 12/27] Update soundtouch to 2.4.1 --- 3rdparty/SoundTouch/soundtouch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/SoundTouch/soundtouch b/3rdparty/SoundTouch/soundtouch index 3982730833..a0fba77b6f 160000 --- a/3rdparty/SoundTouch/soundtouch +++ b/3rdparty/SoundTouch/soundtouch @@ -1 +1 @@ -Subproject commit 3982730833b6daefe77dcfb32b5c282851640c17 +Subproject commit a0fba77b6f9cfbdb71f8bbec58b6ac4e5e3b1097 From f42b09d1fcf9adde7a20fc621644afbc22595abd Mon Sep 17 00:00:00 2001 From: Daniel Risto Date: Sun, 22 Mar 2026 13:15:16 +0100 Subject: [PATCH 13/27] Fix SPRX Loader segfault on Apple ARM64 by excluding MAP_JIT from memory mapping regions On Apple ARM64, memory_reserve unconditionally applies MAP_JIT to all reservations. However, regions marked as is_memory_mapping are later replaced by file-backed MAP_FIXED mappings via shm::map/map_critical. Overlaying a file-backed MAP_FIXED mapping onto a MAP_JIT region causes the resulting pages to be inaccessible, leading to a segfault when the SPRX Loader attempts to write module data into PS3 memory (g_sudo_addr). Fix: Only apply MAP_JIT for non-mapping regions. Memory mapping regions don't need JIT capability since they use shared memory for the PS3 address space, not executable JIT code. Tested on Apple M3 Max, macOS 26.3.1. The SPRX Loader now successfully loads all modules and emulation proceeds past the loading stage. --- rpcs3/util/vm_native.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rpcs3/util/vm_native.cpp b/rpcs3/util/vm_native.cpp index ed1e0060c9..5821300fc3 100644 --- a/rpcs3/util/vm_native.cpp +++ b/rpcs3/util/vm_native.cpp @@ -253,7 +253,11 @@ namespace utils #ifdef __APPLE__ #ifdef ARCH_ARM64 - auto ptr = ::mmap(use_addr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_JIT | c_map_noreserve, -1, 0); + // Memory mapping regions will be replaced by file-backed MAP_FIXED mappings + // (via shm::map), which is incompatible with MAP_JIT. Only use MAP_JIT for + // non-mapping regions that need JIT executable support. + const int jit_flag = is_memory_mapping ? 0 : MAP_JIT; + auto ptr = ::mmap(use_addr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | jit_flag | c_map_noreserve, -1, 0); #else auto ptr = ::mmap(use_addr, size, PROT_NONE, MAP_ANON | MAP_PRIVATE | MAP_JIT | c_map_noreserve, -1, 0); #endif From 22fe8648eff001c246e339621459197684282a8a Mon Sep 17 00:00:00 2001 From: capriots <29807355+capriots@users.noreply.github.com> Date: Sun, 8 Mar 2026 11:04:32 +0100 Subject: [PATCH 14/27] cellDmux implementation --- rpcs3/Emu/Cell/Modules/cellDmux.cpp | 2271 +++++++++++++-------------- rpcs3/Emu/Cell/Modules/cellDmux.h | 122 +- rpcs3/Emu/Cell/lv2/sys_prx.cpp | 2 +- 3 files changed, 1163 insertions(+), 1232 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellDmux.cpp b/rpcs3/Emu/Cell/Modules/cellDmux.cpp index fb1f32837d..4f51539e8e 100644 --- a/rpcs3/Emu/Cell/Modules/cellDmux.cpp +++ b/rpcs3/Emu/Cell/Modules/cellDmux.cpp @@ -1,16 +1,14 @@ #include "stdafx.h" -#include "Emu/System.h" -#include "Emu/IdManager.h" -#include "Emu/Cell/PPUModule.h" +#include "Emu/Cell/lv2/sys_mutex.h" #include "Emu/Cell/lv2/sys_sync.h" +#include "Emu/Cell/lv2/sys_timer.h" +#include "Emu/Cell/PPUModule.h" +#include "Emu/savestate_utils.hpp" +#include "util/asm.hpp" #include "cellPamf.h" #include "cellDmux.h" -#include "util/asm.hpp" - -#include - LOG_CHANNEL(cellDmux); template <> @@ -31,1331 +29,1197 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } -/* Demuxer Thread Classes */ - -enum +static error_code get_error(u32 internal_error) { - /* http://dvd.sourceforge.net/dvdinfo/mpeghdrs.html */ - - PACKET_START_CODE_MASK = 0xffffff00, - PACKET_START_CODE_PREFIX = 0x00000100, - - PACK_START_CODE = 0x000001ba, - SYSTEM_HEADER_START_CODE = 0x000001bb, - PRIVATE_STREAM_1 = 0x000001bd, - PADDING_STREAM = 0x000001be, - PRIVATE_STREAM_2 = 0x000001bf, -}; - -struct DemuxerStream -{ - u32 addr; - u32 size; - u64 userdata; - bool discontinuity; - - template - bool get(T& out) + switch (internal_error) { - if (sizeof(T) > size) return false; + case 0: return CELL_OK; + case 1: return CELL_DMUX_ERROR_FATAL; + case 2: // Error values two to five are all converted to CELL_DMUX_ERROR_ARG. + case 3: + case 4: + case 5: return CELL_DMUX_ERROR_ARG; + default: return CELL_DMUX_ERROR_FATAL; + } +} - std::memcpy(&out, vm::base(addr), sizeof(T)); - addr += sizeof(T); - size -= sizeof(T); +static inline std::span> get_es_handles(vm::ptr handle) +{ + return { vm::pptr::make(handle.addr() + sizeof(DmuxContext)).get_ptr(), static_cast(handle->enabled_es_num) }; +} - return true; +static inline vm::ptr get_au_queue_elements(vm::ptr es_handle) +{ + return vm::ptr::make(es_handle.addr() + sizeof(DmuxEsContext)); +} + +static inline vm::cptr get_core_ops() +{ + return vm::cptr::make(*ppu_module_manager::cellDmuxPamf.variables.find(0x28b2b7b2)->second.export_addr); +} + + +// Callbacks for cellDmuxPamf + +static error_code notify_demux_done(ppu_thread& ppu, vm::ptr core_handle, u32 error, vm::ptr handle) +{ + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; } - template - bool peek(T& out, u32 shift = 0) - { - if (sizeof(T) + shift > size) return false; + cellDmux.trace("notify_demux_done(core_handle=*0x%x, error=%d, handle=*0x%x)", core_handle, error, handle); - std::memcpy(&out, vm::base(addr + shift), sizeof(T)); - return true; + ensure(!!handle); // Not checked on LLE + + ensure(sys_mutex_lock(ppu, handle->_dx_mhd, 0) == CELL_OK); // Failing this check on LLE would result in it dereferencing an invalid pointer. + handle->dmux_state = DMUX_STOPPED; + ensure(sys_mutex_unlock(ppu, handle->_dx_mhd) == CELL_OK); // Failing this check on LLE would result in it dereferencing an invalid pointer. + + if (handle->_this) + { + const vm::var msg{{ .msgType = CELL_DMUX_MSG_TYPE_DEMUX_DONE, .supplementalInfo = handle->user_data }}; + handle->dmux_cb.cbFunc(ppu, handle, msg, handle->dmux_cb.cbArg); } - void skip(u32 count) - { - addr += count; - size = size > count ? size - count : 0; - } + return CELL_OK; +} - bool check(u32 count) const - { - return count <= size; - } - - u64 get_ts(u8 c) - { - u8 v[4]; get(v); - return - ((u64{c} & 0x0e) << 29) | - ((u64{v[0]}) << 21) | - ((u64{v[1]} & 0x7e) << 15) | - ((u64{v[2]}) << 7) | (u64{v[3]} >> 1); - } -}; - -struct PesHeader +static error_code notify_fatal_err(ppu_thread& ppu, vm::ptr core_handle, u32 error, vm::ptr handle) { - u64 pts; - u64 dts; - u8 size; - bool has_ts; - bool is_ok; + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; - PesHeader(DemuxerStream& stream); -}; - -class ElementaryStream; -class Demuxer; - -enum DemuxerJobType -{ - dmuxSetStream, - dmuxResetStream, - dmuxResetStreamAndWaitDone, - dmuxEnableEs, - dmuxDisableEs, - dmuxResetEs, - dmuxFlushEs, - dmuxClose, -}; - -struct DemuxerTask -{ - DemuxerJobType type; - - union + if (!savestate_lock) { - DemuxerStream stream; + ppu.state += cpu_flag::again; + return {}; + } - struct + cellDmux.error("notify_fatal_err(core_handle=*0x%x, error=%d, handle=*0x%x)", core_handle, error, handle); + + ensure(!!handle); // Not checked on LLE + + const vm::var msg{{ .msgType = CELL_DMUX_MSG_TYPE_FATAL_ERR, .supplementalInfo = static_cast(get_error(error)) }}; + return handle->dmux_cb.cbFunc(ppu, handle, msg, handle->dmux_cb.cbArg); +} + +static error_code notify_prog_end_code(ppu_thread& ppu, vm::ptr core_handle, vm::ptr handle) +{ + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellDmux.notice("notify_prog_end_code(core_handle=*0x%x, handle=*0x%x)", core_handle, handle); + + ensure(!!handle); // Not checked on LLE + + if (handle->_this) + { + const vm::var msg{{ .msgType = CELL_DMUX_MSG_TYPE_PROG_END_CODE, .supplementalInfo = handle->user_data }}; + handle->dmux_cb.cbFunc(ppu, handle, msg, handle->dmux_cb.cbArg); + } + + return CELL_OK; +} + +static error_code notify_es_au_found(ppu_thread& ppu, vm::ptr core_es_handle, vm::cptr au_info, vm::ptr es_handle) +{ + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellDmux.trace("notify_es_au_found(core_es_handle=*0x%x, au_info=*0x%x, es_handle=*0x%x)", core_es_handle, au_info, es_handle); + + ensure(!!au_info && !!es_handle); // Not checked on LLE + + const auto fatal_err = [&](be_t es_is_enabled, error_code ret) + { + if (es_is_enabled) { - u32 es; - u32 auInfo_ptr_addr; - u32 auSpec_ptr_addr; - ElementaryStream* es_ptr; - } es; + const vm::var demuxerMsg{{ .msgType = CELL_DMUX_MSG_TYPE_FATAL_ERR, .supplementalInfo = static_cast(ret) }}; + es_handle->dmux_handle->dmux_cb.cbFunc(ppu, es_handle->dmux_handle, demuxerMsg, es_handle->dmux_handle->dmux_cb.cbArg); + } }; - DemuxerTask() - { - } - - DemuxerTask(DemuxerJobType type) - : type(type) - { - } -}; - -class ElementaryStream -{ - std::mutex m_mutex; - - squeue_t entries; // AU starting addresses - u32 put_count = 0; // number of AU written - u32 got_count = 0; // number of AU obtained by GetAu(Ex) - u32 released = 0; // number of AU released - - u32 put; // AU that is being written now - - bool is_full(u32 space); - -public: - static const u32 id_base = 1; - static const u32 id_step = 1; - static const u32 id_count = 1023; - SAVESTATE_INIT_POS(34); - - ElementaryStream(Demuxer* dmux, vm::ptr addr, u32 size, u32 fidMajor, u32 fidMinor, u32 sup1, u32 sup2, vm::ptr cbFunc, vm::ptr cbArg, u32 spec); - - Demuxer* dmux; - const u32 id = idm::last_id(); - const vm::ptr memAddr; - const u32 memSize; - const u32 fidMajor; - const u32 fidMinor; - const u32 sup1; - const u32 sup2; - const vm::ptr cbFunc; - const vm::ptr cbArg; - const u32 spec; //addr - - std::vector raw_data; // demultiplexed data stream (managed by demuxer thread) - usz raw_pos = 0; // should be <= raw_data.size() - u64 last_dts = CODEC_TS_INVALID; - u64 last_pts = CODEC_TS_INVALID; - - void push(DemuxerStream& stream, u32 size); // called by demuxer thread (not multithread-safe) - - bool isfull(u32 space); - - void push_au(u32 size, u64 dts, u64 pts, u64 userdata, bool rap, u32 specific); - - bool release(); - - bool peek(u32& out_data, bool no_ex, u32& out_spec, bool update_index); - - void reset(); -}; - -class Demuxer : public ppu_thread -{ -public: - squeue_t job; - const u32 memAddr; - const u32 memSize; - const vm::ptr cbFunc; - const vm::ptr cbArg; - volatile bool is_finished = false; - volatile bool is_closed = false; - atomic_t is_running = false; - atomic_t is_working = false; - - Demuxer(u32 addr, u32 size, vm::ptr func, vm::ptr arg) - : ppu_thread({}, "", 0) - , memAddr(addr) - , memSize(size) - , cbFunc(func) - , cbArg(arg) - { - } - - void non_task() - { - DemuxerTask task; - DemuxerStream stream = {}; - ElementaryStream* esALL[96]{}; - ElementaryStream** esAVC = &esALL[0]; // AVC (max 16 minus M2V count) - //ElementaryStream** esM2V = &esALL[16]; // M2V (max 16 minus AVC count) - //ElementaryStream** esDATA = &esALL[32]; // user data (max 16) - ElementaryStream** esATX = &esALL[48]; // ATRAC3+ (max 16) - //ElementaryStream** esAC3 = &esALL[64]; // AC3 (max 16) - //ElementaryStream** esPCM = &esALL[80]; // LPCM (max 16) - - u32 cb_add = 0; - - while (true) - { - if (Emu.IsStopped() || is_closed) - { - break; - } - - if (!job.try_peek(task) && is_running && stream.addr) - { - // default task (demuxing) (if there is no other work) - be_t code; - be_t len; - - if (!stream.peek(code)) - { - // demuxing finished - is_running = false; - - // callback - auto dmuxMsg = vm::ptr::make(memAddr + (cb_add ^= 16)); - dmuxMsg->msgType = CELL_DMUX_MSG_TYPE_DEMUX_DONE; - dmuxMsg->supplementalInfo = stream.userdata; - cbFunc(*this, id, dmuxMsg, cbArg); - lv2_obj::sleep(*this); - - is_working = false; - - stream = {}; - - continue; - } - - switch (code) - { - case PACK_START_CODE: - { - if (!stream.check(14)) - { - fmt::throw_exception("End of stream (PACK_START_CODE)"); - } - stream.skip(14); - break; - } - - case SYSTEM_HEADER_START_CODE: - { - if (!stream.check(18)) - { - fmt::throw_exception("End of stream (SYSTEM_HEADER_START_CODE)"); - } - stream.skip(18); - break; - } - - case PADDING_STREAM: - { - if (!stream.check(6)) - { - fmt::throw_exception("End of stream (PADDING_STREAM)"); - } - stream.skip(4); - stream.get(len); - - if (!stream.check(len)) - { - fmt::throw_exception("End of stream (PADDING_STREAM, len=%d)", len); - } - stream.skip(len); - break; - } - - case PRIVATE_STREAM_2: - { - if (!stream.check(6)) - { - fmt::throw_exception("End of stream (PRIVATE_STREAM_2)"); - } - stream.skip(4); - stream.get(len); - - cellDmux.notice("PRIVATE_STREAM_2 (%d)", len); - - if (!stream.check(len)) - { - fmt::throw_exception("End of stream (PRIVATE_STREAM_2, len=%d)", len); - } - stream.skip(len); - break; - } - - case PRIVATE_STREAM_1: - { - // audio and user data stream - DemuxerStream backup = stream; - - if (!stream.check(6)) - { - fmt::throw_exception("End of stream (PRIVATE_STREAM_1)"); - } - stream.skip(4); - stream.get(len); - - if (!stream.check(len)) - { - fmt::throw_exception("End of stream (PRIVATE_STREAM_1, len=%d)", len); - } - - const PesHeader pes(stream); - if (!pes.is_ok) - { - fmt::throw_exception("PesHeader error (PRIVATE_STREAM_1, len=%d)", len); - } - - if (len < pes.size + 4) - { - fmt::throw_exception("End of block (PRIVATE_STREAM_1, PesHeader + fid_minor, len=%d)", len); - } - len -= pes.size + 4; - - u8 fid_minor; - if (!stream.get(fid_minor)) - { - fmt::throw_exception("End of stream (PRIVATE_STREAM1, fid_minor)"); - } - - const u32 ch = fid_minor % 16; - if ((fid_minor & -0x10) == 0 && esATX[ch]) - { - ElementaryStream& es = *esATX[ch]; - if (es.raw_data.size() > 1024 * 1024) - { - stream = backup; - std::this_thread::sleep_for(1ms); // hack - continue; - } - - if (len < 3 || !stream.check(3)) - { - fmt::throw_exception("End of block (ATX, unknown header, len=%d)", len); - } - len -= 3; - stream.skip(3); - - if (pes.has_ts) - { - es.last_dts = pes.dts; - es.last_pts = pes.pts; - } - - es.push(stream, len); - - while (true) - { - auto const size = es.raw_data.size() - es.raw_pos; // size of available new data - auto const data = es.raw_data.data() + es.raw_pos; // pointer to available data - - if (size < 8) break; // skip if cannot read ATS header - - if (data[0] != 0x0f || data[1] != 0xd0) - { - fmt::throw_exception("ATX: 0x0fd0 header not found (ats=0x%llx)", *reinterpret_cast*>(data)); - } - - u32 frame_size = (((u32{data[2]} & 0x3) << 8) | u32{data[3]}) * 8 + 8; - - if (size < frame_size + 8) break; // skip non-complete AU - - if (es.isfull(frame_size + 8)) break; // skip if cannot push AU - - es.push_au(frame_size + 8, es.last_dts, es.last_pts, stream.userdata, false /* TODO: set correct value */, 0); - - //cellDmux.notice("ATX AU pushed (ats=0x%llx, frame_size=%d)", *(be_t*)data, frame_size); - - auto esMsg = vm::ptr::make(memAddr + (cb_add ^= 16)); - esMsg->msgType = CELL_DMUX_ES_MSG_TYPE_AU_FOUND; - esMsg->supplementalInfo = stream.userdata; - es.cbFunc(*this, id, es.id, esMsg, es.cbArg); - lv2_obj::sleep(*this); - } - } - else - { - cellDmux.notice("PRIVATE_STREAM_1 (len=%d, fid_minor=0x%x)", len, fid_minor); - stream.skip(len); - } - break; - } - - case 0x1e0: case 0x1e1: case 0x1e2: case 0x1e3: - case 0x1e4: case 0x1e5: case 0x1e6: case 0x1e7: - case 0x1e8: case 0x1e9: case 0x1ea: case 0x1eb: - case 0x1ec: case 0x1ed: case 0x1ee: case 0x1ef: - { - // video stream (AVC or M2V) - DemuxerStream backup = stream; - - if (!stream.check(6)) - { - fmt::throw_exception("End of stream (video, code=0x%x)", code); - } - stream.skip(4); - stream.get(len); - - if (!stream.check(len)) - { - fmt::throw_exception("End of stream (video, code=0x%x, len=%d)", code, len); - } - - const PesHeader pes(stream); - if (!pes.is_ok) - { - fmt::throw_exception("PesHeader error (video, code=0x%x, len=%d)", code, len); - } - - if (len < pes.size + 3) - { - fmt::throw_exception("End of block (video, code=0x%x, PesHeader)", code); - } - len -= pes.size + 3; - - const u32 ch = code % 16; - if (esAVC[ch]) - { - ElementaryStream& es = *esAVC[ch]; - - const u32 old_size = ::size32(es.raw_data); - if (es.isfull(old_size)) - { - stream = backup; - std::this_thread::sleep_for(1ms); // hack - continue; - } - - if ((pes.has_ts && old_size) || old_size >= 0x69800) - { - // push AU if it becomes too big or the next packet contains PTS/DTS - es.push_au(old_size, es.last_dts, es.last_pts, stream.userdata, false /* TODO: set correct value */, 0); - - // callback - auto esMsg = vm::ptr::make(memAddr + (cb_add ^= 16)); - esMsg->msgType = CELL_DMUX_ES_MSG_TYPE_AU_FOUND; - esMsg->supplementalInfo = stream.userdata; - es.cbFunc(*this, id, es.id, esMsg, es.cbArg); - lv2_obj::sleep(*this); - } - - if (pes.has_ts) - { - // preserve dts/pts for next AU - es.last_dts = pes.dts; - es.last_pts = pes.pts; - } - - // reconstruction of MPEG2-PS stream for vdec module - const u32 size = len + pes.size + 9; - stream = backup; - es.push(stream, size); - } - else - { - cellDmux.notice("Video stream (code=0x%x, len=%d)", code, len); - stream.skip(len); - } - break; - } - - default: - { - if ((code & PACKET_START_CODE_MASK) == PACKET_START_CODE_PREFIX) - { - fmt::throw_exception("Unknown code found (0x%x)", code); - } - - // search - stream.skip(1); - } - } - - continue; - } - - // wait for task if no work - if (!job.pop(task, &is_closed)) - { - break; // Emu is stopped - } - - switch (task.type) - { - case dmuxSetStream: - { - if (task.stream.discontinuity) - { - cellDmux.warning("dmuxSetStream (beginning)"); - for (u32 i = 0; i < std::size(esALL); i++) - { - if (esALL[i]) - { - esALL[i]->reset(); - } - } - } - - stream = task.stream; - //cellDmux.notice("*** stream updated(addr=0x%x, size=0x%x, discont=%d, userdata=0x%llx)", - //stream.addr, stream.size, stream.discontinuity, stream.userdata); - break; - } - - case dmuxResetStream: - case dmuxResetStreamAndWaitDone: - { - // demuxing stopped - if (is_running.exchange(false)) - { - // callback - auto dmuxMsg = vm::ptr::make(memAddr + (cb_add ^= 16)); - dmuxMsg->msgType = CELL_DMUX_MSG_TYPE_DEMUX_DONE; - dmuxMsg->supplementalInfo = stream.userdata; - cbFunc(*this, id, dmuxMsg, cbArg); - lv2_obj::sleep(*this); - - stream = {}; - - is_working = false; - } - - break; - } - - case dmuxEnableEs: - { - ElementaryStream& es = *task.es.es_ptr; - - // TODO: uncomment when ready to use - //if ((es.fidMajor & -0x10) == 0xe0 && es.fidMinor == 0 && es.sup1 == 1 && !es.sup2) - //{ - // esAVC[es.fidMajor % 16] = task.es.es_ptr; - //} - //else if ((es.fidMajor & -0x10) == 0xe0 && es.fidMinor == 0 && !es.sup1 && !es.sup2) - //{ - // esM2V[es.fidMajor % 16] = task.es.es_ptr; - //} - //else if (es.fidMajor == 0xbd && (es.fidMinor & -0x10) == 0 && !es.sup1 && !es.sup2) - //{ - // esATX[es.fidMinor % 16] = task.es.es_ptr; - //} - //else if (es.fidMajor == 0xbd && (es.fidMinor & -0x10) == 0x20 && !es.sup1 && !es.sup2) - //{ - // esDATA[es.fidMinor % 16] = task.es.es_ptr; - //} - //else if (es.fidMajor == 0xbd && (es.fidMinor & -0x10) == 0x30 && !es.sup1 && !es.sup2) - //{ - // esAC3[es.fidMinor % 16] = task.es.es_ptr; - //} - //else if (es.fidMajor == 0xbd && (es.fidMinor & -0x10) == 0x40 && !es.sup1 && !es.sup2) - //{ - // esPCM[es.fidMinor % 16] = task.es.es_ptr; - //} - //else - { - fmt::throw_exception("dmuxEnableEs: unknown filter (0x%x, 0x%x, 0x%x, 0x%x)", es.fidMajor, es.fidMinor, es.sup1, es.sup2); - } - es.dmux = this; - break; - } - - case dmuxDisableEs: - { - ElementaryStream& es = *task.es.es_ptr; - if (es.dmux != this) - { - fmt::throw_exception("dmuxDisableEs: invalid elementary stream"); - } - - for (u32 i = 0; i < std::size(esALL); i++) - { - if (esALL[i] == &es) - { - esALL[i] = nullptr; - } - } - es.dmux = nullptr; - idm::remove(task.es.es); - break; - } - - case dmuxFlushEs: - { - ElementaryStream& es = *task.es.es_ptr; - - const u32 old_size = ::size32(es.raw_data); - if (old_size && (es.fidMajor & -0x10) == 0xe0) - { - // TODO (it's only for AVC, some ATX data may be lost) - while (es.isfull(old_size)) - { - if (Emu.IsStopped() || is_closed) break; - - std::this_thread::sleep_for(1ms); // hack - } - - es.push_au(old_size, es.last_dts, es.last_pts, stream.userdata, false, 0); - - // callback - auto esMsg = vm::ptr::make(memAddr + (cb_add ^= 16)); - esMsg->msgType = CELL_DMUX_ES_MSG_TYPE_AU_FOUND; - esMsg->supplementalInfo = stream.userdata; - es.cbFunc(*this, id, es.id, esMsg, es.cbArg); - lv2_obj::sleep(*this); - } - - if (!es.raw_data.empty()) - { - cellDmux.error("dmuxFlushEs: 0x%x bytes lost (es_id=%d)", ::size32(es.raw_data), es.id); - } - - // callback - auto esMsg = vm::ptr::make(memAddr + (cb_add ^= 16)); - esMsg->msgType = CELL_DMUX_ES_MSG_TYPE_FLUSH_DONE; - esMsg->supplementalInfo = stream.userdata; - es.cbFunc(*this, id, es.id, esMsg, es.cbArg); - lv2_obj::sleep(*this); - break; - } - - case dmuxResetEs: - { - task.es.es_ptr->reset(); - break; - } - - case dmuxClose: - { - break; - } - - default: - { - fmt::throw_exception("Demuxer thread error: unknown task (0x%x)", +task.type); - } - } - } - - is_finished = true; - } -}; - - -PesHeader::PesHeader(DemuxerStream& stream) - : pts(CODEC_TS_INVALID) - , dts(CODEC_TS_INVALID) - , size(0) - , has_ts(false) - , is_ok(false) -{ - u16 header; - if (!stream.get(header)) - { - fmt::throw_exception("End of stream (header)"); - } - if (!stream.get(size)) - { - fmt::throw_exception("End of stream (size)"); - } - if (!stream.check(size)) - { - fmt::throw_exception("End of stream (size=%d)", size); - } - - u8 pos = 0; - while (pos++ < size) - { - u8 v; - if (!stream.get(v)) - { - return; // should never occur - } - - if (v == 0xff) // skip padding bytes - { - continue; - } - - if ((v & 0xf0) == 0x20 && (size - pos) >= 4) // pts only - { - pos += 4; - pts = stream.get_ts(v); - has_ts = true; - } - else if ((v & 0xf0) == 0x30 && (size - pos) >= 9) // pts and dts - { - pos += 5; - pts = stream.get_ts(v); - stream.get(v); - has_ts = true; - - if ((v & 0xf0) != 0x10) - { - cellDmux.error("PesHeader(): dts not found (v=0x%x, size=%d, pos=%d)", v, size, pos - 1); - stream.skip(size - pos); - return; - } - pos += 4; - dts = stream.get_ts(v); - } - else - { - cellDmux.warning("PesHeader(): unknown code (v=0x%x, size=%d, pos=%d)", v, size, pos - 1); - stream.skip(size - pos); - pos = size; - break; - } - } - - is_ok = true; -} - -ElementaryStream::ElementaryStream(Demuxer* dmux, vm::ptr addr, u32 size, u32 fidMajor, u32 fidMinor, u32 sup1, u32 sup2, vm::ptr cbFunc, vm::ptr cbArg, u32 spec) - : put(utils::align(addr.addr(), 128)) - , dmux(dmux) - , memAddr(vm::ptr::make(utils::align(addr.addr(), 128))) - , memSize(size - (addr.addr() - memAddr.addr())) - , fidMajor(fidMajor) - , fidMinor(fidMinor) - , sup1(sup1) - , sup2(sup2) - , cbFunc(cbFunc) - , cbArg(cbArg) - , spec(spec) -{ -} - -bool ElementaryStream::is_full(u32 space) -{ - if (released < put_count) - { - if (entries.is_full()) - { - return true; - } - - u32 first = 0; - if (!entries.peek(first, 0, &dmux->is_closed) || !first) - { - fmt::throw_exception("entries.peek() failed"); - } - else if (first >= put) - { - return first - put < space + 128; - } - else if (put + space + 128 > memAddr.addr() + memSize) - { - return first - memAddr.addr() < space + 128; - } - else - { - return false; - } - } - else - { - return false; - } -} - -bool ElementaryStream::isfull(u32 space) -{ - std::lock_guard lock(m_mutex); - return is_full(space); -} - -void ElementaryStream::push_au(u32 size, u64 dts, u64 pts, u64 userdata, bool rap, u32 specific) -{ - u32 addr; - { - std::lock_guard lock(m_mutex); - ensure(!is_full(size)); - - if (put + size + 128 > memAddr.addr() + memSize) - { - put = memAddr.addr(); - } - - std::memcpy(vm::base(put + 128), raw_data.data(), size); - raw_data.erase(raw_data.begin(), raw_data.begin() + size); - - auto info = vm::ptr::make(put); - info->auAddr.set(put + 128); - info->auSize = size; - info->dts.lower = static_cast(dts); - info->dts.upper = static_cast(dts >> 32); - info->pts.lower = static_cast(pts); - info->pts.upper = static_cast(pts >> 32); - info->isRap = rap; - info->auMaxSize = 0; - info->userData = userdata; - - auto spec = vm::ptr::make(put + u32{sizeof(CellDmuxAuInfoEx)}); - *spec = specific; - - auto inf = vm::ptr::make(put + 64); - inf->auAddr.set(put + 128); - inf->auSize = size; - inf->dts.lower = static_cast(dts); - inf->dts.upper = static_cast(dts >> 32); - inf->pts.lower = static_cast(pts); - inf->pts.upper = static_cast(pts >> 32); - inf->auMaxSize = 0; // ????? - inf->userData = userdata; - - addr = put; - - put = utils::align(put + 128 + size, 128); - - put_count++; - } - - ensure(entries.push(addr, &dmux->is_closed)); -} - -void ElementaryStream::push(DemuxerStream& stream, u32 size) -{ - auto const old_size = raw_data.size(); - - raw_data.resize(old_size + size); - - std::memcpy(raw_data.data() + old_size, vm::base(stream.addr), size); // append bytes - - stream.skip(size); -} - -bool ElementaryStream::release() -{ - std::lock_guard lock(m_mutex); - if (released >= put_count) - { - cellDmux.fatal("es::release() error: buffer is empty"); - return false; - } - if (released >= got_count) - { - cellDmux.fatal("es::release() error: buffer has not been seen yet"); - return false; - } - - u32 addr = 0; - if (!entries.pop(addr, &dmux->is_closed) || !addr) - { - cellDmux.fatal("es::release() error: entries.Pop() failed"); - return false; - } - - released++; - return true; -} - -bool ElementaryStream::peek(u32& out_data, bool no_ex, u32& out_spec, bool update_index) -{ - std::lock_guard lock(m_mutex); - if (got_count < released) - { - cellDmux.fatal("es::peek() error: got_count(%d) < released(%d) (put_count=%d)", got_count, released, put_count); - return false; - } - if (got_count >= put_count) - { - return false; - } - - u32 addr = 0; - if (!entries.peek(addr, got_count - released, &dmux->is_closed) || !addr) - { - cellDmux.fatal("es::peek() error: entries.Peek() failed"); - return false; - } - - out_data = no_ex ? addr + 64 : addr; - out_spec = addr + sizeof(CellDmuxAuInfoEx); - - if (update_index) - { - got_count++; - } - return true; -} - -void ElementaryStream::reset() -{ - std::lock_guard lock(m_mutex); - put = memAddr.addr(); - entries.clear(); - put_count = 0; - got_count = 0; - released = 0; - raw_data.clear(); - raw_pos = 0; -} - -void dmuxQueryAttr(u32 /* info_addr, may be 0 */, vm::ptr attr) -{ - attr->demuxerVerLower = 0x280000; // TODO: check values - attr->demuxerVerUpper = 0x260000; - attr->memSize = 0x10000; // 0x3e8e6 from ps3 -} - -void dmuxQueryEsAttr(u32 /* info, may be 0 */, vm::cptr esFilterId, u32 /*esSpecificInfo*/, vm::ptr attr) -{ - if (esFilterId->filterIdMajor >= 0xe0) - { - attr->memSize = 0x500000; // 0x45fa49 from ps3 - } - else - { - attr->memSize = 0x7000; // 0x73d9 from ps3 - } - - cellDmux.warning("*** filter(0x%x, 0x%x, 0x%x, 0x%x)", esFilterId->filterIdMajor, esFilterId->filterIdMinor, esFilterId->supplementalInfo1, esFilterId->supplementalInfo2); -} - -error_code cellDmuxQueryAttr(vm::cptr type, vm::ptr attr) -{ - cellDmux.warning("cellDmuxQueryAttr(type=*0x%x, attr=*0x%x)", type, attr); - - if (type->streamType != CELL_DMUX_STREAM_TYPE_PAMF) - { - return CELL_DMUX_ERROR_ARG; - } - - dmuxQueryAttr(0, attr); - return CELL_OK; -} - -error_code cellDmuxQueryAttr2(vm::cptr type2, vm::ptr attr) -{ - cellDmux.warning("cellDmuxQueryAttr2(demuxerType2=*0x%x, demuxerAttr=*0x%x)", type2, attr); - - if (type2->streamType != CELL_DMUX_STREAM_TYPE_PAMF) - { - return CELL_DMUX_ERROR_ARG; - } - - dmuxQueryAttr(type2->streamSpecificInfo, attr); - return CELL_OK; -} - -error_code cellDmuxOpen(vm::cptr type, vm::cptr res, vm::cptr cb, vm::ptr handle) -{ - cellDmux.warning("cellDmuxOpen(type=*0x%x, res=*0x%x, cb=*0x%x, handle=*0x%x)", type, res, cb, handle); - - if (type->streamType != CELL_DMUX_STREAM_TYPE_PAMF) - { - return CELL_DMUX_ERROR_ARG; - } - - // TODO: check demuxerResource and demuxerCb arguments - fmt::throw_exception("cellDmux disabled, use LLE."); -} - -error_code cellDmuxOpenEx(vm::cptr type, vm::cptr resEx, vm::cptr cb, vm::ptr handle) -{ - cellDmux.warning("cellDmuxOpenEx(type=*0x%x, resEx=*0x%x, cb=*0x%x, handle=*0x%x)", type, resEx, cb, handle); - - if (type->streamType != CELL_DMUX_STREAM_TYPE_PAMF) - { - return CELL_DMUX_ERROR_ARG; - } - - // TODO: check demuxerResourceEx and demuxerCb arguments - fmt::throw_exception("cellDmux disabled, use LLE."); -} - -error_code cellDmuxOpenExt(vm::cptr type, vm::cptr resEx, vm::cptr cb, vm::ptr handle) -{ - cellDmux.warning("cellDmuxOpenExt(type=*0x%x, resEx=*0x%x, cb=*0x%x, handle=*0x%x)", type, resEx, cb, handle); - - return cellDmuxOpenEx(type, resEx, cb, handle); -} - -error_code cellDmuxOpen2(vm::cptr type2, vm::cptr res2, vm::cptr cb, vm::ptr handle) -{ - cellDmux.warning("cellDmuxOpen2(type2=*0x%x, res2=*0x%x, cb=*0x%x, handle=*0x%x)", type2, res2, cb, handle); - - if (type2->streamType != CELL_DMUX_STREAM_TYPE_PAMF) - { - return CELL_DMUX_ERROR_ARG; - } - - // TODO: check demuxerType2, demuxerResource2 and demuxerCb arguments - fmt::throw_exception("cellDmux disabled, use LLE."); -} - -error_code cellDmuxClose(u32 handle) -{ - cellDmux.warning("cellDmuxClose(handle=0x%x)", handle); - - const auto dmux = idm::get_unlocked(handle); - - if (!dmux) - { - return CELL_DMUX_ERROR_ARG; - } - - dmux->is_closed = true; - dmux->job.try_push(DemuxerTask(dmuxClose)); - - while (!dmux->is_finished) - { - if (Emu.IsStopped()) - { - cellDmux.warning("cellDmuxClose(%d) aborted", handle); - return CELL_OK; - } - - std::this_thread::sleep_for(1ms); // hack - } - - idm::remove(handle); - return CELL_OK; -} - -error_code cellDmuxSetStream(u32 handle, u32 streamAddress, u32 streamSize, b8 discontinuity, u64 userData) -{ - cellDmux.trace("cellDmuxSetStream(handle=0x%x, streamAddress=0x%x, streamSize=%d, discontinuity=%d, userData=0x%llx)", handle, streamAddress, streamSize, discontinuity, userData); - - const auto dmux = idm::get_unlocked(handle); - - if (!dmux) - { - return CELL_DMUX_ERROR_ARG; - } - - if (dmux->is_running.exchange(true)) - { - //std::this_thread::sleep_for(1ms); // hack - return CELL_DMUX_ERROR_BUSY; - } - - DemuxerTask task(dmuxSetStream); - auto& info = task.stream; - info.addr = streamAddress; - info.size = streamSize; - info.discontinuity = discontinuity; - info.userdata = userData; - - dmux->job.push(task, &dmux->is_closed); - return CELL_OK; -} - -error_code cellDmuxResetStream(u32 handle) -{ - cellDmux.warning("cellDmuxResetStream(handle=0x%x)", handle); - - const auto dmux = idm::get_unlocked(handle); - - if (!dmux) - { - return CELL_DMUX_ERROR_ARG; - } - - dmux->job.push(DemuxerTask(dmuxResetStream), &dmux->is_closed); - return CELL_OK; -} - -error_code cellDmuxResetStreamAndWaitDone(u32 handle) -{ - cellDmux.warning("cellDmuxResetStreamAndWaitDone(handle=0x%x)", handle); - - const auto dmux = idm::get_unlocked(handle); - - if (!dmux) - { - return CELL_DMUX_ERROR_ARG; - } - - if (!dmux->is_running) + // This is frequently checked in here because the elementary stream could get disabled at any time by a different thread via cellDmuxDisableEs() or cellDmuxClose(). + if (!es_handle->is_enabled) { return CELL_OK; } - dmux->is_working = true; - - dmux->job.push(DemuxerTask(dmuxResetStreamAndWaitDone), &dmux->is_closed); - - while (dmux->is_running && dmux->is_working && !dmux->is_closed) // TODO: ensure that it is safe + if (const error_code ret = sys_mutex_lock(ppu, es_handle->_dx_mes, 0); ret != CELL_OK) { - if (Emu.IsStopped()) + fatal_err(es_handle->is_enabled, ret); + return 1; + } + + // Check if the access unit queue is full. One slot is reserved for the access unit produced by flushing the stream, so that flushing always succeeds. + if (!es_handle->is_enabled || es_handle->au_queue.allocated_size >= es_handle->au_queue.max_size - !es_handle->flush_started) + { + if (const error_code ret = sys_mutex_unlock(ppu, es_handle->_dx_mes); ret != CELL_OK) { - cellDmux.warning("cellDmuxResetStreamAndWaitDone(%d) aborted", handle); - return CELL_OK; + fatal_err(es_handle->is_enabled, ret); + return 1; } - std::this_thread::sleep_for(1ms); // hack + + return !es_handle->is_enabled ? CELL_OK : not_an_error(1); // Disable error reporting if the queue is full. This is expected to happen frequently. } + DmuxAuInfo& _au_info = get_au_queue_elements(es_handle)[es_handle->au_queue.back].au_info; + + if (const error_code ret = sys_mutex_unlock(ppu, es_handle->_dx_mes); ret != CELL_OK) + { + fatal_err(es_handle->is_enabled, ret); + return 1; + } + + _au_info.info = au_info->info; + std::memcpy(_au_info.specific_info.get_ptr(), au_info->specific_info.get_ptr(), au_info->specific_info_size); + + if (!es_handle->is_enabled) + { + return CELL_OK; + } + + if (const error_code ret = sys_mutex_lock(ppu, es_handle->_dx_mes, 0); ret != CELL_OK) + { + fatal_err(es_handle->is_enabled, ret); + return CELL_OK; // LLE returns CELL_OK + } + + if (!es_handle->is_enabled) + { + if (const error_code ret = sys_mutex_unlock(ppu, es_handle->_dx_mes); ret != CELL_OK) + { + fatal_err(es_handle->is_enabled, ret); + } + + return CELL_OK; + } + + es_handle->au_queue.back = (es_handle->au_queue.back + 1) % es_handle->au_queue.max_size; + es_handle->au_queue.allocated_size++; + es_handle->au_queue.size++; + + if (const error_code ret = sys_mutex_unlock(ppu, es_handle->_dx_mes); ret != CELL_OK) + { + fatal_err(es_handle->is_enabled, ret); + return CELL_OK; // LLE returns CELL_OK + } + + if (!es_handle->is_enabled) + { + return CELL_OK; + } + + const vm::var es_msg{{ .msgType = CELL_DMUX_ES_MSG_TYPE_AU_FOUND, .supplementalInfo = es_handle->dmux_handle->user_data }}; + es_handle->es_cb.cbFunc(ppu, es_handle->dmux_handle, es_handle, es_msg, es_handle->es_cb.cbArg); + return CELL_OK; } -error_code cellDmuxQueryEsAttr(vm::cptr type, vm::cptr esFilterId, u32 esSpecificInfo, vm::ptr esAttr) +static error_code notify_es_flush_done(ppu_thread& ppu, vm::ptr core_es_handle, vm::ptr es_handle) { - cellDmux.warning("cellDmuxQueryEsAttr(demuxerType=*0x%x, esFilterId=*0x%x, esSpecificInfo=*0x%x, esAttr=*0x%x)", type, esFilterId, esSpecificInfo, esAttr); + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; - if (type->streamType != CELL_DMUX_STREAM_TYPE_PAMF) + if (!savestate_lock) { - return CELL_DMUX_ERROR_ARG; + ppu.state += cpu_flag::again; + return {}; } - // TODO: check esFilterId and esSpecificInfo correctly - dmuxQueryEsAttr(0, esFilterId, esSpecificInfo, esAttr); + cellDmux.notice("dmuxEsNotifyFlushDone(unk=*0x%x, es_handle=*0x%x)", core_es_handle, es_handle); + + ensure(!!es_handle); // Not checked on LLE + + if (!es_handle->dmux_handle->_this || !es_handle->is_enabled) + { + return CELL_OK; + } + + es_handle->flush_started = false; + + const vm::var es_msg{{ .msgType = CELL_DMUX_ES_MSG_TYPE_FLUSH_DONE, .supplementalInfo = es_handle->dmux_handle->user_data }}; + es_handle->es_cb.cbFunc(ppu, es_handle->dmux_handle, es_handle, es_msg, es_handle->es_cb.cbArg); + return CELL_OK; } -error_code cellDmuxQueryEsAttr2(vm::cptr type2, vm::cptr esFilterId, u32 esSpecificInfo, vm::ptr esAttr) -{ - cellDmux.warning("cellDmuxQueryEsAttr2(type2=*0x%x, esFilterId=*0x%x, esSpecificInfo=*0x%x, esAttr=*0x%x)", type2, esFilterId, esSpecificInfo, esAttr); - if (type2->streamType != CELL_DMUX_STREAM_TYPE_PAMF) +static error_code query_attr(ppu_thread& ppu, vm::ptr demuxerAttr, vm::cptr streamSpecificInfo) +{ + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) { - return CELL_DMUX_ERROR_ARG; + ppu.state += cpu_flag::again; + return {}; } - // TODO: check demuxerType2, esFilterId and esSpecificInfo correctly - dmuxQueryEsAttr(type2->streamSpecificInfo, esFilterId, esSpecificInfo, esAttr); + const vm::var pamf_attr; + + if (const error_code ret = get_error(get_core_ops()->queryAttr(ppu, streamSpecificInfo, pamf_attr)); ret != CELL_OK) + { + return ret; + } + + demuxerAttr->memSize = utils::align(sizeof(DmuxContext) + (pamf_attr->maxEnabledEsNum * sizeof(vm::addr_t)) + sizeof(DmuxEsContext), alignof(DmuxContext)) + + pamf_attr->memSize + 0xf; + demuxerAttr->demuxerVerUpper = 0x260000; + demuxerAttr->demuxerVerLower = pamf_attr->version; + return CELL_OK; } -error_code cellDmuxEnableEs(u32 handle, vm::cptr esFilterId, vm::cptr esResourceInfo, vm::cptr esCb, u32 esSpecificInfo, vm::ptr esHandle) +error_code cellDmuxQueryAttr(ppu_thread& ppu, vm::cptr demuxerType, vm::ptr demuxerAttr) { - cellDmux.warning("cellDmuxEnableEs(handle=0x%x, esFilterId=*0x%x, esResourceInfo=*0x%x, esCb=*0x%x, esSpecificInfo=*0x%x, esHandle=*0x%x)", handle, esFilterId, esResourceInfo, esCb, esSpecificInfo, esHandle); + cellDmux.notice("cellDmuxQueryAttr(demuxerType=*0x%x, demuxerAttr=*0x%x)", demuxerType, demuxerAttr); - const auto dmux = idm::get_unlocked(handle); - - if (!dmux) + if (!demuxerType || !demuxerAttr || demuxerType->streamType != CELL_DMUX_STREAM_TYPE_PAMF) { return CELL_DMUX_ERROR_ARG; } - // TODO: check esFilterId, esResourceInfo, esCb and esSpecificInfo correctly + return query_attr(ppu, demuxerAttr, vm::null); +} - const auto es = idm::make_ptr(dmux.get(), esResourceInfo->memAddr, esResourceInfo->memSize, - esFilterId->filterIdMajor, esFilterId->filterIdMinor, esFilterId->supplementalInfo1, esFilterId->supplementalInfo2, - esCb->cbFunc, esCb->cbArg, esSpecificInfo); +error_code cellDmuxQueryAttr2(ppu_thread& ppu, vm::cptr demuxerType2, vm::ptr demuxerAttr) +{ + cellDmux.notice("cellDmuxQueryAttr2(demuxerType2=*0x%x, demuxerAttr=*0x%x)", demuxerType2, demuxerAttr); - *esHandle = es->id; + if (!demuxerType2 || !demuxerAttr || demuxerType2->streamType != CELL_DMUX_STREAM_TYPE_PAMF) + { + return CELL_DMUX_ERROR_ARG; + } - cellDmux.warning("*** New ES(dmux=0x%x, addr=0x%x, size=0x%x, filter={0x%x, 0x%x, 0x%x, 0x%x}, cb=0x%x, arg=0x%x, spec=0x%x): id = 0x%x", - handle, es->memAddr, es->memSize, es->fidMajor, es->fidMinor, es->sup1, es->sup2, es->cbFunc, es->cbArg, es->spec, es->id); + return query_attr(ppu, demuxerAttr, demuxerType2->streamSpecificInfo); +} - DemuxerTask task(dmuxEnableEs); - task.es.es = es->id; - task.es.es_ptr = es.get(); +static error_code open(ppu_thread& ppu, vm::cptr demuxerType, vm::cptr demuxerResource, vm::cptr demuxerResourceEx, + vm::cptr demuxerCb, vm::cptr streamSpecificInfo, vm::pptr demuxerHandle) +{ + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + const vm::var type{{ .streamType = demuxerType->streamType, .streamSpecificInfo = streamSpecificInfo }}; + const vm::var attr; + + if (const error_code ret = cellDmuxQueryAttr2(ppu, type, attr); ret != CELL_OK) + { + return ret; + } + + if (attr->memSize > demuxerResource->memSize) + { + return CELL_DMUX_ERROR_ARG; + } + + const vm::var core_attr; + + if (const error_code ret = get_error(get_core_ops()->queryAttr(ppu, streamSpecificInfo, core_attr)); ret != CELL_OK) + { + return ret; + } + + const auto handle = vm::ptr::make(utils::align(demuxerResource->memAddr.addr(), alignof(DmuxContext))); + const u32 es_handles_size = core_attr->maxEnabledEsNum * sizeof(vm::addr_t); + const auto core_mem_addr = vm::ptr::make(utils::align(handle.addr() + sizeof(DmuxContext) + es_handles_size, 0x10)); + + const vm::var core_resource = + {{ + .memAddr = core_mem_addr, + .memSize = demuxerResource->memSize - (core_mem_addr.addr() - demuxerResource->memAddr.addr()), + .ppuThreadPriority = demuxerResource->ppuThreadPriority, + .ppuThreadStackSize = demuxerResource->ppuThreadStackSize, + .spuThreadPriority = demuxerResource->spuThreadPriority, + .numOfSpus = demuxerResource->numOfSpus + }}; + + const vm::var res_spurs; + + if (demuxerResourceEx) + { + res_spurs->spurs = demuxerResourceEx->spurs; + res_spurs->priority = demuxerResourceEx->priority; + res_spurs->maxContention = demuxerResourceEx->maxContention; + } + + const auto demux_done_func = vm::bptr::make(g_fxo->get().func_addr(FIND_FUNC(notify_demux_done))); + const auto prog_end_code_func = vm::bptr::make(g_fxo->get().func_addr(FIND_FUNC(notify_prog_end_code))); + const auto fatal_err_func = vm::bptr::make(g_fxo->get().func_addr(FIND_FUNC(notify_fatal_err))); + const vm::var> cb_demux_done{{ .cbFunc = demux_done_func, .cbArg = handle }}; + const vm::var> cb_prog_end_code{{ .cbFunc = prog_end_code_func, .cbArg = handle }}; + const vm::var> cb_fatal_err{{ .cbFunc = fatal_err_func, .cbArg = handle }}; + + const vm::var> core_handle; + + if (const error_code ret = get_error(get_core_ops()->open(ppu, streamSpecificInfo, core_resource, demuxerResourceEx ? +res_spurs : vm::null, + cb_demux_done, cb_prog_end_code, cb_fatal_err, core_handle)); + ret != CELL_OK) + { + return ret; + } + + handle->_this = handle; + handle->_this_size = sizeof(DmuxContext) + es_handles_size; + handle->version = core_attr->version; + handle->dmux_state = DMUX_STOPPED; + handle->dmux_type = *demuxerType; + handle->dmux_cb = *demuxerCb; + handle->stream_is_set = false; + handle->core_handle = *core_handle; + handle->version_ = core_attr->version; + handle->user_data = 0; + handle->max_enabled_es_num = core_attr->maxEnabledEsNum; + handle->enabled_es_num = 0; + + const vm::var mutex_attr = + {{ + .protocol = SYS_SYNC_PRIORITY, + .recursive = SYS_SYNC_NOT_RECURSIVE, + .pshared = SYS_SYNC_NOT_PROCESS_SHARED, + .adaptive = SYS_SYNC_NOT_ADAPTIVE, + .name_u64 = "_dx_mhd"_u64 + }}; + + if (const error_code ret = sys_mutex_create(ppu, handle.ptr(&DmuxContext::_dx_mhd), mutex_attr); ret != CELL_OK) + { + return ret; + } + + *demuxerHandle = handle; - dmux->job.push(task, &dmux->is_closed); return CELL_OK; } -error_code cellDmuxDisableEs(u32 esHandle) +error_code cellDmuxOpen(ppu_thread& ppu, vm::cptr demuxerType, vm::cptr demuxerResource, vm::cptr demuxerCb, vm::pptr demuxerHandle) { - cellDmux.warning("cellDmuxDisableEs(esHandle=0x%x)", esHandle); + cellDmux.notice("cellDmuxOpen(demuxerType=*0x%x, demuxerResource=*0x%x, demuxerCb=*0x%x, handle=*0x%x)", demuxerType, demuxerResource, demuxerCb, demuxerHandle); - const auto es = idm::get_unlocked(esHandle); - - if (!es) + if (!demuxerType || demuxerType->streamType != CELL_DMUX_STREAM_TYPE_PAMF + || !demuxerResource || !demuxerResource->memAddr || demuxerResource->memSize == umax || demuxerResource->ppuThreadStackSize == umax + || !demuxerCb || !demuxerCb->cbFunc + || !demuxerHandle) { return CELL_DMUX_ERROR_ARG; } - DemuxerTask task(dmuxDisableEs); - task.es.es = esHandle; - task.es.es_ptr = es.get(); - - es->dmux->job.push(task, &es->dmux->is_closed); - return CELL_OK; + return open(ppu, demuxerType, demuxerResource, vm::null, demuxerCb, vm::null, demuxerHandle); } -error_code cellDmuxResetEs(u32 esHandle) +error_code cellDmuxOpenEx(ppu_thread& ppu, vm::cptr demuxerType, vm::cptr demuxerResourceEx, vm::cptr demuxerCb, vm::pptr demuxerHandle) { - cellDmux.trace("cellDmuxResetEs(esHandle=0x%x)", esHandle); + cellDmux.notice("cellDmuxOpenEx(demuxerType=*0x%x, demuxerResourceEx=*0x%x, demuxerCb=*0x%x, demuxerHandle=*0x%x)", demuxerType, demuxerResourceEx, demuxerCb, demuxerHandle); - const auto es = idm::get_unlocked(esHandle); - - if (!es) + if (!demuxerType || demuxerType->streamType != CELL_DMUX_STREAM_TYPE_PAMF + || !demuxerResourceEx || !demuxerResourceEx->memAddr || demuxerResourceEx->memSize == umax || demuxerResourceEx->ppuThreadStackSize == umax + || !demuxerResourceEx->spurs || demuxerResourceEx->maxContention == 0u + || (demuxerResourceEx->priority & 0xf0f0f0f0f0f0f0f0ull) != 0u // Each byte in priority must be less than 0x10 + || !demuxerCb + || !demuxerHandle) { return CELL_DMUX_ERROR_ARG; } - DemuxerTask task(dmuxResetEs); - task.es.es = esHandle; - task.es.es_ptr = es.get(); + const vm::var resource + {{ + .memAddr = demuxerResourceEx->memAddr, + .memSize = demuxerResourceEx->memSize, + .ppuThreadPriority = demuxerResourceEx->ppuThreadPriority, + .ppuThreadStackSize = demuxerResourceEx->ppuThreadStackSize, + .spuThreadPriority = 0xfa, + .numOfSpus = 1 + }}; - es->dmux->job.push(task, &es->dmux->is_closed); - return CELL_OK; + return open(ppu, demuxerType, resource, demuxerResourceEx, demuxerCb, vm::null, demuxerHandle); } -error_code cellDmuxGetAu(u32 esHandle, vm::ptr auInfo, vm::ptr auSpecificInfo) +error_code cellDmuxOpenExt(ppu_thread& ppu, vm::cptr demuxerType, vm::cptr demuxerResourceEx, vm::cptr demuxerCb, vm::pptr demuxerHandle) { - cellDmux.trace("cellDmuxGetAu(esHandle=0x%x, auInfo=**0x%x, auSpecificInfo=**0x%x)", esHandle, auInfo, auSpecificInfo); + cellDmux.notice("cellDmuxOpenExt(demuxerType=*0x%x, demuxerResourceEx=*0x%x, demuxerCb=*0x%x, demuxerHandle=*0x%x)", demuxerType, demuxerResourceEx, demuxerCb, demuxerHandle); - const auto es = idm::get_unlocked(esHandle); - - if (!es) - { - return CELL_DMUX_ERROR_ARG; - } - - u32 info; - u32 spec; - if (!es->peek(info, true, spec, true)) - { - return CELL_DMUX_ERROR_EMPTY; - } - - *auInfo = info; - *auSpecificInfo = spec; - return CELL_OK; + return cellDmuxOpenEx(ppu, demuxerType, demuxerResourceEx, demuxerCb, demuxerHandle); } -error_code cellDmuxPeekAu(u32 esHandle, vm::ptr auInfo, vm::ptr auSpecificInfo) +error_code cellDmuxOpen2(ppu_thread& ppu, vm::cptr demuxerType2, vm::cptr demuxerResource2, vm::cptr demuxerCb, vm::pptr demuxerHandle) { - cellDmux.trace("cellDmuxPeekAu(esHandle=0x%x, auInfo=**0x%x, auSpecificInfo=**0x%x)", esHandle, auInfo, auSpecificInfo); + cellDmux.notice("cellDmuxOpen2(demuxerType2=*0x%x, demuxerResource2=*0x%x, demuxerCb=*0x%x, demuxerHandle=*0x%x)", demuxerType2, demuxerResource2, demuxerCb, demuxerHandle); - const auto es = idm::get_unlocked(esHandle); - - if (!es) + if (!demuxerType2 || demuxerType2->streamType != CELL_DMUX_STREAM_TYPE_PAMF + || !demuxerResource2 + || !demuxerCb || !demuxerCb->cbFunc + || !demuxerHandle) { return CELL_DMUX_ERROR_ARG; } - u32 info; - u32 spec; - if (!es->peek(info, true, spec, false)) + const vm::var type{{ .streamType = CELL_DMUX_STREAM_TYPE_PAMF }}; + + if (demuxerResource2->isResourceEx) { - return CELL_DMUX_ERROR_EMPTY; + if (!demuxerResource2->resourceEx.memAddr || demuxerResource2->resourceEx.memSize == umax || demuxerResource2->resourceEx.ppuThreadStackSize == umax + || !demuxerResource2->resourceEx.spurs || demuxerResource2->resourceEx.maxContention == 0u + || (demuxerResource2->resourceEx.priority & 0xf0f0f0f0f0f0f0f0ull) != 0u) // Each byte in priority must be less than 0x10 + { + return CELL_DMUX_ERROR_ARG; + } + + const vm::var resource + {{ + .memAddr = demuxerResource2->resourceEx.memAddr, + .memSize = demuxerResource2->resourceEx.memSize, + .ppuThreadPriority = demuxerResource2->resourceEx.ppuThreadPriority, + .ppuThreadStackSize = demuxerResource2->resourceEx.ppuThreadStackSize, + .spuThreadPriority = 0xfa, + .numOfSpus = 1 + }}; + + return open(ppu, type, resource, demuxerResource2.ptr(&CellDmuxResource2::resourceEx), demuxerCb, demuxerType2->streamSpecificInfo, demuxerHandle); } - *auInfo = info; - *auSpecificInfo = spec; - return CELL_OK; + if (!demuxerResource2->resource.memAddr || demuxerResource2->resource.memSize == umax || demuxerResource2->resource.ppuThreadStackSize == umax) + { + return CELL_DMUX_ERROR_ARG; + } + + return open(ppu, type, demuxerResource2.ptr(&CellDmuxResource2::resource), vm::null, demuxerCb, demuxerType2->streamSpecificInfo, demuxerHandle); } -error_code cellDmuxGetAuEx(u32 esHandle, vm::ptr auInfoEx, vm::ptr auSpecificInfo) +static error_code disable_es(ppu_thread& ppu, DmuxEsContext& esHandle) { - cellDmux.trace("cellDmuxGetAuEx(esHandle=0x%x, auInfoEx=**0x%x, auSpecificInfo=**0x%x)", esHandle, auInfoEx, auSpecificInfo); - - const auto es = idm::get_unlocked(esHandle); - - if (!es) + if (const error_code ret = sys_mutex_lock(ppu, esHandle._dx_mes, 0); ret != CELL_OK) { - return CELL_DMUX_ERROR_ARG; + return ret; } - u32 info; - u32 spec; - if (!es->peek(info, false, spec, true)) + const error_code core_ret = get_core_ops()->disableEs(ppu, esHandle.core_es_handle); + + esHandle.is_enabled = false; + + if (const error_code ret = sys_mutex_unlock(ppu, esHandle._dx_mes); ret != CELL_OK) { - return CELL_DMUX_ERROR_EMPTY; + return ret; } - *auInfoEx = info; - *auSpecificInfo = spec; - return CELL_OK; + error_code ret; + while ((ret = sys_mutex_destroy(ppu, esHandle._dx_mes)) == static_cast(CELL_EBUSY)) + { + sys_timer_usleep(ppu, 200); + } + + if (ret != CELL_OK) + { + return ret; + } + + esHandle._this = vm::null; + + return get_error(core_ret); } -error_code cellDmuxPeekAuEx(u32 esHandle, vm::ptr auInfoEx, vm::ptr auSpecificInfo) +error_code cellDmuxClose(ppu_thread& ppu, vm::ptr demuxerHandle) { - cellDmux.trace("cellDmuxPeekAuEx(esHandle=0x%x, auInfoEx=**0x%x, auSpecificInfo=**0x%x)", esHandle, auInfoEx, auSpecificInfo); + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; - const auto es = idm::get_unlocked(esHandle); + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } - if (!es) + cellDmux.notice("cellDmuxClose(demuxerHandle=*0x%x)", demuxerHandle); + + if (!demuxerHandle || !demuxerHandle->_this || demuxerHandle->dmux_type.streamType != CELL_DMUX_STREAM_TYPE_PAMF) { return CELL_DMUX_ERROR_ARG; } - u32 info; - u32 spec; - if (!es->peek(info, false, spec, false)) + demuxerHandle->_this = vm::null; + + if (const error_code ret = sys_mutex_lock(ppu, demuxerHandle->_dx_mhd, 0); ret != CELL_OK) { - return CELL_DMUX_ERROR_EMPTY; + demuxerHandle->_this = demuxerHandle; + return ret; } - *auInfoEx = info; - *auSpecificInfo = spec; - return CELL_OK; + for (const vm::ptr es_handle : get_es_handles(demuxerHandle)) + { + if (const error_code ret = disable_es(ppu, *es_handle); ret != CELL_OK) + { + ensure(sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE + demuxerHandle->_this = demuxerHandle; + return ret; + } + + es_handle->dmux_handle = vm::null; + demuxerHandle->enabled_es_num--; + } + + error_code ret = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + ret = ret ? ret : get_error(get_core_ops()->close(ppu, demuxerHandle->core_handle)); + ret = ret ? ret : sys_mutex_destroy(ppu, demuxerHandle->_dx_mhd); + + if (ret != CELL_OK) + { + demuxerHandle->_this = demuxerHandle; + } + + return ret; } -error_code cellDmuxReleaseAu(u32 esHandle) +error_code cellDmuxSetStream(ppu_thread& ppu, vm::ptr demuxerHandle, vm::cptr streamAddress, u32 streamSize, b8 discontinuity, u64 userData) { - cellDmux.trace("cellDmuxReleaseAu(esHandle=0x%x)", esHandle); + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; - const auto es = idm::get_unlocked(esHandle); + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } - if (!es) + cellDmux.trace("cellDmuxSetStream(demuxerHandle=*0x%x, streamAddress=*0x%x, streamSize=0x%x, discontinuity=%d, userData=0x%llx)", + demuxerHandle, streamAddress, streamSize, +discontinuity, userData); + + if (!demuxerHandle || !demuxerHandle->_this || streamSize == 0 || streamSize == umax || demuxerHandle->dmux_type.streamType != CELL_DMUX_STREAM_TYPE_PAMF) { return CELL_DMUX_ERROR_ARG; } - if (!es->release()) + if (!(demuxerHandle->dmux_state & DMUX_STOPPED)) + { + return CELL_DMUX_ERROR_BUSY; + } + + if (const error_code ret = sys_mutex_lock(ppu, demuxerHandle->_dx_mhd, 0); ret != CELL_OK) + { + return ret; + } + + if (const error_code ret = get_error(get_core_ops()->setStream(ppu, demuxerHandle->core_handle, streamAddress, streamSize, discontinuity, userData)); + ret != CELL_OK) + { + const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + return mutex_unlock_ret ? mutex_unlock_ret : ret; + } + + demuxerHandle->stream_is_set = true; + demuxerHandle->dmux_state = DMUX_RUNNING; + demuxerHandle->user_data = userData; + + return sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); +} + +error_code cellDmuxResetStream(ppu_thread& ppu, vm::ptr demuxerHandle) +{ + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellDmux.notice("cellDmuxResetStream(demuxerHandle=*0x%x)", demuxerHandle); + + if (!demuxerHandle || !demuxerHandle->_this || demuxerHandle->dmux_type.streamType != CELL_DMUX_STREAM_TYPE_PAMF) + { + return CELL_DMUX_ERROR_ARG; + } + + if (const error_code ret = sys_mutex_lock(ppu, demuxerHandle->_dx_mhd, 0); ret != CELL_OK) + { + return ret; + } + + const u32 dmux_status = demuxerHandle->dmux_state; + + if (const error_code ret = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); ret != CELL_OK) + { + return ret; + } + + if (!(dmux_status & DMUX_RUNNING) || !demuxerHandle->stream_is_set) { return CELL_DMUX_ERROR_SEQ; } + + if (const error_code ret = get_error(get_core_ops()->resetStream(ppu, demuxerHandle->core_handle)); ret != CELL_OK) + { + return ret; + } + + demuxerHandle->stream_is_set = false; + return CELL_OK; } -error_code cellDmuxFlushEs(u32 esHandle) +error_code cellDmuxResetStreamAndWaitDone(ppu_thread& ppu, vm::ptr demuxerHandle) { - cellDmux.warning("cellDmuxFlushEs(esHandle=0x%x)", esHandle); + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; - const auto es = idm::get_unlocked(esHandle); + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } - if (!es) + cellDmux.notice("cellDmuxResetStreamAndWaitDone(demuxerHandle=*0x%x)", demuxerHandle); + + if (!demuxerHandle || !demuxerHandle->_this || demuxerHandle->dmux_type.streamType != CELL_DMUX_STREAM_TYPE_PAMF) { return CELL_DMUX_ERROR_ARG; } - DemuxerTask task(dmuxFlushEs); - task.es.es = esHandle; - task.es.es_ptr = es.get(); + if (const error_code ret = get_error(get_core_ops()->resetStreamAndWaitDone(ppu, demuxerHandle->core_handle)); ret != CELL_OK) + { + return ret; + } + + // LLE doesn't set DmuxContext::stream_is_set to false + + return CELL_OK; +} + +error_code cellDmuxQueryEsAttr(ppu_thread& ppu, vm::cptr demuxerType, vm::cptr esFilterId, vm::cptr esSpecificInfo, vm::ptr esAttr) +{ + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellDmux.notice("cellDmuxQueryEsAttr(demuxerType=*0x%x, esFilterId=*0x%x, esSpecificInfo=*0x%x, esAttr=*0x%x)", demuxerType, esFilterId, esSpecificInfo, esAttr); + + if (!demuxerType || demuxerType->streamType != CELL_DMUX_STREAM_TYPE_PAMF || !esFilterId || !esAttr) + { + return CELL_DMUX_ERROR_ARG; + } + + const vm::var core_es_attr; + + if (const error_code ret = get_error(get_core_ops()->queryEsAttr(ppu, vm::make_var(*esFilterId), esSpecificInfo, core_es_attr)); + ret != CELL_OK) + { + return ret; + } + + esAttr->memSize = utils::align(sizeof(DmuxEsContext) + ((core_es_attr->auQueueMaxSize + 1) * (core_es_attr->specificInfoSize + sizeof(DmuxAuQueueElement))), alignof(DmuxEsContext)) + + core_es_attr->memSize + 0xf; + + return CELL_OK; +} + +error_code cellDmuxQueryEsAttr2(ppu_thread& ppu, vm::cptr demuxerType2, vm::cptr esFilterId, vm::cptr esSpecificInfo, vm::ptr esAttr) +{ + cellDmux.notice("cellDmuxQueryEsAttr2(demuxerType2=*0x%x, esFilterId=*0x%x, esSpecificInfo=*0x%x, esAttr=*0x%x)", demuxerType2, esFilterId, esSpecificInfo, esAttr); + + ensure(!!demuxerType2); // Not checked on LLE + + const vm::var demuxerType{{ .streamType = demuxerType2->streamType }}; + + return cellDmuxQueryEsAttr(ppu, demuxerType, esFilterId, esSpecificInfo, esAttr); +} + +error_code cellDmuxEnableEs(ppu_thread& ppu, vm::ptr demuxerHandle, vm::cptr esFilterId, vm::cptr esResourceInfo, + vm::cptr esCb, vm::cptr esSpecificInfo, vm::pptr esHandle) +{ + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellDmux.notice("cellDmuxEnableEs(demuxerHandle=*0x%x, esFilterId=*0x%x, esResourceInfo=*0x%x, esCb=*0x%x, esSpecificInfo=*0x%x, esHandle=**0x%x)", + demuxerHandle, esFilterId, esResourceInfo, esCb, esSpecificInfo, esHandle); + + if (!demuxerHandle || !demuxerHandle->_this || demuxerHandle->dmux_type.streamType != CELL_DMUX_STREAM_TYPE_PAMF + || !esFilterId + || !esResourceInfo || !esResourceInfo->memAddr || esResourceInfo->memSize == umax + || !esCb || !esCb->cbFunc + || !esHandle) + { + return CELL_DMUX_ERROR_ARG; + } + + if (const error_code ret = sys_mutex_lock(ppu, demuxerHandle->_dx_mhd, 0); ret != CELL_OK) + { + return ret; + } + + if (demuxerHandle->enabled_es_num >= demuxerHandle->max_enabled_es_num) + { + const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + return mutex_unlock_ret ? mutex_unlock_ret : CELL_DMUX_ERROR_ARG; + } + + const vm::var es_attr; + + if (const error_code ret = cellDmuxQueryEsAttr(ppu, demuxerHandle.ptr(&DmuxContext::dmux_type), esFilterId, esSpecificInfo, es_attr); ret != CELL_OK) + { + const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + return mutex_unlock_ret ? mutex_unlock_ret : ret; + } + + if (es_attr->memSize > esResourceInfo->memSize) + { + const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + return mutex_unlock_ret ? mutex_unlock_ret : CELL_DMUX_ERROR_ARG; + } + + const vm::var es_filter_id{ *esFilterId }; + const vm::var core_es_attr; + + if (const error_code ret = get_error(get_core_ops()->queryEsAttr(ppu, es_filter_id, esSpecificInfo, core_es_attr)); ret != CELL_OK) + { + const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + return mutex_unlock_ret ? mutex_unlock_ret : ret; + } + + core_es_attr->auQueueMaxSize++; // One extra slot for the access unit produced by flushing the stream, so that flushing always succeeds + + const auto es_handle = vm::ptr::make(utils::align(esResourceInfo->memAddr.addr(), alignof(DmuxEsContext))); + const u32 au_queue_elements_size = core_es_attr->auQueueMaxSize * (core_es_attr->specificInfoSize + sizeof(DmuxAuQueueElement)); + const auto core_mem_addr = vm::bptr::make(utils::align(es_handle.addr() + sizeof(DmuxEsContext) + au_queue_elements_size, 0x10)); + + const vm::var core_es_resource + {{ + .memAddr = core_mem_addr, + .memSize = esResourceInfo->memSize - (core_mem_addr.addr() - esResourceInfo->memAddr.addr()) + }}; + + const vm::var mutex_attr = + {{ + .protocol = SYS_SYNC_PRIORITY, + .recursive = SYS_SYNC_NOT_RECURSIVE, + .pshared = SYS_SYNC_NOT_PROCESS_SHARED, + .adaptive = SYS_SYNC_NOT_ADAPTIVE, + .name_u64 = "_dx_mes"_u64 + }}; + + if (const error_code ret = sys_mutex_create(ppu, es_handle.ptr(&DmuxEsContext::_dx_mes), mutex_attr); ret != CELL_OK) + { + ensure(sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE + return ret; + } + + if (const error_code ret = sys_mutex_lock(ppu, es_handle->_dx_mes, 0); ret != CELL_OK) + { + ensure(sys_mutex_destroy(ppu, es_handle->_dx_mes) == CELL_OK); // Not checked on LLE + ensure(sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE + return ret; + } + + const auto au_found_func = vm::bptr::make(g_fxo->get().func_addr(FIND_FUNC(notify_es_au_found))); + const auto flush_done_func = vm::bptr::make(g_fxo->get().func_addr(FIND_FUNC(notify_es_flush_done))); + const vm::var> cb_au_found{{ .cbFunc = au_found_func, .cbArg = es_handle }}; + const vm::var> cb_flush_done{{ .cbFunc = flush_done_func, .cbArg = es_handle }}; + + const vm::var> core_es_handle; + + if (const error_code ret = get_error(get_core_ops()->enableEs(ppu, demuxerHandle->core_handle, es_filter_id, core_es_resource, cb_au_found, cb_flush_done, + esSpecificInfo, core_es_handle)); + ret != CELL_OK) + { + const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, es_handle->_dx_mes); + const error_code mutex_destroy_ret = sys_mutex_destroy(ppu, es_handle->_dx_mes); + + if (mutex_unlock_ret != CELL_OK) + { + ensure(sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE + return mutex_unlock_ret; + } + + if (mutex_destroy_ret != CELL_OK) + { + ensure(sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE + return mutex_destroy_ret; + } + + const error_code mutex_unlock_ret2 = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + return mutex_unlock_ret2 ? mutex_unlock_ret2 : ret; + } + + es_handle->is_enabled = true; + es_handle->error_mem_size = 0; + es_handle->error_count = 0; + // es_handle->error_mem_addr is not initialized on LLE + es_handle->_this = es_handle; + es_handle->_this_size = sizeof(DmuxEsContext) + au_queue_elements_size; + es_handle->_this_index = demuxerHandle->enabled_es_num; + es_handle->dmux_handle = demuxerHandle; + es_handle->es_cb = *esCb; + es_handle->core_es_handle = *core_es_handle; + es_handle->flush_started = bf_t, 0, 1>{}; + es_handle->au_queue.max_size = core_es_attr->auQueueMaxSize; + es_handle->au_queue.allocated_size = 0; + es_handle->au_queue.size = 0; + es_handle->au_queue.front = 0; + es_handle->au_queue.back = 0; + es_handle->au_queue.allocated_back = 0; + + const vm::ptr au_queue_elements = get_au_queue_elements(es_handle); + + for (u32 i = 0; i < core_es_attr->auQueueMaxSize; i++) + { + au_queue_elements[i].index = i; + au_queue_elements[i].unk = 0; + au_queue_elements[i].au_info.info.auAddr = vm::null; + au_queue_elements[i].au_info.info.auMaxSize = 0; + au_queue_elements[i].au_info.specific_info.set(au_queue_elements.addr() + (core_es_attr->auQueueMaxSize * static_cast(sizeof(DmuxAuQueueElement))) + (i * core_es_attr->specificInfoSize)); + au_queue_elements[i].au_info.specific_info_size = core_es_attr->specificInfoSize; + } + + demuxerHandle->enabled_es_num++; + *get_es_handles(demuxerHandle).rbegin() = es_handle; + *esHandle = es_handle; + + if (const error_code ret = sys_mutex_unlock(ppu, es_handle->_dx_mes); ret != CELL_OK) + { + ensure(sys_mutex_destroy(ppu, es_handle->_dx_mes) == CELL_OK); // Not checked on LLE + ensure(sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE + return ret; + } + + return sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); +} + +error_code cellDmuxDisableEs(ppu_thread& ppu, vm::ptr esHandle) +{ + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellDmux.notice("cellDmuxDisableEs(esHandle=*0x%x)", esHandle); + + if (!esHandle || !esHandle->_this || !esHandle->dmux_handle || esHandle->dmux_handle->dmux_type.streamType != CELL_DMUX_STREAM_TYPE_PAMF) + { + return CELL_DMUX_ERROR_ARG; + } + + if (const error_code ret = sys_mutex_lock(ppu, esHandle->dmux_handle->_dx_mhd, 0); ret != CELL_OK) + { + return ret; + } + + if (const error_code ret = disable_es(ppu, *esHandle); ret != CELL_OK) + { + ensure(sys_mutex_unlock(ppu, esHandle->dmux_handle->_dx_mhd) == CELL_OK); // Not checked on LLE + return ret; + } + + const std::span> es_handles = get_es_handles(esHandle->dmux_handle); + + std::shift_left(std::ranges::find(es_handles, static_cast>(esHandle)), es_handles.end(), 1); + + esHandle->dmux_handle->enabled_es_num--; + *es_handles.rbegin() = vm::null; + + return sys_mutex_unlock(ppu, esHandle->dmux_handle->_dx_mhd); +} + +error_code cellDmuxResetEs(ppu_thread& ppu, vm::ptr esHandle) +{ + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellDmux.notice("cellDmuxResetEs(esHandle=*0x%x)", esHandle); + + if (!esHandle || !esHandle->_this || !esHandle->dmux_handle || esHandle->dmux_handle->dmux_type.streamType != CELL_DMUX_STREAM_TYPE_PAMF) + { + return CELL_DMUX_ERROR_ARG; + } + + if (const error_code ret = sys_mutex_lock(ppu, esHandle->dmux_handle->_dx_mhd, 0); ret != CELL_OK) + { + return ret; + } + + const u32 dmux_status = esHandle->dmux_handle->dmux_state; + + if (const error_code ret = sys_mutex_unlock(ppu, esHandle->dmux_handle->_dx_mhd); ret != CELL_OK) + { + return ret; + } + + if (dmux_status & DMUX_STOPPED) + { + return CELL_DMUX_ERROR_SEQ; + } + + if (const error_code ret = sys_mutex_lock(ppu, esHandle->_dx_mes, 0); ret != CELL_OK) + { + return ret; + } + + if (const error_code ret = get_error(get_core_ops()->resetEs(ppu, esHandle->core_es_handle)); ret != CELL_OK) + { + const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, esHandle->_dx_mes); + return mutex_unlock_ret ? mutex_unlock_ret : ret; + } + + const auto au_queue_elements = get_au_queue_elements(esHandle); + + for (s32 i = 0; i < esHandle->au_queue.max_size; i++) + { + au_queue_elements[i].index = i; + au_queue_elements[i].unk = 0; + au_queue_elements[i].au_info.info.auAddr = vm::null; + au_queue_elements[i].au_info.info.auMaxSize = 0; + } + + esHandle->error_mem_size = 0; + esHandle->error_count = 0; + esHandle->au_queue.allocated_size = 0; + esHandle->au_queue.size = 0; + esHandle->au_queue.front = 0; + esHandle->au_queue.back = 0; + esHandle->au_queue.allocated_back = 0; + + return sys_mutex_unlock(ppu, esHandle->_dx_mes); +} + +template +static error_code pop_au(ppu_thread& ppu, vm::ptr esHandle, vm::cpptr auInfo, vm::cpptr auSpecificInfo) +{ + if (!esHandle || !esHandle->_this || !esHandle->dmux_handle || esHandle->dmux_handle->dmux_type.streamType != CELL_DMUX_STREAM_TYPE_PAMF) + { + return CELL_DMUX_ERROR_ARG; + } + + if (const error_code ret = sys_mutex_lock(ppu, esHandle->_dx_mes, 0); ret != CELL_OK) + { + return ret; + } + + if (ppu.state & cpu_flag::again) + { + return {}; + } + + if (esHandle->au_queue.size <= 0) + { + const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, esHandle->_dx_mes); + return mutex_unlock_ret ? mutex_unlock_ret : CELL_DMUX_ERROR_EMPTY; + } + + const vm::ptr au_info = (get_au_queue_elements(esHandle) + esHandle->au_queue.front).ptr(&DmuxAuQueueElement::au_info); + + if (auInfo) + { + *auInfo = au_info.ptr(&DmuxAuInfo::info); + } + + if (auSpecificInfo) + { + *auSpecificInfo = au_info->specific_info; + } + + if constexpr (!is_peek) + { + esHandle->au_queue.front = (esHandle->au_queue.front + 1) % esHandle->au_queue.max_size; + esHandle->au_queue.size--; + } + + return sys_mutex_unlock(ppu, esHandle->_dx_mes); +} + +error_code cellDmuxGetAu(ppu_thread& ppu, vm::ptr esHandle, vm::cpptr auInfo, vm::cpptr auSpecificInfo) +{ + cellDmux.trace("cellDmuxGetAu(esHandle=*0x%x, auInfo=**0x%x, auSpecificInfo=**0x%x)", esHandle, auInfo, auSpecificInfo); + + return pop_au(ppu, esHandle, auInfo, auSpecificInfo); +} + +error_code cellDmuxPeekAu(ppu_thread& ppu, vm::ptr esHandle, vm::cpptr auInfo, vm::cpptr auSpecificInfo) +{ + cellDmux.trace("cellDmuxPeekAu(esHandle=*0x%x, auInfo=**0x%x, auSpecificInfo=**0x%x)", esHandle, auInfo, auSpecificInfo); + + return pop_au(ppu, esHandle, auInfo, auSpecificInfo); +} + +error_code cellDmuxGetAuEx(ppu_thread& ppu, vm::ptr esHandle, vm::cpptr auInfoEx, vm::cpptr auSpecificInfo) +{ + cellDmux.trace("cellDmuxGetAuEx(esHandle=*0x%x, auInfoEx=**0x%x, auSpecificInfo=**0x%x)", esHandle, auInfoEx, auSpecificInfo); + + return pop_au(ppu, esHandle, auInfoEx, auSpecificInfo); +} + +error_code cellDmuxPeekAuEx(ppu_thread& ppu, vm::ptr esHandle, vm::cpptr auInfoEx, vm::cpptr auSpecificInfo) +{ + cellDmux.trace("cellDmuxPeekAuEx(esHandle=*0x%x, auInfoEx=**0x%x, auSpecificInfo=**0x%x)", esHandle, auInfoEx, auSpecificInfo); + + return pop_au(ppu, esHandle, auInfoEx, auSpecificInfo); +} + +error_code cellDmuxReleaseAu(ppu_thread& ppu, vm::ptr esHandle) +{ + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellDmux.trace("cellDmuxReleaseAu(esHandle=*0x%x)", esHandle); + + if (!esHandle || !esHandle->_this || !esHandle->dmux_handle || esHandle->dmux_handle->dmux_type.streamType != CELL_DMUX_STREAM_TYPE_PAMF) + { + return CELL_DMUX_ERROR_ARG; + } + + if (const error_code ret = sys_mutex_lock(ppu, esHandle->_dx_mes, 0); ret != CELL_OK) + { + return ret; + } + + vm::bptr mem_addr; + u32 mem_size; + + if (esHandle->au_queue.allocated_size < 1) + { + if (esHandle->error_count == 0u) + { + const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, esHandle->_dx_mes); + return mutex_unlock_ret ? mutex_unlock_ret : CELL_DMUX_ERROR_SEQ; + } + + mem_addr = esHandle->error_mem_addr; + mem_size = esHandle->error_mem_size; + } + else + { + const DmuxAuInfo& au_info = get_au_queue_elements(esHandle)[esHandle->au_queue.allocated_back].au_info; + + mem_size = + esHandle->error_mem_size += au_info.info.auSize; + + if (esHandle->error_count == 0u) + { + mem_addr = au_info.info.auAddr; + } + else + { + mem_addr = esHandle->error_mem_addr; + } + + esHandle->au_queue.allocated_back = (esHandle->au_queue.allocated_back + 1) % esHandle->au_queue.max_size; + esHandle->au_queue.allocated_size--; + + if (esHandle->au_queue.allocated_size < esHandle->au_queue.size) + { + esHandle->au_queue.front = (esHandle->au_queue.front + 1) % esHandle->au_queue.max_size; + esHandle->au_queue.size--; + } + } + + if (const error_code ret = get_error(get_core_ops()->releaseAu(ppu, esHandle->core_es_handle, mem_addr, mem_size)); ret != CELL_OK) + { + if (esHandle->error_count == 0u) + { + esHandle->error_mem_addr = mem_addr; + } + + esHandle->error_count++; + + const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, esHandle->_dx_mes); + return mutex_unlock_ret ? mutex_unlock_ret : ret; + } + + esHandle->error_count = 0; + esHandle->error_mem_size = 0; + + return sys_mutex_unlock(ppu, esHandle->_dx_mes); +} + +error_code cellDmuxFlushEs(ppu_thread& ppu, vm::ptr esHandle) +{ + // Blocking savestate creation due to ppu_thread::fast_call() + const std::unique_lock savestate_lock{ g_fxo->get(), std::try_to_lock }; + + if (!savestate_lock) + { + ppu.state += cpu_flag::again; + return {}; + } + + cellDmux.notice("cellDmuxFlushEs(esHandle=*0x%x)", esHandle); + + if (!esHandle || !esHandle->_this || !esHandle->dmux_handle || esHandle->dmux_handle->dmux_type.streamType != CELL_DMUX_STREAM_TYPE_PAMF) + { + return CELL_DMUX_ERROR_ARG; + } + + if (const error_code ret = sys_mutex_lock(ppu, esHandle->dmux_handle->_dx_mhd, 0); ret != CELL_OK) + { + return ret; + } + + const u32 dmux_state = esHandle->dmux_handle->dmux_state; + + if (const error_code ret = sys_mutex_unlock(ppu, esHandle->dmux_handle->_dx_mhd); ret != CELL_OK) + { + return ret; + } + + if (!(dmux_state & DMUX_STOPPED)) + { + return CELL_DMUX_ERROR_SEQ; + } + + esHandle->flush_started = true; + + if (const error_code ret = get_error(get_core_ops()->flushEs(ppu, esHandle->core_es_handle)); ret != CELL_OK) + { + esHandle->flush_started = false; + return ret; + } - es->dmux->job.push(task, &es->dmux->is_closed); return CELL_OK; } @@ -1382,4 +1246,11 @@ DECLARE(ppu_module_manager::cellDmux)("cellDmux", []() REG_FUNC(cellDmux, cellDmuxPeekAuEx); REG_FUNC(cellDmux, cellDmuxReleaseAu); REG_FUNC(cellDmux, cellDmuxFlushEs); + + REG_HIDDEN_FUNC(notify_demux_done); + REG_HIDDEN_FUNC(notify_fatal_err); + REG_HIDDEN_FUNC(notify_prog_end_code); + + REG_HIDDEN_FUNC(notify_es_au_found); + REG_HIDDEN_FUNC(notify_es_flush_done); }); diff --git a/rpcs3/Emu/Cell/Modules/cellDmux.h b/rpcs3/Emu/Cell/Modules/cellDmux.h index dc17cb3314..3db8c63bee 100644 --- a/rpcs3/Emu/Cell/Modules/cellDmux.h +++ b/rpcs3/Emu/Cell/Modules/cellDmux.h @@ -1,7 +1,8 @@ #pragma once #include "Emu/Memory/vm_ptr.h" -#include "cellPamf.h" +#include "Emu/Cell/ErrorCodes.h" +#include "Utilities/BitField.h" // Error Codes enum CellDmuxError :u32 @@ -18,6 +19,10 @@ enum CellDmuxStreamType : s32 CELL_DMUX_STREAM_TYPE_UNDEF = 0, CELL_DMUX_STREAM_TYPE_PAMF = 1, CELL_DMUX_STREAM_TYPE_TERMINATOR = 2, + + // Only used in cellSail + CELL_DMUX_STREAM_TYPE_MP4 = 0x81, + CELL_DMUX_STREAM_TYPE_AVI = 0x82 }; enum CellDmuxMsgType : s32 @@ -48,13 +53,14 @@ struct CellDmuxEsMsg struct CellDmuxType { be_t streamType; // CellDmuxStreamType - be_t reserved[2]; + be_t reserved1; + be_t reserved2; }; struct CellDmuxType2 { - be_t streamType; // CellDmuxStreamType - be_t streamSpecificInfo; + be_t streamType; + vm::bcptr streamSpecificInfo; }; struct CellDmuxResource @@ -73,8 +79,8 @@ struct CellDmuxResourceEx be_t memSize; be_t ppuThreadPriority; be_t ppuThreadStackSize; - be_t spurs_addr; - u8 priority[8]; + vm::bptr spurs; // CellSpurs* + be_t priority; be_t maxContention; }; @@ -85,33 +91,23 @@ struct CellDmuxResourceSpurs be_t maxContention; }; -/* -struct CellDmuxResource2Ex -{ - b8 isResourceEx; //true - CellDmuxResourceEx resourceEx; -}; - -struct CellDmuxResource2NoEx -{ - b8 isResourceEx; //false - CellDmuxResource resource; -}; -*/ - struct CellDmuxResource2 { b8 isResourceEx; - be_t memAddr; - be_t memSize; - be_t ppuThreadPriority; - be_t ppuThreadStackSize; - be_t shit[4]; + + union + { + CellDmuxResource resource; + CellDmuxResourceEx resourceEx; + }; }; -using CellDmuxCbMsg = u32(u32 demuxerHandle, vm::cptr demuxerMsg, vm::ptr cbArg); +struct DmuxContext; +struct DmuxEsContext; -using CellDmuxCbEsMsg = u32(u32 demuxerHandle, u32 esHandle, vm::cptr esMsg, vm::ptr cbArg); +using CellDmuxCbMsg = u32(vm::ptr demuxerHandle, vm::cptr demuxerMsg, vm::ptr cbArg); + +using CellDmuxCbEsMsg = u32(vm::ptr demuxerHandle, vm::ptr esHandle, vm::cptr esMsg, vm::ptr cbArg); // Used for internal callbacks as well template @@ -177,6 +173,70 @@ struct DmuxAuInfo be_t specific_info_size; }; +struct DmuxAuQueueElement +{ + be_t index; + u8 unk; // unused + DmuxAuInfo au_info; +}; + +CHECK_SIZE(DmuxAuQueueElement, 0x38); + +enum DmuxState : u32 +{ + DMUX_STOPPED = 1 << 0, + DMUX_RUNNING = 1 << 1, +}; + +struct alignas(0x10) DmuxContext // CellDmuxHandle = DmuxContext* +{ + vm::bptr _this; + be_t _this_size; + be_t version; + be_t dmux_state; + CellDmuxType dmux_type; + CellDmuxCb dmux_cb; + b8 stream_is_set; + vm::bptr core_handle; + be_t version_; // Same value as 'version' + be_t user_data; + be_t max_enabled_es_num; + be_t enabled_es_num; + be_t _dx_mhd; // sys_mutex_t + u8 reserved[0x7c]; +}; + +CHECK_SIZE_ALIGN(DmuxContext, 0xc0, 0x10); + +struct alignas(0x10) DmuxEsContext // CellDmuxEsHandle = DmuxEsContext* +{ + be_t _dx_mes; // sys_mutex_t + be_t is_enabled; + be_t error_mem_size; + be_t error_count; + vm::bptr error_mem_addr; + vm::bptr _this; + be_t _this_size; + be_t _this_index; + vm::bptr dmux_handle; + CellDmuxEsCb es_cb; + vm::bptr core_es_handle; + bf_t, 0, 1> flush_started; + + struct + { + be_t max_size; + be_t allocated_size; + be_t size; + be_t front; + be_t back; + be_t allocated_back; + } + au_queue; +}; + +CHECK_SIZE_ALIGN(DmuxEsContext, 0x50, 0x10); + using DmuxNotifyDemuxDone = error_code(vm::ptr, u32, vm::ptr); using DmuxNotifyFatalErr = error_code(vm::ptr, u32, vm::ptr); using DmuxNotifyProgEndCode = error_code(vm::ptr, vm::ptr); @@ -194,10 +254,10 @@ using CellDmuxCoreOpSetStream = error_code(vm::ptr, vm::cptr, u32, b using CellDmuxCoreOpReleaseAu = error_code(vm::ptr, vm::ptr, u32); using CellDmuxCoreOpQueryEsAttr = error_code(vm::cptr, vm::cptr, vm::ptr); using CellDmuxCoreOpEnableEs = error_code(vm::ptr, vm::cptr, vm::cptr, vm::cptr>, vm::cptr>, vm::cptr, vm::pptr); -using CellDmuxCoreOpDisableEs = u32(vm::ptr); -using CellDmuxCoreOpFlushEs = u32(vm::ptr); -using CellDmuxCoreOpResetEs = u32(vm::ptr); -using CellDmuxCoreOpResetStreamAndWaitDone = u32(vm::ptr); +using CellDmuxCoreOpDisableEs = error_code(vm::ptr); +using CellDmuxCoreOpFlushEs = error_code(vm::ptr); +using CellDmuxCoreOpResetEs = error_code(vm::ptr); +using CellDmuxCoreOpResetStreamAndWaitDone = error_code(vm::ptr); struct CellDmuxCoreOps { diff --git a/rpcs3/Emu/Cell/lv2/sys_prx.cpp b/rpcs3/Emu/Cell/lv2/sys_prx.cpp index f46f3bdafa..6f930e79dd 100644 --- a/rpcs3/Emu/Cell/lv2/sys_prx.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_prx.cpp @@ -63,7 +63,7 @@ extern const std::map g_prx_list { "libcelpenc.sprx", 0 }, { "libddpdec.sprx", 0 }, { "libdivxdec.sprx", 0 }, - { "libdmux.sprx", 0 }, + { "libdmux.sprx", 1 }, { "libdmuxpamf.sprx", 1 }, { "libdtslbrdec.sprx", 0 }, { "libfiber.sprx", 0 }, From 93a2f1edc8aa1503f140557be9ced248b69c1bf0 Mon Sep 17 00:00:00 2001 From: capriots <29807355+capriots@users.noreply.github.com> Date: Sun, 8 Mar 2026 11:04:40 +0100 Subject: [PATCH 15/27] cellPamf: remove unused code --- rpcs3/Emu/Cell/Modules/cellPamf.cpp | 8 - rpcs3/Emu/Cell/Modules/cellPamf.h | 342 ---------------------------- 2 files changed, 350 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellPamf.cpp b/rpcs3/Emu/Cell/Modules/cellPamf.cpp index d63e7bb3e2..4bb383c2d2 100644 --- a/rpcs3/Emu/Cell/Modules/cellPamf.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPamf.cpp @@ -5,14 +5,6 @@ #include #include "cellPamf.h" -const std::function SQUEUE_ALWAYS_EXIT = []() { return true; }; -const std::function SQUEUE_NEVER_EXIT = []() { return false; }; - -bool squeue_test_exit() -{ - return Emu.IsStopped(); -} - LOG_CHANNEL(cellPamf); template<> diff --git a/rpcs3/Emu/Cell/Modules/cellPamf.h b/rpcs3/Emu/Cell/Modules/cellPamf.h index abd89f8852..14608f9100 100644 --- a/rpcs3/Emu/Cell/Modules/cellPamf.h +++ b/rpcs3/Emu/Cell/Modules/cellPamf.h @@ -595,345 +595,3 @@ struct CellPamfReader CHECK_SIZE(CellPamfReader, 128); error_code cellPamfReaderInitialize(vm::ptr pSelf, vm::cptr pAddr, u64 fileSize, u32 attribute); - -#include -#include - -extern const std::function SQUEUE_ALWAYS_EXIT; -extern const std::function SQUEUE_NEVER_EXIT; - -bool squeue_test_exit(); - -// TODO: eliminate this boolshit -template -class squeue_t -{ - struct squeue_sync_var_t - { - struct - { - u32 position : 31; - u32 pop_lock : 1; - }; - struct - { - u32 count : 31; - u32 push_lock : 1; - }; - }; - - atomic_t m_sync; - - mutable std::mutex m_rcv_mutex; - mutable std::mutex m_wcv_mutex; - mutable std::condition_variable m_rcv; - mutable std::condition_variable m_wcv; - - T m_data[sq_size]; - - enum squeue_sync_var_result : u32 - { - SQSVR_OK = 0, - SQSVR_LOCKED = 1, - SQSVR_FAILED = 2, - }; - -public: - squeue_t() - : m_sync(squeue_sync_var_t{}) - { - } - - static u32 get_max_size() - { - return sq_size; - } - - bool is_full() const - { - return m_sync.load().count == sq_size; - } - - bool push(const T& data, const std::function& test_exit) - { - u32 pos = 0; - - while (u32 res = m_sync.atomic_op([&pos](squeue_sync_var_t& sync) -> u32 - { - ensure(sync.count <= sq_size); - ensure(sync.position < sq_size); - - if (sync.push_lock) - { - return SQSVR_LOCKED; - } - if (sync.count == sq_size) - { - return SQSVR_FAILED; - } - - sync.push_lock = 1; - pos = sync.position + sync.count; - return SQSVR_OK; - })) - { - if (res == SQSVR_FAILED && (test_exit() || squeue_test_exit())) - { - return false; - } - - std::unique_lock wcv_lock(m_wcv_mutex); - m_wcv.wait_for(wcv_lock, std::chrono::milliseconds(1)); - } - - m_data[pos >= sq_size ? pos - sq_size : pos] = data; - - m_sync.atomic_op([](squeue_sync_var_t& sync) - { - ensure(sync.count <= sq_size); - ensure(sync.position < sq_size); - ensure(!!sync.push_lock); - sync.push_lock = 0; - sync.count++; - }); - - m_rcv.notify_one(); - m_wcv.notify_one(); - return true; - } - - bool push(const T& data, const volatile bool* do_exit) - { - return push(data, [do_exit]() { return do_exit && *do_exit; }); - } - - bool push(const T& data) - { - return push(data, SQUEUE_NEVER_EXIT); - } - - bool try_push(const T& data) - { - return push(data, SQUEUE_ALWAYS_EXIT); - } - - bool pop(T& data, const std::function& test_exit) - { - u32 pos = 0; - - while (u32 res = m_sync.atomic_op([&pos](squeue_sync_var_t& sync) -> u32 - { - ensure(sync.count <= sq_size); - ensure(sync.position < sq_size); - - if (!sync.count) - { - return SQSVR_FAILED; - } - if (sync.pop_lock) - { - return SQSVR_LOCKED; - } - - sync.pop_lock = 1; - pos = sync.position; - return SQSVR_OK; - })) - { - if (res == SQSVR_FAILED && (test_exit() || squeue_test_exit())) - { - return false; - } - - std::unique_lock rcv_lock(m_rcv_mutex); - m_rcv.wait_for(rcv_lock, std::chrono::milliseconds(1)); - } - - data = m_data[pos]; - - m_sync.atomic_op([](squeue_sync_var_t& sync) - { - ensure(sync.count <= sq_size); - ensure(sync.position < sq_size); - ensure(!!sync.pop_lock); - sync.pop_lock = 0; - sync.position++; - sync.count--; - if (sync.position == sq_size) - { - sync.position = 0; - } - }); - - m_rcv.notify_one(); - m_wcv.notify_one(); - return true; - } - - bool pop(T& data, const volatile bool* do_exit) - { - return pop(data, [do_exit]() { return do_exit && *do_exit; }); - } - - bool pop(T& data) - { - return pop(data, SQUEUE_NEVER_EXIT); - } - - bool try_pop(T& data) - { - return pop(data, SQUEUE_ALWAYS_EXIT); - } - - bool peek(T& data, u32 start_pos, const std::function& test_exit) - { - ensure(start_pos < sq_size); - u32 pos = 0; - - while (u32 res = m_sync.atomic_op([&pos, start_pos](squeue_sync_var_t& sync) -> u32 - { - ensure(sync.count <= sq_size); - ensure(sync.position < sq_size); - - if (sync.count <= start_pos) - { - return SQSVR_FAILED; - } - if (sync.pop_lock) - { - return SQSVR_LOCKED; - } - - sync.pop_lock = 1; - pos = sync.position + start_pos; - return SQSVR_OK; - })) - { - if (res == SQSVR_FAILED && (test_exit() || squeue_test_exit())) - { - return false; - } - - std::unique_lock rcv_lock(m_rcv_mutex); - m_rcv.wait_for(rcv_lock, std::chrono::milliseconds(1)); - } - - data = m_data[pos >= sq_size ? pos - sq_size : pos]; - - m_sync.atomic_op([](squeue_sync_var_t& sync) - { - ensure(sync.count <= sq_size); - ensure(sync.position < sq_size); - ensure(!!sync.pop_lock); - sync.pop_lock = 0; - }); - - m_rcv.notify_one(); - return true; - } - - bool peek(T& data, u32 start_pos, const volatile bool* do_exit) - { - return peek(data, start_pos, [do_exit]() { return do_exit && *do_exit; }); - } - - bool peek(T& data, u32 start_pos = 0) - { - return peek(data, start_pos, SQUEUE_NEVER_EXIT); - } - - bool try_peek(T& data, u32 start_pos = 0) - { - return peek(data, start_pos, SQUEUE_ALWAYS_EXIT); - } - - class squeue_data_t - { - T* const m_data; - const u32 m_pos; - const u32 m_count; - - squeue_data_t(T* data, u32 pos, u32 count) - : m_data(data) - , m_pos(pos) - , m_count(count) - { - } - - public: - T& operator [] (u32 index) - { - ensure(index < m_count); - index += m_pos; - index = index < sq_size ? index : index - sq_size; - return m_data[index]; - } - }; - - void process(void(*proc)(squeue_data_t data)) - { - u32 pos, count; - - while (m_sync.atomic_op([&pos, &count](squeue_sync_var_t& sync) -> u32 - { - ensure(sync.count <= sq_size); - ensure(sync.position < sq_size); - - if (sync.pop_lock || sync.push_lock) - { - return SQSVR_LOCKED; - } - - pos = sync.position; - count = sync.count; - sync.pop_lock = 1; - sync.push_lock = 1; - return SQSVR_OK; - })) - { - std::unique_lock rcv_lock(m_rcv_mutex); - m_rcv.wait_for(rcv_lock, std::chrono::milliseconds(1)); - } - - proc(squeue_data_t(m_data, pos, count)); - - m_sync.atomic_op([](squeue_sync_var_t& sync) - { - ensure(sync.count <= sq_size); - ensure(sync.position < sq_size); - ensure(!!sync.pop_lock); - ensure(!!sync.push_lock); - sync.pop_lock = 0; - sync.push_lock = 0; - }); - - m_wcv.notify_one(); - m_rcv.notify_one(); - } - - void clear() - { - while (m_sync.atomic_op([](squeue_sync_var_t& sync) -> u32 - { - ensure(sync.count <= sq_size); - ensure(sync.position < sq_size); - - if (sync.pop_lock || sync.push_lock) - { - return SQSVR_LOCKED; - } - - sync.pop_lock = 1; - sync.push_lock = 1; - return SQSVR_OK; - })) - { - std::unique_lock rcv_lock(m_rcv_mutex); - m_rcv.wait_for(rcv_lock, std::chrono::milliseconds(1)); - } - - m_sync.exchange({}); - m_wcv.notify_one(); - m_rcv.notify_one(); - } -}; From 87fc45d66163987a0ff5201ab04fd27f74652e0c Mon Sep 17 00:00:00 2001 From: capriots <29807355+capriots@users.noreply.github.com> Date: Sun, 8 Mar 2026 13:48:09 +0100 Subject: [PATCH 16/27] cellDmuxPamf: logging fix --- rpcs3/Emu/Cell/Modules/cellDmuxPamf.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/Cell/Modules/cellDmuxPamf.cpp b/rpcs3/Emu/Cell/Modules/cellDmuxPamf.cpp index 9332fc5f1e..ecf59db508 100644 --- a/rpcs3/Emu/Cell/Modules/cellDmuxPamf.cpp +++ b/rpcs3/Emu/Cell/Modules/cellDmuxPamf.cpp @@ -2591,7 +2591,7 @@ template error_code _CellDmuxCoreOpEnableEs(ppu_thread& ppu, vm::ptr handle, vm::cptr esFilterId, vm::cptr esResource, vm::cptr> notifyAuFound, vm::cptr> notifyFlushDone, vm::cptr esSpecificInfo, vm::pptr esHandle) { - cellDmuxPamf.notice("_CellDmuxCoreOpEnableEs(handle=*0x%x, esFilterId=*0x%x, esResource=*0x%x, notifyAuFound=*0x%x, notifyFlushDone=*0x%x, esSpecificInfo=*0x%x, esHandle)", + cellDmuxPamf.notice("_CellDmuxCoreOpEnableEs(handle=*0x%x, esFilterId=*0x%x, esResource=*0x%x, notifyAuFound=*0x%x, notifyFlushDone=*0x%x, esSpecificInfo=*0x%x, esHandle=**0x%x)", raw_es, handle, esFilterId, esResource, notifyAuFound, notifyFlushDone, esSpecificInfo, esHandle); if (!handle || !esFilterId || !esResource || !esResource->memAddr || esResource->memSize == 0u || !notifyAuFound || !notifyAuFound->cbFunc || !notifyAuFound->cbArg || !notifyFlushDone || !notifyFlushDone->cbFunc || !notifyFlushDone->cbArg) From e95a9806514f0086ccddb676e3a22a0d1f18de6e Mon Sep 17 00:00:00 2001 From: capriots <29807355+capriots@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:54:05 +0100 Subject: [PATCH 17/27] cellDmux: process PPU state after lv2 syscalls --- rpcs3/Emu/Cell/Modules/cellDmux.cpp | 128 +++++++++++++++------------- 1 file changed, 70 insertions(+), 58 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellDmux.cpp b/rpcs3/Emu/Cell/Modules/cellDmux.cpp index 4f51539e8e..7c2fe6f1a9 100644 --- a/rpcs3/Emu/Cell/Modules/cellDmux.cpp +++ b/rpcs3/Emu/Cell/Modules/cellDmux.cpp @@ -58,6 +58,18 @@ static inline vm::cptr get_core_ops() return vm::cptr::make(*ppu_module_manager::cellDmuxPamf.variables.find(0x28b2b7b2)->second.export_addr); } +template +static auto lv2_syscall(ppu_thread& ppu, auto&&... args) +{ + const auto ret = Syscall(ppu, std::forward(args)...); + + if (ppu.test_stopped()) + { + ppu.state += cpu_flag::again; + } + + return ret; +} // Callbacks for cellDmuxPamf @@ -76,9 +88,9 @@ static error_code notify_demux_done(ppu_thread& ppu, vm::ptr core_handle, ensure(!!handle); // Not checked on LLE - ensure(sys_mutex_lock(ppu, handle->_dx_mhd, 0) == CELL_OK); // Failing this check on LLE would result in it dereferencing an invalid pointer. + ensure(lv2_syscall(ppu, handle->_dx_mhd, 0) == CELL_OK); // Failing this check on LLE would result in it dereferencing an invalid pointer. handle->dmux_state = DMUX_STOPPED; - ensure(sys_mutex_unlock(ppu, handle->_dx_mhd) == CELL_OK); // Failing this check on LLE would result in it dereferencing an invalid pointer. + ensure(lv2_syscall(ppu, handle->_dx_mhd) == CELL_OK); // Failing this check on LLE would result in it dereferencing an invalid pointer. if (handle->_this) { @@ -162,7 +174,7 @@ static error_code notify_es_au_found(ppu_thread& ppu, vm::ptr core_es_hand return CELL_OK; } - if (const error_code ret = sys_mutex_lock(ppu, es_handle->_dx_mes, 0); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, es_handle->_dx_mes, 0); ret != CELL_OK) { fatal_err(es_handle->is_enabled, ret); return 1; @@ -171,7 +183,7 @@ static error_code notify_es_au_found(ppu_thread& ppu, vm::ptr core_es_hand // Check if the access unit queue is full. One slot is reserved for the access unit produced by flushing the stream, so that flushing always succeeds. if (!es_handle->is_enabled || es_handle->au_queue.allocated_size >= es_handle->au_queue.max_size - !es_handle->flush_started) { - if (const error_code ret = sys_mutex_unlock(ppu, es_handle->_dx_mes); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, es_handle->_dx_mes); ret != CELL_OK) { fatal_err(es_handle->is_enabled, ret); return 1; @@ -182,7 +194,7 @@ static error_code notify_es_au_found(ppu_thread& ppu, vm::ptr core_es_hand DmuxAuInfo& _au_info = get_au_queue_elements(es_handle)[es_handle->au_queue.back].au_info; - if (const error_code ret = sys_mutex_unlock(ppu, es_handle->_dx_mes); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, es_handle->_dx_mes); ret != CELL_OK) { fatal_err(es_handle->is_enabled, ret); return 1; @@ -196,7 +208,7 @@ static error_code notify_es_au_found(ppu_thread& ppu, vm::ptr core_es_hand return CELL_OK; } - if (const error_code ret = sys_mutex_lock(ppu, es_handle->_dx_mes, 0); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, es_handle->_dx_mes, 0); ret != CELL_OK) { fatal_err(es_handle->is_enabled, ret); return CELL_OK; // LLE returns CELL_OK @@ -204,7 +216,7 @@ static error_code notify_es_au_found(ppu_thread& ppu, vm::ptr core_es_hand if (!es_handle->is_enabled) { - if (const error_code ret = sys_mutex_unlock(ppu, es_handle->_dx_mes); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, es_handle->_dx_mes); ret != CELL_OK) { fatal_err(es_handle->is_enabled, ret); } @@ -216,7 +228,7 @@ static error_code notify_es_au_found(ppu_thread& ppu, vm::ptr core_es_hand es_handle->au_queue.allocated_size++; es_handle->au_queue.size++; - if (const error_code ret = sys_mutex_unlock(ppu, es_handle->_dx_mes); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, es_handle->_dx_mes); ret != CELL_OK) { fatal_err(es_handle->is_enabled, ret); return CELL_OK; // LLE returns CELL_OK @@ -405,7 +417,7 @@ static error_code open(ppu_thread& ppu, vm::cptr demuxerType, vm:: .name_u64 = "_dx_mhd"_u64 }}; - if (const error_code ret = sys_mutex_create(ppu, handle.ptr(&DmuxContext::_dx_mhd), mutex_attr); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, handle.ptr(&DmuxContext::_dx_mhd), mutex_attr); ret != CELL_OK) { return ret; } @@ -510,7 +522,7 @@ error_code cellDmuxOpen2(ppu_thread& ppu, vm::cptr demuxerType2, static error_code disable_es(ppu_thread& ppu, DmuxEsContext& esHandle) { - if (const error_code ret = sys_mutex_lock(ppu, esHandle._dx_mes, 0); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, esHandle._dx_mes, 0); ret != CELL_OK) { return ret; } @@ -519,15 +531,15 @@ static error_code disable_es(ppu_thread& ppu, DmuxEsContext& esHandle) esHandle.is_enabled = false; - if (const error_code ret = sys_mutex_unlock(ppu, esHandle._dx_mes); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, esHandle._dx_mes); ret != CELL_OK) { return ret; } error_code ret; - while ((ret = sys_mutex_destroy(ppu, esHandle._dx_mes)) == static_cast(CELL_EBUSY)) + while ((ret = lv2_syscall(ppu, esHandle._dx_mes)) == static_cast(CELL_EBUSY)) { - sys_timer_usleep(ppu, 200); + lv2_syscall(ppu, 200); } if (ret != CELL_OK) @@ -560,7 +572,7 @@ error_code cellDmuxClose(ppu_thread& ppu, vm::ptr demuxerHandle) demuxerHandle->_this = vm::null; - if (const error_code ret = sys_mutex_lock(ppu, demuxerHandle->_dx_mhd, 0); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, demuxerHandle->_dx_mhd, 0); ret != CELL_OK) { demuxerHandle->_this = demuxerHandle; return ret; @@ -570,7 +582,7 @@ error_code cellDmuxClose(ppu_thread& ppu, vm::ptr demuxerHandle) { if (const error_code ret = disable_es(ppu, *es_handle); ret != CELL_OK) { - ensure(sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE + ensure(lv2_syscall(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE demuxerHandle->_this = demuxerHandle; return ret; } @@ -579,9 +591,9 @@ error_code cellDmuxClose(ppu_thread& ppu, vm::ptr demuxerHandle) demuxerHandle->enabled_es_num--; } - error_code ret = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + error_code ret = lv2_syscall(ppu, demuxerHandle->_dx_mhd); ret = ret ? ret : get_error(get_core_ops()->close(ppu, demuxerHandle->core_handle)); - ret = ret ? ret : sys_mutex_destroy(ppu, demuxerHandle->_dx_mhd); + ret = ret ? ret : lv2_syscall(ppu, demuxerHandle->_dx_mhd); if (ret != CELL_OK) { @@ -615,7 +627,7 @@ error_code cellDmuxSetStream(ppu_thread& ppu, vm::ptr demuxerHandle return CELL_DMUX_ERROR_BUSY; } - if (const error_code ret = sys_mutex_lock(ppu, demuxerHandle->_dx_mhd, 0); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, demuxerHandle->_dx_mhd, 0); ret != CELL_OK) { return ret; } @@ -623,7 +635,7 @@ error_code cellDmuxSetStream(ppu_thread& ppu, vm::ptr demuxerHandle if (const error_code ret = get_error(get_core_ops()->setStream(ppu, demuxerHandle->core_handle, streamAddress, streamSize, discontinuity, userData)); ret != CELL_OK) { - const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + const error_code mutex_unlock_ret = lv2_syscall(ppu, demuxerHandle->_dx_mhd); return mutex_unlock_ret ? mutex_unlock_ret : ret; } @@ -631,7 +643,7 @@ error_code cellDmuxSetStream(ppu_thread& ppu, vm::ptr demuxerHandle demuxerHandle->dmux_state = DMUX_RUNNING; demuxerHandle->user_data = userData; - return sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + return lv2_syscall(ppu, demuxerHandle->_dx_mhd); } error_code cellDmuxResetStream(ppu_thread& ppu, vm::ptr demuxerHandle) @@ -652,14 +664,14 @@ error_code cellDmuxResetStream(ppu_thread& ppu, vm::ptr demuxerHand return CELL_DMUX_ERROR_ARG; } - if (const error_code ret = sys_mutex_lock(ppu, demuxerHandle->_dx_mhd, 0); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, demuxerHandle->_dx_mhd, 0); ret != CELL_OK) { return ret; } const u32 dmux_status = demuxerHandle->dmux_state; - if (const error_code ret = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, demuxerHandle->_dx_mhd); ret != CELL_OK) { return ret; } @@ -774,14 +786,14 @@ error_code cellDmuxEnableEs(ppu_thread& ppu, vm::ptr demuxerHandle, return CELL_DMUX_ERROR_ARG; } - if (const error_code ret = sys_mutex_lock(ppu, demuxerHandle->_dx_mhd, 0); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, demuxerHandle->_dx_mhd, 0); ret != CELL_OK) { return ret; } if (demuxerHandle->enabled_es_num >= demuxerHandle->max_enabled_es_num) { - const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + const error_code mutex_unlock_ret = lv2_syscall(ppu, demuxerHandle->_dx_mhd); return mutex_unlock_ret ? mutex_unlock_ret : CELL_DMUX_ERROR_ARG; } @@ -789,13 +801,13 @@ error_code cellDmuxEnableEs(ppu_thread& ppu, vm::ptr demuxerHandle, if (const error_code ret = cellDmuxQueryEsAttr(ppu, demuxerHandle.ptr(&DmuxContext::dmux_type), esFilterId, esSpecificInfo, es_attr); ret != CELL_OK) { - const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + const error_code mutex_unlock_ret = lv2_syscall(ppu, demuxerHandle->_dx_mhd); return mutex_unlock_ret ? mutex_unlock_ret : ret; } if (es_attr->memSize > esResourceInfo->memSize) { - const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + const error_code mutex_unlock_ret = lv2_syscall(ppu, demuxerHandle->_dx_mhd); return mutex_unlock_ret ? mutex_unlock_ret : CELL_DMUX_ERROR_ARG; } @@ -804,7 +816,7 @@ error_code cellDmuxEnableEs(ppu_thread& ppu, vm::ptr demuxerHandle, if (const error_code ret = get_error(get_core_ops()->queryEsAttr(ppu, es_filter_id, esSpecificInfo, core_es_attr)); ret != CELL_OK) { - const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + const error_code mutex_unlock_ret = lv2_syscall(ppu, demuxerHandle->_dx_mhd); return mutex_unlock_ret ? mutex_unlock_ret : ret; } @@ -829,16 +841,16 @@ error_code cellDmuxEnableEs(ppu_thread& ppu, vm::ptr demuxerHandle, .name_u64 = "_dx_mes"_u64 }}; - if (const error_code ret = sys_mutex_create(ppu, es_handle.ptr(&DmuxEsContext::_dx_mes), mutex_attr); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, es_handle.ptr(&DmuxEsContext::_dx_mes), mutex_attr); ret != CELL_OK) { - ensure(sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE + ensure(lv2_syscall(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE return ret; } - if (const error_code ret = sys_mutex_lock(ppu, es_handle->_dx_mes, 0); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, es_handle->_dx_mes, 0); ret != CELL_OK) { - ensure(sys_mutex_destroy(ppu, es_handle->_dx_mes) == CELL_OK); // Not checked on LLE - ensure(sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE + ensure(lv2_syscall(ppu, es_handle->_dx_mes) == CELL_OK); // Not checked on LLE + ensure(lv2_syscall(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE return ret; } @@ -853,22 +865,22 @@ error_code cellDmuxEnableEs(ppu_thread& ppu, vm::ptr demuxerHandle, esSpecificInfo, core_es_handle)); ret != CELL_OK) { - const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, es_handle->_dx_mes); - const error_code mutex_destroy_ret = sys_mutex_destroy(ppu, es_handle->_dx_mes); + const error_code mutex_unlock_ret = lv2_syscall(ppu, es_handle->_dx_mes); + const error_code mutex_destroy_ret = lv2_syscall(ppu, es_handle->_dx_mes); if (mutex_unlock_ret != CELL_OK) { - ensure(sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE + ensure(lv2_syscall(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE return mutex_unlock_ret; } if (mutex_destroy_ret != CELL_OK) { - ensure(sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE + ensure(lv2_syscall(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE return mutex_destroy_ret; } - const error_code mutex_unlock_ret2 = sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + const error_code mutex_unlock_ret2 = lv2_syscall(ppu, demuxerHandle->_dx_mhd); return mutex_unlock_ret2 ? mutex_unlock_ret2 : ret; } @@ -906,14 +918,14 @@ error_code cellDmuxEnableEs(ppu_thread& ppu, vm::ptr demuxerHandle, *get_es_handles(demuxerHandle).rbegin() = es_handle; *esHandle = es_handle; - if (const error_code ret = sys_mutex_unlock(ppu, es_handle->_dx_mes); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, es_handle->_dx_mes); ret != CELL_OK) { - ensure(sys_mutex_destroy(ppu, es_handle->_dx_mes) == CELL_OK); // Not checked on LLE - ensure(sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE + ensure(lv2_syscall(ppu, es_handle->_dx_mes) == CELL_OK); // Not checked on LLE + ensure(lv2_syscall(ppu, demuxerHandle->_dx_mhd) == CELL_OK); // Not checked on LLE return ret; } - return sys_mutex_unlock(ppu, demuxerHandle->_dx_mhd); + return lv2_syscall(ppu, demuxerHandle->_dx_mhd); } error_code cellDmuxDisableEs(ppu_thread& ppu, vm::ptr esHandle) @@ -934,14 +946,14 @@ error_code cellDmuxDisableEs(ppu_thread& ppu, vm::ptr esHandle) return CELL_DMUX_ERROR_ARG; } - if (const error_code ret = sys_mutex_lock(ppu, esHandle->dmux_handle->_dx_mhd, 0); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, esHandle->dmux_handle->_dx_mhd, 0); ret != CELL_OK) { return ret; } if (const error_code ret = disable_es(ppu, *esHandle); ret != CELL_OK) { - ensure(sys_mutex_unlock(ppu, esHandle->dmux_handle->_dx_mhd) == CELL_OK); // Not checked on LLE + ensure(lv2_syscall(ppu, esHandle->dmux_handle->_dx_mhd) == CELL_OK); // Not checked on LLE return ret; } @@ -952,7 +964,7 @@ error_code cellDmuxDisableEs(ppu_thread& ppu, vm::ptr esHandle) esHandle->dmux_handle->enabled_es_num--; *es_handles.rbegin() = vm::null; - return sys_mutex_unlock(ppu, esHandle->dmux_handle->_dx_mhd); + return lv2_syscall(ppu, esHandle->dmux_handle->_dx_mhd); } error_code cellDmuxResetEs(ppu_thread& ppu, vm::ptr esHandle) @@ -973,14 +985,14 @@ error_code cellDmuxResetEs(ppu_thread& ppu, vm::ptr esHandle) return CELL_DMUX_ERROR_ARG; } - if (const error_code ret = sys_mutex_lock(ppu, esHandle->dmux_handle->_dx_mhd, 0); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, esHandle->dmux_handle->_dx_mhd, 0); ret != CELL_OK) { return ret; } const u32 dmux_status = esHandle->dmux_handle->dmux_state; - if (const error_code ret = sys_mutex_unlock(ppu, esHandle->dmux_handle->_dx_mhd); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, esHandle->dmux_handle->_dx_mhd); ret != CELL_OK) { return ret; } @@ -990,14 +1002,14 @@ error_code cellDmuxResetEs(ppu_thread& ppu, vm::ptr esHandle) return CELL_DMUX_ERROR_SEQ; } - if (const error_code ret = sys_mutex_lock(ppu, esHandle->_dx_mes, 0); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, esHandle->_dx_mes, 0); ret != CELL_OK) { return ret; } if (const error_code ret = get_error(get_core_ops()->resetEs(ppu, esHandle->core_es_handle)); ret != CELL_OK) { - const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, esHandle->_dx_mes); + const error_code mutex_unlock_ret = lv2_syscall(ppu, esHandle->_dx_mes); return mutex_unlock_ret ? mutex_unlock_ret : ret; } @@ -1019,7 +1031,7 @@ error_code cellDmuxResetEs(ppu_thread& ppu, vm::ptr esHandle) esHandle->au_queue.back = 0; esHandle->au_queue.allocated_back = 0; - return sys_mutex_unlock(ppu, esHandle->_dx_mes); + return lv2_syscall(ppu, esHandle->_dx_mes); } template @@ -1030,7 +1042,7 @@ static error_code pop_au(ppu_thread& ppu, vm::ptr esHandle, vm::c return CELL_DMUX_ERROR_ARG; } - if (const error_code ret = sys_mutex_lock(ppu, esHandle->_dx_mes, 0); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, esHandle->_dx_mes, 0); ret != CELL_OK) { return ret; } @@ -1042,7 +1054,7 @@ static error_code pop_au(ppu_thread& ppu, vm::ptr esHandle, vm::c if (esHandle->au_queue.size <= 0) { - const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, esHandle->_dx_mes); + const error_code mutex_unlock_ret = lv2_syscall(ppu, esHandle->_dx_mes); return mutex_unlock_ret ? mutex_unlock_ret : CELL_DMUX_ERROR_EMPTY; } @@ -1064,7 +1076,7 @@ static error_code pop_au(ppu_thread& ppu, vm::ptr esHandle, vm::c esHandle->au_queue.size--; } - return sys_mutex_unlock(ppu, esHandle->_dx_mes); + return lv2_syscall(ppu, esHandle->_dx_mes); } error_code cellDmuxGetAu(ppu_thread& ppu, vm::ptr esHandle, vm::cpptr auInfo, vm::cpptr auSpecificInfo) @@ -1113,7 +1125,7 @@ error_code cellDmuxReleaseAu(ppu_thread& ppu, vm::ptr esHandle) return CELL_DMUX_ERROR_ARG; } - if (const error_code ret = sys_mutex_lock(ppu, esHandle->_dx_mes, 0); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, esHandle->_dx_mes, 0); ret != CELL_OK) { return ret; } @@ -1125,7 +1137,7 @@ error_code cellDmuxReleaseAu(ppu_thread& ppu, vm::ptr esHandle) { if (esHandle->error_count == 0u) { - const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, esHandle->_dx_mes); + const error_code mutex_unlock_ret = lv2_syscall(ppu, esHandle->_dx_mes); return mutex_unlock_ret ? mutex_unlock_ret : CELL_DMUX_ERROR_SEQ; } @@ -1167,14 +1179,14 @@ error_code cellDmuxReleaseAu(ppu_thread& ppu, vm::ptr esHandle) esHandle->error_count++; - const error_code mutex_unlock_ret = sys_mutex_unlock(ppu, esHandle->_dx_mes); + const error_code mutex_unlock_ret = lv2_syscall(ppu, esHandle->_dx_mes); return mutex_unlock_ret ? mutex_unlock_ret : ret; } esHandle->error_count = 0; esHandle->error_mem_size = 0; - return sys_mutex_unlock(ppu, esHandle->_dx_mes); + return lv2_syscall(ppu, esHandle->_dx_mes); } error_code cellDmuxFlushEs(ppu_thread& ppu, vm::ptr esHandle) @@ -1195,14 +1207,14 @@ error_code cellDmuxFlushEs(ppu_thread& ppu, vm::ptr esHandle) return CELL_DMUX_ERROR_ARG; } - if (const error_code ret = sys_mutex_lock(ppu, esHandle->dmux_handle->_dx_mhd, 0); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, esHandle->dmux_handle->_dx_mhd, 0); ret != CELL_OK) { return ret; } const u32 dmux_state = esHandle->dmux_handle->dmux_state; - if (const error_code ret = sys_mutex_unlock(ppu, esHandle->dmux_handle->_dx_mhd); ret != CELL_OK) + if (const error_code ret = lv2_syscall(ppu, esHandle->dmux_handle->_dx_mhd); ret != CELL_OK) { return ret; } From ce12c29441a3c1755610d427bb23edbbaa169641 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 5 Apr 2026 18:12:19 +0300 Subject: [PATCH 18/27] rsx: Allow negative src and dst pitch in nv0039 - Confirmed with hardware tests --- rpcs3/Emu/RSX/NV47/HW/nv0039.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/NV47/HW/nv0039.cpp b/rpcs3/Emu/RSX/NV47/HW/nv0039.cpp index fcd30c45f8..d929caa488 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv0039.cpp +++ b/rpcs3/Emu/RSX/NV47/HW/nv0039.cpp @@ -14,7 +14,7 @@ namespace rsx namespace nv0039 { // Transfer with stride - inline void block2d_copy_with_stride(u8* dst, const u8* src, u32 width, u32 height, u32 src_pitch, u32 dst_pitch, u8 src_stride, u8 dst_stride) + inline void block2d_copy_with_stride(u8* dst, const u8* src, u32 width, u32 height, s32 src_pitch, s32 dst_pitch, u8 src_stride, u8 dst_stride) { for (u32 row = 0; row < height; ++row) { @@ -33,7 +33,7 @@ namespace rsx } } - inline void block2d_copy(u8* dst, const u8* src, u32 width, u32 height, u32 src_pitch, u32 dst_pitch) + inline void block2d_copy(u8* dst, const u8* src, u32 width, u32 height, s32 src_pitch, s32 dst_pitch) { for (u32 i = 0; i < height; ++i) { From c279583f1f360ffb0641afeb6f3b468573643a1d Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 5 Apr 2026 23:23:02 +0300 Subject: [PATCH 19/27] vk: Allow cubemap unwrap to generate more than 1 mipmap level --- rpcs3/Emu/RSX/VK/VKDraw.cpp | 4 ++++ rpcs3/Emu/RSX/VK/VKTextureCache.cpp | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKDraw.cpp b/rpcs3/Emu/RSX/VK/VKDraw.cpp index 5c3737fdf2..f7dffc3029 100644 --- a/rpcs3/Emu/RSX/VK/VKDraw.cpp +++ b/rpcs3/Emu/RSX/VK/VKDraw.cpp @@ -471,6 +471,10 @@ void VKGSRender::load_texture_env() // Clamp min and max lod actual_mipmaps = static_cast(sampler_state->external_subresource_desc.sections_to_copy.size()); } + else if (sampler_state->external_subresource_desc.op == rsx::deferred_request_command::cubemap_unwrap) + { + actual_mipmaps = static_cast(sampler_state->external_subresource_desc.mipmaps); + } else { actual_mipmaps = 1.f; diff --git a/rpcs3/Emu/RSX/VK/VKTextureCache.cpp b/rpcs3/Emu/RSX/VK/VKTextureCache.cpp index c9d9599b9e..206903bf64 100644 --- a/rpcs3/Emu/RSX/VK/VKTextureCache.cpp +++ b/rpcs3/Emu/RSX/VK/VKTextureCache.cpp @@ -761,8 +761,9 @@ namespace vk const rsx::simple_array& sections_to_copy, const rsx::texture_channel_remap_t& remap_vector) { auto _template = get_template_from_collection_impl(sections_to_copy); + const u8 mip_count = 1 + sections_to_copy.reduce(0, FN(std::max(x, y.level))); auto result = create_temporary_subresource_view_impl(cmd, _template, VK_IMAGE_TYPE_2D, - VK_IMAGE_VIEW_TYPE_CUBE, gcm_format, 0, 0, size, size, 1, 1, remap_vector, false); + VK_IMAGE_VIEW_TYPE_CUBE, gcm_format, 0, 0, size, size, 1, mip_count, remap_vector, false); if (!result) { @@ -772,7 +773,7 @@ namespace vk const auto image = result->image(); VkImageAspectFlags dst_aspect = vk::get_aspect_flags(result->info.format); - VkImageSubresourceRange dst_range = { dst_aspect, 0, 1, 0, 6 }; + VkImageSubresourceRange dst_range = { dst_aspect, 0, mip_count, 0, 6 }; vk::change_image_layout(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, dst_range); if (!(dst_aspect & VK_IMAGE_ASPECT_DEPTH_BIT)) From b700a7abd9201ad6227903f6fcc7a31f545470a3 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 5 Apr 2026 23:35:29 +0300 Subject: [PATCH 20/27] gl: Extend mipcount support for reconstructed images --- rpcs3/Emu/RSX/GL/GLDraw.cpp | 16 +++++++++++++++- rpcs3/Emu/RSX/GL/GLTextureCache.h | 3 ++- rpcs3/Emu/RSX/GL/glutils/sampler.cpp | 5 ++--- rpcs3/Emu/RSX/GL/glutils/sampler.h | 2 +- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/rpcs3/Emu/RSX/GL/GLDraw.cpp b/rpcs3/Emu/RSX/GL/GLDraw.cpp index d0c2e233e9..0abf0111e6 100644 --- a/rpcs3/Emu/RSX/GL/GLDraw.cpp +++ b/rpcs3/Emu/RSX/GL/GLDraw.cpp @@ -384,7 +384,21 @@ void GLGSRender::load_texture_env() } } - m_fs_sampler_states[i].apply(tex, fs_sampler_state[i].get()); + u32 actual_mipcount = 1; + if (sampler_state->upload_context == rsx::texture_upload_context::shader_read) + { + actual_mipcount = tex.get_exact_mipmap_count(); + } + else if (sampler_state->external_subresource_desc.op == rsx::deferred_request_command::mipmap_gather) + { + actual_mipcount = sampler_state->external_subresource_desc.sections_to_copy.size(); + } + else if (sampler_state->external_subresource_desc.op == rsx::deferred_request_command::cubemap_unwrap) + { + actual_mipcount = sampler_state->external_subresource_desc.mipmaps; + } + + m_fs_sampler_states[i].apply(tex, fs_sampler_state[i].get(), actual_mipcount > 1); const auto texture_format = sampler_state->format_ex.format(); // Depth format redirected to BGRA8 resample stage. Do not filter to avoid bits leaking. diff --git a/rpcs3/Emu/RSX/GL/GLTextureCache.h b/rpcs3/Emu/RSX/GL/GLTextureCache.h index 27b455374e..93c0ba2f5c 100644 --- a/rpcs3/Emu/RSX/GL/GLTextureCache.h +++ b/rpcs3/Emu/RSX/GL/GLTextureCache.h @@ -586,7 +586,8 @@ namespace gl gl::texture_view* generate_cubemap_from_images(gl::command_context& cmd, u32 gcm_format, u16 size, const rsx::simple_array& sources, const rsx::texture_channel_remap_t& remap_vector) override { auto _template = get_template_from_collection_impl(sources); - auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_CUBE_MAP, gcm_format, 0, 0, size, size, 1, 1, remap_vector, false); + const u8 mip_count = 1 + sources.reduce(0, FN(std::max(x, y.level))); + auto result = create_temporary_subresource_impl(cmd, _template, GL_NONE, GL_TEXTURE_CUBE_MAP, gcm_format, 0, 0, size, size, 1, mip_count, remap_vector, false); copy_transfer_regions_impl(cmd, result->image(), sources); return result; diff --git a/rpcs3/Emu/RSX/GL/glutils/sampler.cpp b/rpcs3/Emu/RSX/GL/glutils/sampler.cpp index 387228983c..4b1b603fc6 100644 --- a/rpcs3/Emu/RSX/GL/glutils/sampler.cpp +++ b/rpcs3/Emu/RSX/GL/glutils/sampler.cpp @@ -72,7 +72,7 @@ namespace gl } // Apply sampler state settings - void sampler_state::apply(const rsx::fragment_texture& tex, const rsx::sampled_image_descriptor_base* sampled_image) + void sampler_state::apply(const rsx::fragment_texture& tex, const rsx::sampled_image_descriptor_base* sampled_image, bool allow_mipmaps) { set_parameteri(GL_TEXTURE_WRAP_S, wrap_mode(tex.wrap_s())); set_parameteri(GL_TEXTURE_WRAP_T, wrap_mode(tex.wrap_t())); @@ -114,8 +114,7 @@ namespace gl } } - if (sampled_image->upload_context != rsx::texture_upload_context::shader_read || - tex.get_exact_mipmap_count() == 1) + if (!allow_mipmaps || tex.get_exact_mipmap_count() == 1) { GLint min_filter = tex_min_filter(tex.min_filter()); diff --git a/rpcs3/Emu/RSX/GL/glutils/sampler.h b/rpcs3/Emu/RSX/GL/glutils/sampler.h index 89200915f8..8e8482f196 100644 --- a/rpcs3/Emu/RSX/GL/glutils/sampler.h +++ b/rpcs3/Emu/RSX/GL/glutils/sampler.h @@ -75,7 +75,7 @@ namespace gl return (prop == m_propertiesf.end()) ? 0 : prop->second; } - void apply(const rsx::fragment_texture& tex, const rsx::sampled_image_descriptor_base* sampled_image); + void apply(const rsx::fragment_texture& tex, const rsx::sampled_image_descriptor_base* sampled_image, bool allow_mipmaps = true); void apply(const rsx::vertex_texture& tex, const rsx::sampled_image_descriptor_base* sampled_image); void apply_defaults(GLenum default_filter = GL_NEAREST); From 34c26eff68c948ff4a6520e886badcc2b58ddad0 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 5 Apr 2026 23:36:27 +0300 Subject: [PATCH 21/27] rsx: Extend cubemap_unwrap decode to handle cubemaps with mipmaps --- rpcs3/Emu/RSX/Common/texture_cache.h | 42 +++++++++++++++++----------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/rpcs3/Emu/RSX/Common/texture_cache.h b/rpcs3/Emu/RSX/Common/texture_cache.h index 8aed0ccc34..295090a5bc 100644 --- a/rpcs3/Emu/RSX/Common/texture_cache.h +++ b/rpcs3/Emu/RSX/Common/texture_cache.h @@ -1731,24 +1731,34 @@ namespace rsx } case deferred_request_command::cubemap_unwrap: { - rsx::simple_array sections(6); - for (u16 n = 0; n < 6; ++n) + rsx::simple_array sections(6 * desc.mipmaps); + for (u16 n = 0, section_id = 0; n < 6; ++n) { - sections[n] = + u16 mip_w = desc.width, mip_h = desc.height; + u16 y_offset = static_cast(desc.slice_h * n); + + for (u8 mip = 0; mip < desc.mipmaps; ++mip) { - .src = desc.external_handle, - .xform = surface_transform::coordinate_transform, - .level = 0, - .src_x = 0, - .src_y = static_cast(desc.slice_h * n), - .dst_x = 0, - .dst_y = 0, - .dst_z = n, - .src_w = desc.width, - .src_h = desc.height, - .dst_w = desc.width, - .dst_h = desc.height - }; + sections[section_id++] = + { + .src = desc.external_handle, + .xform = surface_transform::coordinate_transform, + .level = mip, + .src_x = 0, + .src_y = y_offset, + .dst_x = 0, + .dst_y = 0, + .dst_z = n, + .src_w = mip_w, + .src_h = mip_h, + .dst_w = mip_w, + .dst_h = mip_h + }; + + y_offset += mip_h; + mip_w = std::max(mip_w / 2, 1); + mip_h = std::max(mip_h / 2, 1); + } } result = generate_cubemap_from_images(cmd, desc.gcm_format, desc.width, sections, desc.remap); From cb276f0da7f1a886044d3f191d95955230e7e32d Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 6 Apr 2026 22:55:06 +0300 Subject: [PATCH 22/27] vk: Insert transfer->transfer barriers before creating aggregates --- rpcs3/Emu/RSX/VK/VKTextureCache.cpp | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/rpcs3/Emu/RSX/VK/VKTextureCache.cpp b/rpcs3/Emu/RSX/VK/VKTextureCache.cpp index 206903bf64..004061d881 100644 --- a/rpcs3/Emu/RSX/VK/VKTextureCache.cpp +++ b/rpcs3/Emu/RSX/VK/VKTextureCache.cpp @@ -787,6 +787,14 @@ namespace vk vkCmdClearDepthStencilImage(cmd, image->value, image->current_layout, &clear, 1, &dst_range); } + vk::insert_image_memory_barrier( + cmd, + image->handle(), + image->current_layout, image->current_layout, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + dst_range); + copy_transfer_regions_impl(cmd, image, sections_to_copy); vk::change_image_layout(cmd, image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, dst_range); @@ -822,6 +830,14 @@ namespace vk vkCmdClearDepthStencilImage(cmd, image->value, image->current_layout, &clear, 1, &dst_range); } + vk::insert_image_memory_barrier( + cmd, + image->handle(), + image->current_layout, image->current_layout, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + dst_range); + copy_transfer_regions_impl(cmd, image, sections_to_copy); vk::change_image_layout(cmd, image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, dst_range); @@ -860,6 +876,14 @@ namespace vk } } + vk::insert_image_memory_barrier( + cmd, + image->handle(), + image->current_layout, image->current_layout, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + dst_range); + copy_transfer_regions_impl(cmd, image, sections_to_copy); vk::change_image_layout(cmd, image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, dst_range); @@ -896,6 +920,14 @@ namespace vk vkCmdClearDepthStencilImage(cmd, image->value, image->current_layout, &clear, 1, &dst_range); } + vk::insert_image_memory_barrier( + cmd, + image->handle(), + image->current_layout, image->current_layout, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + dst_range); + copy_transfer_regions_impl(cmd, image, sections_to_copy); vk::change_image_layout(cmd, image, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, dst_range); @@ -1016,6 +1048,14 @@ namespace vk VkClearDepthStencilValue clear{ 1.f, 255 }; vkCmdClearDepthStencilImage(cmd, image->value, image->current_layout, &clear, 1, &range); } + + vk::insert_image_memory_barrier( + cmd, + image->handle(), + image->current_layout, image->current_layout, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, + range); } } } From 5311f004d09e7771792405d74df510774a8e44e0 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 6 Apr 2026 22:55:46 +0300 Subject: [PATCH 23/27] vk: Insert barriers on the scratch buffer when detiling memory --- rpcs3/Emu/RSX/VK/VKTexture.cpp | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKTexture.cpp b/rpcs3/Emu/RSX/VK/VKTexture.cpp index bc6aabf2d4..c5fd4f37e9 100644 --- a/rpcs3/Emu/RSX/VK/VKTexture.cpp +++ b/rpcs3/Emu/RSX/VK/VKTexture.cpp @@ -1304,6 +1304,15 @@ namespace vk .image_bpp = bpp }; + // Pre-Transfer barrier + vk::insert_buffer_memory_barrier( + cmd, + scratch_buf->value, + tiled_data_scratch_offset, section_length, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT + ); + // Transfer VkBufferCopy copy_rgn { @@ -1313,16 +1322,25 @@ namespace vk }; vkCmdCopyBuffer(cmd, dma_mapping.second->value, scratch_buf->value, 1, ©_rgn); - // Barrier + // Post-Transfer barrier vk::insert_buffer_memory_barrier( - cmd, scratch_buf->value, linear_data_scratch_offset, section_length, + cmd, scratch_buf->value, tiled_data_scratch_offset, section_length, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); + // Pre-Compute barrier + vk::insert_buffer_memory_barrier( + cmd, + scratch_buf->value, + linear_data_scratch_offset, static_cast(width) * height * bpp, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_MEMORY_WRITE_BIT + ); + // Detile vk::get_compute_task>()->run(cmd, config); - // Barrier + // Post-Compute barrier vk::insert_buffer_memory_barrier( cmd, scratch_buf->value, linear_data_scratch_offset, static_cast(width) * height * bpp, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, From b9f05ba71b53d2074465b0b85ad9c8a21d9f70e1 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 6 Apr 2026 22:56:16 +0300 Subject: [PATCH 24/27] vk: Insert a all_commands->transfer barrier before copying query results to scratch --- rpcs3/Emu/RSX/VK/VKGSRender.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 5906c14824..d83e7490c8 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -2915,9 +2915,19 @@ void VKGSRender::begin_conditional_rendering(const std::vectorvalue, 0, num_hw_queries * 4, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT); + } + const auto count = (query_range.last - query_range.first + 1); m_occlusion_query_manager->get_query_result_indirect(*m_current_command_buffer, query_range.first, count, scratch->value, dst_offset); dst_offset += count * 4; From 8ab0ceaa6762c9d807d7980b15abf44a7bdfc318 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 6 Apr 2026 23:50:44 +0300 Subject: [PATCH 25/27] vk: Wrap scratch buffer access with proper memory barriers --- rpcs3/Emu/RSX/VK/VKGSRender.cpp | 15 ++++------- rpcs3/Emu/RSX/VK/VKRenderTargets.h | 2 +- rpcs3/Emu/RSX/VK/VKTexture.cpp | 39 +++++++--------------------- rpcs3/Emu/RSX/VK/VKTextureCache.cpp | 13 ++++++---- rpcs3/Emu/RSX/VK/vkutils/scratch.cpp | 8 +++++- rpcs3/Emu/RSX/VK/vkutils/scratch.h | 8 +++++- 6 files changed, 37 insertions(+), 48 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index d83e7490c8..8da72284d9 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -2115,7 +2115,7 @@ void VKGSRender::load_program_env() if (vk::emulate_conditional_rendering()) { - const vk::buffer& predicate = m_cond_render_buffer ? *m_cond_render_buffer : *vk::get_scratch_buffer(*m_current_command_buffer, 4); + const vk::buffer& predicate = m_cond_render_buffer ? *m_cond_render_buffer : *vk::get_scratch_buffer(*m_current_command_buffer, 4, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_ACCESS_NONE); const u32 offset = cond_render_ctrl.hw_cond_active ? 0 : 4; m_program->bind_uniform({ predicate, offset, 4 }, vk::glsl::binding_set_index_vertex, m_vs_binding_table->cr_pred_buffer_location); } @@ -2910,22 +2910,17 @@ void VKGSRender::begin_conditional_rendering(const std::vector 0) { // We'll need to do some result aggregation using a compute shader. - auto scratch = vk::get_scratch_buffer(*m_current_command_buffer, num_hw_queries * 4); + vk::buffer* scratch = nullptr; // Range latching. Because of how the query pool manages allocations using a stack, we get an inverse sequential set of handles/indices that we can easily group together. // This drastically boosts performance on some drivers like the NVIDIA proprietary one that seems to have a rather high cost for every individual query transer command. struct { u32 first, last; } query_range = { umax, 0 }; - bool need_barrier = true; auto copy_query_range_impl = [&]() { - if (need_barrier) + if (!scratch) { - need_barrier = false; - vk::insert_buffer_memory_barrier(*m_current_command_buffer, scratch->value, 0, num_hw_queries * 4, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT); + scratch = vk::get_scratch_buffer(*m_current_command_buffer, num_hw_queries * 4, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); } const auto count = (query_range.last - query_range.first + 1); @@ -2974,7 +2969,7 @@ void VKGSRender::begin_conditional_rendering(const std::vectorsize()); + ensure(scratch && dst_offset <= scratch->size()); if (!partial_eval) { diff --git a/rpcs3/Emu/RSX/VK/VKRenderTargets.h b/rpcs3/Emu/RSX/VK/VKRenderTargets.h index c040e9bca0..7da26f3b5c 100644 --- a/rpcs3/Emu/RSX/VK/VKRenderTargets.h +++ b/rpcs3/Emu/RSX/VK/VKRenderTargets.h @@ -598,7 +598,7 @@ namespace vk const auto transfer_size = surface->get_memory_range().length(); if (transfer_size > max_copy_length || src_offset_in_buffer || surface->is_depth_surface()) { - auto scratch = vk::get_scratch_buffer(cmd, transfer_size * 4); + auto scratch = vk::get_scratch_buffer(cmd, transfer_size * 4, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); dest = scratch; } diff --git a/rpcs3/Emu/RSX/VK/VKTexture.cpp b/rpcs3/Emu/RSX/VK/VKTexture.cpp index c5fd4f37e9..c69064dbe4 100644 --- a/rpcs3/Emu/RSX/VK/VKTexture.cpp +++ b/rpcs3/Emu/RSX/VK/VKTexture.cpp @@ -376,7 +376,7 @@ namespace vk const auto min_scratch_size = calculate_working_buffer_size(src_length, src->aspect() | dst->aspect()); // Initialize scratch memory - auto scratch_buf = vk::get_scratch_buffer(cmd, min_scratch_size); + auto scratch_buf = vk::get_scratch_buffer(cmd, min_scratch_size, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); for (u32 mip_level = 0; mip_level < mipmaps; ++mip_level) { @@ -601,7 +601,7 @@ namespace vk const auto dst_w = dst_rect.width(); const auto dst_h = dst_rect.height(); - auto scratch_buf = vk::get_scratch_buffer(cmd, std::max(src_w, dst_w) * std::max(src_h, dst_h) * 4); + auto scratch_buf = vk::get_scratch_buffer(cmd, std::max(src_w, dst_w) * std::max(src_h, dst_h) * 4, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); //1. Copy unscaled to typeless surface VkBufferImageCopy info{}; @@ -1124,7 +1124,7 @@ namespace vk scratch_buf_size += (image_linear_size * 5) / 4; } - scratch_buf = vk::get_scratch_buffer(cmd2, scratch_buf_size); + scratch_buf = vk::get_scratch_buffer(cmd2, scratch_buf_size, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); buffer_copies.reserve(subresource_layout.size()); } @@ -1183,13 +1183,6 @@ namespace vk { ensure(scratch_buf); - // WAW hazard - complete previous work before executing any transfers - insert_buffer_memory_barrier( - cmd2, scratch_buf->value, 0, scratch_offset, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT); - if (upload_commands.size() > 1) { auto range_ptr = buffer_copies.data(); @@ -1199,8 +1192,9 @@ namespace vk range_ptr += op.second; } } - else if (!buffer_copies.empty()) + else { + ensure(!buffer_copies.empty()); vkCmdCopyBuffer(cmd2, upload_buffer->value, scratch_buf->value, static_cast(buffer_copies.size()), buffer_copies.data()); } @@ -1279,7 +1273,10 @@ namespace vk vk::load_dma(range.start, section_length); // Allocate scratch and prepare for the GPU job - const auto scratch_buf = vk::get_scratch_buffer(cmd, section_length * 3); // 0 = linear data, 1 = padding (deswz), 2 = tiled data + const auto scratch_buf = vk::get_scratch_buffer(cmd, section_length * 3, // 0 = linear data, 1 = padding (deswz), 2 = tiled data + VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT); + const auto tiled_data_scratch_offset = section_length * 2; const auto linear_data_scratch_offset = 0u; @@ -1304,15 +1301,6 @@ namespace vk .image_bpp = bpp }; - // Pre-Transfer barrier - vk::insert_buffer_memory_barrier( - cmd, - scratch_buf->value, - tiled_data_scratch_offset, section_length, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT - ); - // Transfer VkBufferCopy copy_rgn { @@ -1328,15 +1316,6 @@ namespace vk VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); - // Pre-Compute barrier - vk::insert_buffer_memory_barrier( - cmd, - scratch_buf->value, - linear_data_scratch_offset, static_cast(width) * height * bpp, - VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, VK_ACCESS_MEMORY_WRITE_BIT - ); - // Detile vk::get_compute_task>()->run(cmd, config); diff --git a/rpcs3/Emu/RSX/VK/VKTextureCache.cpp b/rpcs3/Emu/RSX/VK/VKTextureCache.cpp index 004061d881..a3fc048a27 100644 --- a/rpcs3/Emu/RSX/VK/VKTextureCache.cpp +++ b/rpcs3/Emu/RSX/VK/VKTextureCache.cpp @@ -130,9 +130,10 @@ namespace vk dma_sync_region = tiled_region.tile_align(dma_sync_region); } #endif - - auto working_buffer = vk::get_scratch_buffer(cmd, working_buffer_length); u32 result_offset = 0; + auto working_buffer = vk::get_scratch_buffer(cmd, working_buffer_length, + VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT); VkBufferImageCopy region = {}; region.imageSubresource = { src->aspect(), 0, 0, 1 }; @@ -220,7 +221,7 @@ namespace vk // Transfer -> Compute barrier vk::insert_buffer_memory_barrier(cmd, working_buffer->value, dst_offset, dma_sync_region.length(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_WRITE_BIT); + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_WRITE_BIT); } // Prepare payload @@ -284,8 +285,10 @@ namespace vk if (require_rw_barrier) { vk::insert_buffer_memory_barrier(cmd, working_buffer->value, result_offset, dma_sync_region.length(), - VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); + VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT, + VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT); } if (rsx_pitch == real_pitch) [[likely]] diff --git a/rpcs3/Emu/RSX/VK/vkutils/scratch.cpp b/rpcs3/Emu/RSX/VK/vkutils/scratch.cpp index 041067bea6..04cfded55c 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/scratch.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/scratch.cpp @@ -177,7 +177,7 @@ namespace vk return { scratch_buffer.get(), is_new }; } - vk::buffer* get_scratch_buffer(const vk::command_buffer& cmd, u64 min_required_size, bool zero_memory) + vk::buffer* get_scratch_buffer(const vk::command_buffer& cmd, u64 min_required_size, VkPipelineStageFlags dst_stage_flags, VkAccessFlags dst_access, bool zero_memory) { const auto [buf, init_mem] = get_scratch_buffer(cmd.get_queue_family(), min_required_size); @@ -191,6 +191,12 @@ namespace vk VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT); } + else if (dst_access != VK_ACCESS_NONE) + { + insert_buffer_memory_barrier(cmd, buf->value, 0, min_required_size, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, dst_stage_flags, + VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, dst_access); + } return buf; } diff --git a/rpcs3/Emu/RSX/VK/vkutils/scratch.h b/rpcs3/Emu/RSX/VK/vkutils/scratch.h index 312db68c8a..5b31289462 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/scratch.h +++ b/rpcs3/Emu/RSX/VK/vkutils/scratch.h @@ -6,7 +6,13 @@ namespace vk VkSampler null_sampler(); image_view* null_image_view(const command_buffer& cmd, VkImageViewType type); image* get_typeless_helper(VkFormat format, rsx::format_class format_class, u32 requested_width, u32 requested_height); - buffer* get_scratch_buffer(const command_buffer& cmd, u64 min_required_size, bool zero_memory = false); + + buffer* get_scratch_buffer( + const command_buffer& cmd, + u64 min_required_size, + VkPipelineStageFlags dst_stage_flags, + VkAccessFlags dst_access, + bool zero_memory = false); void clear_scratch_resources(); } From 59468f1e1ea06339212e41cd747df1ac3f9a0ac3 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 7 Apr 2026 01:12:25 +0300 Subject: [PATCH 26/27] vk: Handle WAW and RAW hazards when performing "flush" operations --- rpcs3/Emu/RSX/VK/VKTextureCache.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/VK/VKTextureCache.cpp b/rpcs3/Emu/RSX/VK/VKTextureCache.cpp index a3fc048a27..671529f87d 100644 --- a/rpcs3/Emu/RSX/VK/VKTextureCache.cpp +++ b/rpcs3/Emu/RSX/VK/VKTextureCache.cpp @@ -100,7 +100,7 @@ namespace vk auto dma_sync_region = valid_range; dma_mapping_handle dma_mapping = { 0, nullptr }; - auto dma_sync = [&dma_sync_region, &dma_mapping](bool load, bool force = false) + auto dma_sync = [&](bool load, bool force = false) { if (dma_mapping.second && !force) { @@ -335,6 +335,14 @@ namespace vk vkCmdCopyImageToBuffer(cmd, src->value, src->current_layout, dma_mapping.second->value, 1, ®ion); } + // Post-transfer barrier on dma layer + vk::insert_buffer_memory_barrier( + cmd, dma_mapping.second->value, + dma_mapping.first, dma_sync_region.length(), + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT + ); + src->pop_layout(cmd); VkBufferMemoryBarrier2KHR mem_barrier = From e0c3df5328d7beefc64e475535f714a385298886 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 7 Apr 2026 01:42:15 +0300 Subject: [PATCH 27/27] vk: Fix crash when running CPU detiler path --- rpcs3/Emu/RSX/VK/VKHelpers.h | 9 +++++---- rpcs3/Emu/RSX/VK/VKRenderTargets.cpp | 1 + rpcs3/Emu/RSX/VK/VKTexture.cpp | 6 +++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/VKHelpers.h b/rpcs3/Emu/RSX/VK/VKHelpers.h index 9d07a4581e..fae75c0724 100644 --- a/rpcs3/Emu/RSX/VK/VKHelpers.h +++ b/rpcs3/Emu/RSX/VK/VKHelpers.h @@ -71,10 +71,11 @@ namespace vk enum image_upload_options { - upload_contents_async = 1, - initialize_image_layout = 2, - preserve_image_layout = 4, - source_is_gpu_resident = 8, + upload_contents_async = 0x0001, + initialize_image_layout = 0x0002, + preserve_image_layout = 0x0004, + source_is_gpu_resident = 0x0008, + source_is_userptr = 0x0010, // meta-flags upload_contents_inline = 0, diff --git a/rpcs3/Emu/RSX/VK/VKRenderTargets.cpp b/rpcs3/Emu/RSX/VK/VKRenderTargets.cpp index 138f7e46aa..736b886a77 100644 --- a/rpcs3/Emu/RSX/VK/VKRenderTargets.cpp +++ b/rpcs3/Emu/RSX/VK/VKRenderTargets.cpp @@ -724,6 +724,7 @@ namespace vk subres.height_in_block ); subres.data = std::span(ext_data); + upload_flags |= source_is_userptr; #else const auto [scratch_buf, linear_data_scratch_offset] = vk::detile_memory_block(cmd, tiled_region, range, subres.width_in_block, subres.height_in_block, get_bpp()); diff --git a/rpcs3/Emu/RSX/VK/VKTexture.cpp b/rpcs3/Emu/RSX/VK/VKTexture.cpp index c69064dbe4..eaf5c0b710 100644 --- a/rpcs3/Emu/RSX/VK/VKTexture.cpp +++ b/rpcs3/Emu/RSX/VK/VKTexture.cpp @@ -990,7 +990,7 @@ namespace vk auto pdev = vk::get_current_renderer(); rsx::texture_uploader_capabilities caps{ .supports_dxt = pdev->get_texture_compression_bc_support(), .alignment = heap_align }; rsx::texture_memory_info opt{}; - bool check_caps = true; + bool check_hw_caps = !(image_setup_flags & source_is_userptr); vk::buffer* scratch_buf = nullptr; u32 scratch_offset = 0; @@ -1015,13 +1015,13 @@ namespace vk image_linear_size = row_pitch * layout.depth * (rsx::is_compressed_host_format(caps, format) ? layout.height_in_block : layout.height_in_texel); // Only do GPU-side conversion if occupancy is good - if (check_caps) + if (check_hw_caps) { caps.supports_byteswap = (image_linear_size >= 1024) || (image_setup_flags & source_is_gpu_resident); caps.supports_hw_deswizzle = caps.supports_byteswap; caps.supports_zero_copy = caps.supports_byteswap; caps.supports_vtc_decoding = false; - check_caps = false; + check_hw_caps = false; } auto buf_allocator = [&](usz) -> std::tuple