From f97c0deea966e200dc5d675dfaec90f3148a3865 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Tue, 13 May 2025 21:29:47 +0300 Subject: [PATCH 01/45] ngs2: removed possible nullptr value from logging (#2924) --- src/core/libraries/ngs2/ngs2.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/libraries/ngs2/ngs2.cpp b/src/core/libraries/ngs2/ngs2.cpp index 743be5fd6..9bb73536c 100644 --- a/src/core/libraries/ngs2/ngs2.cpp +++ b/src/core/libraries/ngs2/ngs2.cpp @@ -380,8 +380,7 @@ s32 PS4_SYSV_ABI sceNgs2GeomApply(const OrbisNgs2GeomListenerWork* listener, s32 PS4_SYSV_ABI sceNgs2PanInit(OrbisNgs2PanWork* work, const float* aSpeakerAngle, float unitAngle, u32 numSpeakers) { - LOG_ERROR(Lib_Ngs2, "aSpeakerAngle = {}, unitAngle = {}, numSpeakers = {}", *aSpeakerAngle, - unitAngle, numSpeakers); + LOG_ERROR(Lib_Ngs2, "unitAngle = {}, numSpeakers = {}", unitAngle, numSpeakers); return ORBIS_OK; } From 7334fb620bb760e998d10a3dce8d2c726440d906 Mon Sep 17 00:00:00 2001 From: Fire Cube Date: Tue, 13 May 2025 21:16:53 +0200 Subject: [PATCH 02/45] sceKernelAddTimerEvent implementation (#2906) * implementation * add backend (WIP) * now should be good - fix implementation based on homebrew tests - demote log to debug - make squidbus happy (hopefully) * fix moved m_name * fix clang * replace existing event when its same id and filter * run timercallback after addEvent and remove useless code * move KernelSignalRequest to the end * clang (i hate you) --- src/core/libraries/kernel/equeue.cpp | 88 ++++++++++++++++++++++++++++ src/core/libraries/kernel/equeue.h | 2 + 2 files changed, 90 insertions(+) diff --git a/src/core/libraries/kernel/equeue.cpp b/src/core/libraries/kernel/equeue.cpp index a4916208a..7e579614f 100644 --- a/src/core/libraries/kernel/equeue.cpp +++ b/src/core/libraries/kernel/equeue.cpp @@ -152,6 +152,16 @@ int EqueueInternal::WaitForSmallTimer(SceKernelEvent* ev, int num, u32 micros) { return count; } +bool EqueueInternal::EventExists(u64 id, s16 filter) { + std::scoped_lock lock{m_mutex}; + + const auto& it = std::ranges::find_if(m_events, [id, filter](auto& ev) { + return ev.event.ident == id && ev.event.filter == filter; + }); + + return it != m_events.cend(); +} + extern boost::asio::io_context io_context; extern void KernelSignalRequest(); @@ -300,6 +310,82 @@ int PS4_SYSV_ABI sceKernelDeleteHRTimerEvent(SceKernelEqueue eq, int id) { } } +static void TimerCallback(const boost::system::error_code& error, SceKernelEqueue eq, + SceKernelEvent kevent, SceKernelUseconds interval_ms) { + if (error) { + LOG_ERROR(Kernel_Event, "Timer callback error: {}", error.message()); + return; + } + + if (eq->EventExists(kevent.ident, kevent.filter)) { + eq->TriggerEvent(kevent.ident, SceKernelEvent::Filter::Timer, kevent.udata); + + if (!(kevent.flags & SceKernelEvent::Flags::OneShot)) { + auto timer = std::make_shared( + io_context, std::chrono::milliseconds(interval_ms)); + + timer->async_wait( + [eq, kevent, interval_ms, timer](const boost::system::error_code& ec) { + TimerCallback(ec, eq, kevent, interval_ms); + }); + } + } +} + +int PS4_SYSV_ABI sceKernelAddTimerEvent(SceKernelEqueue eq, int id, SceKernelUseconds usec, + void* udata) { + if (eq == nullptr) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + const u64 interval_ms = static_cast(usec & 0xFFFFFFFF) / 1000; + + EqueueEvent event{}; + event.event.ident = static_cast(id); + event.event.filter = SceKernelEvent::Filter::Timer; + event.event.flags = SceKernelEvent::Flags::Add; + event.event.fflags = 0; + event.event.data = interval_ms; + event.event.udata = udata; + event.time_added = std::chrono::steady_clock::now(); + + if (eq->EventExists(event.event.ident, event.event.filter)) { + eq->RemoveEvent(id, SceKernelEvent::Filter::Timer); + LOG_DEBUG(Kernel_Event, + "Timer event already exists, removing it: queue name={}, queue id={}", + eq->GetName(), event.event.ident); + } + + LOG_DEBUG(Kernel_Event, + "Added timing event: queue name={}, queue id={}, ms-intevall={}, pointer={:x}", + eq->GetName(), event.event.ident, interval_ms, reinterpret_cast(udata)); + + auto timer = std::make_shared( + io_context, std::chrono::milliseconds(interval_ms)); + + if (!eq->AddEvent(event)) { + return ORBIS_KERNEL_ERROR_ENOMEM; + } + + timer->async_wait( + [eq, event_data = event.event, interval_ms, timer](const boost::system::error_code& ec) { + TimerCallback(ec, eq, event_data, interval_ms); + }); + + KernelSignalRequest(); + + return ORBIS_OK; +} + +int PS4_SYSV_ABI sceKernelDeleteTimerEvent(SceKernelEqueue eq, int id) { + if (eq == nullptr) { + return ORBIS_KERNEL_ERROR_EBADF; + } + + return eq->RemoveEvent(id, SceKernelEvent::Filter::Timer) ? ORBIS_OK + : ORBIS_KERNEL_ERROR_ENOENT; +} + int PS4_SYSV_ABI sceKernelAddUserEvent(SceKernelEqueue eq, int id) { if (eq == nullptr) { return ORBIS_KERNEL_ERROR_EBADF; @@ -380,6 +466,8 @@ void RegisterEventQueue(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("WDszmSbWuDk", "libkernel", 1, "libkernel", 1, 1, sceKernelAddUserEventEdge); LIB_FUNCTION("R74tt43xP6k", "libkernel", 1, "libkernel", 1, 1, sceKernelAddHRTimerEvent); LIB_FUNCTION("J+LF6LwObXU", "libkernel", 1, "libkernel", 1, 1, sceKernelDeleteHRTimerEvent); + LIB_FUNCTION("57ZK+ODEXWY", "libkernel", 1, "libkernel", 1, 1, sceKernelAddTimerEvent); + LIB_FUNCTION("YWQFUyXIVdU", "libkernel", 1, "libkernel", 1, 1, sceKernelDeleteTimerEvent); LIB_FUNCTION("F6e0kwo4cnk", "libkernel", 1, "libkernel", 1, 1, sceKernelTriggerUserEvent); LIB_FUNCTION("LJDwdSNTnDg", "libkernel", 1, "libkernel", 1, 1, sceKernelDeleteUserEvent); LIB_FUNCTION("mJ7aghmgvfc", "libkernel", 1, "libkernel", 1, 1, sceKernelGetEventId); diff --git a/src/core/libraries/kernel/equeue.h b/src/core/libraries/kernel/equeue.h index 2bd7ef510..636496604 100644 --- a/src/core/libraries/kernel/equeue.h +++ b/src/core/libraries/kernel/equeue.h @@ -152,6 +152,8 @@ public: int WaitForSmallTimer(SceKernelEvent* ev, int num, u32 micros); + bool EventExists(u64 id, s16 filter); + private: std::string m_name; std::mutex m_mutex; From 484fbcc3209a2b3248f4f23a2354f5d5ad53eb65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Tue, 13 May 2025 22:19:56 +0200 Subject: [PATCH 03/45] Handle -1 as V_CMP_NE_U64 argument (#2919) --- .../frontend/translate/vector_alu.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 3ce86c131..6171cca07 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -989,13 +989,22 @@ void Translator::V_CMP_NE_U64(const GcnInst& inst) { } }; const IR::U1 src0{get_src(inst.src[0])}; - ASSERT(inst.src[1].field == OperandField::ConstZero); // src0 != 0 + auto op = [&inst, this](auto x) { + switch (inst.src[1].field) { + case OperandField::ConstZero: + return x; + case OperandField::SignedConstIntNeg: + return ir.LogicalNot(x); + default: + UNREACHABLE_MSG("unhandled V_CMP_NE_U64 source argument {}", u32(inst.src[1].field)); + } + }; switch (inst.dst[1].field) { case OperandField::VccLo: - ir.SetVcc(src0); + ir.SetVcc(op(src0)); break; case OperandField::ScalarGPR: - ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), src0); + ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), op(src0)); break; default: UNREACHABLE(); From 1832ec2ac2f9fe1a507a34fb2734cd6b6f3490a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Tue, 13 May 2025 22:54:22 +0200 Subject: [PATCH 04/45] Implement sceKernelIsStack (#2917) --- src/core/libraries/kernel/memory.cpp | 8 ++++++++ src/core/libraries/kernel/memory.h | 1 + src/core/memory.cpp | 29 ++++++++++++++++++++++++++++ src/core/memory.h | 2 ++ 4 files changed, 40 insertions(+) diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index dd0e07302..7af67d6d3 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -290,6 +290,13 @@ int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut directMemoryEndOut); } +int PS4_SYSV_ABI sceKernelIsStack(void* addr, void** start, void** end) { + LOG_DEBUG(Kernel_Vmm, "called, addr = {:#x}, start = {:#x}, end = {:#x}", fmt::ptr(addr), + fmt::ptr(start), fmt::ptr(end)); + auto* memory = Core::Memory::Instance(); + return memory->IsStack(std::bit_cast(addr), start, end); +} + s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, int numEntries, int* numEntriesOut) { return sceKernelBatchMap2(entries, numEntries, numEntriesOut, @@ -636,6 +643,7 @@ void RegisterMemory(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("7oxv3PPCumo", "libkernel", 1, "libkernel", 1, 1, sceKernelReserveVirtualRange); LIB_FUNCTION("BC+OG5m9+bw", "libkernel", 1, "libkernel", 1, 1, sceKernelGetDirectMemoryType); LIB_FUNCTION("pO96TwzOm5E", "libkernel", 1, "libkernel", 1, 1, sceKernelGetDirectMemorySize); + LIB_FUNCTION("yDBwVAolDgg", "libkernel", 1, "libkernel", 1, 1, sceKernelIsStack); LIB_FUNCTION("NcaWUxfMNIQ", "libkernel", 1, "libkernel", 1, 1, sceKernelMapNamedDirectMemory); LIB_FUNCTION("L-Q3LEjIbgA", "libkernel", 1, "libkernel", 1, 1, sceKernelMapDirectMemory); LIB_FUNCTION("WFcfL2lzido", "libkernel", 1, "libkernel", 1, 1, sceKernelQueryMemoryProtection); diff --git a/src/core/libraries/kernel/memory.h b/src/core/libraries/kernel/memory.h index 3e2bf8de5..92e158a00 100644 --- a/src/core/libraries/kernel/memory.h +++ b/src/core/libraries/kernel/memory.h @@ -158,6 +158,7 @@ void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func[]); int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut, void** directMemoryStartOut, void** directMemoryEndOut); +int PS4_SYSV_ABI sceKernelIsStack(void* addr, void** start, void** end); s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, int numEntries, int* numEntriesOut); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 6438670d3..ec03d6c5e 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -949,4 +949,33 @@ int MemoryManager::GetDirectMemoryType(PAddr addr, int* directMemoryTypeOut, return ORBIS_OK; } +int MemoryManager::IsStack(VAddr addr, void** start, void** end) { + auto vma_handle = FindVMA(addr); + if (vma_handle == vma_map.end()) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + + const VirtualMemoryArea& vma = vma_handle->second; + if (!vma.Contains(addr, 0) || vma.IsFree()) { + return ORBIS_KERNEL_ERROR_EACCES; + } + + auto stack_start = 0ul; + auto stack_end = 0ul; + if (vma.type == VMAType::Stack) { + stack_start = vma.base; + stack_end = vma.base + vma.size; + } + + if (start != nullptr) { + *start = reinterpret_cast(stack_start); + } + + if (end != nullptr) { + *end = reinterpret_cast(stack_end); + } + + return ORBIS_OK; +} + } // namespace Core diff --git a/src/core/memory.h b/src/core/memory.h index 4920aa397..4c143ff6f 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -223,6 +223,8 @@ public: void InvalidateMemory(VAddr addr, u64 size) const; + int IsStack(VAddr addr, void** start, void** end); + private: VMAHandle FindVMA(VAddr target) { return std::prev(vma_map.upper_bound(target)); From e5b675d607502bbe8d78204794a95c22ef2c50af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Tue, 13 May 2025 22:56:20 +0200 Subject: [PATCH 05/45] Handle IT_WAIT_REG_MEM with Register argument (#2927) --- src/core/libraries/gnmdriver/gnmdriver.cpp | 2 +- src/video_core/amdgpu/liverpool.cpp | 6 ++--- src/video_core/amdgpu/pm4_cmds.h | 26 +++++++++++++++------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index f2f40e0e3..9cf340050 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -179,7 +179,7 @@ s32 PS4_SYSV_ABI sceGnmComputeWaitOnAddress(u32* cmdbuf, u32 size, uintptr_t add auto* wait_reg_mem = reinterpret_cast(cmdbuf); wait_reg_mem->header = PM4Type3Header{PM4ItOpcode::WaitRegMem, 5}; wait_reg_mem->raw = (is_mem << 4u) | (cmp_func & 7u); - wait_reg_mem->poll_addr_lo = u32(addr & addr_mask); + wait_reg_mem->poll_addr_lo_raw = u32(addr & addr_mask); wait_reg_mem->poll_addr_hi = u32(addr >> 32u); wait_reg_mem->ref = ref; wait_reg_mem->mask = mask; diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 598288085..0fbfa8b9b 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -696,10 +696,10 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanAddress(); if (vo_port->IsVoLabel(wait_addr) && num_submits == mapped_queues[GfxQueueId].submits.size()) { - vo_port->WaitVoLabel([&] { return wait_reg_mem->Test(); }); + vo_port->WaitVoLabel([&] { return wait_reg_mem->Test(regs.reg_array); }); break; } - while (!wait_reg_mem->Test()) { + while (!wait_reg_mem->Test(regs.reg_array)) { YIELD_GFX(); } break; @@ -934,7 +934,7 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq case PM4ItOpcode::WaitRegMem: { const auto* wait_reg_mem = reinterpret_cast(header); ASSERT(wait_reg_mem->engine.Value() == PM4CmdWaitRegMem::Engine::Me); - while (!wait_reg_mem->Test()) { + while (!wait_reg_mem->Test(regs.reg_array)) { YIELD_ASC(vqid); } break; diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index cd175f6c9..066fa4b62 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -474,7 +474,12 @@ struct PM4CmdWaitRegMem { BitField<8, 1, Engine> engine; u32 raw; }; - u32 poll_addr_lo; + union { + BitField<0, 16, u32> reg; + BitField<2, 30, u32> poll_addr_lo; + BitField<0, 2, u32> swap; + u32 poll_addr_lo_raw; + }; u32 poll_addr_hi; u32 ref; u32 mask; @@ -485,28 +490,33 @@ struct PM4CmdWaitRegMem { return std::bit_cast((uintptr_t(poll_addr_hi) << 32) | poll_addr_lo); } - bool Test() const { + u32 Reg() const { + return reg.Value(); + } + + bool Test(const std::array& regs) const { + u32 value = mem_space.Value() == MemSpace::Memory ? *Address() : regs[Reg()]; switch (function.Value()) { case Function::Always: { return true; } case Function::LessThan: { - return (*Address() & mask) < ref; + return (value & mask) < ref; } case Function::LessThanEqual: { - return (*Address() & mask) <= ref; + return (value & mask) <= ref; } case Function::Equal: { - return (*Address() & mask) == ref; + return (value & mask) == ref; } case Function::NotEqual: { - return (*Address() & mask) != ref; + return (value & mask) != ref; } case Function::GreaterThanEqual: { - return (*Address() & mask) >= ref; + return (value & mask) >= ref; } case Function::GreaterThan: { - return (*Address() & mask) > ref; + return (value & mask) > ref; } case Function::Reserved: [[fallthrough]]; From 0d127a82dda5dc655592bab5a662d5820cf97b78 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 13 May 2025 14:05:29 -0700 Subject: [PATCH 06/45] equeue: Clean up timers implementation. (#2925) --- src/core/libraries/kernel/equeue.cpp | 128 +++++++++++++++------------ src/core/libraries/kernel/equeue.h | 9 +- 2 files changed, 78 insertions(+), 59 deletions(-) diff --git a/src/core/libraries/kernel/equeue.cpp b/src/core/libraries/kernel/equeue.cpp index 7e579614f..8b3ae90cb 100644 --- a/src/core/libraries/kernel/equeue.cpp +++ b/src/core/libraries/kernel/equeue.cpp @@ -12,12 +12,25 @@ namespace Libraries::Kernel { +extern boost::asio::io_context io_context; +extern void KernelSignalRequest(); + +static constexpr auto HrTimerSpinlockThresholdUs = 1200u; + // Events are uniquely identified by id and filter. bool EqueueInternal::AddEvent(EqueueEvent& event) { std::scoped_lock lock{m_mutex}; event.time_added = std::chrono::steady_clock::now(); + if (event.event.filter == SceKernelEvent::Filter::Timer || + event.event.filter == SceKernelEvent::Filter::HrTimer) { + // HrTimer events are offset by the threshold of time at the end that we spinlock for + // greater accuracy. + const auto offset = + event.event.filter == SceKernelEvent::Filter::HrTimer ? HrTimerSpinlockThresholdUs : 0u; + event.timer_interval = std::chrono::microseconds(event.event.data - offset); + } const auto& it = std::ranges::find(m_events, event); if (it != m_events.cend()) { @@ -29,6 +42,47 @@ bool EqueueInternal::AddEvent(EqueueEvent& event) { return true; } +bool EqueueInternal::ScheduleEvent(u64 id, s16 filter, + void (*callback)(SceKernelEqueue, const SceKernelEvent&)) { + std::scoped_lock lock{m_mutex}; + + const auto& it = std::ranges::find_if(m_events, [id, filter](auto& ev) { + return ev.event.ident == id && ev.event.filter == filter; + }); + if (it == m_events.cend()) { + return false; + } + + const auto& event = *it; + ASSERT(event.event.filter == SceKernelEvent::Filter::Timer || + event.event.filter == SceKernelEvent::Filter::HrTimer); + + if (!it->timer) { + it->timer = std::make_unique(io_context, event.timer_interval); + } else { + // If the timer already exists we are scheduling a reoccurrence after the next period. + // Set the expiration time to the previous occurrence plus the period. + it->timer->expires_at(it->timer->expires_at() + event.timer_interval); + } + + it->timer->async_wait( + [this, event_data = event.event, callback](const boost::system::error_code& ec) { + if (ec) { + if (ec.value() != boost::system::errc::operation_canceled) { + LOG_ERROR(Kernel_Event, "Timer callback error: {}", ec.message()); + } else { + // Timer was cancelled (removed) before it triggered + LOG_DEBUG(Kernel_Event, "Timer cancelled"); + } + return; + } + callback(this, event_data); + }); + KernelSignalRequest(); + + return true; +} + bool EqueueInternal::RemoveEvent(u64 id, s16 filter) { bool has_found = false; std::scoped_lock lock{m_mutex}; @@ -162,20 +216,6 @@ bool EqueueInternal::EventExists(u64 id, s16 filter) { return it != m_events.cend(); } -extern boost::asio::io_context io_context; -extern void KernelSignalRequest(); - -static constexpr auto HrTimerSpinlockThresholdUs = 1200u; - -static void SmallTimerCallback(const boost::system::error_code& error, SceKernelEqueue eq, - SceKernelEvent kevent) { - static EqueueEvent event; - event.event = kevent; - event.event.data = HrTimerSpinlockThresholdUs; - eq->AddSmallTimer(event); - eq->TriggerEvent(kevent.ident, SceKernelEvent::Filter::HrTimer, kevent.udata); -} - int PS4_SYSV_ABI sceKernelCreateEqueue(SceKernelEqueue* eq, const char* name) { if (eq == nullptr) { LOG_ERROR(Kernel_Event, "Event queue is null!"); @@ -253,6 +293,14 @@ int PS4_SYSV_ABI sceKernelWaitEqueue(SceKernelEqueue eq, SceKernelEvent* ev, int return ORBIS_OK; } +static void HrTimerCallback(SceKernelEqueue eq, const SceKernelEvent& kevent) { + static EqueueEvent event; + event.event = kevent; + event.event.data = HrTimerSpinlockThresholdUs; + eq->AddSmallTimer(event); + eq->TriggerEvent(kevent.ident, SceKernelEvent::Filter::HrTimer, kevent.udata); +} + s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, timespec* ts, void* udata) { if (eq == nullptr) { return ORBIS_KERNEL_ERROR_EBADF; @@ -283,17 +331,10 @@ s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, timespec* return eq->AddSmallTimer(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM; } - event.timer = std::make_unique( - io_context, std::chrono::microseconds(total_us - HrTimerSpinlockThresholdUs)); - - event.timer->async_wait(std::bind(SmallTimerCallback, std::placeholders::_1, eq, event.event)); - - if (!eq->AddEvent(event)) { + if (!eq->AddEvent(event) || + !eq->ScheduleEvent(id, SceKernelEvent::Filter::HrTimer, HrTimerCallback)) { return ORBIS_KERNEL_ERROR_ENOMEM; } - - KernelSignalRequest(); - return ORBIS_OK; } @@ -310,24 +351,13 @@ int PS4_SYSV_ABI sceKernelDeleteHRTimerEvent(SceKernelEqueue eq, int id) { } } -static void TimerCallback(const boost::system::error_code& error, SceKernelEqueue eq, - SceKernelEvent kevent, SceKernelUseconds interval_ms) { - if (error) { - LOG_ERROR(Kernel_Event, "Timer callback error: {}", error.message()); - return; - } - +static void TimerCallback(SceKernelEqueue eq, const SceKernelEvent& kevent) { if (eq->EventExists(kevent.ident, kevent.filter)) { eq->TriggerEvent(kevent.ident, SceKernelEvent::Filter::Timer, kevent.udata); if (!(kevent.flags & SceKernelEvent::Flags::OneShot)) { - auto timer = std::make_shared( - io_context, std::chrono::milliseconds(interval_ms)); - - timer->async_wait( - [eq, kevent, interval_ms, timer](const boost::system::error_code& ec) { - TimerCallback(ec, eq, kevent, interval_ms); - }); + // Reschedule the event for its next period. + eq->ScheduleEvent(kevent.ident, kevent.filter, TimerCallback); } } } @@ -338,16 +368,13 @@ int PS4_SYSV_ABI sceKernelAddTimerEvent(SceKernelEqueue eq, int id, SceKernelUse return ORBIS_KERNEL_ERROR_EBADF; } - const u64 interval_ms = static_cast(usec & 0xFFFFFFFF) / 1000; - EqueueEvent event{}; event.event.ident = static_cast(id); event.event.filter = SceKernelEvent::Filter::Timer; event.event.flags = SceKernelEvent::Flags::Add; event.event.fflags = 0; - event.event.data = interval_ms; + event.event.data = usec; event.event.udata = udata; - event.time_added = std::chrono::steady_clock::now(); if (eq->EventExists(event.event.ident, event.event.filter)) { eq->RemoveEvent(id, SceKernelEvent::Filter::Timer); @@ -356,24 +383,13 @@ int PS4_SYSV_ABI sceKernelAddTimerEvent(SceKernelEqueue eq, int id, SceKernelUse eq->GetName(), event.event.ident); } - LOG_DEBUG(Kernel_Event, - "Added timing event: queue name={}, queue id={}, ms-intevall={}, pointer={:x}", - eq->GetName(), event.event.ident, interval_ms, reinterpret_cast(udata)); + LOG_DEBUG(Kernel_Event, "Added timing event: queue name={}, queue id={}, usec={}, pointer={:x}", + eq->GetName(), event.event.ident, usec, reinterpret_cast(udata)); - auto timer = std::make_shared( - io_context, std::chrono::milliseconds(interval_ms)); - - if (!eq->AddEvent(event)) { + if (!eq->AddEvent(event) || + !eq->ScheduleEvent(id, SceKernelEvent::Filter::Timer, TimerCallback)) { return ORBIS_KERNEL_ERROR_ENOMEM; } - - timer->async_wait( - [eq, event_data = event.event, interval_ms, timer](const boost::system::error_code& ec) { - TimerCallback(ec, eq, event_data, interval_ms); - }); - - KernelSignalRequest(); - return ORBIS_OK; } diff --git a/src/core/libraries/kernel/equeue.h b/src/core/libraries/kernel/equeue.h index 636496604..a0367c66a 100644 --- a/src/core/libraries/kernel/equeue.h +++ b/src/core/libraries/kernel/equeue.h @@ -21,6 +21,9 @@ namespace Libraries::Kernel { class EqueueInternal; struct EqueueEvent; +using SceKernelUseconds = u32; +using SceKernelEqueue = EqueueInternal*; + struct SceKernelEvent { enum Filter : s16 { None = 0, @@ -77,6 +80,7 @@ struct EqueueEvent { SceKernelEvent event; void* data = nullptr; std::chrono::steady_clock::time_point time_added; + std::chrono::microseconds timer_interval; std::unique_ptr timer; void ResetTriggerState() { @@ -133,6 +137,8 @@ public: } bool AddEvent(EqueueEvent& event); + bool ScheduleEvent(u64 id, s16 filter, + void (*callback)(SceKernelEqueue, const SceKernelEvent&)); bool RemoveEvent(u64 id, s16 filter); int WaitForEvents(SceKernelEvent* ev, int num, u32 micros); bool TriggerEvent(u64 ident, s16 filter, void* trigger_data); @@ -162,9 +168,6 @@ private: std::condition_variable m_cond; }; -using SceKernelUseconds = u32; -using SceKernelEqueue = EqueueInternal*; - u64 PS4_SYSV_ABI sceKernelGetEventData(const SceKernelEvent* ev); void RegisterEventQueue(Core::Loader::SymbolsResolver* sym); From 073f93172985480961ba09fece0048d94d763d6c Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 13 May 2025 14:14:11 -0700 Subject: [PATCH 07/45] fix: PM4CmdWaitRegMem memory address --- src/video_core/amdgpu/pm4_cmds.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index 066fa4b62..6dc7d97a6 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -487,7 +487,7 @@ struct PM4CmdWaitRegMem { template T Address() const { - return std::bit_cast((uintptr_t(poll_addr_hi) << 32) | poll_addr_lo); + return std::bit_cast((uintptr_t(poll_addr_hi) << 32) | (poll_addr_lo << 2)); } u32 Reg() const { From 3ab69e24db5f976d7d675fea395b550e1ccdfc1d Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 13 May 2025 14:22:44 -0700 Subject: [PATCH 08/45] fix: Compiling with newer Boost --- src/core/libraries/kernel/equeue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/kernel/equeue.cpp b/src/core/libraries/kernel/equeue.cpp index 8b3ae90cb..ec3b50cd5 100644 --- a/src/core/libraries/kernel/equeue.cpp +++ b/src/core/libraries/kernel/equeue.cpp @@ -62,7 +62,7 @@ bool EqueueInternal::ScheduleEvent(u64 id, s16 filter, } else { // If the timer already exists we are scheduling a reoccurrence after the next period. // Set the expiration time to the previous occurrence plus the period. - it->timer->expires_at(it->timer->expires_at() + event.timer_interval); + it->timer->expires_at(it->timer->expiry() + event.timer_interval); } it->timer->async_wait( From 6abda17532fbf1e44e4ced877f79db33845a347c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Tue, 13 May 2025 23:31:14 +0200 Subject: [PATCH 09/45] Avoid post-increment of SGPR in S_*_LOAD_DWORD (#2928) --- src/shader_recompiler/frontend/translate/scalar_memory.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/scalar_memory.cpp b/src/shader_recompiler/frontend/translate/scalar_memory.cpp index 89426e080..376cc304e 100644 --- a/src/shader_recompiler/frontend/translate/scalar_memory.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_memory.cpp @@ -53,7 +53,7 @@ void Translator::S_LOAD_DWORD(int num_dwords, const GcnInst& inst) { ir.CompositeConstruct(ir.GetScalarReg(sbase), ir.GetScalarReg(sbase + 1)); IR::ScalarReg dst_reg{inst.dst[0].code}; for (u32 i = 0; i < num_dwords; i++) { - ir.SetScalarReg(dst_reg++, ir.ReadConst(base, ir.Imm32(dword_offset + i))); + ir.SetScalarReg(dst_reg + i, ir.ReadConst(base, ir.Imm32(dword_offset + i))); } } @@ -75,7 +75,7 @@ void Translator::S_BUFFER_LOAD_DWORD(int num_dwords, const GcnInst& inst) { IR::ScalarReg dst_reg{inst.dst[0].code}; for (u32 i = 0; i < num_dwords; i++) { const IR::U32 index = ir.IAdd(dword_offset, ir.Imm32(i)); - ir.SetScalarReg(dst_reg++, ir.ReadConstBuffer(vsharp, index)); + ir.SetScalarReg(dst_reg + i, ir.ReadConstBuffer(vsharp, index)); } } From 647b1d3ee476ce15fbf542e8b0f57e6debb12d81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Tue, 13 May 2025 23:34:22 +0200 Subject: [PATCH 10/45] Handle VgtStreamoutFlush event (#2929) --- src/video_core/amdgpu/liverpool.cpp | 21 ++++- src/video_core/amdgpu/liverpool.h | 13 +++- src/video_core/amdgpu/pm4_cmds.h | 114 ++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 2 deletions(-) diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 0fbfa8b9b..686e8e84f 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -584,7 +584,16 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); + const auto* event = reinterpret_cast(header); + LOG_DEBUG(Render_Vulkan, + "Encountered EventWrite: event_type = {}, event_index = {}", + magic_enum::enum_name(event->event_type.Value()), + magic_enum::enum_name(event->event_index.Value())); + if (event->event_type.Value() == EventType::SoVgtStreamoutFlush) { + // TODO: handle proper synchronization, for now signal that update is done + // immediately + regs.cp_strmout_cntl.offset_update_done = 1; + } break; } case PM4ItOpcode::EventWriteEos: { @@ -732,6 +741,16 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); + LOG_WARNING(Render_Vulkan, + "Unimplemented IT_STRMOUT_BUFFER_UPDATE, update_memory = {}, " + "source_select = {}, buffer_select = {}", + strmout->update_memory.Value(), + magic_enum::enum_name(strmout->source_select.Value()), + strmout->buffer_select.Value()); + break; + } default: UNREACHABLE_MSG("Unknown PM4 type 3 opcode {:#x} with count {}", static_cast(opcode), count); diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index c4bebd05f..a62141099 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -1175,6 +1175,14 @@ struct Liverpool { BitField<22, 2, u32> onchip; }; + union StreamOutControl { + u32 raw; + struct { + u32 offset_update_done : 1; + u32 : 31; + }; + }; + union StreamOutConfig { u32 raw; struct { @@ -1378,7 +1386,9 @@ struct Liverpool { AaConfig aa_config; INSERT_PADDING_WORDS(0xA318 - 0xA2F8 - 1); ColorBuffer color_buffers[NumColorBuffers]; - INSERT_PADDING_WORDS(0xC242 - 0xA390); + INSERT_PADDING_WORDS(0xC03F - 0xA390); + StreamOutControl cp_strmout_cntl; + INSERT_PADDING_WORDS(0xC242 - 0xC040); PrimitiveType primitive_type; INSERT_PADDING_WORDS(0xC24C - 0xC243); u32 num_indices; @@ -1668,6 +1678,7 @@ static_assert(GFX6_3D_REG_INDEX(color_buffers[0].base_address) == 0xA318); static_assert(GFX6_3D_REG_INDEX(color_buffers[0].pitch) == 0xA319); static_assert(GFX6_3D_REG_INDEX(color_buffers[0].slice) == 0xA31A); static_assert(GFX6_3D_REG_INDEX(color_buffers[7].base_address) == 0xA381); +static_assert(GFX6_3D_REG_INDEX(cp_strmout_cntl) == 0xC03F); static_assert(GFX6_3D_REG_INDEX(primitive_type) == 0xC242); static_assert(GFX6_3D_REG_INDEX(num_instances) == 0xC24D); static_assert(GFX6_3D_REG_INDEX(vgt_tf_memory_base) == 0xc250); diff --git a/src/video_core/amdgpu/pm4_cmds.h b/src/video_core/amdgpu/pm4_cmds.h index 6dc7d97a6..58ecda93e 100644 --- a/src/video_core/amdgpu/pm4_cmds.h +++ b/src/video_core/amdgpu/pm4_cmds.h @@ -246,6 +246,46 @@ struct PM4CmdNop { }; }; +enum class SourceSelect : u32 { + BufferOffset = 0, + VgtStrmoutBufferFilledSize = 1, + SrcAddress = 2, + None = 3, +}; + +struct PM4CmdStrmoutBufferUpdate { + PM4Type3Header header; + union { + BitField<0, 1, u32> update_memory; + BitField<1, 2, SourceSelect> source_select; + BitField<8, 2, u32> buffer_select; + u32 control; + }; + union { + BitField<2, 30, u32> dst_address_lo; + BitField<0, 2, u32> swap_dst; + }; + u32 dst_address_hi; + union { + u32 buffer_offset; + BitField<2, 30, u32> src_address_lo; + BitField<0, 2, u32> swap_src; + }; + u32 src_address_hi; + + template + T DstAddress() const { + ASSERT(update_memory.Value() == 1); + return reinterpret_cast(dst_address_lo.Value() | u64(dst_address_hi & 0xFFFF) << 32); + } + + template + T SrcAddress() const { + ASSERT(source_select.Value() == SourceSelect::SrcAddress); + return reinterpret_cast(src_address_lo.Value() | u64(src_address_hi & 0xFFFF) << 32); + } +}; + struct PM4CmdDrawIndexOffset2 { PM4Type3Header header; u32 max_size; ///< Maximum number of indices @@ -303,6 +343,80 @@ static u64 GetGpuClock64() { return static_cast(ticks); } +// VGT_EVENT_INITIATOR.EVENT_TYPE +enum class EventType : u32 { + SampleStreamoutStats1 = 1, + SampleStreamoutStats2 = 2, + SampleStreamoutStats3 = 3, + CacheFlushTs = 4, + ContextDone = 5, + CacheFlush = 6, + CsPartialFlush = 7, + VgtStreamoutSync = 8, + VgtStreamoutReset = 10, + EndOfPipeIncrDe = 11, + EndOfPipeIbEnd = 12, + RstPixCnt = 13, + VsPartialFlush = 15, + PsPartialFlush = 16, + FlushHsOutput = 17, + FlushLsOutput = 18, + CacheFlushAndInvTsEvent = 20, + ZpassDone = 21, + CacheFlushAndInvEvent = 22, + PerfcounterStart = 23, + PerfcounterStop = 24, + PipelineStatStart = 25, + PipelineStatStop = 26, + PerfcounterSample = 27, + FlushEsOutput = 28, + FlushGsOutput = 29, + SamplePipelineStat = 30, + SoVgtStreamoutFlush = 31, + SampleStreamoutStats = 32, + ResetVtxCnt = 33, + VgtFlush = 36, + ScSendDbVpz = 39, + BottomOfPipeTs = 40, + DbCacheFlushAndInv = 42, + FlushAndInvDbDataTs = 43, + FlushAndInvDbMeta = 44, + FlushAndInvCbDataTs = 45, + FlushAndInvCbMeta = 46, + CsDone = 47, + PsDone = 48, + FlushAndInvCbPixelData = 49, + ThreadTraceStart = 51, + ThreadTraceStop = 52, + ThreadTraceFlush = 54, + ThreadTraceFinish = 55, + PixelPipeStatControl = 56, + PixelPipeStatDump = 57, + PixelPipeStatReset = 58, +}; + +enum class EventIndex : u32 { + Other = 0, + ZpassDone = 1, + SamplePipelineStat = 2, + SampleStreamoutStatSx = 3, + CsVsPsPartialFlush = 4, + EopReserved = 5, + EosReserved = 6, + CacheFlush = 7, +}; + +struct PM4CmdEventWrite { + PM4Type3Header header; + union { + u32 event_control; + BitField<0, 6, EventType> event_type; ///< Event type written to VGT_EVENT_INITIATOR + BitField<8, 4, EventIndex> event_index; ///< Event index + BitField<20, 1, u32> inv_l2; ///< Send WBINVL2 op to the TC L2 cache when EVENT_INDEX = 0111 + }; + u32 address[]; +}; + struct PM4CmdEventWriteEop { PM4Type3Header header; union { From 1639640902f5e4aa5cc088a6becb36c3c8c57864 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 13 May 2025 14:46:59 -0700 Subject: [PATCH 11/45] event_flag: Lower error logs to debug. --- src/core/libraries/kernel/threads/event_flag.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/libraries/kernel/threads/event_flag.cpp b/src/core/libraries/kernel/threads/event_flag.cpp index 24ddcb927..91b17bd49 100644 --- a/src/core/libraries/kernel/threads/event_flag.cpp +++ b/src/core/libraries/kernel/threads/event_flag.cpp @@ -315,7 +315,7 @@ int PS4_SYSV_ABI sceKernelPollEventFlag(OrbisKernelEventFlag ef, u64 bitPattern, auto result = ef->Poll(bitPattern, wait, clear, pResultPat); if (result != ORBIS_OK && result != ORBIS_KERNEL_ERROR_EBUSY) { - LOG_ERROR(Kernel_Event, "returned {}", result); + LOG_DEBUG(Kernel_Event, "returned {:#x}", result); } return result; @@ -361,7 +361,7 @@ int PS4_SYSV_ABI sceKernelWaitEventFlag(OrbisKernelEventFlag ef, u64 bitPattern, u32 result = ef->Wait(bitPattern, wait, clear, pResultPat, pTimeout); if (result != ORBIS_OK && result != ORBIS_KERNEL_ERROR_ETIMEDOUT) { - LOG_ERROR(Kernel_Event, "returned {:#x}", result); + LOG_DEBUG(Kernel_Event, "returned {:#x}", result); } return result; From 99eaba7c963797b4282af4a4b78ec8d8ea358d80 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 13 May 2025 16:08:25 -0700 Subject: [PATCH 12/45] liverpool: Lower SetQueueReg to warning log. (#2930) --- src/video_core/amdgpu/liverpool.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 686e8e84f..d1cd98634 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -885,8 +885,9 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq } case PM4ItOpcode::SetQueueReg: { const auto* set_data = reinterpret_cast(header); - UNREACHABLE_MSG("Encountered compute SetQueueReg: vqid = {}, reg_offset = {:#x}", - set_data->vqid.Value(), set_data->reg_offset.Value()); + LOG_WARNING(Render, "Encountered compute SetQueueReg: vqid = {}, reg_offset = {:#x}", + set_data->vqid.Value(), set_data->reg_offset.Value()); + break; } case PM4ItOpcode::DispatchDirect: { const auto* dispatch_direct = reinterpret_cast(header); From 26c965cf4aee39903067015c2759f38f7f4444b0 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Tue, 13 May 2025 17:31:23 -0700 Subject: [PATCH 13/45] equeue: Fix timer cancel error code check. --- src/core/libraries/kernel/equeue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/kernel/equeue.cpp b/src/core/libraries/kernel/equeue.cpp index ec3b50cd5..958019cd3 100644 --- a/src/core/libraries/kernel/equeue.cpp +++ b/src/core/libraries/kernel/equeue.cpp @@ -68,7 +68,7 @@ bool EqueueInternal::ScheduleEvent(u64 id, s16 filter, it->timer->async_wait( [this, event_data = event.event, callback](const boost::system::error_code& ec) { if (ec) { - if (ec.value() != boost::system::errc::operation_canceled) { + if (ec != boost::system::errc::operation_canceled) { LOG_ERROR(Kernel_Event, "Timer callback error: {}", ec.message()); } else { // Timer was cancelled (removed) before it triggered From 6d38100a4118f161fdbf50cc0a1012d863d475d0 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Wed, 14 May 2025 09:09:23 -0500 Subject: [PATCH 14/45] Avoid logging nulls in sceKernelIsStack (#2933) Games would crash if providing nullptr to start or end. Also don't need the :# part when logging pointers, as they're automatically formatted. --- src/core/libraries/kernel/memory.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index 7af67d6d3..bddfcfc84 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -291,8 +291,7 @@ int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut } int PS4_SYSV_ABI sceKernelIsStack(void* addr, void** start, void** end) { - LOG_DEBUG(Kernel_Vmm, "called, addr = {:#x}, start = {:#x}, end = {:#x}", fmt::ptr(addr), - fmt::ptr(start), fmt::ptr(end)); + LOG_DEBUG(Kernel_Vmm, "called, addr = {}", fmt::ptr(addr)); auto* memory = Core::Memory::Instance(); return memory->IsStack(std::bit_cast(addr), start, end); } From 98faff425ea325c693ce6e6d00f65d32e15d6665 Mon Sep 17 00:00:00 2001 From: MajorP93 Date: Wed, 14 May 2025 19:37:16 +0200 Subject: [PATCH 15/45] build: Target x86-64-v3 CPU architecture (#2934) --- CMakeLists.txt | 6 +++--- documents/Quickstart/Quickstart.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6780db417..8daa98dea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,9 +54,9 @@ else() endif() if (ARCHITECTURE STREQUAL "x86_64") - # Target the same CPU architecture as the PS4, to maintain the same level of compatibility. - # Exclude SSE4a as it is only available on AMD CPUs. - add_compile_options(-march=btver2 -mtune=generic -mno-sse4a) + # Target x86-64-v3 CPU architecture as this is a good balance between supporting performance critical + # instructions like AVX2 and maintaining support for older CPUs. + add_compile_options(-march=x86-64-v3) endif() if (APPLE AND ARCHITECTURE STREQUAL "x86_64" AND CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64") diff --git a/documents/Quickstart/Quickstart.md b/documents/Quickstart/Quickstart.md index 62df95e71..e2145ebbd 100644 --- a/documents/Quickstart/Quickstart.md +++ b/documents/Quickstart/Quickstart.md @@ -21,9 +21,9 @@ SPDX-License-Identifier: GPL-2.0-or-later - A processor with at least 4 cores and 6 threads - Above 2.5 GHz frequency -- A CPU supporting the following instruction sets: MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, AVX, F16C, CLMUL, AES, BMI1, MOVBE, XSAVE, ABM +- A CPU supporting the x86-64-v3 baseline. - **Intel**: Haswell generation or newer - - **AMD**: Jaguar generation or newer + - **AMD**: Excavator generation or newer - **Apple**: Rosetta 2 on macOS 15.4 or newer ### GPU @@ -55,4 +55,4 @@ To configure the emulator, you can go through the interface and go to "settings" You can also configure the emulator by editing the `config.toml` file located in the `user` folder created after the application is started (Mostly useful if you are using the SDL version). Some settings may be related to more technical development and debugging.\ -For more information on this, see [**Debugging**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md#configuration). \ No newline at end of file +For more information on this, see [**Debugging**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Debugging/Debugging.md#configuration). From aeb453698821f5d346de9602d9d7e8114f94a89a Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 15 May 2025 13:59:34 -0700 Subject: [PATCH 16/45] externals: Remove winpthreads. (#2932) --- .gitmodules | 4 - CMakeLists.txt | 9 +- externals/CMakeLists.txt | 6 - externals/winpthreads | 1 - src/common/thread.cpp | 39 +- src/common/thread.h | 3 + src/core/libraries/kernel/kernel.cpp | 3 + src/core/libraries/kernel/time.cpp | 560 +++++++++++++++------------ src/core/libraries/kernel/time.h | 10 +- 9 files changed, 362 insertions(+), 273 deletions(-) delete mode 160000 externals/winpthreads diff --git a/.gitmodules b/.gitmodules index 065a4570f..25b5d307b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -30,10 +30,6 @@ path = externals/xbyak url = https://github.com/herumi/xbyak.git shallow = true -[submodule "externals/winpthreads"] - path = externals/winpthreads - url = https://github.com/shadps4-emu/winpthreads.git - shallow = true [submodule "externals/magic_enum"] path = externals/magic_enum url = https://github.com/Neargye/magic_enum.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 8daa98dea..6c5dde7bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -239,13 +239,6 @@ if (APPLE) endif() list(POP_BACK CMAKE_MODULE_PATH) -# Note: Windows always has these functions through winpthreads -include(CheckSymbolExists) -check_symbol_exists(pthread_mutex_timedlock "pthread.h" HAVE_PTHREAD_MUTEX_TIMEDLOCK) -if(HAVE_PTHREAD_MUTEX_TIMEDLOCK OR WIN32) - add_compile_options(-DHAVE_PTHREAD_MUTEX_TIMEDLOCK) -endif() - if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") # libc++ requires -fexperimental-library to enable std::jthread and std::stop_token support. include(CheckCXXSymbolExists) @@ -1156,7 +1149,7 @@ if (ENABLE_QT_GUI) endif() if (WIN32) - target_link_libraries(shadps4 PRIVATE mincore winpthreads) + target_link_libraries(shadps4 PRIVATE mincore) if (MSVC) # MSVC likes putting opinions on what people can use, disable: diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index b92e13795..89b0fbfdd 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -137,12 +137,6 @@ if (NOT TARGET Zydis::Zydis) add_subdirectory(zydis) endif() -# Winpthreads -if (WIN32) - add_subdirectory(winpthreads) - target_include_directories(winpthreads INTERFACE winpthreads/include) -endif() - # sirit add_subdirectory(sirit) if (WIN32) diff --git a/externals/winpthreads b/externals/winpthreads deleted file mode 160000 index f35b0948d..000000000 --- a/externals/winpthreads +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f35b0948d36a736e6a2d052ae295a3ffde09703f diff --git a/src/common/thread.cpp b/src/common/thread.cpp index 9ef1e86d8..982041ebb 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include #include #include @@ -104,14 +105,24 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) { SetThreadPriority(handle, windows_priority); } -static void AccurateSleep(std::chrono::nanoseconds duration) { +bool AccurateSleep(const std::chrono::nanoseconds duration, std::chrono::nanoseconds* remaining, + const bool interruptible) { + const auto begin_sleep = std::chrono::high_resolution_clock::now(); + LARGE_INTEGER interval{ .QuadPart = -1 * (duration.count() / 100u), }; HANDLE timer = ::CreateWaitableTimer(NULL, TRUE, NULL); SetWaitableTimer(timer, &interval, 0, NULL, NULL, 0); - WaitForSingleObject(timer, INFINITE); + const auto ret = WaitForSingleObjectEx(timer, INFINITE, interruptible); ::CloseHandle(timer); + + if (remaining) { + const auto end_sleep = std::chrono::high_resolution_clock::now(); + const auto sleep_time = end_sleep - begin_sleep; + *remaining = duration > sleep_time ? duration - sleep_time : std::chrono::nanoseconds(0); + } + return ret == WAIT_OBJECT_0; } #else @@ -134,8 +145,24 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) { pthread_setschedparam(this_thread, scheduling_type, ¶ms); } -static void AccurateSleep(std::chrono::nanoseconds duration) { - std::this_thread::sleep_for(duration); +bool AccurateSleep(const std::chrono::nanoseconds duration, std::chrono::nanoseconds* remaining, + const bool interruptible) { + timespec request = { + .tv_sec = duration.count() / 1'000'000'000, + .tv_nsec = duration.count() % 1'000'000'000, + }; + timespec remain; + int ret; + while ((ret = nanosleep(&request, &remain)) < 0 && errno == EINTR) { + if (interruptible) { + break; + } + request = remain; + } + if (remaining) { + *remaining = std::chrono::nanoseconds(remain.tv_sec * 1'000'000'000 + remain.tv_nsec); + } + return ret == 0 || errno != EINTR; } #endif @@ -196,9 +223,9 @@ AccurateTimer::AccurateTimer(std::chrono::nanoseconds target_interval) : target_interval(target_interval) {} void AccurateTimer::Start() { - auto begin_sleep = std::chrono::high_resolution_clock::now(); + const auto begin_sleep = std::chrono::high_resolution_clock::now(); if (total_wait.count() > 0) { - AccurateSleep(total_wait); + AccurateSleep(total_wait, nullptr, false); } start_time = std::chrono::high_resolution_clock::now(); total_wait -= std::chrono::duration_cast(start_time - begin_sleep); diff --git a/src/common/thread.h b/src/common/thread.h index 92cc0c59d..5bd83d35c 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -25,6 +25,9 @@ void SetCurrentThreadName(const char* name); void SetThreadName(void* thread, const char* name); +bool AccurateSleep(std::chrono::nanoseconds duration, std::chrono::nanoseconds* remaining, + bool interruptible); + class AccurateTimer { std::chrono::nanoseconds target_interval{}; std::chrono::nanoseconds total_wait{}; diff --git a/src/core/libraries/kernel/kernel.cpp b/src/core/libraries/kernel/kernel.cpp index c7eafe799..180850217 100644 --- a/src/core/libraries/kernel/kernel.cpp +++ b/src/core/libraries/kernel/kernel.cpp @@ -108,6 +108,9 @@ void SetPosixErrno(int e) { case EACCES: g_posix_errno = POSIX_EACCES; break; + case EFAULT: + g_posix_errno = POSIX_EFAULT; + break; case EINVAL: g_posix_errno = POSIX_EINVAL; break; diff --git a/src/core/libraries/kernel/time.cpp b/src/core/libraries/kernel/time.cpp index b7e4c1756..2fe74d0a3 100644 --- a/src/core/libraries/kernel/time.cpp +++ b/src/core/libraries/kernel/time.cpp @@ -5,24 +5,23 @@ #include "common/assert.h" #include "common/native_clock.h" +#include "common/thread.h" #include "core/libraries/kernel/kernel.h" #include "core/libraries/kernel/orbis_error.h" +#include "core/libraries/kernel/posix_error.h" #include "core/libraries/kernel/time.h" #include "core/libraries/libs.h" #ifdef _WIN64 -#include #include - #include "common/ntapi.h" - #else #if __APPLE__ #include #endif +#include #include #include -#include #include #endif @@ -52,88 +51,116 @@ u64 PS4_SYSV_ABI sceKernelReadTsc() { return clock->GetUptime(); } -int PS4_SYSV_ABI sceKernelUsleep(u32 microseconds) { -#ifdef _WIN64 - const auto start_time = std::chrono::high_resolution_clock::now(); - auto total_wait_time = std::chrono::microseconds(microseconds); +static s32 posix_nanosleep_impl(const OrbisKernelTimespec* rqtp, OrbisKernelTimespec* rmtp, + const bool interruptible) { + if (!rqtp || rqtp->tv_sec < 0 || rqtp->tv_nsec < 0 || rqtp->tv_nsec >= 1'000'000'000) { + SetPosixErrno(EINVAL); + return -1; + } + const auto duration = std::chrono::nanoseconds(rqtp->tv_sec * 1'000'000'000 + rqtp->tv_nsec); + std::chrono::nanoseconds remain; + const auto uninterrupted = Common::AccurateSleep(duration, &remain, interruptible); + if (rmtp) { + rmtp->tv_sec = remain.count() / 1'000'000'000; + rmtp->tv_nsec = remain.count() % 1'000'000'000; + } + if (!uninterrupted) { + SetPosixErrno(EINTR); + return -1; + } + return 0; +} - while (total_wait_time.count() > 0) { - auto wait_time = std::chrono::ceil(total_wait_time).count(); - u64 res = SleepEx(static_cast(wait_time), true); - if (res == WAIT_IO_COMPLETION) { - auto elapsedTime = std::chrono::high_resolution_clock::now() - start_time; - auto elapsedMicroseconds = - std::chrono::duration_cast(elapsedTime).count(); - total_wait_time = std::chrono::microseconds(microseconds - elapsedMicroseconds); - } else { - break; - } +s32 PS4_SYSV_ABI posix_nanosleep(const OrbisKernelTimespec* rqtp, OrbisKernelTimespec* rmtp) { + return posix_nanosleep_impl(rqtp, rmtp, true); +} + +s32 PS4_SYSV_ABI sceKernelNanosleep(const OrbisKernelTimespec* rqtp, OrbisKernelTimespec* rmtp) { + if (const auto ret = posix_nanosleep_impl(rqtp, rmtp, false); ret < 0) { + return ErrnoToSceKernelError(*__Error()); + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI posix_usleep(u32 microseconds) { + const OrbisKernelTimespec ts = { + .tv_sec = microseconds / 1'000'000, + .tv_nsec = (microseconds % 1'000'000) * 1'000, + }; + return posix_nanosleep(&ts, nullptr); +} + +s32 PS4_SYSV_ABI sceKernelUsleep(u32 microseconds) { + const OrbisKernelTimespec ts = { + .tv_sec = microseconds / 1'000'000, + .tv_nsec = (microseconds % 1'000'000) * 1'000, + }; + return sceKernelNanosleep(&ts, nullptr); +} + +u32 PS4_SYSV_ABI posix_sleep(u32 seconds) { + const OrbisKernelTimespec ts = { + .tv_sec = seconds, + .tv_nsec = 0, + }; + OrbisKernelTimespec rm; + if (const auto ret = posix_nanosleep(&ts, &rm); ret < 0) { + return *__Error() == POSIX_EINTR ? rm.tv_sec + (rm.tv_nsec == 0 ? 0 : 1) : seconds; + } + return 0; +} + +s32 PS4_SYSV_ABI sceKernelSleep(u32 seconds) { + return sceKernelUsleep(seconds * 1'000'000); +} + +s32 PS4_SYSV_ABI posix_clock_gettime(u32 clock_id, OrbisKernelTimespec* ts) { + if (ts == nullptr) { + SetPosixErrno(EFAULT); + return -1; } - return 0; -#else - timespec start; - timespec remain; - start.tv_sec = microseconds / 1000000; - start.tv_nsec = (microseconds % 1000000) * 1000; - timespec* requested = &start; - int ret = 0; - do { - ret = nanosleep(requested, &remain); - requested = &remain; - } while (ret != 0); - return ret; -#endif -} + if (clock_id == ORBIS_CLOCK_PROCTIME) { + const auto us = sceKernelGetProcessTime(); + ts->tv_sec = static_cast(us / 1'000'000); + ts->tv_nsec = static_cast((us % 1'000'000) * 1000); + return 0; + } + if (clock_id == ORBIS_CLOCK_EXT_NETWORK || clock_id == ORBIS_CLOCK_EXT_DEBUG_NETWORK || + clock_id == ORBIS_CLOCK_EXT_AD_NETWORK || clock_id == ORBIS_CLOCK_EXT_RAW_NETWORK) { + LOG_ERROR(Lib_Kernel, "Unsupported clock type {}, using CLOCK_MONOTONIC", clock_id); + clock_id = ORBIS_CLOCK_MONOTONIC; + } -int PS4_SYSV_ABI posix_usleep(u32 microseconds) { - return sceKernelUsleep(microseconds); -} - -u32 PS4_SYSV_ABI sceKernelSleep(u32 seconds) { - std::this_thread::sleep_for(std::chrono::seconds(seconds)); - return 0; -} - -#ifdef _WIN64 -#ifndef CLOCK_REALTIME -#define CLOCK_REALTIME 0 -#endif -#ifndef CLOCK_MONOTONIC -#define CLOCK_MONOTONIC 1 -#endif -#ifndef CLOCK_PROCESS_CPUTIME_ID -#define CLOCK_PROCESS_CPUTIME_ID 2 -#endif -#ifndef CLOCK_THREAD_CPUTIME_ID -#define CLOCK_THREAD_CPUTIME_ID 3 -#endif -#ifndef CLOCK_REALTIME_COARSE -#define CLOCK_REALTIME_COARSE 5 -#endif -#ifndef CLOCK_MONOTONIC_COARSE -#define CLOCK_MONOTONIC_COARSE 6 -#endif - -#define DELTA_EPOCH_IN_100NS 116444736000000000ULL - -static u64 FileTimeTo100Ns(FILETIME& ft) { - return *reinterpret_cast(&ft); -} - -static s32 clock_gettime(u32 clock_id, struct timespec* ts) { +#ifdef _WIN32 + static const auto FileTimeTo100Ns = [](FILETIME& ft) { return *reinterpret_cast(&ft); }; switch (clock_id) { - case CLOCK_REALTIME: - case CLOCK_REALTIME_COARSE: { + case ORBIS_CLOCK_REALTIME: + case ORBIS_CLOCK_REALTIME_PRECISE: { FILETIME ft; - GetSystemTimeAsFileTime(&ft); - const u64 ns = FileTimeTo100Ns(ft) - DELTA_EPOCH_IN_100NS; + GetSystemTimePreciseAsFileTime(&ft); + static constexpr u64 DeltaEpochIn100ns = 116444736000000000ULL; + const u64 ns = FileTimeTo100Ns(ft) - DeltaEpochIn100ns; ts->tv_sec = ns / 10'000'000; ts->tv_nsec = (ns % 10'000'000) * 100; return 0; } - case CLOCK_MONOTONIC: - case CLOCK_MONOTONIC_COARSE: { + case ORBIS_CLOCK_SECOND: + case ORBIS_CLOCK_REALTIME_FAST: { + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + static constexpr u64 DeltaEpochIn100ns = 116444736000000000ULL; + const u64 ns = FileTimeTo100Ns(ft) - DeltaEpochIn100ns; + ts->tv_sec = ns / 10'000'000; + ts->tv_nsec = (ns % 10'000'000) * 100; + return 0; + } + case ORBIS_CLOCK_UPTIME: + case ORBIS_CLOCK_UPTIME_PRECISE: + case ORBIS_CLOCK_MONOTONIC: + case ORBIS_CLOCK_MONOTONIC_PRECISE: + case ORBIS_CLOCK_UPTIME_FAST: + case ORBIS_CLOCK_MONOTONIC_FAST: { static LARGE_INTEGER pf = [] { LARGE_INTEGER res{}; QueryPerformanceFrequency(&pf); @@ -141,43 +168,53 @@ static s32 clock_gettime(u32 clock_id, struct timespec* ts) { }(); LARGE_INTEGER pc{}; - QueryPerformanceCounter(&pc); + if (!QueryPerformanceCounter(&pc)) { + SetPosixErrno(EFAULT); + return -1; + } ts->tv_sec = pc.QuadPart / pf.QuadPart; ts->tv_nsec = ((pc.QuadPart % pf.QuadPart) * 1000'000'000) / pf.QuadPart; return 0; } - case CLOCK_PROCESS_CPUTIME_ID: { + case ORBIS_CLOCK_THREAD_CPUTIME_ID: { FILETIME ct, et, kt, ut; - if (!GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) { - return EFAULT; + if (!GetThreadTimes(GetCurrentThread(), &ct, &et, &kt, &ut)) { + SetPosixErrno(EFAULT); + return -1; } const u64 ns = FileTimeTo100Ns(ut) + FileTimeTo100Ns(kt); ts->tv_sec = ns / 10'000'000; ts->tv_nsec = (ns % 10'000'000) * 100; return 0; } - case CLOCK_THREAD_CPUTIME_ID: { + case ORBIS_CLOCK_VIRTUAL: { FILETIME ct, et, kt, ut; - if (!GetThreadTimes(GetCurrentThread(), &ct, &et, &kt, &ut)) { - return EFAULT; + if (!GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) { + SetPosixErrno(EFAULT); + return -1; } - const u64 ns = FileTimeTo100Ns(ut) + FileTimeTo100Ns(kt); + const u64 ns = FileTimeTo100Ns(ut); + ts->tv_sec = ns / 10'000'000; + ts->tv_nsec = (ns % 10'000'000) * 100; + return 0; + } + case ORBIS_CLOCK_PROF: { + FILETIME ct, et, kt, ut; + if (!GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) { + SetPosixErrno(EFAULT); + return -1; + } + const u64 ns = FileTimeTo100Ns(kt); ts->tv_sec = ns / 10'000'000; ts->tv_nsec = (ns % 10'000'000) * 100; return 0; } default: - return EINVAL; + SetPosixErrno(EFAULT); + return -1; } -} -#endif - -int PS4_SYSV_ABI orbis_clock_gettime(s32 clock_id, struct OrbisKernelTimespec* ts) { - if (ts == nullptr) { - return ORBIS_KERNEL_ERROR_EFAULT; - } - - clockid_t pclock_id = CLOCK_MONOTONIC; +#else + clockid_t pclock_id; switch (clock_id) { case ORBIS_CLOCK_REALTIME: case ORBIS_CLOCK_REALTIME_PRECISE: @@ -185,7 +222,7 @@ int PS4_SYSV_ABI orbis_clock_gettime(s32 clock_id, struct OrbisKernelTimespec* t break; case ORBIS_CLOCK_SECOND: case ORBIS_CLOCK_REALTIME_FAST: -#ifndef __APPLE__ +#ifdef CLOCK_REALTIME_COARSE pclock_id = CLOCK_REALTIME_COARSE; #else pclock_id = CLOCK_REALTIME; @@ -199,7 +236,7 @@ int PS4_SYSV_ABI orbis_clock_gettime(s32 clock_id, struct OrbisKernelTimespec* t break; case ORBIS_CLOCK_UPTIME_FAST: case ORBIS_CLOCK_MONOTONIC_FAST: -#ifndef __APPLE__ +#ifdef CLOCK_MONOTONIC_COARSE pclock_id = CLOCK_MONOTONIC_COARSE; #else pclock_id = CLOCK_MONOTONIC; @@ -208,196 +245,226 @@ int PS4_SYSV_ABI orbis_clock_gettime(s32 clock_id, struct OrbisKernelTimespec* t case ORBIS_CLOCK_THREAD_CPUTIME_ID: pclock_id = CLOCK_THREAD_CPUTIME_ID; break; - case ORBIS_CLOCK_PROCTIME: { - const auto us = sceKernelGetProcessTime(); - ts->tv_sec = us / 1'000'000; - ts->tv_nsec = (us % 1'000'000) * 1000; - return 0; - } case ORBIS_CLOCK_VIRTUAL: { -#ifdef _WIN64 - FILETIME ct, et, kt, ut; - if (!GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) { - return EFAULT; - } - const u64 ns = FileTimeTo100Ns(ut); - ts->tv_sec = ns / 10'000'000; - ts->tv_nsec = (ns % 10'000'000) * 100; -#else - struct rusage ru; + rusage ru; const auto res = getrusage(RUSAGE_SELF, &ru); if (res < 0) { - return res; + SetPosixErrno(EFAULT); + return -1; } ts->tv_sec = ru.ru_utime.tv_sec; ts->tv_nsec = ru.ru_utime.tv_usec * 1000; -#endif return 0; } case ORBIS_CLOCK_PROF: { -#ifdef _WIN64 - FILETIME ct, et, kt, ut; - if (!GetProcessTimes(GetCurrentProcess(), &ct, &et, &kt, &ut)) { - return EFAULT; - } - const u64 ns = FileTimeTo100Ns(kt); - ts->tv_sec = ns / 10'000'000; - ts->tv_nsec = (ns % 10'000'000) * 100; -#else - struct rusage ru; + rusage ru; const auto res = getrusage(RUSAGE_SELF, &ru); if (res < 0) { - return res; + SetPosixErrno(EFAULT); + return -1; } ts->tv_sec = ru.ru_stime.tv_sec; ts->tv_nsec = ru.ru_stime.tv_usec * 1000; -#endif return 0; } - case ORBIS_CLOCK_EXT_NETWORK: - case ORBIS_CLOCK_EXT_DEBUG_NETWORK: - case ORBIS_CLOCK_EXT_AD_NETWORK: - case ORBIS_CLOCK_EXT_RAW_NETWORK: - pclock_id = CLOCK_MONOTONIC; - LOG_ERROR(Lib_Kernel, "unsupported = {} using CLOCK_MONOTONIC", clock_id); - break; default: - return EINVAL; + SetPosixErrno(EFAULT); + return -1; } timespec t{}; - int result = clock_gettime(pclock_id, &t); + const auto result = clock_gettime(pclock_id, &t); ts->tv_sec = t.tv_sec; ts->tv_nsec = t.tv_nsec; - return result; -} - -int PS4_SYSV_ABI sceKernelClockGettime(s32 clock_id, OrbisKernelTimespec* tp) { - const auto res = orbis_clock_gettime(clock_id, tp); - if (res < 0) { - return ErrnoToSceKernelError(res); + if (result < 0) { + SetPosixErrno(errno); + return -1; } - return ORBIS_OK; -} - -int PS4_SYSV_ABI posix_nanosleep(const OrbisKernelTimespec* rqtp, OrbisKernelTimespec* rmtp) { - const auto* request = reinterpret_cast(rqtp); - auto* remain = reinterpret_cast(rmtp); - return nanosleep(request, remain); -} - -int PS4_SYSV_ABI sceKernelNanosleep(const OrbisKernelTimespec* rqtp, OrbisKernelTimespec* rmtp) { - if (!rqtp || !rmtp) { - return ORBIS_KERNEL_ERROR_EFAULT; - } - - if (rqtp->tv_sec < 0 || rqtp->tv_nsec < 0) { - return ORBIS_KERNEL_ERROR_EINVAL; - } - - return posix_nanosleep(rqtp, rmtp); -} - -int PS4_SYSV_ABI sceKernelGettimeofday(OrbisKernelTimeval* tp) { - if (!tp) { - return ORBIS_KERNEL_ERROR_EFAULT; - } - -#ifdef _WIN64 - FILETIME filetime; - GetSystemTimePreciseAsFileTime(&filetime); - - constexpr u64 UNIX_TIME_START = 0x295E9648864000; - constexpr u64 TICKS_PER_SECOND = 1000000; - - u64 ticks = filetime.dwHighDateTime; - ticks <<= 32; - ticks |= filetime.dwLowDateTime; - ticks /= 10; - ticks -= UNIX_TIME_START; - - tp->tv_sec = ticks / TICKS_PER_SECOND; - tp->tv_usec = ticks % TICKS_PER_SECOND; -#else - timeval tv; - gettimeofday(&tv, nullptr); - tp->tv_sec = tv.tv_sec; - tp->tv_usec = tv.tv_usec; + return 0; #endif +} + +s32 PS4_SYSV_ABI sceKernelClockGettime(const u32 clock_id, OrbisKernelTimespec* ts) { + if (const auto ret = posix_clock_gettime(clock_id, ts); ret < 0) { + return ErrnoToSceKernelError(*__Error()); + } return ORBIS_OK; } -int PS4_SYSV_ABI gettimeofday(OrbisKernelTimeval* tp, OrbisKernelTimezone* tz) { - // FreeBSD docs mention that the kernel generally does not track these values - // and they are usually returned as zero. - if (tz) { - tz->tz_minuteswest = 0; - tz->tz_dsttime = 0; - } - return sceKernelGettimeofday(tp); -} - -s32 PS4_SYSV_ABI sceKernelGettimezone(OrbisKernelTimezone* tz) { -#ifdef _WIN64 - ASSERT(tz); - static int tzflag = 0; - if (!tzflag) { - _tzset(); - tzflag++; - } - tz->tz_minuteswest = _timezone / 60; - tz->tz_dsttime = _daylight; -#else - struct timezone tzz; - struct timeval tv; - gettimeofday(&tv, &tzz); - tz->tz_dsttime = tzz.tz_dsttime; - tz->tz_minuteswest = tzz.tz_minuteswest; -#endif - return ORBIS_OK; -} - -int PS4_SYSV_ABI posix_clock_getres(u32 clock_id, OrbisKernelTimespec* res) { +s32 PS4_SYSV_ABI posix_clock_getres(u32 clock_id, OrbisKernelTimespec* res) { if (res == nullptr) { - return ORBIS_KERNEL_ERROR_EFAULT; + SetPosixErrno(EFAULT); + return -1; } - clockid_t pclock_id = CLOCK_REALTIME; + + if (clock_id == ORBIS_CLOCK_EXT_NETWORK || clock_id == ORBIS_CLOCK_EXT_DEBUG_NETWORK || + clock_id == ORBIS_CLOCK_EXT_AD_NETWORK || clock_id == ORBIS_CLOCK_EXT_RAW_NETWORK) { + LOG_ERROR(Lib_Kernel, "Unsupported clock type {}, using CLOCK_MONOTONIC", clock_id); + clock_id = ORBIS_CLOCK_MONOTONIC; + } + +#ifdef _WIN32 + switch (clock_id) { + case ORBIS_CLOCK_SECOND: + case ORBIS_CLOCK_REALTIME_FAST: { + DWORD timeAdjustment; + DWORD timeIncrement; + BOOL isTimeAdjustmentDisabled; + if (!GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement, &isTimeAdjustmentDisabled)) { + SetPosixErrno(EFAULT); + return -1; + } + res->tv_sec = 0; + res->tv_nsec = timeIncrement * 100; + return 0; + } + case ORBIS_CLOCK_REALTIME: + case ORBIS_CLOCK_REALTIME_PRECISE: + case ORBIS_CLOCK_UPTIME: + case ORBIS_CLOCK_UPTIME_PRECISE: + case ORBIS_CLOCK_MONOTONIC: + case ORBIS_CLOCK_MONOTONIC_PRECISE: + case ORBIS_CLOCK_UPTIME_FAST: + case ORBIS_CLOCK_MONOTONIC_FAST: { + LARGE_INTEGER pf; + if (!QueryPerformanceFrequency(&pf)) { + SetPosixErrno(EFAULT); + return -1; + } + res->tv_sec = 0; + res->tv_nsec = + std::max(static_cast((1000000000 + (pf.QuadPart >> 1)) / pf.QuadPart), 1); + return 0; + } + default: + UNREACHABLE(); + } +#else + clockid_t pclock_id; switch (clock_id) { case ORBIS_CLOCK_REALTIME: case ORBIS_CLOCK_REALTIME_PRECISE: - case ORBIS_CLOCK_REALTIME_FAST: pclock_id = CLOCK_REALTIME; break; case ORBIS_CLOCK_SECOND: + case ORBIS_CLOCK_REALTIME_FAST: +#ifdef CLOCK_REALTIME_COARSE + pclock_id = CLOCK_REALTIME_COARSE; +#else + pclock_id = CLOCK_REALTIME; +#endif + break; + case ORBIS_CLOCK_UPTIME: + case ORBIS_CLOCK_UPTIME_PRECISE: case ORBIS_CLOCK_MONOTONIC: case ORBIS_CLOCK_MONOTONIC_PRECISE: - case ORBIS_CLOCK_MONOTONIC_FAST: pclock_id = CLOCK_MONOTONIC; break; + case ORBIS_CLOCK_UPTIME_FAST: + case ORBIS_CLOCK_MONOTONIC_FAST: +#ifdef CLOCK_MONOTONIC_COARSE + pclock_id = CLOCK_MONOTONIC_COARSE; +#else + pclock_id = CLOCK_MONOTONIC; +#endif + break; default: UNREACHABLE(); } timespec t{}; - int result = clock_getres(pclock_id, &t); + const auto result = clock_getres(pclock_id, &t); res->tv_sec = t.tv_sec; res->tv_nsec = t.tv_nsec; - if (result == 0) { - return ORBIS_OK; + if (result < 0) { + SetPosixErrno(errno); + return -1; } - return ORBIS_KERNEL_ERROR_EINVAL; + return 0; +#endif } -int PS4_SYSV_ABI sceKernelConvertLocaltimeToUtc(time_t param_1, int64_t param_2, time_t* seconds, - OrbisKernelTimezone* timezone, int* dst_seconds) { +s32 PS4_SYSV_ABI sceKernelClockGetres(const u32 clock_id, OrbisKernelTimespec* res) { + if (const auto ret = posix_clock_getres(clock_id, res); ret < 0) { + return ErrnoToSceKernelError(*__Error()); + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI posix_gettimeofday(OrbisKernelTimeval* tp, OrbisKernelTimezone* tz) { +#ifdef _WIN64 + if (tp) { + FILETIME filetime; + GetSystemTimePreciseAsFileTime(&filetime); + + constexpr u64 UNIX_TIME_START = 0x295E9648864000; + constexpr u64 TICKS_PER_SECOND = 1000000; + + u64 ticks = filetime.dwHighDateTime; + ticks <<= 32; + ticks |= filetime.dwLowDateTime; + ticks /= 10; + ticks -= UNIX_TIME_START; + + tp->tv_sec = ticks / TICKS_PER_SECOND; + tp->tv_usec = ticks % TICKS_PER_SECOND; + } + if (tz) { + static int tzflag = 0; + if (!tzflag) { + _tzset(); + tzflag++; + } + tz->tz_minuteswest = _timezone / 60; + tz->tz_dsttime = _daylight; + } + return 0; +#else + struct timezone tzz; + timeval tv; + const auto ret = gettimeofday(&tv, &tzz); + if (tp) { + tp->tv_sec = tv.tv_sec; + tp->tv_usec = tv.tv_usec; + } + if (tz) { + tz->tz_dsttime = tzz.tz_dsttime; + tz->tz_minuteswest = tzz.tz_minuteswest; + } + if (ret < 0) { + SetPosixErrno(errno); + return -1; + } + return 0; +#endif +} + +s32 PS4_SYSV_ABI sceKernelGettimeofday(OrbisKernelTimeval* tp) { + if (const auto ret = posix_gettimeofday(tp, nullptr); ret < 0) { + return ErrnoToSceKernelError(*__Error()); + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceKernelGettimezone(OrbisKernelTimezone* tz) { + if (const auto ret = posix_gettimeofday(nullptr, tz); ret < 0) { + return ErrnoToSceKernelError(*__Error()); + } + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceKernelConvertLocaltimeToUtc(time_t param_1, int64_t param_2, time_t* seconds, + OrbisKernelTimezone* timezone, s32* dst_seconds) { LOG_INFO(Kernel, "called"); if (timezone) { sceKernelGettimezone(timezone); param_1 -= (timezone->tz_minuteswest + timezone->tz_dsttime) * 60; - if (seconds) + if (seconds) { *seconds = param_1; - if (dst_seconds) + } + if (dst_seconds) { *dst_seconds = timezone->tz_dsttime * 60; + } } else { return ORBIS_KERNEL_ERROR_EINVAL; } @@ -415,7 +482,7 @@ Common::NativeClock* GetClock() { } // namespace Dev -int PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time, +s32 PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time, struct OrbisTimesec* st, u64* dst_sec) { LOG_TRACE(Kernel, "Called"); #ifdef __APPLE__ @@ -444,28 +511,35 @@ int PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time, void RegisterTime(Core::Loader::SymbolsResolver* sym) { clock = std::make_unique(); initial_ptc = clock->GetUptime(); + + // POSIX + LIB_FUNCTION("yS8U2TGCe1A", "libkernel", 1, "libkernel", 1, 1, posix_nanosleep); + LIB_FUNCTION("yS8U2TGCe1A", "libScePosix", 1, "libkernel", 1, 1, posix_nanosleep); + LIB_FUNCTION("QcteRwbsnV0", "libkernel", 1, "libkernel", 1, 1, posix_usleep); + LIB_FUNCTION("QcteRwbsnV0", "libScePosix", 1, "libkernel", 1, 1, posix_usleep); + LIB_FUNCTION("0wu33hunNdE", "libkernel", 1, "libkernel", 1, 1, posix_sleep); + LIB_FUNCTION("0wu33hunNdE", "libScePosix", 1, "libkernel", 1, 1, posix_sleep); + LIB_FUNCTION("lLMT9vJAck0", "libkernel", 1, "libkernel", 1, 1, posix_clock_gettime); + LIB_FUNCTION("lLMT9vJAck0", "libScePosix", 1, "libkernel", 1, 1, posix_clock_gettime); + LIB_FUNCTION("smIj7eqzZE8", "libkernel", 1, "libkernel", 1, 1, posix_clock_getres); + LIB_FUNCTION("smIj7eqzZE8", "libScePosix", 1, "libkernel", 1, 1, posix_clock_getres); + LIB_FUNCTION("n88vx3C5nW8", "libkernel", 1, "libkernel", 1, 1, posix_gettimeofday); + LIB_FUNCTION("n88vx3C5nW8", "libScePosix", 1, "libkernel", 1, 1, posix_gettimeofday); + + // Orbis LIB_FUNCTION("4J2sUJmuHZQ", "libkernel", 1, "libkernel", 1, 1, sceKernelGetProcessTime); LIB_FUNCTION("fgxnMeTNUtY", "libkernel", 1, "libkernel", 1, 1, sceKernelGetProcessTimeCounter); LIB_FUNCTION("BNowx2l588E", "libkernel", 1, "libkernel", 1, 1, sceKernelGetProcessTimeCounterFrequency); LIB_FUNCTION("-2IRUCO--PM", "libkernel", 1, "libkernel", 1, 1, sceKernelReadTsc); LIB_FUNCTION("1j3S3n-tTW4", "libkernel", 1, "libkernel", 1, 1, sceKernelGetTscFrequency); - LIB_FUNCTION("ejekcaNQNq0", "libkernel", 1, "libkernel", 1, 1, sceKernelGettimeofday); - LIB_FUNCTION("n88vx3C5nW8", "libkernel", 1, "libkernel", 1, 1, gettimeofday); - LIB_FUNCTION("n88vx3C5nW8", "libScePosix", 1, "libkernel", 1, 1, gettimeofday); LIB_FUNCTION("QvsZxomvUHs", "libkernel", 1, "libkernel", 1, 1, sceKernelNanosleep); LIB_FUNCTION("1jfXLRVzisc", "libkernel", 1, "libkernel", 1, 1, sceKernelUsleep); - LIB_FUNCTION("QcteRwbsnV0", "libkernel", 1, "libkernel", 1, 1, posix_usleep); - LIB_FUNCTION("QcteRwbsnV0", "libScePosix", 1, "libkernel", 1, 1, posix_usleep); LIB_FUNCTION("-ZR+hG7aDHw", "libkernel", 1, "libkernel", 1, 1, sceKernelSleep); - LIB_FUNCTION("0wu33hunNdE", "libScePosix", 1, "libkernel", 1, 1, sceKernelSleep); - LIB_FUNCTION("yS8U2TGCe1A", "libkernel", 1, "libkernel", 1, 1, posix_nanosleep); - LIB_FUNCTION("yS8U2TGCe1A", "libScePosix", 1, "libkernel", 1, 1, posix_nanosleep); LIB_FUNCTION("QBi7HCK03hw", "libkernel", 1, "libkernel", 1, 1, sceKernelClockGettime); + LIB_FUNCTION("wRYVA5Zolso", "libkernel", 1, "libkernel", 1, 1, sceKernelClockGetres); + LIB_FUNCTION("ejekcaNQNq0", "libkernel", 1, "libkernel", 1, 1, sceKernelGettimeofday); LIB_FUNCTION("kOcnerypnQA", "libkernel", 1, "libkernel", 1, 1, sceKernelGettimezone); - LIB_FUNCTION("lLMT9vJAck0", "libkernel", 1, "libkernel", 1, 1, orbis_clock_gettime); - LIB_FUNCTION("lLMT9vJAck0", "libScePosix", 1, "libkernel", 1, 1, orbis_clock_gettime); - LIB_FUNCTION("smIj7eqzZE8", "libScePosix", 1, "libkernel", 1, 1, posix_clock_getres); LIB_FUNCTION("0NTHN1NKONI", "libkernel", 1, "libkernel", 1, 1, sceKernelConvertLocaltimeToUtc); LIB_FUNCTION("-o5uEDpN+oY", "libkernel", 1, "libkernel", 1, 1, sceKernelConvertUtcToLocaltime); } diff --git a/src/core/libraries/kernel/time.h b/src/core/libraries/kernel/time.h index 407b6f9ed..c80de7bc4 100644 --- a/src/core/libraries/kernel/time.h +++ b/src/core/libraries/kernel/time.h @@ -75,14 +75,14 @@ u64 PS4_SYSV_ABI sceKernelGetProcessTime(); u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounter(); u64 PS4_SYSV_ABI sceKernelGetProcessTimeCounterFrequency(); u64 PS4_SYSV_ABI sceKernelReadTsc(); -int PS4_SYSV_ABI sceKernelClockGettime(s32 clock_id, OrbisKernelTimespec* tp); +s32 PS4_SYSV_ABI sceKernelClockGettime(u32 clock_id, OrbisKernelTimespec* tp); s32 PS4_SYSV_ABI sceKernelGettimezone(OrbisKernelTimezone* tz); -int PS4_SYSV_ABI sceKernelConvertLocaltimeToUtc(time_t param_1, int64_t param_2, time_t* seconds, - OrbisKernelTimezone* timezone, int* dst_seconds); +s32 PS4_SYSV_ABI sceKernelConvertLocaltimeToUtc(time_t param_1, int64_t param_2, time_t* seconds, + OrbisKernelTimezone* timezone, s32* dst_seconds); -int PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time, OrbisTimesec* st, +s32 PS4_SYSV_ABI sceKernelConvertUtcToLocaltime(time_t time, time_t* local_time, OrbisTimesec* st, u64* dst_sec); -int PS4_SYSV_ABI sceKernelUsleep(u32 microseconds); +s32 PS4_SYSV_ABI sceKernelUsleep(u32 microseconds); void RegisterTime(Core::Loader::SymbolsResolver* sym); From 18ff65efc81bce0c2bb731418aac157a67f347fb Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Fri, 16 May 2025 07:38:40 -0500 Subject: [PATCH 17/45] Implement sceKernelMapDirectMemory2 (#2940) * Implement sceKernelMapDirectMemory2 Behaves similarly to sceKernelMapDirectMemory, but has a type parameter. * Simplify No need to copy all the MapDirectMemory code over, can just call the function, then do the SetDirectMemoryType call * Clang --- src/core/libraries/kernel/memory.cpp | 14 ++++++++++++++ src/core/memory.cpp | 13 +++++++++++++ src/core/memory.h | 2 ++ 3 files changed, 29 insertions(+) diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index bddfcfc84..cb41a664a 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -209,6 +209,19 @@ int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int fl "anon"); } +s32 PS4_SYSV_ABI sceKernelMapDirectMemory2(void** addr, u64 len, s32 type, s32 prot, s32 flags, + s64 phys_addr, u64 alignment) { + LOG_INFO(Kernel_Vmm, "called, redirected to sceKernelMapNamedDirectMemory"); + const s32 ret = + sceKernelMapNamedDirectMemory(addr, len, prot, flags, phys_addr, alignment, "anon"); + + if (ret == 0) { + auto* memory = Core::Memory::Instance(); + memory->SetDirectMemoryType(phys_addr, type); + } + return ret; +} + s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, std::size_t len, int prot, int flags, const char* name) { @@ -645,6 +658,7 @@ void RegisterMemory(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("yDBwVAolDgg", "libkernel", 1, "libkernel", 1, 1, sceKernelIsStack); LIB_FUNCTION("NcaWUxfMNIQ", "libkernel", 1, "libkernel", 1, 1, sceKernelMapNamedDirectMemory); LIB_FUNCTION("L-Q3LEjIbgA", "libkernel", 1, "libkernel", 1, 1, sceKernelMapDirectMemory); + LIB_FUNCTION("BQQniolj9tQ", "libkernel", 1, "libkernel", 1, 1, sceKernelMapDirectMemory2); LIB_FUNCTION("WFcfL2lzido", "libkernel", 1, "libkernel", 1, 1, sceKernelQueryMemoryProtection); LIB_FUNCTION("BHouLQzh0X0", "libkernel", 1, "libkernel", 1, 1, sceKernelDirectMemoryQuery); LIB_FUNCTION("MBuItvba6z8", "libkernel", 1, "libkernel", 1, 1, sceKernelReleaseDirectMemory); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index ec03d6c5e..13290336c 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -783,6 +783,19 @@ int MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, si return ORBIS_OK; } +s32 MemoryManager::SetDirectMemoryType(s64 phys_addr, s32 memory_type) { + std::scoped_lock lk{mutex}; + + auto& dmem_area = FindDmemArea(phys_addr)->second; + + ASSERT_MSG(phys_addr <= dmem_area.GetEnd() && !dmem_area.is_free, + "Direct memory area is not mapped"); + + dmem_area.memory_type = memory_type; + + return ORBIS_OK; +} + void MemoryManager::NameVirtualRange(VAddr virtual_addr, size_t size, std::string_view name) { auto it = FindVMA(virtual_addr); diff --git a/src/core/memory.h b/src/core/memory.h index 4c143ff6f..883b48854 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -219,6 +219,8 @@ public: int GetDirectMemoryType(PAddr addr, int* directMemoryTypeOut, void** directMemoryStartOut, void** directMemoryEndOut); + s32 SetDirectMemoryType(s64 phys_addr, s32 memory_type); + void NameVirtualRange(VAddr virtual_addr, size_t size, std::string_view name); void InvalidateMemory(VAddr addr, u64 size) const; From 7de6aec337b68d01c88fa2696e4f3cd7e27ed7a6 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Fri, 16 May 2025 16:17:37 +0300 Subject: [PATCH 18/45] [Libs] Companion httpd (#2942) * stubbed companion httpd * sceCompanionHttpdGetEvent returns disconnected device * more function definations * added error codes file --- CMakeLists.txt | 5 + src/common/logging/filter.cpp | 1 + src/common/logging/types.h | 1 + .../libraries/companion/companion_error.h | 20 +++ .../libraries/companion/companion_httpd.cpp | 142 ++++++++++++++++++ .../libraries/companion/companion_httpd.h | 91 +++++++++++ src/core/libraries/libs.cpp | 2 + 7 files changed, 262 insertions(+) create mode 100644 src/core/libraries/companion/companion_error.h create mode 100644 src/core/libraries/companion/companion_httpd.cpp create mode 100644 src/core/libraries/companion/companion_httpd.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c5dde7bf..0e14b8467 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -599,6 +599,10 @@ set(CAMERA_LIBS src/core/libraries/camera/camera.cpp src/core/libraries/camera/camera_error.h ) +set(COMPANION_LIBS src/core/libraries/companion/companion_httpd.cpp + src/core/libraries/companion/companion_httpd.h + src/core/libraries/companion/companion_error.h +) set(DEV_TOOLS src/core/devtools/layer.cpp src/core/devtools/layer.h src/core/devtools/options.cpp @@ -763,6 +767,7 @@ set(CORE src/core/aerolib/stubs.cpp ${VDEC_LIB} ${VR_LIBS} ${CAMERA_LIBS} + ${COMPANION_LIBS} ${DEV_TOOLS} src/core/debug_state.cpp src/core/debug_state.h diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index 1b605e9ed..231cbf849 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -139,6 +139,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, Hmd) \ SUB(Lib, SigninDialog) \ SUB(Lib, Camera) \ + SUB(Lib, CompanionHttpd) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Vulkan) \ diff --git a/src/common/logging/types.h b/src/common/logging/types.h index 5746b648e..e4eae59af 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -106,6 +106,7 @@ enum class Class : u8 { Lib_Hmd, ///< The LibSceHmd implementation. Lib_SigninDialog, ///< The LibSigninDialog implementation. Lib_Camera, ///< The LibCamera implementation. + Lib_CompanionHttpd, ///< The LibCompanionHttpd implementation. Frontend, ///< Emulator UI Render, ///< Video Core Render_Vulkan, ///< Vulkan backend diff --git a/src/core/libraries/companion/companion_error.h b/src/core/libraries/companion/companion_error.h new file mode 100644 index 000000000..2d1a3833c --- /dev/null +++ b/src/core/libraries/companion/companion_error.h @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +// companion_httpd error codes +constexpr int ORBIS_COMPANION_HTTPD_ERROR_UNKNOWN = 0x80E40001; +constexpr int ORBIS_COMPANION_HTTPD_ERROR_FATAL = 0x80E40002; +constexpr int ORBIS_COMPANION_HTTPD_ERROR_NOMEM = 0x80E40003; +constexpr int ORBIS_COMPANION_HTTPD_ERROR_INVALID_PARAM = 0x80E40004; +constexpr int ORBIS_COMPANION_HTTPD_ERROR_INVALID_OPERATION = 0x80E40005; +constexpr int ORBIS_COMPANION_HTTPD_ERROR_NOT_INITIALIZED = 0x80E40006; +constexpr int ORBIS_COMPANION_HTTPD_ERROR_ALREADY_INITIALIZED = 0x80E40007; +constexpr int ORBIS_COMPANION_HTTPD_ERROR_NO_EVENT = 0x80E40008; +constexpr int ORBIS_COMPANION_HTTPD_ERROR_NOT_GENERATE_RESPONSE = 0x80E40009; +constexpr int ORBIS_COMPANION_HTTPD_ERROR_ALREADY_STARTED = 0x80E4000A; +constexpr int ORBIS_COMPANION_HTTPD_ERROR_NOT_STARTED = 0x80E4000B; +constexpr int ORBIS_COMPANION_HTTPD_ERROR_ALREADY_REGISTERED = 0x80E4000; +constexpr int ORBIS_COMPANION_HTTPD_ERROR_NOT_CONNECTED = 0x80E4000D; +constexpr int ORBIS_COMPANION_HTTPD_ERROR_USER_NOT_FOUND = 0x80E4000E; diff --git a/src/core/libraries/companion/companion_httpd.cpp b/src/core/libraries/companion/companion_httpd.cpp new file mode 100644 index 000000000..39081fa4e --- /dev/null +++ b/src/core/libraries/companion/companion_httpd.cpp @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/logging/log.h" +#include "companion_error.h" +#include "core/libraries/companion/companion_httpd.h" +#include "core/libraries/error_codes.h" +#include "core/libraries/libs.h" + +namespace Libraries::CompanionHttpd { + +s32 PS4_SYSV_ABI sceCompanionHttpdAddHeader(const char* key, const char* value, + OrbisCompanionHttpdResponse* response) { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI +sceCompanionHttpdGet2ndScreenStatus(Libraries::UserService::OrbisUserServiceUserId) { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceCompanionHttpdGetEvent(OrbisCompanionHttpdEvent* pEvent) { + pEvent->event = ORBIS_COMPANION_HTTPD_EVENT_DISCONNECT; // disconnected + LOG_DEBUG(Lib_CompanionHttpd, "device disconnected"); + return ORBIS_COMPANION_HTTPD_ERROR_NO_EVENT; // No events to obtain +} + +s32 PS4_SYSV_ABI +sceCompanionHttpdGetUserId(u32 addr, Libraries::UserService::OrbisUserServiceUserId* userId) { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceCompanionHttpdInitialize() { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceCompanionHttpdInitialize2() { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceCompanionHttpdOptParamInitialize() { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceCompanionHttpdRegisterRequestBodyReceptionCallback( + OrbisCompanionHttpdRequestBodyReceptionCallback function, void* param) { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI +sceCompanionHttpdRegisterRequestCallback(OrbisCompanionHttpdRequestCallback function, void* param) { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceCompanionHttpdRegisterRequestCallback2( + OrbisCompanionHttpdRequestCallback function, void* param) { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceCompanionHttpdSetBody(const char* body, u64 bodySize, + OrbisCompanionHttpdResponse* response) { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceCompanionHttpdSetStatus(s32 status, OrbisCompanionHttpdResponse* response) { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceCompanionHttpdStart() { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceCompanionHttpdStop() { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceCompanionHttpdTerminate() { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceCompanionHttpdUnregisterRequestBodyReceptionCallback() { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +s32 PS4_SYSV_ABI sceCompanionHttpdUnregisterRequestCallback() { + LOG_ERROR(Lib_CompanionHttpd, "(STUBBED) called"); + return ORBIS_OK; +} + +void RegisterlibSceCompanionHttpd(Core::Loader::SymbolsResolver* sym) { + LIB_FUNCTION("8pWltDG7h6A", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdAddHeader); + LIB_FUNCTION("B-QBMeFdNgY", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdGet2ndScreenStatus); + LIB_FUNCTION("Vku4big+IYM", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdGetEvent); + LIB_FUNCTION("0SySxcuVNG0", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdGetUserId); + LIB_FUNCTION("ykNpWs3ktLY", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdInitialize); + LIB_FUNCTION("OA6FbORefbo", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdInitialize2); + LIB_FUNCTION("r-2-a0c7Kfc", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdOptParamInitialize); + LIB_FUNCTION("fHNmij7kAUM", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdRegisterRequestBodyReceptionCallback); + LIB_FUNCTION("OaWw+IVEdbI", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdRegisterRequestCallback); + LIB_FUNCTION("-0c9TCTwnGs", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdRegisterRequestCallback2); + LIB_FUNCTION("h3OvVxzX4qM", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdSetBody); + LIB_FUNCTION("w7oz0AWHpT4", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdSetStatus); + LIB_FUNCTION("k7F0FcDM-Xc", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdStart); + LIB_FUNCTION("0SCgzfVQHpo", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdStop); + LIB_FUNCTION("+-du9tWgE9s", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdTerminate); + LIB_FUNCTION("ZSHiUfYK+QI", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdUnregisterRequestBodyReceptionCallback); + LIB_FUNCTION("xweOi2QT-BE", "libSceCompanionHttpd", 1, "libSceCompanionHttpd", 1, 1, + sceCompanionHttpdUnregisterRequestCallback); +}; + +} // namespace Libraries::CompanionHttpd \ No newline at end of file diff --git a/src/core/libraries/companion/companion_httpd.h b/src/core/libraries/companion/companion_httpd.h new file mode 100644 index 000000000..b6d441653 --- /dev/null +++ b/src/core/libraries/companion/companion_httpd.h @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/types.h" +#include "core/libraries/network/net.h" +#include "core/libraries/system/userservice.h" + +namespace Core::Loader { +class SymbolsResolver; +} + +namespace Libraries::CompanionHttpd { + +// OrbisCompanionHttpdEvent event codes +constexpr int ORBIS_COMPANION_HTTPD_EVENT_CONNECT = 0x10000001; +constexpr int ORBIS_COMPANION_HTTPD_EVENT_DISCONNECT = 0x10000002; + +struct OrbisCompanionHttpdHeader { + char* key; + char* value; + struct OrbisCompanionHttpdHeader* header; +}; + +struct OrbisCompanionHttpdRequest { + s32 method; + char* url; + OrbisCompanionHttpdHeader* header; + char* body; + u64 bodySize; +}; + +struct OrbisCompanionHttpdResponse { + s32 status; + OrbisCompanionHttpdHeader* header; + char* body; + u64 bodySize; +}; + +using OrbisCompanionHttpdRequestBodyReceptionCallback = + PS4_SYSV_ABI s32 (*)(s32 event, Libraries::UserService::OrbisUserServiceUserId userId, + const OrbisCompanionHttpdRequest* httpRequest, void* param); + +using OrbisCompanionHttpdRequestCallback = + PS4_SYSV_ABI s32 (*)(Libraries::UserService::OrbisUserServiceUserId userId, + const OrbisCompanionHttpdRequest* httpRequest, + OrbisCompanionHttpdResponse* httpResponse, void* param); + +struct OrbisCompanionUtilDeviceInfo { + Libraries::UserService::OrbisUserServiceUserId userId; + Libraries::Net::OrbisNetSockaddrIn addr; + char reserved[236]; +}; + +struct OrbisCompanionHttpdEvent { + s32 event; + union { + OrbisCompanionUtilDeviceInfo deviceInfo; + Libraries::UserService::OrbisUserServiceUserId userId; + char reserved[256]; + } data; +}; + +s32 PS4_SYSV_ABI sceCompanionHttpdAddHeader(const char* key, const char* value, + OrbisCompanionHttpdResponse* response); +s32 PS4_SYSV_ABI +sceCompanionHttpdGet2ndScreenStatus(Libraries::UserService::OrbisUserServiceUserId userId); +s32 PS4_SYSV_ABI sceCompanionHttpdGetEvent(OrbisCompanionHttpdEvent* pEvent); +s32 PS4_SYSV_ABI sceCompanionHttpdGetUserId(u32 addr, + Libraries::UserService::OrbisUserServiceUserId* userId); +s32 PS4_SYSV_ABI sceCompanionHttpdInitialize(); +s32 PS4_SYSV_ABI sceCompanionHttpdInitialize2(); +s32 PS4_SYSV_ABI sceCompanionHttpdOptParamInitialize(); +s32 PS4_SYSV_ABI sceCompanionHttpdRegisterRequestBodyReceptionCallback( + OrbisCompanionHttpdRequestBodyReceptionCallback function, void* param); +s32 PS4_SYSV_ABI +sceCompanionHttpdRegisterRequestCallback(OrbisCompanionHttpdRequestCallback function, void* param); +s32 PS4_SYSV_ABI +sceCompanionHttpdRegisterRequestCallback2(OrbisCompanionHttpdRequestCallback function, void* param); +s32 PS4_SYSV_ABI sceCompanionHttpdSetBody(const char* body, u64 bodySize, + OrbisCompanionHttpdResponse* response); +s32 PS4_SYSV_ABI sceCompanionHttpdSetStatus(s32 status, OrbisCompanionHttpdResponse* response); +s32 PS4_SYSV_ABI sceCompanionHttpdStart(); +s32 PS4_SYSV_ABI sceCompanionHttpdStop(); +s32 PS4_SYSV_ABI sceCompanionHttpdTerminate(); +s32 PS4_SYSV_ABI sceCompanionHttpdUnregisterRequestBodyReceptionCallback(); +s32 PS4_SYSV_ABI sceCompanionHttpdUnregisterRequestCallback(); + +void RegisterlibSceCompanionHttpd(Core::Loader::SymbolsResolver* sym); +} // namespace Libraries::CompanionHttpd \ No newline at end of file diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 5ef4b259d..2ab46d3a0 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -9,6 +9,7 @@ #include "core/libraries/audio3d/audio3d.h" #include "core/libraries/avplayer/avplayer.h" #include "core/libraries/camera/camera.h" +#include "core/libraries/companion/companion_httpd.h" #include "core/libraries/disc_map/disc_map.h" #include "core/libraries/game_live_streaming/gamelivestreaming.h" #include "core/libraries/gnmdriver/gnmdriver.h" @@ -124,6 +125,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::Ulobjmgr::RegisterlibSceUlobjmgr(sym); Libraries::SigninDialog::RegisterlibSceSigninDialog(sym); Libraries::Camera::RegisterlibSceCamera(sym); + Libraries::CompanionHttpd::RegisterlibSceCompanionHttpd(sym); } } // namespace Libraries From 4d769d9c7ebf14d8e993cb72c565f1d8209c92c0 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Fri, 16 May 2025 09:41:22 -0500 Subject: [PATCH 19/45] Proper error handling for MapMemory errors (#2938) * Properly handle ENOMEM error return in MapMemory Needed for Assassin's Creed Unity to behave properly. * Change error message If I left the message as-is, we'd probably see inexperienced people claiming the assert means your device needs more memory, which is completely false. * Clang You know you're doing something right when Clang complains. * Attempt to handle MemoryMapFlags::NoOverwrite Based on my interpretation of red_prig's descriptions. These changes are untested, as I'm not able to test right now. * Fix flag description * Move overwrite check to while condition --- src/core/memory.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 13290336c..a08f8b0e9 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -376,19 +376,22 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M // To account for this, unmap any reserved areas within this mapping range first. auto unmap_addr = mapped_addr; auto unmap_size = size; - while (!vma.IsMapped() && unmap_addr < mapped_addr + size && remaining_size < size) { + // If flag NoOverwrite is provided, don't overwrite mapped VMAs. + // When it isn't provided, VMAs can be overwritten regardless of if they're mapped. + while ((False(flags & MemoryMapFlags::NoOverwrite) || !vma.IsMapped()) && + unmap_addr < mapped_addr + size && remaining_size < size) { auto unmapped = UnmapBytesFromEntry(unmap_addr, vma, unmap_size); unmap_addr += unmapped; unmap_size -= unmapped; vma = FindVMA(unmap_addr)->second; } - // This should return SCE_KERNEL_ERROR_ENOMEM but rarely happens. vma = FindVMA(mapped_addr)->second; remaining_size = vma.base + vma.size - mapped_addr; - ASSERT_MSG(!vma.IsMapped() && remaining_size >= size, - "Memory region {:#x} to {:#x} isn't free enough to map region {:#x} to {:#x}", - vma.base, vma.base + vma.size, virtual_addr, virtual_addr + size); + if (vma.IsMapped() || remaining_size < size) { + LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at address {:#x}", size, mapped_addr); + return ORBIS_KERNEL_ERROR_ENOMEM; + } } // Find the first free area starting with provided virtual address. From ffd31589cf2c51b744a97456ed3781d319a70f65 Mon Sep 17 00:00:00 2001 From: mailwl Date: Fri, 16 May 2025 19:22:47 +0300 Subject: [PATCH 20/45] Fix reading not existing savedata (#2941) * Fix reading not existing savedata * alloc save memory instead return error * save memory saze to save slot instead of global * remove unused enum * remove unneeded memory clean --- CMakeLists.txt | 1 + src/core/libraries/save_data/save_memory.cpp | 15 +++++----- src/core/libraries/save_data/save_memory.h | 7 +++-- src/core/libraries/save_data/savedata.cpp | 28 +++---------------- src/core/libraries/save_data/savedata_error.h | 27 ++++++++++++++++++ 5 files changed, 44 insertions(+), 34 deletions(-) create mode 100644 src/core/libraries/save_data/savedata_error.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0e14b8467..ef2425aff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -409,6 +409,7 @@ set(SYSTEM_LIBS src/core/libraries/system/commondialog.cpp src/core/libraries/save_data/save_memory.h src/core/libraries/save_data/savedata.cpp src/core/libraries/save_data/savedata.h + src/core/libraries/save_data/savedata_error.h src/core/libraries/save_data/dialog/savedatadialog.cpp src/core/libraries/save_data/dialog/savedatadialog.h src/core/libraries/save_data/dialog/savedatadialog_ui.cpp diff --git a/src/core/libraries/save_data/save_memory.cpp b/src/core/libraries/save_data/save_memory.cpp index 13e122c60..ab3ce2d4f 100644 --- a/src/core/libraries/save_data/save_memory.cpp +++ b/src/core/libraries/save_data/save_memory.cpp @@ -10,15 +10,14 @@ #include #include -#include - -#include "common/assert.h" +#include "boost/icl/concept/interval.hpp" #include "common/elf_info.h" #include "common/logging/log.h" #include "common/path_util.h" #include "common/singleton.h" #include "common/thread.h" #include "core/file_sys/fs.h" +#include "core/libraries/system/msgdialog_ui.h" #include "save_instance.h" using Common::FS::IOFile; @@ -35,11 +34,12 @@ namespace Libraries::SaveData::SaveMemory { static Core::FileSys::MntPoints* g_mnt = Common::Singleton::Instance(); struct SlotData { - OrbisUserServiceUserId user_id; + OrbisUserServiceUserId user_id{}; std::string game_serial; std::filesystem::path folder_path; PSF sfo; std::vector memory_cache; + size_t memory_cache_size{}; }; static std::mutex g_slot_mtx; @@ -97,7 +97,8 @@ std::filesystem::path GetSavePath(OrbisUserServiceUserId user_id, u32 slot_id, return SaveInstance::MakeDirSavePath(user_id, Common::ElfInfo::Instance().GameSerial(), dir); } -size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial) { +size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial, + size_t memory_size) { std::lock_guard lck{g_slot_mtx}; const auto save_dir = GetSavePath(user_id, slot_id, game_serial); @@ -109,6 +110,7 @@ size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_ .folder_path = save_dir, .sfo = {}, .memory_cache = {}, + .memory_cache_size = memory_size, }; SaveInstance::SetupDefaultParamSFO(data.sfo, GetSaveDir(slot_id), std::string{game_serial}); @@ -196,9 +198,9 @@ void ReadMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset) { auto& data = g_attached_slots[slot_id]; auto& memory = data.memory_cache; if (memory.empty()) { // Load file + memory.resize(data.memory_cache_size); IOFile f{data.folder_path / FilenameSaveDataMemory, Common::FS::FileAccessMode::Read}; if (f.IsOpen()) { - memory.resize(f.GetSize()); f.Seek(0); f.ReadSpan(std::span{memory}); } @@ -222,5 +224,4 @@ void WriteMemory(u32 slot_id, void* buf, size_t buf_size, int64_t offset) { Backup::NewRequest(data.user_id, data.game_serial, GetSaveDir(slot_id), Backup::OrbisSaveDataEventType::__DO_NOT_SAVE); } - } // namespace Libraries::SaveData::SaveMemory \ No newline at end of file diff --git a/src/core/libraries/save_data/save_memory.h b/src/core/libraries/save_data/save_memory.h index 681865634..7765b04cd 100644 --- a/src/core/libraries/save_data/save_memory.h +++ b/src/core/libraries/save_data/save_memory.h @@ -4,13 +4,13 @@ #pragma once #include -#include "save_backup.h" +#include "core/libraries/save_data/save_backup.h" class PSF; namespace Libraries::SaveData { using OrbisUserServiceUserId = s32; -} +} // namespace Libraries::SaveData namespace Libraries::SaveData::SaveMemory { @@ -22,7 +22,8 @@ void PersistMemory(u32 slot_id, bool lock = true); std::string_view game_serial); // returns the size of the save memory if exists -size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial); +size_t SetupSaveMemory(OrbisUserServiceUserId user_id, u32 slot_id, std::string_view game_serial, + size_t memory_size); // Write the icon. Set buf to null to read the standard icon. void SetIcon(u32 slot_id, void* buf = nullptr, size_t buf_size = 0); diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index e9ad77d69..932bcc1ec 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include "common/assert.h" @@ -20,7 +19,9 @@ #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "core/libraries/save_data/savedata.h" +#include "core/libraries/save_data/savedata_error.h" #include "core/libraries/system/msgdialog.h" +#include "core/libraries/system/msgdialog_ui.h" #include "save_backup.h" #include "save_instance.h" #include "save_memory.h" @@ -33,27 +34,6 @@ using Common::ElfInfo; namespace Libraries::SaveData { -enum class Error : u32 { - OK = 0, - USER_SERVICE_NOT_INITIALIZED = 0x80960002, - PARAMETER = 0x809F0000, - NOT_INITIALIZED = 0x809F0001, - OUT_OF_MEMORY = 0x809F0002, - BUSY = 0x809F0003, - NOT_MOUNTED = 0x809F0004, - EXISTS = 0x809F0007, - NOT_FOUND = 0x809F0008, - NO_SPACE_FS = 0x809F000A, - INTERNAL = 0x809F000B, - MOUNT_FULL = 0x809F000C, - BAD_MOUNTED = 0x809F000D, - BROKEN = 0x809F000F, - INVALID_LOGIN_USER = 0x809F0011, - MEMORY_NOT_READY = 0x809F0012, - BACKUP_BUSY = 0x809F0013, - BUSY_FOR_SAVING = 0x809F0016, -}; - enum class OrbisSaveDataSaveDataMemoryOption : u32 { NONE = 0, SET_PARAM = 1 << 0, @@ -1593,8 +1573,8 @@ Error PS4_SYSV_ABI sceSaveDataSetupSaveDataMemory2(const OrbisSaveDataMemorySetu } try { - size_t existed_size = - SaveMemory::SetupSaveMemory(setupParam->userId, slot_id, g_game_serial); + size_t existed_size = SaveMemory::SetupSaveMemory(setupParam->userId, slot_id, + g_game_serial, setupParam->memorySize); if (existed_size == 0) { // Just created if (g_fw_ver >= ElfInfo::FW_45 && setupParam->initParam != nullptr) { auto& sfo = SaveMemory::GetParamSFO(slot_id); diff --git a/src/core/libraries/save_data/savedata_error.h b/src/core/libraries/save_data/savedata_error.h new file mode 100644 index 000000000..ef347e855 --- /dev/null +++ b/src/core/libraries/save_data/savedata_error.h @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +namespace Libraries::SaveData { +enum class Error : u32 { + OK = 0, + USER_SERVICE_NOT_INITIALIZED = 0x80960002, + PARAMETER = 0x809F0000, + NOT_INITIALIZED = 0x809F0001, + OUT_OF_MEMORY = 0x809F0002, + BUSY = 0x809F0003, + NOT_MOUNTED = 0x809F0004, + EXISTS = 0x809F0007, + NOT_FOUND = 0x809F0008, + NO_SPACE_FS = 0x809F000A, + INTERNAL = 0x809F000B, + MOUNT_FULL = 0x809F000C, + BAD_MOUNTED = 0x809F000D, + BROKEN = 0x809F000F, + INVALID_LOGIN_USER = 0x809F0011, + MEMORY_NOT_READY = 0x809F0012, + BACKUP_BUSY = 0x809F0013, + BUSY_FOR_SAVING = 0x809F0016, +}; +} // namespace Libraries::SaveData From a0acb471850647193e1ace1c98de16573645fe15 Mon Sep 17 00:00:00 2001 From: Stephen Miller <56742918+StevenMiller123@users.noreply.github.com> Date: Fri, 16 May 2025 16:21:13 -0500 Subject: [PATCH 21/45] Only align when flags Fixed is not present (#2945) Should fix some remaining memory issues. --- src/core/memory.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/core/memory.cpp b/src/core/memory.cpp index a08f8b0e9..ca6a0d6cd 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -362,11 +362,8 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M return ORBIS_KERNEL_ERROR_ENOMEM; } - // When virtual addr is zero, force it to virtual_base. The guest cannot pass Fixed - // flag so we will take the branch that searches for free (or reserved) mappings. - virtual_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr; - alignment = alignment > 0 ? alignment : 16_KB; - VAddr mapped_addr = alignment > 0 ? Common::AlignUp(virtual_addr, alignment) : virtual_addr; + // Limit the minumum address to SystemManagedVirtualBase to prevent hardware-specific issues. + VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr; // Fixed mapping means the virtual address must exactly match the provided one. if (True(flags & MemoryMapFlags::Fixed)) { @@ -396,7 +393,9 @@ int MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, size_t size, M // Find the first free area starting with provided virtual address. if (False(flags & MemoryMapFlags::Fixed)) { - mapped_addr = SearchFree(mapped_addr, size, alignment); + // Provided address needs to be aligned before we can map. + alignment = alignment > 0 ? alignment : 16_KB; + mapped_addr = SearchFree(Common::AlignUp(mapped_addr, alignment), size, alignment); if (mapped_addr == -1) { // No suitable memory areas to map to return ORBIS_KERNEL_ERROR_ENOMEM; From 251d0f0d7cc9bebc1ed80629abc075cdad3f386a Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sat, 17 May 2025 11:38:04 +0300 Subject: [PATCH 22/45] [ci skip] Qt GUI: Update Translation. (#2910) Co-authored-by: georgemoralis <4313123+georgemoralis@users.noreply.github.com> --- src/qt_gui/translations/en_US.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index 780f089e8..cc854120f 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -1347,10 +1347,6 @@ Game List Game List - - * Unsupported Vulkan Version - * Unsupported Vulkan Version - Download Cheats For All Installed Games Download Cheats For All Installed Games @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer From a09f7158b899dfeb3a0e54d91d358f3f3bfa6643 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sat, 17 May 2025 11:38:38 +0300 Subject: [PATCH 23/45] New translations en_us.ts (Korean) (#2943) --- src/qt_gui/translations/ko_KR.ts | 206 +++++++++++++++---------------- 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index b79959d38..b735a0d49 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -7,15 +7,15 @@ AboutDialog About shadPS4 - About shadPS4 + shadPS4에 관하여 shadPS4 is an experimental open-source emulator for the PlayStation 4. - shadPS4 is an experimental open-source emulator for the PlayStation 4. + shadPS4는 PlayStation 4용 실험적인 오픈 소스 에뮬레이터입니다. This software should not be used to play games you have not legally obtained. - This software should not be used to play games you have not legally obtained. + 이 소프트웨어는 합법적으로 얻지 않은 게임을 플레이하는 데 사용되어서는 안 됩니다. @@ -26,238 +26,238 @@ Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n - Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n + 치트/패치는 실험적인 기능입니다.\n사용 시 주의하시기 바랍니다.\n\n치트를 개별적으로 다운로드하려면, 저장소를 선택한 후 다운로드 버튼을 클릭하세요.\n패치 탭에서는 모든 패치를 한 번에 다운로드할 수 있으며, 원하는 항목을 선택하고 저장할 수 있습니다.\n\n치트/패치는 저희가 개발한 것이 아니므로,\n문제가 발생하면 해당 치트 제작자에게 문의해 주세요.\n\n새로운 치트를 만들었나요? 방문해 주세요:\n No Image Available - No Image Available + 사용 가능한 이미지 없음 Serial: - Serial: + 시리얼: Version: - Version: + 버전: Size: - Size: + 사이즈: Select Cheat File: - Select Cheat File: + 치트 파일 선택: Repository: - Repository: + 저장소: Download Cheats - Download Cheats + 치트 다운로드 Delete File - Delete File + 파일 삭제 No files selected. - No files selected. + 파일 선택되지 않음. You can delete the cheats you don't want after downloading them. - You can delete the cheats you don't want after downloading them. + 다운로드한 후 원하지 않는 치트는 삭제할 수 있습니다. Do you want to delete the selected file?\n%1 - Do you want to delete the selected file?\n%1 + 선택한 파일을 삭제하시겠습니까?\n%1 Select Patch File: - Select Patch File: + 패치 파일 선택: Download Patches - Download Patches + 패치 다운로드 Save - Save + 저장 Cheats - Cheats + 치트 Patches - Patches + 패치 Error - Error + 오류 No patch selected. - No patch selected. + 패치 선택되지 않음. Unable to open files.json for reading. - Unable to open files.json for reading. + Files.json을 읽기 위해 열 수 없습니다. No patch file found for the current serial. - No patch file found for the current serial. + 현재 시리얼에 해당하는 패치 파일을 찾을 수 없습니다. Unable to open the file for reading. - Unable to open the file for reading. + 파일을 읽기 위해 열 수 없습니다. Unable to open the file for writing. - Unable to open the file for writing. + 파일을 쓰기 위해 열 수 없습니다. Failed to parse XML: - Failed to parse XML: + XML 구문 분석에 실패했습니다: Success - Success + 성공 Options saved successfully. - Options saved successfully. + 옵션이 성공적으로 저장되었습니다. Invalid Source - Invalid Source + 잘못된 출처 The selected source is invalid. - The selected source is invalid. + 선택한 출처가 올바르지 않습니다. File Exists - File Exists + 파일이 이미 존재합니다 File already exists. Do you want to replace it? - File already exists. Do you want to replace it? + 파일이 이미 존재합니다. 덮어쓰시겠습니까? Failed to save file: - Failed to save file: + 파일 저장 실패: Failed to download file: - Failed to download file: + 파일 다운로드 실패: Cheats Not Found - Cheats Not Found + 치트 찾을 수 없음 No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. - No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. + 선택한 저장소의 이 버전에서 해당 게임에 대한 치트를 찾을 수 없습니다. 다른 저장소나 게임의 다른 버전을 시도해 보세요. Cheats Downloaded Successfully - Cheats Downloaded Successfully + 치트가 성공적으로 다운로드되었습니다 You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. - You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. + 선택한 저장소에서 이 게임 버전의 치트를 성공적으로 다운로드했습니다. 다른 저장소에서 다운로드할 수 있는 경우, 목록에서 파일을 선택하여 사용할 수도 있습니다. Failed to save: - Failed to save: + 저장 실패: Failed to download: - Failed to download: + 다운로드 실패: Download Complete - Download Complete + 다운로드 완료 Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. - Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. + 패치가 성공적으로 다운로드되었습니다! 모든 게임에 적용 가능한 모든 패치가 다운로드되었으므로, 치트처럼 각 게임마다 개별적으로 다운로드할 필요가 없습니다. 만약 패치가 나타나지 않는다면, 해당 게임의 특정 시리얼 및 버전에 해당 패치가 없기 때문일 수 있습니다. Failed to parse JSON data from HTML. - Failed to parse JSON data from HTML. + HTML에서 JSON 데이터를 구문 분석하는 데 실패했습니다. Failed to retrieve HTML page. - Failed to retrieve HTML page. + HTML 페이지를 가져오지 못했습니다. The game is in version: %1 - The game is in version: %1 + 게임 버전: %1 The downloaded patch only works on version: %1 - The downloaded patch only works on version: %1 + 다운로드한 패치는 버전 %1 에서만 작동합니다. You may need to update your game. - You may need to update your game. + 게임을 업데이트해야 할 수도 있습니다. Incompatibility Notice - Incompatibility Notice + 호환성 경고 Failed to open file: - Failed to open file: + 파일을 열지 못했습니다: XML ERROR: - XML ERROR: + XML 오류: Failed to open files.json for writing - Failed to open files.json for writing + files.json 파일을 쓰기 위해 열지 못했습니다 Author: - Author: + 제작자: Directory does not exist: - Directory does not exist: + 디렉터리가 존재하지 않습니다: Failed to open files.json for reading. - Failed to open files.json for reading. + files.json 파일을 읽기 위해 열지 못했습니다. Name: - Name: + 이름: Can't apply cheats before the game is started - Can't apply cheats before the game is started + 게임이 시작되기 전에 치트를 적용할 수 없습니다 Close - Close + 닫기 CheckUpdate Auto Updater - Auto Updater + 자동 업데이트 Error - Error + 오류 Network error: - Network error: + 네트워크 오류: The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later. @@ -265,91 +265,91 @@ Failed to parse update information. - Failed to parse update information. + 업데이트 정보 구문 분석에 실패했습니다. No pre-releases found. - No pre-releases found. + 사전 릴리스가 없습니다. Invalid release data. - Invalid release data. + 잘못된 릴리스 데이터입니다. No download URL found for the specified asset. - No download URL found for the specified asset. + 지정된 자산에 대한 다운로드 URL을 찾을 수 없습니다. Your version is already up to date! - Your version is already up to date! + 버전이 이미 최신입니다! Update Available - Update Available + 업데이트 가능 Update Channel - Update Channel + 업데이트 채널 Current Version - Current Version + 현재 버전 Latest Version - Latest Version + 최신 버전 Do you want to update? - Do you want to update? + 업데이트하시겠습니까? Show Changelog - Show Changelog + 변경 사항 보기 Check for Updates at Startup - Check for Updates at Startup + 시작할 때 업데이트 확인 Update - Update + 업데이트 No - No + 아니요 Hide Changelog - Hide Changelog + 변경 사항 숨기기 Changes - Changes + 변경 사항 Network error occurred while trying to access the URL - Network error occurred while trying to access the URL + URL에 접근하는 동안 네트워크 오류가 발생했습니다 Download Complete - Download Complete + 다운로드 완료 The update has been downloaded, press OK to install. - The update has been downloaded, press OK to install. + 업데이트가 다운로드 되었습니다. 설치하려면 확인을 눌러주세요. Failed to save the update file at - Failed to save the update file at + 업데이트 파일을 다음 위치에 저장하지 못했습니다 Starting Update... - Starting Update... + 업데이트를 시작합니다... Failed to create the update script file - Failed to create the update script file + 업데이트 스크립트 파일을 생성하지 못했습니다 @@ -407,83 +407,83 @@ ControlSettings Configure Controls - Configure Controls + 컨트롤 설정 D-Pad - D-Pad + D-패드 Up - Up + Left - Left + 왼쪽 Right - Right + 오른쪽 Down - Down + 아래 Left Stick Deadzone (def:2 max:127) - Left Stick Deadzone (def:2 max:127) + 왼쪽 스틱 데드존 (기본값:2 최대값:127) Left Deadzone - Left Deadzone + 왼쪽 데드존 Left Stick - Left Stick + 왼쪽 스틱 Config Selection - Config Selection + 설정 선택 Common Config - Common Config + 공통 설정 Use per-game configs - Use per-game configs + 게임 별 설정 사용 L1 / LB - L1 / LB + L1 / LB L2 / LT - L2 / LT + L2 / LT Back - Back + 뒤로 R1 / RB - R1 / RB + R1 / RB R2 / RT - R2 / RT + R2 / RT L3 - L3 + L3 Options / Start - Options / Start + 옵션 / 시작 R3 - R3 + R3 Face Buttons From e2513d50bed1aea85d041425c1ce9891d08bac3b Mon Sep 17 00:00:00 2001 From: tlarok <116431383+tlarok@users.noreply.github.com> Date: Sat, 17 May 2025 10:39:35 +0200 Subject: [PATCH 24/45] New kbm icon (#2898) * Update kbm_gui.cpp * Update kbm_gui.cpp * Update kbm_gui.h * Update kbm_gui.cpp * lunix test * linux test * Update kbm_gui.h * Update kbm_gui.cpp * Update kbm_gui.cpp * Update kbm_gui.cpp * Update kbm_gui.cpp * Update kbm_gui.h * Update kbm_gui.cpp * Update kbm_gui.h * Update kbm_gui.cpp * Update kbm_gui.h * Update kbm_gui.cpp * kbm_gui.cpp's names fix just cleaning my code * name fix * Update kbm_gui.cpp * Update kbm_gui.h * Update kbm_gui.cpp * Update src/qt_gui/kbm_gui.cpp Co-authored-by: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> * clean up from main * bruh, welp here we go again * Update KBM.png * resize kbm.png * Update kbm_gui.ui * test update kbm_gui.ui --------- Co-authored-by: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> --- src/images/KBM.png | Bin 241859 -> 136232 bytes src/qt_gui/kbm_gui.ui | 10 ++++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/images/KBM.png b/src/images/KBM.png index 37f52d549da567eb88eae45a60b174def40691b2..feab9fa0ff9803998cdb191d2a26243b843fd7a5 100644 GIT binary patch literal 136232 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 literal 241859 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 + + 48 + Qt::FocusPolicy::NoFocus @@ -841,6 +844,9 @@ 0 + + 48 + Qt::FocusPolicy::NoFocus @@ -983,8 +989,8 @@ - 500 - 200 + 424 + 250 From 5f447a815e1614b791f669c465a05c7279634b5d Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Sat, 17 May 2025 10:41:16 -0300 Subject: [PATCH 25/45] SaveDataDialog: fix possible null access (#2947) --- src/core/libraries/save_data/dialog/savedatadialog_ui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp index edb5caa07..05df67eeb 100644 --- a/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp +++ b/src/core/libraries/save_data/dialog/savedatadialog_ui.cpp @@ -155,7 +155,7 @@ SaveDialogState::SaveDialogState(const OrbisSaveDataDialogParam& param) { if (item->focusPos != FocusPos::DIRNAME) { this->focus_pos = item->focusPos; - } else { + } else if (item->focusPosDirName != nullptr) { this->focus_pos = item->focusPosDirName->data.to_string(); } this->style = item->itemStyle; From 6ee205d4e2b032ab7dfd19986bd9c45ac01bcd4b Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Sat, 17 May 2025 17:33:24 +0300 Subject: [PATCH 26/45] New Crowdin updates (#2946) * New translations en_us.ts (Korean) * New translations en_us.ts (Romanian) * New translations en_us.ts (French) * New translations en_us.ts (Spanish) * New translations en_us.ts (Arabic) * New translations en_us.ts (Danish) * New translations en_us.ts (German) * New translations en_us.ts (Greek) * New translations en_us.ts (Finnish) * New translations en_us.ts (Hungarian) * New translations en_us.ts (Italian) * New translations en_us.ts (Japanese) * New translations en_us.ts (Lithuanian) * New translations en_us.ts (Dutch) * New translations en_us.ts (Polish) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Russian) * New translations en_us.ts (Slovenian) * New translations en_us.ts (Albanian) * New translations en_us.ts (Swedish) * New translations en_us.ts (Turkish) * New translations en_us.ts (Ukrainian) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Chinese Traditional) * New translations en_us.ts (Vietnamese) * New translations en_us.ts (Portuguese, Brazilian) * New translations en_us.ts (Indonesian) * New translations en_us.ts (Persian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (Chinese Simplified) * New translations en_us.ts (Italian) --- src/qt_gui/translations/ar_SA.ts | 8 ++++---- src/qt_gui/translations/da_DK.ts | 8 ++++---- src/qt_gui/translations/de_DE.ts | 8 ++++---- src/qt_gui/translations/el_GR.ts | 8 ++++---- src/qt_gui/translations/es_ES.ts | 8 ++++---- src/qt_gui/translations/fa_IR.ts | 8 ++++---- src/qt_gui/translations/fi_FI.ts | 8 ++++---- src/qt_gui/translations/fr_FR.ts | 8 ++++---- src/qt_gui/translations/hu_HU.ts | 8 ++++---- src/qt_gui/translations/id_ID.ts | 8 ++++---- src/qt_gui/translations/it_IT.ts | 8 ++++---- src/qt_gui/translations/ja_JP.ts | 8 ++++---- src/qt_gui/translations/ko_KR.ts | 8 ++++---- src/qt_gui/translations/lt_LT.ts | 8 ++++---- src/qt_gui/translations/nb_NO.ts | 8 ++++---- src/qt_gui/translations/nl_NL.ts | 8 ++++---- src/qt_gui/translations/pl_PL.ts | 8 ++++---- src/qt_gui/translations/pt_BR.ts | 8 ++++---- src/qt_gui/translations/pt_PT.ts | 8 ++++---- src/qt_gui/translations/ro_RO.ts | 8 ++++---- src/qt_gui/translations/ru_RU.ts | 8 ++++---- src/qt_gui/translations/sl_SI.ts | 8 ++++---- src/qt_gui/translations/sq_AL.ts | 8 ++++---- src/qt_gui/translations/sv_SE.ts | 8 ++++---- src/qt_gui/translations/tr_TR.ts | 8 ++++---- src/qt_gui/translations/uk_UA.ts | 8 ++++---- src/qt_gui/translations/vi_VN.ts | 8 ++++---- src/qt_gui/translations/zh_CN.ts | 8 ++++---- src/qt_gui/translations/zh_TW.ts | 8 ++++---- 29 files changed, 116 insertions(+), 116 deletions(-) diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index e434b3259..26e768720 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -1347,10 +1347,6 @@ Game List قائمة الألعاب - - * Unsupported Vulkan Version - * إصدار Vulkan غير مدعوم - Download Cheats For All Installed Games تحميل الشفرات لجميع الألعاب المثبتة @@ -2051,6 +2047,10 @@ Nightly: نُسخ تحتوي على أحدث الميزات، لكنها أقل Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. افتح مجلد الصور/الأصوات الخاصة بالجوائز المخصصة:\nيمكنك إضافة صور مخصصة للجوائز وصوت مرفق.\nأضف الملفات إلى مجلد custom_trophy بالأسماء التالية:\ntrophy.wav أو trophy.mp3، bronze.png، gold.png، platinum.png، silver.png\nملاحظة: الصوت سيعمل فقط في الإصدارات التي تستخدم QT. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 131a989e1..1023c584b 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -1347,10 +1347,6 @@ Game List Spiloversigt - - * Unsupported Vulkan Version - * Ikke understøttet Vulkan-version - Download Cheats For All Installed Games Hent snyd til alle installerede spil @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index c7a18dd99..1d44eb717 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -1347,10 +1347,6 @@ Game List Spieleliste - - * Unsupported Vulkan Version - * Nicht unterstützte Vulkan-Version - Download Cheats For All Installed Games Cheats für alle installierten Spiele herunterladen @@ -2054,6 +2050,10 @@ Fügen Sie die Dateien dem Ordner custom_trophy mit folgenden Namen hinzu:\n trophy.wav ODER trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\n Hinweis: Der Sound funktioniert nur in Qt-Versionen. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index c91e0c731..765185c9e 100644 --- a/src/qt_gui/translations/el_GR.ts +++ b/src/qt_gui/translations/el_GR.ts @@ -1347,10 +1347,6 @@ Game List Λίστα παιχνιδιών - - * Unsupported Vulkan Version - * Μη υποστηριζόμενη έκδοση Vulkan - Download Cheats For All Installed Games Λήψη Cheats για όλα τα εγκατεστημένα παιχνίδια @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 035aac6a3..e73386c96 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -1347,10 +1347,6 @@ Game List Lista de Juegos - - * Unsupported Vulkan Version - * Versión de Vulkan no soportada - Download Cheats For All Installed Games Descargar trucos para todos los juegos instalados @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Abre la carpeta de trofeos/sonidos personalizados:\nPuedes añadir imágenes y un audio personalizados a los trofeos.\nAñade los archivos a custom_trophy con los siguientes nombres:\ntrophy.wav o trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNota: El sonido sólo funcionará en versiones QT. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 552a0ff23..b9c2282fa 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -1347,10 +1347,6 @@ Game List لیست بازی - - * Unsupported Vulkan Version - شما پشتیبانی نمیشود Vulkan ورژن * - Download Cheats For All Installed Games دانلود چیت برای همه بازی ها @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index 44c668560..c77c63b3e 100644 --- a/src/qt_gui/translations/fi_FI.ts +++ b/src/qt_gui/translations/fi_FI.ts @@ -1347,10 +1347,6 @@ Game List Pelilista - - * Unsupported Vulkan Version - * Ei Tuettu Vulkan-versio - Download Cheats For All Installed Games Lataa Huijaukset Kaikille Asennetuille Peleille @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index 13e1be9f5..7a3f1b51c 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -1347,10 +1347,6 @@ Game List Liste de jeux - - * Unsupported Vulkan Version - * Version de Vulkan non prise en charge - Download Cheats For All Installed Games Télécharger les Cheats pour tous les jeux installés @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Ouvrez le dossier des images/sons des trophées personnalisés:\nVous pouvez ajouter des images personnalisées aux trophées et aux sons.\nAjoutez les fichiers à custom_trophy avec les noms suivants:\ntrophy.wav OU trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote : Le son ne fonctionnera que dans les versions QT. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 58857d0d7..e396cc4f5 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -1347,10 +1347,6 @@ Game List Játéklista - - * Unsupported Vulkan Version - * Nem támogatott Vulkan verzió - Download Cheats For All Installed Games Csalások letöltése minden telepített játékhoz @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index de19824f7..b4fe48637 100644 --- a/src/qt_gui/translations/id_ID.ts +++ b/src/qt_gui/translations/id_ID.ts @@ -1347,10 +1347,6 @@ Game List Daftar game - - * Unsupported Vulkan Version - * Versi Vulkan Tidak Didukung - Download Cheats For All Installed Games Unduh Cheat Untuk Semua Game Yang Terpasang @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index 8c9e53611..a1aa0bb3f 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -1347,10 +1347,6 @@ Game List Elenco giochi - - * Unsupported Vulkan Version - * Versione Vulkan non supportata - Download Cheats For All Installed Games Scarica Trucchi per tutti i giochi installati @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Apri la cartella personalizzata delle immagini/suoni trofei:\nÈ possibile aggiungere immagini personalizzate ai trofei e un audio.\nAggiungi i file a custom_trophy con i seguenti nomi:\ntrophy.wav OPPURE trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNota: Il suono funzionerà solo nelle versioni QT. + + * Unsupported Vulkan Version + * Versione Vulkan non supportata + TrophyViewer diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 146caa515..7bd7fed8a 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -1347,10 +1347,6 @@ Game List ゲームリスト - - * Unsupported Vulkan Version - * サポートされていないVulkanバージョン - Download Cheats For All Installed Games すべてのインストール済みゲームのチートをダウンロード @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index b735a0d49..6e5b232a0 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -1347,10 +1347,6 @@ Game List Game List - - * Unsupported Vulkan Version - * Unsupported Vulkan Version - Download Cheats For All Installed Games Download Cheats For All Installed Games @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 03ff5a003..a0b047dbb 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -1347,10 +1347,6 @@ Game List Žaidimų sąrašas - - * Unsupported Vulkan Version - * Nepalaikoma Vulkan versija - Download Cheats For All Installed Games Atsisiųsti sukčiavimus visiems įdiegtiems žaidimams @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index e937287fd..856755865 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -1347,10 +1347,6 @@ Game List Spilliste - - * Unsupported Vulkan Version - * Ustøttet Vulkan-versjon - Download Cheats For All Installed Games Last ned juks for alle installerte spill @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Åpne mappa med tilpassede bilder og lyder for trofé:\nDu kan legge til tilpassede bilder til trofeer og en lyd.\nLegg filene til custom_trophy med følgende navn:\ntrophy.wav ELLER trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nMerk: Lyden avspilles kun i Qt-versjonen. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index 2d75b74eb..ec676d360 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -1347,10 +1347,6 @@ Game List Lijst met spellen - - * Unsupported Vulkan Version - * Niet ondersteunde Vulkan-versie - Download Cheats For All Installed Games Download cheats voor alle geïnstalleerde spellen @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index bd59a1894..c1343cd2e 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -1347,10 +1347,6 @@ Game List Lista gier - - * Unsupported Vulkan Version - * Nieobsługiwana wersja Vulkan - Download Cheats For All Installed Games Pobierz kody do wszystkich zainstalowanych gier @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Otwórz niestandardowy folder obrazów/dźwięków:\nMożesz dodać własne obrazy dla trofeów i ich dźwięki.\nDodaj pliki do custom_trophy o następujących nazwach:\ntrophy.wav LUB trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nUwaga: Dźwięki działają tylko w wersji QT. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 584d6dc19..34d31f240 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -1347,10 +1347,6 @@ Game List Lista de Jogos - - * Unsupported Vulkan Version - * Versão Vulkan não suportada - Download Cheats For All Installed Games Baixar Trapaças para Todos os Jogos Instalados @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Abrir a pasta de imagens e sons de troféus personalizados:\nVocê pode adicionar imagens personalizadas aos troféus e um áudio.\nAdicione os arquivos na pasta custom_trophy com os seguintes nomes:\ntrophy.wav OU trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nObservação: O som funcionará apenas em versões Qt. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index 70a73afe7..bfc3900dc 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -1347,10 +1347,6 @@ Game List Lista de Jogos - - * Unsupported Vulkan Version - * Versão do Vulkan não suportada - Download Cheats For All Installed Games Transferir Cheats para Todos os Jogos Instalados @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Abra a pasta de imagens/sons de troféus personalizados:\nPoderá adicionar imagens personalizadas aos troféus e um áudio.\nAdicione os arquivos na pasta custom_trophy com os seguintes nomes:\ntrophy.mp3 ou trophy.wav, bronze.png, gold.png, platinum.png, silver.png\nObservação: O som funcionará apenas nas versões Qt. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 78dd79c53..a05bb06a8 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -1347,10 +1347,6 @@ Game List Lista jocurilor - - * Unsupported Vulkan Version - * Versiune Vulkan nesuportată - Download Cheats For All Installed Games Descarcă Cheats pentru toate jocurile instalate @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 0f16efc2c..176c6e737 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -1347,10 +1347,6 @@ Game List Список игр - - * Unsupported Vulkan Version - * Неподдерживаемая версия Vulkan - Download Cheats For All Installed Games Скачать читы для всех установленных игр @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Открыть папку с пользовательскими изображениями/звуками трофеев:\nВы можете добавить пользовательские изображения к трофеям и аудио.\nДобавьте файлы в custom_trophy со следующими именами:\ntrophy.wav ИЛИ trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nПримечание: звук будет работать только в QT-версии. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/sl_SI.ts b/src/qt_gui/translations/sl_SI.ts index ab61a5d3a..47c5f5534 100644 --- a/src/qt_gui/translations/sl_SI.ts +++ b/src/qt_gui/translations/sl_SI.ts @@ -1347,10 +1347,6 @@ Game List Game List - - * Unsupported Vulkan Version - * Unsupported Vulkan Version - Download Cheats For All Installed Games Download Cheats For All Installed Games @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index 50314a9b2..003b089f6 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -1347,10 +1347,6 @@ Game List Lista e lojërave - - * Unsupported Vulkan Version - * Version i pambështetur i Vulkan - Download Cheats For All Installed Games Shkarko mashtrime për të gjitha lojërat e instaluara @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Hap dosjen e imazheve/tingujve të trofeve të personalizuar:\nMund të shtosh imazhe të personalizuara për trofetë dhe një audio.\nShto skedarët në dosjen custom_trophy me emrat që vijojnë:\ntrophy.wav ose trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nShënim: Tingulli do të punojë vetëm në versionet QT. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index ce0da785c..9533864b8 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -1347,10 +1347,6 @@ Game List Spellista - - * Unsupported Vulkan Version - * Vulkan-versionen stöds inte - Download Cheats For All Installed Games Hämta fusk för alla installerade spel @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Öppna mappen för anpassade trofébilder/ljud:\nDu kan lägga till egna bilder till troféerna och ett ljud.\nLägg till filerna i custom_trophy med följande namn:\ntrophy.wav ELLER trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nObservera: Ljudet fungerar endast i QT-versioner. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index f5f7b65e5..9539ca139 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -1347,10 +1347,6 @@ Game List Oyun Listesi - - * Unsupported Vulkan Version - * Desteklenmeyen Vulkan Sürümü - Download Cheats For All Installed Games Tüm Yüklenmiş Oyunlar İçin Hileleri İndir @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Özel kupa görüntüleri/sesleri klasörünü aç:\nKupalara özel görüntüler ve sesler ekleyebilirsiniz.\nDosyaları aşağıdaki adlarla custom_trophy'ye ekleyin:\ntrophy.wav ya da trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNot: Ses yalnızca QT sürümlerinde çalışacaktır. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 3eb88bcab..8b83ae62f 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -1347,10 +1347,6 @@ Game List Список ігор - - * Unsupported Vulkan Version - * Непідтримувана версія Vulkan - Download Cheats For All Installed Games Завантажити чити для усіх встановлених ігор @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Відкрити папку користувацьких зображень трофеїв/звуків:\nВи можете додати користувацькі зображення до трофеїв та звук.\nДодайте файли до теки custom_trophy з такими назвами:\ntrophy.wav АБО trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nПримітка: Звук буде працювати лише у версіях ShadPS4 з графічним інтерфейсом. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index c657888bf..1e26f626c 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -1347,10 +1347,6 @@ Game List Danh sách trò chơi - - * Unsupported Vulkan Version - * Phiên bản Vulkan không được hỗ trợ - Download Cheats For All Installed Games Tải xuống cheat cho tất cả các trò chơi đã cài đặt @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 120310810..2bc635c41 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -1347,10 +1347,6 @@ Game List 游戏列表 - - * Unsupported Vulkan Version - * 不支持的 Vulkan 版本 - Download Cheats For All Installed Games 下载所有已安装游戏的作弊码 @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. 打开自定义奖杯图像/声音文件夹:\n您可以自定义奖杯图像和声音。\n将文件添加到 custom_trophy 文件夹中,文件名如下:\ntrophy.wav 或 trophy.mp3、bronze.png、gold.png、platinum.png、silver.png。\n注意:自定义声音只能在 QT 版本中生效。 + + * Unsupported Vulkan Version + * 不支持的 Vulkan 版本 + TrophyViewer diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index bd051651d..320f73c83 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -1347,10 +1347,6 @@ Game List 遊戲列表 - - * Unsupported Vulkan Version - * 不支援的 Vulkan 版本 - Download Cheats For All Installed Games 下載所有已安裝遊戲的金手指 @@ -2050,6 +2046,10 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. 開啟自訂獎盃影像/聲音資料夾:\n您可以將自訂影像新增至獎盃和音訊。 \n將檔案加入 custom_tropy,名稱如下:\ntropy.wav OR trophy.mp3、bronze.png、gold.png、platinum.png、silver.png\n注意:聲音僅在 QT 版本中有效。 + + * Unsupported Vulkan Version + * Unsupported Vulkan Version + TrophyViewer From 5eb58799fe937443542f9dac231bb236969c7fa3 Mon Sep 17 00:00:00 2001 From: Vinicius Rangel Date: Sat, 17 May 2025 12:00:35 -0300 Subject: [PATCH 27/45] SaveData: respect install dir in param.sfo to select the game save folder (#2951) --- src/core/libraries/save_data/savedata.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 932bcc1ec..0731392cd 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -316,7 +316,9 @@ static std::array, 16> g_mount_slots; static void initialize() { g_initialized = true; - g_game_serial = ElfInfo::Instance().GameSerial(); + g_game_serial = Common::Singleton::Instance() + ->GetString("INSTALL_DIR_SAVEDATA") + .value_or(ElfInfo::Instance().GameSerial()); g_fw_ver = ElfInfo::Instance().FirmwareVer(); Backup::StartThread(); } From 034ae8cffac3bd7534f2a9481d06ccaa0e886938 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Sun, 18 May 2025 13:16:31 -0700 Subject: [PATCH 28/45] qt: Update save data dir open to use name from PSF. (#2954) --- src/qt_gui/game_info.h | 5 +++++ src/qt_gui/game_list_utils.h | 1 + src/qt_gui/gui_context_menus.h | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/qt_gui/game_info.h b/src/qt_gui/game_info.h index 09e5a4557..723142e1c 100644 --- a/src/qt_gui/game_info.h +++ b/src/qt_gui/game_info.h @@ -79,6 +79,11 @@ public: if (const auto play_time = psf.GetString("PLAY_TIME"); play_time.has_value()) { game.play_time = *play_time; } + if (const auto save_dir = psf.GetString("INSTALL_DIR_SAVEDATA"); save_dir.has_value()) { + game.save_dir = *save_dir; + } else { + game.save_dir = game.serial; + } } return game; } diff --git a/src/qt_gui/game_list_utils.h b/src/qt_gui/game_list_utils.h index 804f0e4b7..e19cf364e 100644 --- a/src/qt_gui/game_list_utils.h +++ b/src/qt_gui/game_list_utils.h @@ -25,6 +25,7 @@ struct GameInfo { std::string version = "Unknown"; std::string region = "Unknown"; std::string fw = "Unknown"; + std::string save_dir = "Unknown"; std::string play_time = "Unknown"; CompatibilityEntry compatibility = CompatibilityEntry{CompatibilityStatus::Unknown}; diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 2fd4588d2..f435a3e38 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -160,7 +160,7 @@ public: Common::FS::PathToQString(userPath, Common::FS::GetUserPath(Common::FS::PathType::UserDir)); QString saveDataPath = - userPath + "/savedata/1/" + QString::fromStdString(m_games[itemID].serial); + userPath + "/savedata/1/" + QString::fromStdString(m_games[itemID].save_dir); QDir(saveDataPath).mkpath(saveDataPath); QDesktopServices::openUrl(QUrl::fromLocalFile(saveDataPath)); } From 60224e1d2278db88162c85ec93192be5da8ecb4d Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Wed, 21 May 2025 13:57:52 +0300 Subject: [PATCH 29/45] New Crowdin updates (#2953) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Portuguese) * New translations en_us.ts (Albanian) * New translations en_us.ts (Russian) * New translations en_us.ts (Norwegian Bokmal) * New translations en_us.ts (French) * New translations en_us.ts (Swedish) * New translations en_us.ts (Norwegian Bokmal) --- src/qt_gui/translations/fr_FR.ts | 2 +- src/qt_gui/translations/nb_NO.ts | 12 +++---- src/qt_gui/translations/pt_PT.ts | 54 ++++++++++++++++---------------- src/qt_gui/translations/ru_RU.ts | 2 +- src/qt_gui/translations/sq_AL.ts | 2 +- src/qt_gui/translations/sv_SE.ts | 2 +- 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index 7a3f1b51c..75e424ad0 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -2048,7 +2048,7 @@ * Unsupported Vulkan Version - * Unsupported Vulkan Version + * Version de Vulkan non prise en charge diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index 856755865..cb209cfb1 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -1253,11 +1253,11 @@ List View - Liste-visning + Listevisning Grid View - Rute-visning + Rutenettvisning Elf Viewer @@ -1756,7 +1756,7 @@ Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. - Loggfilter:\nFiltrerer loggen for å kun skrive ut spesifikk informasjon.\nEksempler: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" \nNivåer: Trace, Debug, Info, Warning, Error, Critical - i denne rekkefølgen, et spesifikt nivå demper alle tidligere nivåer i lista og loggfører alle nivåer etter det. + Loggfilter:\nFiltrerer loggen for å kun skrive ut spesifikk informasjon.\nEksempler: «Core:Trace» «Lib.Pad:Debug Common.Filesystem:Error» «*:Critical» \nNivåer: Trace, Debug, Info, Warning, Error, Critical - i denne rekkefølgen, et spesifikt nivå demper alle tidligere nivåer i lista og loggfører alle nivåer etter det. Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. @@ -1788,7 +1788,7 @@ Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. - Vis kompatibilitets-data:\nViser informasjon om spillkompatibilitet i tabellvisning. Bruk "Oppdater kompatibilitets-data ved oppstart" for oppdatert informasjon. + Vis kompatibilitets-data:\nViser informasjon om spillkompatibilitet i tabellvisning. Bruk «Oppdater database ved oppstart» for oppdatert informasjon. Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. @@ -1828,7 +1828,7 @@ Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. - Grafikkenhet:\nSystemer med flere GPU-er, kan emulatoren velge hvilken enhet som skal brukes fra rullegardinlista,\neller velg "Velg automatisk". + Grafikkenhet:\nSystemer med flere GPU-er, kan emulatoren velge hvilken enhet som skal brukes fra rullegardinlista,\neller bruk «Velg automatisk». Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. @@ -2048,7 +2048,7 @@ * Unsupported Vulkan Version - * Unsupported Vulkan Version + *Ustøttet Vulkan-versjon diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index bfc3900dc..a543d0ec3 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -543,7 +543,7 @@ Unable to Save - Não é possível salvar + Não foi possível guardar Cannot bind axis values more than once @@ -551,7 +551,7 @@ Save - Salvar + Guardar Apply @@ -559,7 +559,7 @@ Restore Defaults - Restaurar o Padrão + Restaurar Predefinições Cancel @@ -570,11 +570,11 @@ EditorDialog Edit Keyboard + Mouse and Controller input bindings - Editar comandos do Teclado + Mouse e do Controle + Editar configurações de entrada do Teclado + Rato e do Comando Use Per-Game configs - Use uma configuração para cada jogo + Utilizar configurações por jogo Error @@ -582,19 +582,19 @@ Could not open the file for reading - Não foi possível abrir o arquivo para ler + Não foi possível abrir o ficheiro para leitura Could not open the file for writing - Não foi possível abrir o arquivo para escrever + Não foi possível abrir o ficheiro para escrita Save Changes - Salvar mudanças + Guardar as alterações Do you want to save changes? - Salvar as mudanças? + Pretende guardar as alterações? Help @@ -610,7 +610,7 @@ Reset to Default - Resetar ao Padrão + Repor para o Padrão @@ -1150,7 +1150,7 @@ Unable to Save - Não é possível salvar + Não foi possível guardar Cannot bind any unique input more than once @@ -1166,11 +1166,11 @@ Mousewheel cannot be mapped to stick outputs - Roda do rato não pode ser mapeada para saídas empates + Roda do rato não pode ser mapeada para saídas dos manípulos Save - Salvar + Guardar Apply @@ -1178,7 +1178,7 @@ Restore Defaults - Restaurar Definições + Restaurar Predefinições Cancel @@ -1405,43 +1405,43 @@ Play - Play + Reproduzir Pause - Pause + Pausa Stop - Stop + Parar Restart - Restart + Reiniciar Full Screen - Full Screen + Ecrã Inteiro Controllers - Controllers + Comandos Keyboard - Keyboard + Teclado Refresh List - Refresh List + Atualizar Lista Resume - Resume + Continuar Show Labels Under Icons - Show Labels Under Icons + Mostrar Etiquetas Debaixo dos Ícones @@ -2028,7 +2028,7 @@ Cannot create portable user folder - Não é possível criar pasta de utilizador portátil + Não foi possível criar pasta de utilizador portátil %1 already exists @@ -2044,11 +2044,11 @@ Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Abra a pasta de imagens/sons de troféus personalizados:\nPoderá adicionar imagens personalizadas aos troféus e um áudio.\nAdicione os arquivos na pasta custom_trophy com os seguintes nomes:\ntrophy.mp3 ou trophy.wav, bronze.png, gold.png, platinum.png, silver.png\nObservação: O som funcionará apenas nas versões Qt. + Abra a pasta de imagens/sons de troféus personalizados:\nPoderá adicionar imagens personalizadas aos troféus e um áudio.\nAdicione os ficheiros na pasta custom_trophy com os seguintes nomes:\ntrophy.mp3 ou trophy.wav, bronze.png, gold.png, platinum.png, silver.png\nObservação: O som funcionará apenas nas versões Qt. * Unsupported Vulkan Version - * Unsupported Vulkan Version + * Versão do Vulkan não suportada diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 176c6e737..a56127ece 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -2048,7 +2048,7 @@ * Unsupported Vulkan Version - * Unsupported Vulkan Version + * Неподдерживаемая версия Vulkan diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index 003b089f6..c554a283a 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -2048,7 +2048,7 @@ * Unsupported Vulkan Version - * Unsupported Vulkan Version + * Version i pambështetur i Vulkan diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index 9533864b8..b8fab701c 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -2048,7 +2048,7 @@ * Unsupported Vulkan Version - * Unsupported Vulkan Version + * Versionen av Vulkan stöds inte From 1b952bf1733de477975c432f22e4aff46e49268b Mon Sep 17 00:00:00 2001 From: Lander Gallastegi Date: Wed, 21 May 2025 18:08:27 +0200 Subject: [PATCH 30/45] ReadConst debug msg (#2964) --- .../backend/spirv/emit_spirv_context_get_set.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index 83e8afd78..6442ae9f8 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" +#include "common/logging/log.h" #include "shader_recompiler/backend/spirv/emit_spirv_instructions.h" #include "shader_recompiler/backend/spirv/spirv_emit_context.h" #include "shader_recompiler/ir/attribute.h" @@ -167,6 +168,7 @@ Id EmitReadConst(EmitContext& ctx, IR::Inst* inst) { const auto& srt_flatbuf = ctx.buffers.back(); ASSERT(srt_flatbuf.binding >= 0 && flatbuf_off_dw > 0 && srt_flatbuf.buffer_type == BufferType::ReadConstUbo); + LOG_DEBUG(Render_Recompiler, "ReadConst from flatbuf dword {}", flatbuf_off_dw); const auto [id, pointer_type] = srt_flatbuf[BufferAlias::U32]; const Id ptr{ ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, ctx.ConstU32(flatbuf_off_dw))}; From eb21083078ea81a750914fd5213a5a25635360bf Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Wed, 21 May 2025 21:22:38 +0200 Subject: [PATCH 31/45] Lower stack size to clear from 13 to 12 KB (#2967) --- src/core/tls.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/tls.h b/src/core/tls.h index e9e2b9e6a..470553d85 100644 --- a/src/core/tls.h +++ b/src/core/tls.h @@ -53,7 +53,7 @@ template ReturnType ExecuteGuest(PS4_SYSV_ABI ReturnType (*func)(FuncArgs...), CallArgs&&... args) { EnsureThreadInitialized(); // clear stack to avoid trash from EnsureThreadInitialized - ClearStack<13_KB>(); + ClearStack<12_KB>(); return func(std::forward(args)...); } From 786ad6f71e736955197155c5e11e0e6857daa88e Mon Sep 17 00:00:00 2001 From: kalaposfos13 <153381648+kalaposfos13@users.noreply.github.com> Date: Wed, 21 May 2025 22:00:11 +0200 Subject: [PATCH 32/45] Fork detection fixes IIIIX (#2966) * Fix local builds * Add remote fork windows title to release builds too * Remove trailing slah from remote links before processing --- CMakeLists.txt | 7 ++++--- src/emulator.cpp | 26 +++++++++++++++++--------- src/qt_gui/main_window.cpp | 25 ++++++++++++++++--------- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ef2425aff..e993061bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -134,6 +134,7 @@ if (GIT_REMOTE_RESULT OR GIT_REMOTE_NAME STREQUAL "") ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE ) + message("got remote: ${GIT_REMOTE_NAME}") endif() # If running in GitHub Actions and the above fails @@ -177,7 +178,7 @@ if (GIT_REMOTE_RESULT OR GIT_REMOTE_NAME STREQUAL "") set(GIT_BRANCH "${GITHUB_BRANCH}") elseif ("${PR_NUMBER}" STREQUAL "" AND NOT "${GITHUB_REF}" STREQUAL "") set(GIT_BRANCH "${GITHUB_REF}") - else() + elseif("${GIT_BRANCH}" STREQUAL "") message("couldn't find branch") set(GIT_BRANCH "detached-head") endif() @@ -186,8 +187,8 @@ else() string(FIND "${GIT_REMOTE_NAME}" "/" INDEX) if (INDEX GREATER -1) string(SUBSTRING "${GIT_REMOTE_NAME}" 0 "${INDEX}" GIT_REMOTE_NAME) - else() - # If no remote is present (only a branch name), default to origin + elseif("${GIT_REMOTE_NAME}" STREQUAL "") + message("reset to origin") set(GIT_REMOTE_NAME "origin") endif() endif() diff --git a/src/emulator.cpp b/src/emulator.cpp index ebb34054b..9a0429d5d 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -190,16 +190,24 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector", id, title, app_version); std::string window_title = ""; - if (Common::g_is_release) { - window_title = fmt::format("shadPS4 v{} | {}", Common::g_version, game_title); - } else { - std::string remote_url(Common::g_scm_remote_url); - std::string remote_host; - try { - remote_host = remote_url.substr(19, remote_url.rfind('/') - 19); - } catch (...) { - remote_host = "unknown"; + std::string remote_url(Common::g_scm_remote_url); + std::string remote_host; + try { + if (*remote_url.rbegin() == '/') { + remote_url.pop_back(); } + remote_host = remote_url.substr(19, remote_url.rfind('/') - 19); + } catch (...) { + remote_host = "unknown"; + } + if (Common::g_is_release) { + if (remote_host == "shadps4-emu" || remote_url.length() == 0) { + window_title = fmt::format("shadPS4 v{} | {}", Common::g_version, game_title); + } else { + window_title = + fmt::format("shadPS4 {}/v{} | {}", remote_host, Common::g_version, game_title); + } + } else { if (remote_host == "shadps4-emu" || remote_url.length() == 0) { window_title = fmt::format("shadPS4 v{} {} {} | {}", Common::g_version, Common::g_scm_branch, Common::g_scm_desc, game_title); diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 8eeec3536..1966aa52b 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -55,16 +55,23 @@ bool MainWindow::Init() { // show ui setMinimumSize(720, 405); std::string window_title = ""; - if (Common::g_is_release) { - window_title = fmt::format("shadPS4 v{}", Common::g_version); - } else { - std::string remote_url(Common::g_scm_remote_url); - std::string remote_host; - try { - remote_host = remote_url.substr(19, remote_url.rfind('/') - 19); - } catch (...) { - remote_host = "unknown"; + std::string remote_url(Common::g_scm_remote_url); + std::string remote_host; + try { + if (*remote_url.rbegin() == '/') { + remote_url.pop_back(); } + remote_host = remote_url.substr(19, remote_url.rfind('/') - 19); + } catch (...) { + remote_host = "unknown"; + } + if (Common::g_is_release) { + if (remote_host == "shadps4-emu" || remote_url.length() == 0) { + window_title = fmt::format("shadPS4 v{}", Common::g_version); + } else { + window_title = fmt::format("shadPS4 {}/v{}", remote_host, Common::g_version); + } + } else { if (remote_host == "shadps4-emu" || remote_url.length() == 0) { window_title = fmt::format("shadPS4 v{} {} {}", Common::g_version, Common::g_scm_branch, Common::g_scm_desc); From 6935b24440044fb093e029adf5402ef12c650cd4 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 22 May 2025 01:28:41 -0700 Subject: [PATCH 33/45] savedata: Fix missing uses of config based save data dir. (#2971) --- src/common/config.cpp | 2 +- src/common/path_util.cpp | 1 - src/common/path_util.h | 2 -- src/core/libraries/save_data/savedata.cpp | 3 ++- src/qt_gui/gui_context_menus.h | 11 ++++------- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 111c0cfa9..6bccd0f37 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -154,7 +154,7 @@ bool GetLoadGameSizeEnabled() { std::filesystem::path GetSaveDataPath() { if (save_data_path.empty()) { - return Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir); + return Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "savedata"; } return save_data_path; } diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index 1a6ff9ec8..3270c24dd 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -128,7 +128,6 @@ static auto UserPaths = [] { create_path(PathType::LogDir, user_dir / LOG_DIR); create_path(PathType::ScreenshotsDir, user_dir / SCREENSHOTS_DIR); create_path(PathType::ShaderDir, user_dir / SHADER_DIR); - create_path(PathType::SaveDataDir, user_dir / SAVEDATA_DIR); create_path(PathType::GameDataDir, user_dir / GAMEDATA_DIR); create_path(PathType::TempDataDir, user_dir / TEMPDATA_DIR); create_path(PathType::SysModuleDir, user_dir / SYSMODULES_DIR); diff --git a/src/common/path_util.h b/src/common/path_util.h index 2fd9b1588..b8053a229 100644 --- a/src/common/path_util.h +++ b/src/common/path_util.h @@ -18,7 +18,6 @@ enum class PathType { LogDir, // Where log files are stored. ScreenshotsDir, // Where screenshots are stored. ShaderDir, // Where shaders are stored. - SaveDataDir, // Where guest save data is stored. TempDataDir, // Where game temp data is stored. GameDataDir, // Where game data is stored. SysModuleDir, // Where system modules are stored. @@ -36,7 +35,6 @@ constexpr auto PORTABLE_DIR = "user"; constexpr auto LOG_DIR = "log"; constexpr auto SCREENSHOTS_DIR = "screenshots"; constexpr auto SHADER_DIR = "shader"; -constexpr auto SAVEDATA_DIR = "savedata"; constexpr auto GAMEDATA_DIR = "data"; constexpr auto TEMPDATA_DIR = "temp"; constexpr auto SYSMODULES_DIR = "sys_modules"; diff --git a/src/core/libraries/save_data/savedata.cpp b/src/core/libraries/save_data/savedata.cpp index 0731392cd..b25ebde6c 100644 --- a/src/core/libraries/save_data/savedata.cpp +++ b/src/core/libraries/save_data/savedata.cpp @@ -8,6 +8,7 @@ #include #include "common/assert.h" +#include "common/config.h" #include "common/cstring.h" #include "common/elf_info.h" #include "common/enum.h" @@ -438,7 +439,7 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info, LOG_INFO(Lib_SaveData, "called with invalid block size"); } - const auto root_save = Common::FS::GetUserPath(Common::FS::PathType::SaveDataDir); + const auto root_save = Config::GetSaveDataPath(); fs::create_directories(root_save); const auto available = fs::space(root_save).available; diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index f435a3e38..46a40c5cd 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -156,11 +156,9 @@ public: } if (selected == openSaveDataFolder) { - QString userPath; - Common::FS::PathToQString(userPath, - Common::FS::GetUserPath(Common::FS::PathType::UserDir)); - QString saveDataPath = - userPath + "/savedata/1/" + QString::fromStdString(m_games[itemID].save_dir); + QString saveDataPath; + Common::FS::PathToQString(saveDataPath, + Config::GetSaveDataPath() / "1" / m_games[itemID].save_dir); QDir(saveDataPath).mkpath(saveDataPath); QDesktopServices::openUrl(QUrl::fromLocalFile(saveDataPath)); } @@ -485,8 +483,7 @@ public: dlc_path, Config::getAddonInstallDir() / Common::FS::PathFromQString(folder_path).parent_path().filename()); Common::FS::PathToQString(save_data_path, - Common::FS::GetUserPath(Common::FS::PathType::UserDir) / - "savedata/1" / m_games[itemID].serial); + Config::GetSaveDataPath() / "1" / m_games[itemID].save_dir); Common::FS::PathToQString(trophy_data_path, Common::FS::GetUserPath(Common::FS::PathType::MetaDataDir) / From f4eb0b9b9e0626527b834f170ad14d5b0f190730 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 22 May 2025 03:03:24 -0700 Subject: [PATCH 34/45] shader_recompiler: Fix buffer type reading from step rate attribute. (#2973) --- .../spirv/emit_spirv_context_get_set.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index 6442ae9f8..eff562955 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -175,19 +175,24 @@ Id EmitReadConst(EmitContext& ctx, IR::Inst* inst) { return ctx.OpLoad(ctx.U32[1], ptr); } -Id EmitReadConstBuffer(EmitContext& ctx, u32 handle, Id index) { +template +Id ReadConstBuffer(EmitContext& ctx, u32 handle, Id index) { const auto& buffer = ctx.buffers[handle]; index = ctx.OpIAdd(ctx.U32[1], index, buffer.offset_dwords); - const auto [id, pointer_type] = buffer[BufferAlias::U32]; + const auto [id, pointer_type] = buffer[alias]; + const auto value_type = alias == BufferAlias::U32 ? ctx.U32[1] : ctx.F32[1]; const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)}; - const Id result{ctx.OpLoad(ctx.U32[1], ptr)}; + const Id result{ctx.OpLoad(value_type, ptr)}; if (Sirit::ValidId(buffer.size_dwords)) { const Id in_bounds = ctx.OpULessThan(ctx.U1[1], index, buffer.size_dwords); - return ctx.OpSelect(ctx.U32[1], in_bounds, result, ctx.u32_zero_value); - } else { - return result; + return ctx.OpSelect(value_type, in_bounds, result, ctx.u32_zero_value); } + return result; +} + +Id EmitReadConstBuffer(EmitContext& ctx, u32 handle, Id index) { + return ReadConstBuffer(ctx, handle, index); } Id EmitReadStepRate(EmitContext& ctx, int rate_idx) { @@ -246,7 +251,7 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp, Id index) { ctx.OpUDiv(ctx.U32[1], ctx.OpLoad(ctx.U32[1], ctx.instance_id), step_rate), ctx.ConstU32(param.num_components)), ctx.ConstU32(comp)); - return EmitReadConstBuffer(ctx, param.buffer_handle, offset); + return ReadConstBuffer(ctx, param.buffer_handle, offset); } Id result; From 3f949d2b6cdb5fa4bfe6cc86defa977a435863bd Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 22 May 2025 03:16:20 -0700 Subject: [PATCH 35/45] amdgpu: Handle 32-bit Unorm formats. (#2974) --- src/shader_recompiler/ir/reinterpret.h | 10 ++++++++++ src/video_core/amdgpu/types.h | 27 ++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/shader_recompiler/ir/reinterpret.h b/src/shader_recompiler/ir/reinterpret.h index 99819cbb9..2a18f394a 100644 --- a/src/shader_recompiler/ir/reinterpret.h +++ b/src/shader_recompiler/ir/reinterpret.h @@ -46,6 +46,10 @@ inline F32 ApplyReadNumberConversion(IREmitter& ir, const F32& value, const IR::F32 max = ir.Imm32(float(std::numeric_limits::max())); return ir.FPDiv(left, max); } + case AmdGpu::NumberConversion::Uint32ToUnorm: { + const auto float_val = ir.ConvertUToF(32, 32, ir.BitCast(value)); + return ir.FPDiv(float_val, ir.Imm32(static_cast(std::numeric_limits::max()))); + } default: UNREACHABLE(); } @@ -92,6 +96,12 @@ inline F32 ApplyWriteNumberConversion(IREmitter& ir, const F32& value, const IR::U32 raw = ir.ConvertFToS(32, ir.FPDiv(left, ir.Imm32(2.f))); return ir.BitCast(raw); } + case AmdGpu::NumberConversion::Uint32ToUnorm: { + const auto clamped = ir.FPClamp(value, ir.Imm32(0.f), ir.Imm32(1.f)); + const auto unnormalized = + ir.FPMul(clamped, ir.Imm32(static_cast(std::numeric_limits::max()))); + return ir.BitCast(U32{ir.ConvertFToU(32, unnormalized)}); + } default: UNREACHABLE(); } diff --git a/src/video_core/amdgpu/types.h b/src/video_core/amdgpu/types.h index ab0df689e..f7536f7e2 100644 --- a/src/video_core/amdgpu/types.h +++ b/src/video_core/amdgpu/types.h @@ -197,8 +197,9 @@ enum class NumberConversion : u32 { UintToUscaled = 1, SintToSscaled = 2, UnormToUbnorm = 3, - Sint8ToSnormNz = 5, - Sint16ToSnormNz = 6, + Sint8ToSnormNz = 4, + Sint16ToSnormNz = 5, + Uint32ToUnorm = 6, }; struct CompMapping { @@ -286,6 +287,17 @@ inline DataFormat RemapDataFormat(const DataFormat format) { inline NumberFormat RemapNumberFormat(const NumberFormat format, const DataFormat data_format) { switch (format) { + case NumberFormat::Unorm: { + switch (data_format) { + case DataFormat::Format32: + case DataFormat::Format32_32: + case DataFormat::Format32_32_32: + case DataFormat::Format32_32_32_32: + return NumberFormat::Uint; + default: + return format; + } + } case NumberFormat::Uscaled: return NumberFormat::Uint; case NumberFormat::Sscaled: @@ -341,6 +353,17 @@ inline CompMapping RemapSwizzle(const DataFormat format, const CompMapping swizz inline NumberConversion MapNumberConversion(const NumberFormat num_fmt, const DataFormat data_fmt) { switch (num_fmt) { + case NumberFormat::Unorm: { + switch (data_fmt) { + case DataFormat::Format32: + case DataFormat::Format32_32: + case DataFormat::Format32_32_32: + case DataFormat::Format32_32_32_32: + return NumberConversion::Uint32ToUnorm; + default: + return NumberConversion::None; + } + } case NumberFormat::Uscaled: return NumberConversion::UintToUscaled; case NumberFormat::Sscaled: From b55c3f45559a0a5685b879adbe4c220c7304f859 Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 22 May 2025 15:35:00 +0300 Subject: [PATCH 36/45] Trophy fix (#2975) * fixed invalid handle destroy * removed log --- src/core/libraries/np_trophy/np_trophy.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/libraries/np_trophy/np_trophy.cpp b/src/core/libraries/np_trophy/np_trophy.cpp index a951d5655..6de84bd93 100644 --- a/src/core/libraries/np_trophy/np_trophy.cpp +++ b/src/core/libraries/np_trophy/np_trophy.cpp @@ -206,6 +206,10 @@ s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(OrbisNpTrophyHandle handle) { if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE) return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; + if (handle >= trophy_handles.size()) { + LOG_ERROR(Lib_NpTrophy, "Invalid handle {}", handle); + return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; + } if (!trophy_handles.is_allocated({static_cast(handle)})) { return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE; } From 9c2f71326a0c09795864a138050ee87ffd2d010f Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 22 May 2025 18:52:44 +0300 Subject: [PATCH 37/45] tagged 0.9.0 release --- CMakeLists.txt | 8 ++++---- dist/net.shadps4.shadPS4.metainfo.xml | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e993061bd..440a4b185 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,13 +203,13 @@ execute_process( # Set Version set(EMULATOR_VERSION_MAJOR "0") -set(EMULATOR_VERSION_MINOR "8") -set(EMULATOR_VERSION_PATCH "1") +set(EMULATOR_VERSION_MINOR "9") +set(EMULATOR_VERSION_PATCH "0") set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}") -set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH} WIP") -set(APP_IS_RELEASE false) +set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}") +set(APP_IS_RELEASE true) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp" @ONLY) message("end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}") diff --git a/dist/net.shadps4.shadPS4.metainfo.xml b/dist/net.shadps4.shadPS4.metainfo.xml index 9f7b4f9c5..493dc0df6 100644 --- a/dist/net.shadps4.shadPS4.metainfo.xml +++ b/dist/net.shadps4.shadPS4.metainfo.xml @@ -37,7 +37,10 @@ Game - + + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.9.0 + + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.8.0 From 37887e8fded641c0da06ac55b22e50200fb2876d Mon Sep 17 00:00:00 2001 From: georgemoralis Date: Thu, 22 May 2025 19:11:25 +0300 Subject: [PATCH 38/45] starting 0.9.1 WIP --- CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 440a4b185..73fff2c73 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -204,12 +204,12 @@ execute_process( # Set Version set(EMULATOR_VERSION_MAJOR "0") set(EMULATOR_VERSION_MINOR "9") -set(EMULATOR_VERSION_PATCH "0") +set(EMULATOR_VERSION_PATCH "1") set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}") -set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH}") -set(APP_IS_RELEASE true) +set(APP_VERSION "${EMULATOR_VERSION_MAJOR}.${EMULATOR_VERSION_MINOR}.${EMULATOR_VERSION_PATCH} WIP") +set(APP_IS_RELEASE false) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/src/common/scm_rev.cpp" @ONLY) message("end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}") From f9bbde9c79e0cf225e4e919118ab81f42d63b42d Mon Sep 17 00:00:00 2001 From: Lander Gallastegi Date: Thu, 22 May 2025 20:00:15 +0200 Subject: [PATCH 39/45] video_core: Implement DMA. (#2819) * Import memory * 64K pages and fix memory mapping * Queue coverage * Buffer syncing, faulted readback adn BDA in Buffer * Base DMA implementation * Preparations for implementing SPV DMA access * Base impl (pending 16K pages and getbuffersize) * 16K pages and stack overflow fix * clang-format * clang-format but for real this time * Try to fix macOS build * Correct decltype * Add testing log * Fix stride and patch phi node blocks * No need to check if it is a deleted buffer * Clang format once more * Offset in bytes * Removed host buffers (may do it in another PR) Also some random barrier fixes * Add IR dumping from my read-const branch * clang-format * Correct size insteed of end * Fix incorrect assert * Possible fix for NieR deadlock * Copy to avoid deadlock * Use 2 mutexes insteed of copy * Attempt to range sync error * Revert "Attempt to range sync error" This reverts commit dd287b48682b50f215680bb0956e39c2809bf3fe. * Fix size truncated when syncing range And memory barrier * Some fixes (and async testing (doesn't work)) * Use compute to parse fault buffer * Process faults on submit * Only sync in the first time we see a readconst Thsi is partialy wrong. We need to save the state into the submission context itself, not the rasterizer since we can yield and process another sumission (if im not understanding wrong). * Use spec const and 32 bit atomic * 32 bit counter * Fix store_index * Better sync (WIP, breaks PR now) * Fixes for better sync * Better sync * Remove memory coveragte logic * Point sirit to upstream * Less waiting and barriers * Correctly checkout moltenvk * Bring back applying pending operations in wait * Sync the whole buffer insteed of only the range * Implement recursive shared/scoped locks * Iterators * Faster syncing with ranges * Some alignment fixes * fixed clang format * Fix clang-format again * Port page_manager from readbacks-poc * clang-format * Defer memory protect * Remove RENDERER_TRACE * Experiment: only sync on first readconst * Added profiling (will be removed) * Don't sync entire buffers * Added logging for testing * Updated temporary workaround to use 4k pages * clang.-format * Cleanup part 1 * Make ReadConst a SPIR-V function --------- Co-authored-by: georgemoralis --- CMakeLists.txt | 3 + externals/sirit | 2 +- src/common/recursive_lock.cpp | 37 ++ src/common/recursive_lock.h | 67 +++ src/common/slot_vector.h | 110 +++- .../backend/spirv/emit_spirv.cpp | 11 +- .../backend/spirv/emit_spirv_atomic.cpp | 6 +- .../spirv/emit_spirv_context_get_set.cpp | 81 ++- .../backend/spirv/emit_spirv_instructions.h | 2 +- .../backend/spirv/spirv_emit_context.cpp | 198 +++++++- .../backend/spirv/spirv_emit_context.h | 117 ++++- .../frontend/translate/scalar_memory.cpp | 13 +- src/shader_recompiler/info.h | 14 +- .../ir/abstract_syntax_list.cpp | 44 ++ .../ir/abstract_syntax_list.h | 5 + .../ir/passes/shader_info_collection_pass.cpp | 31 +- src/shader_recompiler/ir/program.cpp | 34 +- src/shader_recompiler/ir/program.h | 2 +- src/shader_recompiler/recompiler.cpp | 2 + src/video_core/amdgpu/liverpool.cpp | 1 + src/video_core/amdgpu/resource.h | 7 + src/video_core/buffer_cache/buffer.cpp | 14 +- src/video_core/buffer_cache/buffer.h | 6 + src/video_core/buffer_cache/buffer_cache.cpp | 470 +++++++++++++++--- src/video_core/buffer_cache/buffer_cache.h | 86 +++- .../buffer_cache/memory_tracker_base.h | 24 +- src/video_core/buffer_cache/range_set.h | 175 ++++++- src/video_core/buffer_cache/word_manager.h | 32 +- src/video_core/host_shaders/CMakeLists.txt | 1 + .../host_shaders/fault_buffer_process.comp | 42 ++ src/video_core/page_manager.cpp | 180 ++++--- src/video_core/page_manager.h | 32 +- src/video_core/renderdoc.cpp | 1 + .../renderer_vulkan/vk_instance.cpp | 3 + src/video_core/renderer_vulkan/vk_instance.h | 6 + .../renderer_vulkan/vk_rasterizer.cpp | 48 +- .../renderer_vulkan/vk_rasterizer.h | 15 +- .../renderer_vulkan/vk_scheduler.cpp | 14 + src/video_core/renderer_vulkan/vk_scheduler.h | 4 + .../texture_cache/texture_cache.cpp | 12 +- 40 files changed, 1641 insertions(+), 311 deletions(-) create mode 100644 src/common/recursive_lock.cpp create mode 100644 src/common/recursive_lock.h create mode 100644 src/shader_recompiler/ir/abstract_syntax_list.cpp create mode 100644 src/video_core/host_shaders/fault_buffer_process.comp diff --git a/CMakeLists.txt b/CMakeLists.txt index 73fff2c73..53a2281ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -674,6 +674,8 @@ set(COMMON src/common/logging/backend.cpp src/common/polyfill_thread.h src/common/rdtsc.cpp src/common/rdtsc.h + src/common/recursive_lock.cpp + src/common/recursive_lock.h src/common/sha1.h src/common/signal_context.h src/common/signal_context.cpp @@ -864,6 +866,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/exception.h src/shader_recompiler/ir/passes/shared_memory_barrier_pass.cpp src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp + src/shader_recompiler/ir/abstract_syntax_list.cpp src/shader_recompiler/ir/abstract_syntax_list.h src/shader_recompiler/ir/attribute.cpp src/shader_recompiler/ir/attribute.h diff --git a/externals/sirit b/externals/sirit index 09a1416ab..6b450704f 160000 --- a/externals/sirit +++ b/externals/sirit @@ -1 +1 @@ -Subproject commit 09a1416ab1b59ddfebd2618412f118f2004f3b2c +Subproject commit 6b450704f6fedb9413d0c89a9eb59d028eb1e6c0 diff --git a/src/common/recursive_lock.cpp b/src/common/recursive_lock.cpp new file mode 100644 index 000000000..2471a2ee0 --- /dev/null +++ b/src/common/recursive_lock.cpp @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/assert.h" +#include "common/recursive_lock.h" + +namespace Common::Detail { + +struct RecursiveLockState { + RecursiveLockType type; + int count; +}; + +thread_local std::unordered_map g_recursive_locks; + +bool IncrementRecursiveLock(void* mutex, RecursiveLockType type) { + auto& state = g_recursive_locks[mutex]; + if (state.count == 0) { + ASSERT(state.type == RecursiveLockType::None); + state.type = type; + } + ASSERT(state.type == type); + return state.count++ == 0; +} + +bool DecrementRecursiveLock(void* mutex, RecursiveLockType type) { + auto& state = g_recursive_locks[mutex]; + ASSERT(state.type == type && state.count > 0); + if (--state.count == 0) { + g_recursive_locks.erase(mutex); + return true; + } + return false; +} + +} // namespace Common::Detail diff --git a/src/common/recursive_lock.h b/src/common/recursive_lock.h new file mode 100644 index 000000000..5a5fc6658 --- /dev/null +++ b/src/common/recursive_lock.h @@ -0,0 +1,67 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +namespace Common { + +namespace Detail { + +enum class RecursiveLockType { None, Shared, Exclusive }; + +bool IncrementRecursiveLock(void* mutex, RecursiveLockType type); +bool DecrementRecursiveLock(void* mutex, RecursiveLockType type); + +} // namespace Detail + +template +class RecursiveScopedLock { +public: + explicit RecursiveScopedLock(MutexType& mutex) : m_mutex(mutex), m_locked(false) { + if (Detail::IncrementRecursiveLock(&m_mutex, Detail::RecursiveLockType::Exclusive)) { + m_locked = true; + m_lock.emplace(m_mutex); + } + } + + ~RecursiveScopedLock() { + Detail::DecrementRecursiveLock(&m_mutex, Detail::RecursiveLockType::Exclusive); + if (m_locked) { + m_lock.reset(); + } + } + +private: + MutexType& m_mutex; + std::optional> m_lock; + bool m_locked = false; +}; + +template +class RecursiveSharedLock { +public: + explicit RecursiveSharedLock(MutexType& mutex) : m_mutex(mutex), m_locked(false) { + if (Detail::IncrementRecursiveLock(&m_mutex, Detail::RecursiveLockType::Shared)) { + m_locked = true; + m_lock.emplace(m_mutex); + } + } + + ~RecursiveSharedLock() { + Detail::DecrementRecursiveLock(&m_mutex, Detail::RecursiveLockType::Shared); + if (m_locked) { + m_lock.reset(); + } + } + +private: + MutexType& m_mutex; + std::optional> m_lock; + bool m_locked = false; +}; + +} // namespace Common \ No newline at end of file diff --git a/src/common/slot_vector.h b/src/common/slot_vector.h index d4ac51361..2f693fb28 100644 --- a/src/common/slot_vector.h +++ b/src/common/slot_vector.h @@ -14,6 +14,9 @@ namespace Common { struct SlotId { static constexpr u32 INVALID_INDEX = std::numeric_limits::max(); + SlotId() noexcept = default; + constexpr SlotId(u32 index) noexcept : index(index) {} + constexpr auto operator<=>(const SlotId&) const noexcept = default; constexpr explicit operator bool() const noexcept { @@ -28,6 +31,63 @@ class SlotVector { constexpr static std::size_t InitialCapacity = 2048; public: + template + class Iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = ValueType; + using difference_type = std::ptrdiff_t; + using pointer = Pointer; + using reference = Reference; + + Iterator(SlotVector& vector_, SlotId index_) : vector(vector_), slot(index_) { + AdvanceToValid(); + } + + reference operator*() const { + return vector[slot]; + } + + pointer operator->() const { + return &vector[slot]; + } + + Iterator& operator++() { + ++slot.index; + AdvanceToValid(); + return *this; + } + + Iterator operator++(int) { + Iterator temp = *this; + ++(*this); + return temp; + } + + bool operator==(const Iterator& other) const { + return slot == other.slot; + } + + bool operator!=(const Iterator& other) const { + return !(*this == other); + } + + private: + void AdvanceToValid() { + while (slot < vector.values_capacity && !vector.ReadStorageBit(slot.index)) { + ++slot.index; + } + } + + SlotVector& vector; + SlotId slot; + }; + + using iterator = Iterator; + using const_iterator = Iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + SlotVector() { Reserve(InitialCapacity); } @@ -60,7 +120,7 @@ public: } template - [[nodiscard]] SlotId insert(Args&&... args) noexcept { + SlotId insert(Args&&... args) noexcept { const u32 index = FreeValueIndex(); new (&values[index].object) T(std::forward(args)...); SetStorageBit(index); @@ -78,6 +138,54 @@ public: return values_capacity - free_list.size(); } + iterator begin() noexcept { + return iterator(*this, 0); + } + + const_iterator begin() const noexcept { + return const_iterator(*this, 0); + } + + const_iterator cbegin() const noexcept { + return begin(); + } + + iterator end() noexcept { + return iterator(*this, values_capacity); + } + + const_iterator end() const noexcept { + return const_iterator(*this, values_capacity); + } + + const_iterator cend() const noexcept { + return end(); + } + + reverse_iterator rbegin() noexcept { + return reverse_iterator(end()); + } + + const_reverse_iterator rbegin() const noexcept { + return const_reverse_iterator(end()); + } + + const_reverse_iterator crbegin() const noexcept { + return rbegin(); + } + + reverse_iterator rend() noexcept { + return reverse_iterator(begin()); + } + + const_reverse_iterator rend() const noexcept { + return const_reverse_iterator(begin()); + } + + const_reverse_iterator crend() const noexcept { + return rend(); + } + private: struct NonTrivialDummy { NonTrivialDummy() noexcept {} diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 9ebb842cc..f2e6279f4 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -154,6 +154,7 @@ void Traverse(EmitContext& ctx, const IR::Program& program) { for (IR::Inst& inst : node.data.block->Instructions()) { EmitInst(ctx, &inst); } + ctx.first_to_last_label_map[label.value] = ctx.last_label; break; } case IR::AbstractSyntaxNode::Type::If: { @@ -298,6 +299,10 @@ void SetupCapabilities(const Info& info, const Profile& profile, EmitContext& ct if (stage == LogicalStage::TessellationControl || stage == LogicalStage::TessellationEval) { ctx.AddCapability(spv::Capability::Tessellation); } + if (info.dma_types != IR::Type::Void) { + ctx.AddCapability(spv::Capability::PhysicalStorageBufferAddresses); + ctx.AddExtension("SPV_KHR_physical_storage_buffer"); + } } void DefineEntryPoint(const Info& info, EmitContext& ctx, Id main) { @@ -387,7 +392,7 @@ void SetupFloatMode(EmitContext& ctx, const Profile& profile, const RuntimeInfo& void PatchPhiNodes(const IR::Program& program, EmitContext& ctx) { auto inst{program.blocks.front()->begin()}; size_t block_index{0}; - ctx.PatchDeferredPhi([&](size_t phi_arg) { + ctx.PatchDeferredPhi([&](u32 phi_arg, Id first_parent) { if (phi_arg == 0) { ++inst; if (inst == program.blocks[block_index]->end() || @@ -398,7 +403,9 @@ void PatchPhiNodes(const IR::Program& program, EmitContext& ctx) { } while (inst->GetOpcode() != IR::Opcode::Phi); } } - return ctx.Def(inst->Arg(phi_arg)); + const Id arg = ctx.Def(inst->Arg(phi_arg)); + const Id parent = ctx.first_to_last_label_map[first_parent.value]; + return std::make_pair(arg, parent); }); } } // Anonymous namespace diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index c3799fb4b..d7c73ca8f 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -60,7 +60,7 @@ Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); } const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); - const auto [id, pointer_type] = buffer[EmitContext::BufferAlias::U32]; + const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index); const auto [scope, semantics]{AtomicArgs(ctx)}; return BufferAtomicU32BoundsCheck(ctx, index, buffer.size_dwords, [&] { @@ -257,7 +257,7 @@ Id EmitImageAtomicExchange32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id co Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding) { const auto& buffer = ctx.buffers[binding]; - const auto [id, pointer_type] = buffer[EmitContext::BufferAlias::U32]; + const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, ctx.ConstU32(gds_addr)); const auto [scope, semantics]{AtomicArgs(ctx)}; return ctx.OpAtomicIIncrement(ctx.U32[1], ptr, scope, semantics); @@ -265,7 +265,7 @@ Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding) { Id EmitDataConsume(EmitContext& ctx, u32 gds_addr, u32 binding) { const auto& buffer = ctx.buffers[binding]; - const auto [id, pointer_type] = buffer[EmitContext::BufferAlias::U32]; + const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, ctx.ConstU32(gds_addr)); const auto [scope, semantics]{AtomicArgs(ctx)}; return ctx.OpAtomicIDecrement(ctx.U32[1], ptr, scope, semantics); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index eff562955..658d4759f 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -161,26 +161,25 @@ void EmitGetGotoVariable(EmitContext&) { UNREACHABLE_MSG("Unreachable instruction"); } -using BufferAlias = EmitContext::BufferAlias; +using PointerType = EmitContext::PointerType; -Id EmitReadConst(EmitContext& ctx, IR::Inst* inst) { +Id EmitReadConst(EmitContext& ctx, IR::Inst* inst, Id addr, Id offset) { const u32 flatbuf_off_dw = inst->Flags(); - const auto& srt_flatbuf = ctx.buffers.back(); - ASSERT(srt_flatbuf.binding >= 0 && flatbuf_off_dw > 0 && - srt_flatbuf.buffer_type == BufferType::ReadConstUbo); - LOG_DEBUG(Render_Recompiler, "ReadConst from flatbuf dword {}", flatbuf_off_dw); - const auto [id, pointer_type] = srt_flatbuf[BufferAlias::U32]; - const Id ptr{ - ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, ctx.ConstU32(flatbuf_off_dw))}; - return ctx.OpLoad(ctx.U32[1], ptr); + // We can only provide a fallback for immediate offsets. + if (flatbuf_off_dw == 0) { + return ctx.OpFunctionCall(ctx.U32[1], ctx.read_const_dynamic, addr, offset); + } else { + return ctx.OpFunctionCall(ctx.U32[1], ctx.read_const, addr, offset, + ctx.ConstU32(flatbuf_off_dw)); + } } -template +template Id ReadConstBuffer(EmitContext& ctx, u32 handle, Id index) { const auto& buffer = ctx.buffers[handle]; index = ctx.OpIAdd(ctx.U32[1], index, buffer.offset_dwords); - const auto [id, pointer_type] = buffer[alias]; - const auto value_type = alias == BufferAlias::U32 ? ctx.U32[1] : ctx.F32[1]; + const auto [id, pointer_type] = buffer[type]; + const auto value_type = type == PointerType::U32 ? ctx.U32[1] : ctx.F32[1]; const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)}; const Id result{ctx.OpLoad(value_type, ptr)}; @@ -192,7 +191,7 @@ Id ReadConstBuffer(EmitContext& ctx, u32 handle, Id index) { } Id EmitReadConstBuffer(EmitContext& ctx, u32 handle, Id index) { - return ReadConstBuffer(ctx, handle, index); + return ReadConstBuffer(ctx, handle, index); } Id EmitReadStepRate(EmitContext& ctx, int rate_idx) { @@ -251,7 +250,7 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp, Id index) { ctx.OpUDiv(ctx.U32[1], ctx.OpLoad(ctx.U32[1], ctx.instance_id), step_rate), ctx.ConstU32(param.num_components)), ctx.ConstU32(comp)); - return ReadConstBuffer(ctx, param.buffer_handle, offset); + return ReadConstBuffer(ctx, param.buffer_handle, offset); } Id result; @@ -437,7 +436,7 @@ static Id EmitLoadBufferBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, return result; } -template +template static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { const auto flags = inst->Flags(); const auto& spv_buffer = ctx.buffers[handle]; @@ -445,7 +444,7 @@ static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id a address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); } const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); - const auto& data_types = alias == BufferAlias::U32 ? ctx.U32 : ctx.F32; + const auto& data_types = alias == PointerType::U32 ? ctx.U32 : ctx.F32; const auto [id, pointer_type] = spv_buffer[alias]; boost::container::static_vector ids; @@ -456,7 +455,7 @@ static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id a if (!flags.typed) { // Untyped loads have bounds checking per-component. ids.push_back(EmitLoadBufferBoundsCheck<1>(ctx, index_i, spv_buffer.size_dwords, - result_i, alias == BufferAlias::F32)); + result_i, alias == PointerType::F32)); } else { ids.push_back(result_i); } @@ -466,7 +465,7 @@ static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id a if (flags.typed) { // Typed loads have single bounds check for the whole load. return EmitLoadBufferBoundsCheck(ctx, index, spv_buffer.size_dwords, result, - alias == BufferAlias::F32); + alias == PointerType::F32); } return result; } @@ -476,7 +475,7 @@ Id EmitLoadBufferU8(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { if (Sirit::ValidId(spv_buffer.offset)) { address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); } - const auto [id, pointer_type] = spv_buffer[BufferAlias::U8]; + const auto [id, pointer_type] = spv_buffer[PointerType::U8]; const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)}; const Id result{ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, ptr))}; return EmitLoadBufferBoundsCheck<1>(ctx, address, spv_buffer.size, result, false); @@ -487,7 +486,7 @@ Id EmitLoadBufferU16(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { if (Sirit::ValidId(spv_buffer.offset)) { address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); } - const auto [id, pointer_type] = spv_buffer[BufferAlias::U16]; + const auto [id, pointer_type] = spv_buffer[PointerType::U16]; const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(1u)); const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)}; const Id result{ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, ptr))}; @@ -495,35 +494,35 @@ Id EmitLoadBufferU16(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { } Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { - return EmitLoadBufferB32xN<1, BufferAlias::U32>(ctx, inst, handle, address); + return EmitLoadBufferB32xN<1, PointerType::U32>(ctx, inst, handle, address); } Id EmitLoadBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { - return EmitLoadBufferB32xN<2, BufferAlias::U32>(ctx, inst, handle, address); + return EmitLoadBufferB32xN<2, PointerType::U32>(ctx, inst, handle, address); } Id EmitLoadBufferU32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { - return EmitLoadBufferB32xN<3, BufferAlias::U32>(ctx, inst, handle, address); + return EmitLoadBufferB32xN<3, PointerType::U32>(ctx, inst, handle, address); } Id EmitLoadBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { - return EmitLoadBufferB32xN<4, BufferAlias::U32>(ctx, inst, handle, address); + return EmitLoadBufferB32xN<4, PointerType::U32>(ctx, inst, handle, address); } Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { - return EmitLoadBufferB32xN<1, BufferAlias::F32>(ctx, inst, handle, address); + return EmitLoadBufferB32xN<1, PointerType::F32>(ctx, inst, handle, address); } Id EmitLoadBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { - return EmitLoadBufferB32xN<2, BufferAlias::F32>(ctx, inst, handle, address); + return EmitLoadBufferB32xN<2, PointerType::F32>(ctx, inst, handle, address); } Id EmitLoadBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { - return EmitLoadBufferB32xN<3, BufferAlias::F32>(ctx, inst, handle, address); + return EmitLoadBufferB32xN<3, PointerType::F32>(ctx, inst, handle, address); } Id EmitLoadBufferF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { - return EmitLoadBufferB32xN<4, BufferAlias::F32>(ctx, inst, handle, address); + return EmitLoadBufferB32xN<4, PointerType::F32>(ctx, inst, handle, address); } Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { @@ -553,7 +552,7 @@ void EmitStoreBufferBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, auto emit_func(); } -template +template static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { const auto flags = inst->Flags(); @@ -562,7 +561,7 @@ static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, I address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); } const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); - const auto& data_types = alias == BufferAlias::U32 ? ctx.U32 : ctx.F32; + const auto& data_types = alias == PointerType::U32 ? ctx.U32 : ctx.F32; const auto [id, pointer_type] = spv_buffer[alias]; auto store = [&] { @@ -593,7 +592,7 @@ void EmitStoreBufferU8(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id v if (Sirit::ValidId(spv_buffer.offset)) { address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); } - const auto [id, pointer_type] = spv_buffer[BufferAlias::U8]; + const auto [id, pointer_type] = spv_buffer[PointerType::U8]; const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)}; const Id result{ctx.OpUConvert(ctx.U8, value)}; EmitStoreBufferBoundsCheck<1>(ctx, address, spv_buffer.size, [&] { ctx.OpStore(ptr, result); }); @@ -604,7 +603,7 @@ void EmitStoreBufferU16(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id if (Sirit::ValidId(spv_buffer.offset)) { address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); } - const auto [id, pointer_type] = spv_buffer[BufferAlias::U16]; + const auto [id, pointer_type] = spv_buffer[PointerType::U16]; const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(1u)); const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)}; const Id result{ctx.OpUConvert(ctx.U16, value)}; @@ -613,35 +612,35 @@ void EmitStoreBufferU16(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id } void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferB32xN<1, BufferAlias::U32>(ctx, inst, handle, address, value); + EmitStoreBufferB32xN<1, PointerType::U32>(ctx, inst, handle, address, value); } void EmitStoreBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferB32xN<2, BufferAlias::U32>(ctx, inst, handle, address, value); + EmitStoreBufferB32xN<2, PointerType::U32>(ctx, inst, handle, address, value); } void EmitStoreBufferU32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferB32xN<3, BufferAlias::U32>(ctx, inst, handle, address, value); + EmitStoreBufferB32xN<3, PointerType::U32>(ctx, inst, handle, address, value); } void EmitStoreBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferB32xN<4, BufferAlias::U32>(ctx, inst, handle, address, value); + EmitStoreBufferB32xN<4, PointerType::U32>(ctx, inst, handle, address, value); } void EmitStoreBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferB32xN<1, BufferAlias::F32>(ctx, inst, handle, address, value); + EmitStoreBufferB32xN<1, PointerType::F32>(ctx, inst, handle, address, value); } void EmitStoreBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferB32xN<2, BufferAlias::F32>(ctx, inst, handle, address, value); + EmitStoreBufferB32xN<2, PointerType::F32>(ctx, inst, handle, address, value); } void EmitStoreBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferB32xN<3, BufferAlias::F32>(ctx, inst, handle, address, value); + EmitStoreBufferB32xN<3, PointerType::F32>(ctx, inst, handle, address, value); } void EmitStoreBufferF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { - EmitStoreBufferB32xN<4, BufferAlias::F32>(ctx, inst, handle, address, value); + EmitStoreBufferB32xN<4, PointerType::F32>(ctx, inst, handle, address, value); } void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 269f372d5..09f9732bf 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -61,7 +61,7 @@ void EmitSetVectorRegister(EmitContext& ctx); void EmitSetGotoVariable(EmitContext& ctx); void EmitGetGotoVariable(EmitContext& ctx); void EmitSetScc(EmitContext& ctx); -Id EmitReadConst(EmitContext& ctx, IR::Inst* inst); +Id EmitReadConst(EmitContext& ctx, IR::Inst* inst, Id addr, Id offset); Id EmitReadConstBuffer(EmitContext& ctx, u32 handle, Id index); Id EmitLoadBufferU8(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); Id EmitLoadBufferU16(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 2640030df..68bfcc0d0 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -7,6 +7,7 @@ #include "shader_recompiler/frontend/fetch_shader.h" #include "shader_recompiler/runtime_info.h" #include "video_core/amdgpu/types.h" +#include "video_core/buffer_cache/buffer_cache.h" #include #include @@ -70,6 +71,12 @@ EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_inf Bindings& binding_) : Sirit::Module(profile_.supported_spirv), info{info_}, runtime_info{runtime_info_}, profile{profile_}, stage{info.stage}, l_stage{info.l_stage}, binding{binding_} { + if (info.dma_types != IR::Type::Void) { + SetMemoryModel(spv::AddressingModel::PhysicalStorageBuffer64, spv::MemoryModel::GLSL450); + } else { + SetMemoryModel(spv::AddressingModel::Logical, spv::MemoryModel::GLSL450); + } + AddCapability(spv::Capability::Shader); DefineArithmeticTypes(); DefineInterfaces(); @@ -137,9 +144,13 @@ void EmitContext::DefineArithmeticTypes() { true_value = ConstantTrue(U1[1]); false_value = ConstantFalse(U1[1]); + u8_one_value = Constant(U8, 1U); + u8_zero_value = Constant(U8, 0U); u32_one_value = ConstU32(1U); u32_zero_value = ConstU32(0U); f32_zero_value = ConstF32(0.0f); + u64_one_value = Constant(U64, 1ULL); + u64_zero_value = Constant(U64, 0ULL); pi_x2 = ConstF32(2.0f * float{std::numbers::pi}); @@ -157,6 +168,35 @@ void EmitContext::DefineArithmeticTypes() { if (info.uses_fp64) { frexp_result_f64 = Name(TypeStruct(F64[1], S32[1]), "frexp_result_f64"); } + + if (True(info.dma_types & IR::Type::F64)) { + physical_pointer_types[PointerType::F64] = + TypePointer(spv::StorageClass::PhysicalStorageBuffer, F64[1]); + } + if (True(info.dma_types & IR::Type::U64)) { + physical_pointer_types[PointerType::U64] = + TypePointer(spv::StorageClass::PhysicalStorageBuffer, U64); + } + if (True(info.dma_types & IR::Type::F32)) { + physical_pointer_types[PointerType::F32] = + TypePointer(spv::StorageClass::PhysicalStorageBuffer, F32[1]); + } + if (True(info.dma_types & IR::Type::U32)) { + physical_pointer_types[PointerType::U32] = + TypePointer(spv::StorageClass::PhysicalStorageBuffer, U32[1]); + } + if (True(info.dma_types & IR::Type::F16)) { + physical_pointer_types[PointerType::F16] = + TypePointer(spv::StorageClass::PhysicalStorageBuffer, F16[1]); + } + if (True(info.dma_types & IR::Type::U16)) { + physical_pointer_types[PointerType::U16] = + TypePointer(spv::StorageClass::PhysicalStorageBuffer, U16); + } + if (True(info.dma_types & IR::Type::U8)) { + physical_pointer_types[PointerType::U8] = + TypePointer(spv::StorageClass::PhysicalStorageBuffer, U8); + } } void EmitContext::DefineInterfaces() { @@ -195,9 +235,10 @@ EmitContext::SpirvAttribute EmitContext::GetAttributeInfo(AmdGpu::NumberFormat f } Id EmitContext::GetBufferSize(const u32 sharp_idx) { - const auto& srt_flatbuf = buffers.back(); - ASSERT(srt_flatbuf.buffer_type == BufferType::ReadConstUbo); - const auto [id, pointer_type] = srt_flatbuf[BufferAlias::U32]; + // Can this be done with memory access? Like we do now with ReadConst + const auto& srt_flatbuf = buffers[flatbuf_index]; + ASSERT(srt_flatbuf.buffer_type == BufferType::Flatbuf); + const auto [id, pointer_type] = srt_flatbuf[PointerType::U32]; const auto rsrc1{ OpLoad(U32[1], OpAccessChain(pointer_type, id, u32_zero_value, ConstU32(sharp_idx + 1)))}; @@ -690,8 +731,14 @@ EmitContext::BufferSpv EmitContext::DefineBuffer(bool is_storage, bool is_writte case Shader::BufferType::GdsBuffer: Name(id, "gds_buffer"); break; - case Shader::BufferType::ReadConstUbo: - Name(id, "srt_flatbuf_ubo"); + case Shader::BufferType::Flatbuf: + Name(id, "srt_flatbuf"); + break; + case Shader::BufferType::BdaPagetable: + Name(id, "bda_pagetable"); + break; + case Shader::BufferType::FaultBuffer: + Name(id, "fault_buffer"); break; case Shader::BufferType::SharedMemory: Name(id, "ssbo_shmem"); @@ -705,35 +752,53 @@ EmitContext::BufferSpv EmitContext::DefineBuffer(bool is_storage, bool is_writte }; void EmitContext::DefineBuffers() { - if (!profile.supports_robust_buffer_access && !info.has_readconst) { - // In case ReadConstUbo has not already been bound by IR and is needed + if (!profile.supports_robust_buffer_access && + info.readconst_types == Info::ReadConstType::None) { + // In case Flatbuf has not already been bound by IR and is needed // to query buffer sizes, bind it now. info.buffers.push_back({ .used_types = IR::Type::U32, - .inline_cbuf = AmdGpu::Buffer::Null(), - .buffer_type = BufferType::ReadConstUbo, + // We can't guarantee that flatbuf will not grow past UBO + // limit if there are a lot of ReadConsts. (We could specialize) + .inline_cbuf = AmdGpu::Buffer::Placeholder(std::numeric_limits::max()), + .buffer_type = BufferType::Flatbuf, }); + // In the future we may want to read buffer sizes from GPU memory if available. + // info.readconst_types |= Info::ReadConstType::Immediate; } for (const auto& desc : info.buffers) { const auto buf_sharp = desc.GetSharp(info); const bool is_storage = desc.IsStorage(buf_sharp, profile); + // Set indexes for special buffers. + if (desc.buffer_type == BufferType::Flatbuf) { + flatbuf_index = buffers.size(); + } else if (desc.buffer_type == BufferType::BdaPagetable) { + bda_pagetable_index = buffers.size(); + } else if (desc.buffer_type == BufferType::FaultBuffer) { + fault_buffer_index = buffers.size(); + } + // Define aliases depending on the shader usage. auto& spv_buffer = buffers.emplace_back(binding.buffer++, desc.buffer_type); + if (True(desc.used_types & IR::Type::U64)) { + spv_buffer[PointerType::U64] = + DefineBuffer(is_storage, desc.is_written, 3, desc.buffer_type, U64); + } if (True(desc.used_types & IR::Type::U32)) { - spv_buffer[BufferAlias::U32] = + spv_buffer[PointerType::U32] = DefineBuffer(is_storage, desc.is_written, 2, desc.buffer_type, U32[1]); } if (True(desc.used_types & IR::Type::F32)) { - spv_buffer[BufferAlias::F32] = + spv_buffer[PointerType::F32] = DefineBuffer(is_storage, desc.is_written, 2, desc.buffer_type, F32[1]); } if (True(desc.used_types & IR::Type::U16)) { - spv_buffer[BufferAlias::U16] = + spv_buffer[PointerType::U16] = DefineBuffer(is_storage, desc.is_written, 1, desc.buffer_type, U16); } if (True(desc.used_types & IR::Type::U8)) { - spv_buffer[BufferAlias::U8] = + spv_buffer[PointerType::U8] = DefineBuffer(is_storage, desc.is_written, 0, desc.buffer_type, U8); } ++binding.unified; @@ -1003,6 +1068,101 @@ Id EmitContext::DefineUfloatM5ToFloat32(u32 mantissa_bits, const std::string_vie return func; } +Id EmitContext::DefineGetBdaPointer() { + const auto caching_pagebits{ + Constant(U64, static_cast(VideoCore::BufferCache::CACHING_PAGEBITS))}; + const auto caching_pagemask{Constant(U64, VideoCore::BufferCache::CACHING_PAGESIZE - 1)}; + + const auto func_type{TypeFunction(U64, U64)}; + const auto func{OpFunction(U64, spv::FunctionControlMask::MaskNone, func_type)}; + const auto address{OpFunctionParameter(U64)}; + Name(func, "get_bda_pointer"); + AddLabel(); + + const auto fault_label{OpLabel()}; + const auto available_label{OpLabel()}; + const auto merge_label{OpLabel()}; + + // Get page BDA + const auto page{OpShiftRightLogical(U64, address, caching_pagebits)}; + const auto page32{OpUConvert(U32[1], page)}; + const auto& bda_buffer{buffers[bda_pagetable_index]}; + const auto [bda_buffer_id, bda_pointer_type] = bda_buffer[PointerType::U64]; + const auto bda_ptr{OpAccessChain(bda_pointer_type, bda_buffer_id, u32_zero_value, page32)}; + const auto bda{OpLoad(U64, bda_ptr)}; + + // Check if page is GPU cached + const auto is_fault{OpIEqual(U1[1], bda, u64_zero_value)}; + OpSelectionMerge(merge_label, spv::SelectionControlMask::MaskNone); + OpBranchConditional(is_fault, fault_label, available_label); + + // First time acces, mark as fault + AddLabel(fault_label); + const auto& fault_buffer{buffers[fault_buffer_index]}; + const auto [fault_buffer_id, fault_pointer_type] = fault_buffer[PointerType::U8]; + const auto page_div8{OpShiftRightLogical(U32[1], page32, ConstU32(3U))}; + const auto page_mod8{OpBitwiseAnd(U32[1], page32, ConstU32(7U))}; + const auto page_mask{OpShiftLeftLogical(U8, u8_one_value, page_mod8)}; + const auto fault_ptr{ + OpAccessChain(fault_pointer_type, fault_buffer_id, u32_zero_value, page_div8)}; + const auto fault_value{OpLoad(U8, fault_ptr)}; + const auto fault_value_masked{OpBitwiseOr(U8, fault_value, page_mask)}; + OpStore(fault_ptr, fault_value_masked); + + // Return null pointer + const auto fallback_result{u64_zero_value}; + OpBranch(merge_label); + + // Value is available, compute address + AddLabel(available_label); + const auto offset_in_bda{OpBitwiseAnd(U64, address, caching_pagemask)}; + const auto addr{OpIAdd(U64, bda, offset_in_bda)}; + OpBranch(merge_label); + + // Merge + AddLabel(merge_label); + const auto result{OpPhi(U64, addr, available_label, fallback_result, fault_label)}; + OpReturnValue(result); + OpFunctionEnd(); + return func; +} + +Id EmitContext::DefineReadConst(bool dynamic) { + const auto func_type{!dynamic ? TypeFunction(U32[1], U32[2], U32[1], U32[1]) + : TypeFunction(U32[1], U32[2], U32[1])}; + const auto func{OpFunction(U32[1], spv::FunctionControlMask::MaskNone, func_type)}; + const auto base{OpFunctionParameter(U32[2])}; + const auto offset{OpFunctionParameter(U32[1])}; + const auto flatbuf_offset{!dynamic ? OpFunctionParameter(U32[1]) : Id{}}; + Name(func, dynamic ? "read_const_dynamic" : "read_const"); + AddLabel(); + + const auto base_lo{OpUConvert(U64, OpCompositeExtract(U32[1], base, 0))}; + const auto base_hi{OpUConvert(U64, OpCompositeExtract(U32[1], base, 1))}; + const auto base_shift{OpShiftLeftLogical(U64, base_hi, ConstU32(32U))}; + const auto base_addr{OpBitwiseOr(U64, base_lo, base_shift)}; + const auto offset_bytes{OpShiftLeftLogical(U32[1], offset, ConstU32(2U))}; + const auto addr{OpIAdd(U64, base_addr, OpUConvert(U64, offset_bytes))}; + + const auto result = EmitMemoryRead(U32[1], addr, [&]() { + if (dynamic) { + return u32_zero_value; + } else { + const auto& flatbuf_buffer{buffers[flatbuf_index]}; + ASSERT(flatbuf_buffer.binding >= 0 && + flatbuf_buffer.buffer_type == BufferType::Flatbuf); + const auto [flatbuf_buffer_id, flatbuf_pointer_type] = flatbuf_buffer[PointerType::U32]; + const auto ptr{OpAccessChain(flatbuf_pointer_type, flatbuf_buffer_id, u32_zero_value, + flatbuf_offset)}; + return OpLoad(U32[1], ptr); + } + }); + + OpReturnValue(result); + OpFunctionEnd(); + return func; +} + void EmitContext::DefineFunctions() { if (info.uses_pack_10_11_11) { f32_to_uf11 = DefineFloat32ToUfloatM5(6, "f32_to_uf11"); @@ -1012,6 +1172,18 @@ void EmitContext::DefineFunctions() { uf11_to_f32 = DefineUfloatM5ToFloat32(6, "uf11_to_f32"); uf10_to_f32 = DefineUfloatM5ToFloat32(5, "uf10_to_f32"); } + if (info.dma_types != IR::Type::Void) { + get_bda_pointer = DefineGetBdaPointer(); + } + + if (True(info.readconst_types & Info::ReadConstType::Immediate)) { + LOG_DEBUG(Render_Recompiler, "Shader {:#x} uses immediate ReadConst", info.pgm_hash); + read_const = DefineReadConst(false); + } + if (True(info.readconst_types & Info::ReadConstType::Dynamic)) { + LOG_DEBUG(Render_Recompiler, "Shader {:#x} uses dynamic ReadConst", info.pgm_hash); + read_const_dynamic = DefineReadConst(true); + } } } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 38d55e0e4..a2e0d2f47 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include "shader_recompiler/backend/bindings.h" @@ -41,6 +42,17 @@ public: Bindings& binding); ~EmitContext(); + enum class PointerType : u32 { + U8, + U16, + F16, + U32, + F32, + U64, + F64, + NumAlias, + }; + Id Def(const IR::Value& value); void DefineBufferProperties(); @@ -133,12 +145,72 @@ public: return ConstantComposite(type, constituents); } + inline Id AddLabel() { + last_label = Module::AddLabel(); + return last_label; + } + + inline Id AddLabel(Id label) { + last_label = Module::AddLabel(label); + return last_label; + } + + PointerType PointerTypeFromType(Id type) { + if (type.value == U8.value) + return PointerType::U8; + if (type.value == U16.value) + return PointerType::U16; + if (type.value == F16[1].value) + return PointerType::F16; + if (type.value == U32[1].value) + return PointerType::U32; + if (type.value == F32[1].value) + return PointerType::F32; + if (type.value == U64.value) + return PointerType::U64; + if (type.value == F64[1].value) + return PointerType::F64; + UNREACHABLE_MSG("Unknown type for pointer"); + } + + Id EmitMemoryRead(Id type, Id address, auto&& fallback) { + const Id available_label = OpLabel(); + const Id fallback_label = OpLabel(); + const Id merge_label = OpLabel(); + + const Id addr = OpFunctionCall(U64, get_bda_pointer, address); + const Id is_available = OpINotEqual(U1[1], addr, u64_zero_value); + OpSelectionMerge(merge_label, spv::SelectionControlMask::MaskNone); + OpBranchConditional(is_available, available_label, fallback_label); + + // Available + AddLabel(available_label); + const auto pointer_type = PointerTypeFromType(type); + const Id pointer_type_id = physical_pointer_types[pointer_type]; + const Id addr_ptr = OpConvertUToPtr(pointer_type_id, addr); + const Id result = OpLoad(type, addr_ptr, spv::MemoryAccessMask::Aligned, 4u); + OpBranch(merge_label); + + // Fallback + AddLabel(fallback_label); + const Id fallback_result = fallback(); + OpBranch(merge_label); + + // Merge + AddLabel(merge_label); + const Id final_result = + OpPhi(type, fallback_result, fallback_label, result, available_label); + return final_result; + } + Info& info; const RuntimeInfo& runtime_info; const Profile& profile; Stage stage; LogicalStage l_stage{}; + Id last_label{}; + Id void_id{}; Id U8{}; Id S8{}; @@ -161,9 +233,13 @@ public: Id true_value{}; Id false_value{}; + Id u8_one_value{}; + Id u8_zero_value{}; Id u32_one_value{}; Id u32_zero_value{}; Id f32_zero_value{}; + Id u64_one_value{}; + Id u64_zero_value{}; Id shared_u8{}; Id shared_u16{}; @@ -231,14 +307,6 @@ public: bool is_storage = false; }; - enum class BufferAlias : u32 { - U8, - U16, - U32, - F32, - NumAlias, - }; - struct BufferSpv { Id id; Id pointer_type; @@ -252,22 +320,40 @@ public: Id size; Id size_shorts; Id size_dwords; - std::array aliases; + std::array aliases; - const BufferSpv& operator[](BufferAlias alias) const { + const BufferSpv& operator[](PointerType alias) const { return aliases[u32(alias)]; } - BufferSpv& operator[](BufferAlias alias) { + BufferSpv& operator[](PointerType alias) { return aliases[u32(alias)]; } }; + struct PhysicalPointerTypes { + std::array types; + + const Id& operator[](PointerType type) const { + return types[u32(type)]; + } + + Id& operator[](PointerType type) { + return types[u32(type)]; + } + }; + Bindings& binding; boost::container::small_vector buf_type_ids; boost::container::small_vector buffers; boost::container::small_vector images; boost::container::small_vector samplers; + PhysicalPointerTypes physical_pointer_types; + std::unordered_map first_to_last_label_map; + + size_t flatbuf_index{}; + size_t bda_pagetable_index{}; + size_t fault_buffer_index{}; Id sampler_type{}; Id sampler_pointer_type{}; @@ -292,6 +378,11 @@ public: Id uf10_to_f32{}; Id f32_to_uf10{}; + Id get_bda_pointer{}; + + Id read_const{}; + Id read_const_dynamic{}; + private: void DefineArithmeticTypes(); void DefineInterfaces(); @@ -312,6 +403,10 @@ private: Id DefineFloat32ToUfloatM5(u32 mantissa_bits, std::string_view name); Id DefineUfloatM5ToFloat32(u32 mantissa_bits, std::string_view name); + Id DefineGetBdaPointer(); + + Id DefineReadConst(bool dynamic); + Id GetBufferSize(u32 sharp_idx); }; diff --git a/src/shader_recompiler/frontend/translate/scalar_memory.cpp b/src/shader_recompiler/frontend/translate/scalar_memory.cpp index 376cc304e..3c6fd3968 100644 --- a/src/shader_recompiler/frontend/translate/scalar_memory.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_memory.cpp @@ -39,21 +39,22 @@ void Translator::EmitScalarMemory(const GcnInst& inst) { void Translator::S_LOAD_DWORD(int num_dwords, const GcnInst& inst) { const auto& smrd = inst.control.smrd; - const u32 dword_offset = [&] -> u32 { + const IR::ScalarReg sbase{inst.src[0].code * 2}; + const IR::U32 dword_offset = [&] -> IR::U32 { if (smrd.imm) { - return smrd.offset; + return ir.Imm32(smrd.offset); } if (smrd.offset == SQ_SRC_LITERAL) { - return inst.src[1].code; + return ir.Imm32(inst.src[1].code); } - UNREACHABLE(); + return ir.ShiftRightLogical(ir.GetScalarReg(IR::ScalarReg(smrd.offset)), ir.Imm32(2)); }(); - const IR::ScalarReg sbase{inst.src[0].code * 2}; const IR::Value base = ir.CompositeConstruct(ir.GetScalarReg(sbase), ir.GetScalarReg(sbase + 1)); IR::ScalarReg dst_reg{inst.dst[0].code}; for (u32 i = 0; i < num_dwords; i++) { - ir.SetScalarReg(dst_reg + i, ir.ReadConst(base, ir.Imm32(dword_offset + i))); + IR::U32 index = ir.IAdd(dword_offset, ir.Imm32(i)); + ir.SetScalarReg(dst_reg + i, ir.ReadConst(base, index)); } } diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h index ba28d7e43..d349d7827 100644 --- a/src/shader_recompiler/info.h +++ b/src/shader_recompiler/info.h @@ -41,7 +41,9 @@ constexpr u32 NUM_TEXTURE_TYPES = 7; enum class BufferType : u32 { Guest, - ReadConstUbo, + Flatbuf, + BdaPagetable, + FaultBuffer, GdsBuffer, SharedMemory, }; @@ -215,11 +217,18 @@ struct Info { bool stores_tess_level_outer{}; bool stores_tess_level_inner{}; bool translation_failed{}; - bool has_readconst{}; u8 mrt_mask{0u}; bool has_fetch_shader{false}; u32 fetch_shader_sgpr_base{0u}; + enum class ReadConstType { + None = 0, + Immediate = 1 << 0, + Dynamic = 1 << 1, + }; + ReadConstType readconst_types{}; + IR::Type dma_types{IR::Type::Void}; + explicit Info(Stage stage_, LogicalStage l_stage_, ShaderParams params) : stage{stage_}, l_stage{l_stage_}, pgm_hash{params.hash}, pgm_base{params.Base()}, user_data{params.user_data} {} @@ -277,6 +286,7 @@ struct Info { sizeof(tess_constants)); } }; +DECLARE_ENUM_FLAG_OPERATORS(Info::ReadConstType); constexpr AmdGpu::Buffer BufferResource::GetSharp(const Info& info) const noexcept { return inline_cbuf ? inline_cbuf : info.ReadUdSharp(sharp_idx); diff --git a/src/shader_recompiler/ir/abstract_syntax_list.cpp b/src/shader_recompiler/ir/abstract_syntax_list.cpp new file mode 100644 index 000000000..0d967ac11 --- /dev/null +++ b/src/shader_recompiler/ir/abstract_syntax_list.cpp @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "abstract_syntax_list.h" + +namespace Shader::IR { + +std::string DumpASLNode(const AbstractSyntaxNode& node, + const std::map& block_to_index, + const std::map& inst_to_index) { + switch (node.type) { + case AbstractSyntaxNode::Type::Block: + return fmt::format("Block: ${}", block_to_index.at(node.data.block)); + case AbstractSyntaxNode::Type::If: + return fmt::format("If: cond = %{}, body = ${}, merge = ${}", + inst_to_index.at(node.data.if_node.cond.Inst()), + block_to_index.at(node.data.if_node.body), + block_to_index.at(node.data.if_node.merge)); + case AbstractSyntaxNode::Type::EndIf: + return fmt::format("EndIf: merge = ${}", block_to_index.at(node.data.end_if.merge)); + case AbstractSyntaxNode::Type::Loop: + return fmt::format("Loop: body = ${}, continue = ${}, merge = ${}", + block_to_index.at(node.data.loop.body), + block_to_index.at(node.data.loop.continue_block), + block_to_index.at(node.data.loop.merge)); + case AbstractSyntaxNode::Type::Repeat: + return fmt::format("Repeat: cond = %{}, header = ${}, merge = ${}", + inst_to_index.at(node.data.repeat.cond.Inst()), + block_to_index.at(node.data.repeat.loop_header), + block_to_index.at(node.data.repeat.merge)); + case AbstractSyntaxNode::Type::Break: + return fmt::format("Break: cond = %{}, merge = ${}, skip = ${}", + inst_to_index.at(node.data.break_node.cond.Inst()), + block_to_index.at(node.data.break_node.merge), + block_to_index.at(node.data.break_node.skip)); + case AbstractSyntaxNode::Type::Return: + return "Return"; + case AbstractSyntaxNode::Type::Unreachable: + return "Unreachable"; + }; + UNREACHABLE(); +} + +} // namespace Shader::IR \ No newline at end of file diff --git a/src/shader_recompiler/ir/abstract_syntax_list.h b/src/shader_recompiler/ir/abstract_syntax_list.h index 313a23abc..a620baccb 100644 --- a/src/shader_recompiler/ir/abstract_syntax_list.h +++ b/src/shader_recompiler/ir/abstract_syntax_list.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include "shader_recompiler/ir/value.h" @@ -53,4 +54,8 @@ struct AbstractSyntaxNode { }; using AbstractSyntaxList = std::vector; +std::string DumpASLNode(const AbstractSyntaxNode& node, + const std::map& block_to_index, + const std::map& inst_to_index); + } // namespace Shader::IR diff --git a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp index f53a0f4d4..d4759b32e 100644 --- a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp +++ b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "shader_recompiler/ir/program.h" +#include "video_core/buffer_cache/buffer_cache.h" namespace Shader::Optimization { @@ -79,14 +80,21 @@ void Visit(Info& info, const IR::Inst& inst) { info.uses_lane_id = true; break; case IR::Opcode::ReadConst: - if (!info.has_readconst) { + if (info.readconst_types == Info::ReadConstType::None) { info.buffers.push_back({ .used_types = IR::Type::U32, - .inline_cbuf = AmdGpu::Buffer::Null(), - .buffer_type = BufferType::ReadConstUbo, + // We can't guarantee that flatbuf will not grow past UBO + // limit if there are a lot of ReadConsts. (We could specialize) + .inline_cbuf = AmdGpu::Buffer::Placeholder(std::numeric_limits::max()), + .buffer_type = BufferType::Flatbuf, }); - info.has_readconst = true; } + if (inst.Flags() != 0) { + info.readconst_types |= Info::ReadConstType::Immediate; + } else { + info.readconst_types |= Info::ReadConstType::Dynamic; + } + info.dma_types |= IR::Type::U32; break; case IR::Opcode::PackUfloat10_11_11: info.uses_pack_10_11_11 = true; @@ -105,6 +113,21 @@ void CollectShaderInfoPass(IR::Program& program) { Visit(program.info, inst); } } + + if (program.info.dma_types != IR::Type::Void) { + program.info.buffers.push_back({ + .used_types = IR::Type::U64, + .inline_cbuf = AmdGpu::Buffer::Placeholder(VideoCore::BufferCache::BDA_PAGETABLE_SIZE), + .buffer_type = BufferType::BdaPagetable, + .is_written = true, + }); + program.info.buffers.push_back({ + .used_types = IR::Type::U8, + .inline_cbuf = AmdGpu::Buffer::Placeholder(VideoCore::BufferCache::FAULT_BUFFER_SIZE), + .buffer_type = BufferType::FaultBuffer, + .is_written = true, + }); + } } } // namespace Shader::Optimization diff --git a/src/shader_recompiler/ir/program.cpp b/src/shader_recompiler/ir/program.cpp index 7728a3ccb..f2f6e34fa 100644 --- a/src/shader_recompiler/ir/program.cpp +++ b/src/shader_recompiler/ir/program.cpp @@ -6,13 +6,30 @@ #include +#include "common/config.h" +#include "common/io_file.h" +#include "common/path_util.h" #include "shader_recompiler/ir/basic_block.h" #include "shader_recompiler/ir/program.h" #include "shader_recompiler/ir/value.h" namespace Shader::IR { -std::string DumpProgram(const Program& program) { +void DumpProgram(const Program& program, const Info& info, const std::string& type) { + using namespace Common::FS; + + if (!Config::dumpShaders()) { + return; + } + + const auto dump_dir = GetUserPath(PathType::ShaderDir) / "dumps"; + if (!std::filesystem::exists(dump_dir)) { + std::filesystem::create_directories(dump_dir); + } + const auto ir_filename = + fmt::format("{}_{:#018x}.{}irprogram.txt", info.stage, info.pgm_hash, type); + const auto ir_file = IOFile{dump_dir / ir_filename, FileAccessMode::Write, FileType::TextFile}; + size_t index{0}; std::map inst_to_index; std::map block_to_index; @@ -21,11 +38,20 @@ std::string DumpProgram(const Program& program) { block_to_index.emplace(block, index); ++index; } - std::string ret; + for (const auto& block : program.blocks) { - ret += IR::DumpBlock(*block, block_to_index, inst_to_index, index) + '\n'; + std::string s = IR::DumpBlock(*block, block_to_index, inst_to_index, index) + '\n'; + ir_file.WriteString(s); + } + + const auto asl_filename = fmt::format("{}_{:#018x}.{}asl.txt", info.stage, info.pgm_hash, type); + const auto asl_file = + IOFile{dump_dir / asl_filename, FileAccessMode::Write, FileType::TextFile}; + + for (const auto& node : program.syntax_list) { + std::string s = IR::DumpASLNode(node, block_to_index, inst_to_index) + '\n'; + asl_file.WriteString(s); } - return ret; } } // namespace Shader::IR diff --git a/src/shader_recompiler/ir/program.h b/src/shader_recompiler/ir/program.h index 84a1a2d40..3ffd4dc96 100644 --- a/src/shader_recompiler/ir/program.h +++ b/src/shader_recompiler/ir/program.h @@ -21,6 +21,6 @@ struct Program { Info& info; }; -[[nodiscard]] std::string DumpProgram(const Program& program); +void DumpProgram(const Program& program, const Info& info, const std::string& type = ""); } // namespace Shader::IR diff --git a/src/shader_recompiler/recompiler.cpp b/src/shader_recompiler/recompiler.cpp index 3e0bd98d2..9f92857d6 100644 --- a/src/shader_recompiler/recompiler.cpp +++ b/src/shader_recompiler/recompiler.cpp @@ -85,6 +85,8 @@ IR::Program TranslateProgram(std::span code, Pools& pools, Info& info Shader::Optimization::ConstantPropagationPass(program.post_order_blocks); Shader::Optimization::CollectShaderInfoPass(program); + Shader::IR::DumpProgram(program, info); + return program; } diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index d1cd98634..706e94c2b 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -133,6 +133,7 @@ void Liverpool::Process(std::stop_token stoken) { VideoCore::EndCapture(); if (rasterizer) { + rasterizer->ProcessFaults(); rasterizer->Flush(); } submit_done = false; diff --git a/src/video_core/amdgpu/resource.h b/src/video_core/amdgpu/resource.h index 9060074fb..89ac04f9a 100644 --- a/src/video_core/amdgpu/resource.h +++ b/src/video_core/amdgpu/resource.h @@ -37,6 +37,13 @@ struct Buffer { return buffer; } + static constexpr Buffer Placeholder(u32 size) { + Buffer buffer{}; + buffer.base_address = 1; + buffer.num_records = size; + return buffer; + } + bool Valid() const { return type == 0u; } diff --git a/src/video_core/buffer_cache/buffer.cpp b/src/video_core/buffer_cache/buffer.cpp index 15ef746cd..15bf0d81e 100644 --- a/src/video_core/buffer_cache/buffer.cpp +++ b/src/video_core/buffer_cache/buffer.cpp @@ -70,8 +70,11 @@ UniqueBuffer::~UniqueBuffer() { void UniqueBuffer::Create(const vk::BufferCreateInfo& buffer_ci, MemoryUsage usage, VmaAllocationInfo* out_alloc_info) { + const bool with_bda = bool(buffer_ci.usage & vk::BufferUsageFlagBits::eShaderDeviceAddress); + const VmaAllocationCreateFlags bda_flag = + with_bda ? VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT : 0; const VmaAllocationCreateInfo alloc_ci = { - .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | MemoryUsageVmaFlags(usage), + .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | bda_flag | MemoryUsageVmaFlags(usage), .usage = MemoryUsageVma(usage), .requiredFlags = 0, .preferredFlags = MemoryUsagePreferredVmaFlags(usage), @@ -86,6 +89,15 @@ void UniqueBuffer::Create(const vk::BufferCreateInfo& buffer_ci, MemoryUsage usa ASSERT_MSG(result == VK_SUCCESS, "Failed allocating buffer with error {}", vk::to_string(vk::Result{result})); buffer = vk::Buffer{unsafe_buffer}; + + if (with_bda) { + vk::BufferDeviceAddressInfo bda_info{ + .buffer = buffer, + }; + auto bda_result = device.getBufferAddress(bda_info); + ASSERT_MSG(bda_result != 0, "Failed to get buffer device address"); + bda_addr = bda_result; + } } Buffer::Buffer(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, MemoryUsage usage_, diff --git a/src/video_core/buffer_cache/buffer.h b/src/video_core/buffer_cache/buffer.h index 188b4b2ca..530968787 100644 --- a/src/video_core/buffer_cache/buffer.h +++ b/src/video_core/buffer_cache/buffer.h @@ -68,6 +68,7 @@ struct UniqueBuffer { VmaAllocator allocator; VmaAllocation allocation; vk::Buffer buffer{}; + vk::DeviceAddress bda_addr = 0; }; class Buffer { @@ -115,6 +116,11 @@ public: return buffer; } + vk::DeviceAddress BufferDeviceAddress() const noexcept { + ASSERT_MSG(buffer.bda_addr != 0, "Can't get BDA from a non BDA buffer"); + return buffer.bda_addr; + } + std::optional GetBarrier( vk::Flags dst_acess_mask, vk::PipelineStageFlagBits2 dst_stage, u32 offset = 0) { diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index c993ef3e5..45863d8e8 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -3,13 +3,17 @@ #include #include "common/alignment.h" +#include "common/debug.h" #include "common/scope_exit.h" #include "common/types.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/buffer_cache/buffer_cache.h" +#include "video_core/host_shaders/fault_buffer_process_comp.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" #include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_rasterizer.h" #include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" #include "video_core/texture_cache/texture_cache.h" namespace VideoCore { @@ -17,17 +21,26 @@ namespace VideoCore { static constexpr size_t DataShareBufferSize = 64_KB; static constexpr size_t StagingBufferSize = 512_MB; static constexpr size_t UboStreamBufferSize = 128_MB; +static constexpr size_t DownloadBufferSize = 128_MB; +static constexpr size_t MaxPageFaults = 1024; BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, - AmdGpu::Liverpool* liverpool_, TextureCache& texture_cache_, - PageManager& tracker_) - : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, + Vulkan::Rasterizer& rasterizer_, AmdGpu::Liverpool* liverpool_, + TextureCache& texture_cache_, PageManager& tracker_) + : instance{instance_}, scheduler{scheduler_}, rasterizer{rasterizer_}, liverpool{liverpool_}, texture_cache{texture_cache_}, tracker{tracker_}, staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize}, stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize}, + download_buffer(instance, scheduler, MemoryUsage::Download, DownloadBufferSize), gds_buffer{instance, scheduler, MemoryUsage::Stream, 0, AllFlags, DataShareBufferSize}, - memory_tracker{&tracker} { + bda_pagetable_buffer{instance, scheduler, MemoryUsage::DeviceLocal, + 0, AllFlags, BDA_PAGETABLE_SIZE}, + fault_buffer(instance, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, FAULT_BUFFER_SIZE), + memory_tracker{tracker} { Vulkan::SetObjectName(instance.GetDevice(), gds_buffer.Handle(), "GDS Buffer"); + Vulkan::SetObjectName(instance.GetDevice(), bda_pagetable_buffer.Handle(), + "BDA Page Table Buffer"); + Vulkan::SetObjectName(instance.GetDevice(), fault_buffer.Handle(), "Fault Buffer"); // Ensure the first slot is used for the null buffer const auto null_id = @@ -35,15 +48,93 @@ BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& s ASSERT(null_id.index == 0); const vk::Buffer& null_buffer = slot_buffers[null_id].buffer; Vulkan::SetObjectName(instance.GetDevice(), null_buffer, "Null Buffer"); + + // Prepare the fault buffer parsing pipeline + boost::container::static_vector bindings{ + { + .binding = 0, + .descriptorType = vk::DescriptorType::eStorageBuffer, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eCompute, + }, + { + .binding = 1, + .descriptorType = vk::DescriptorType::eStorageBuffer, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eCompute, + }, + }; + + const vk::DescriptorSetLayoutCreateInfo desc_layout_ci = { + .flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR, + .bindingCount = static_cast(bindings.size()), + .pBindings = bindings.data(), + }; + auto [desc_layout_result, desc_layout] = + instance.GetDevice().createDescriptorSetLayoutUnique(desc_layout_ci); + ASSERT_MSG(desc_layout_result == vk::Result::eSuccess, + "Failed to create descriptor set layout: {}", vk::to_string(desc_layout_result)); + fault_process_desc_layout = std::move(desc_layout); + + const auto& module = Vulkan::Compile(HostShaders::FAULT_BUFFER_PROCESS_COMP, + vk::ShaderStageFlagBits::eCompute, instance.GetDevice()); + Vulkan::SetObjectName(instance.GetDevice(), module, "Fault Buffer Parser"); + + const vk::SpecializationMapEntry specialization_map_entry = { + .constantID = 0, + .offset = 0, + .size = sizeof(u32), + }; + + const vk::SpecializationInfo specialization_info = { + .mapEntryCount = 1, + .pMapEntries = &specialization_map_entry, + .dataSize = sizeof(u32), + .pData = &CACHING_PAGEBITS, + }; + + const vk::PipelineShaderStageCreateInfo shader_ci = { + .stage = vk::ShaderStageFlagBits::eCompute, + .module = module, + .pName = "main", + .pSpecializationInfo = &specialization_info, + }; + + const vk::PipelineLayoutCreateInfo layout_info = { + .setLayoutCount = 1U, + .pSetLayouts = &(*fault_process_desc_layout), + }; + auto [layout_result, layout] = instance.GetDevice().createPipelineLayoutUnique(layout_info); + ASSERT_MSG(layout_result == vk::Result::eSuccess, "Failed to create pipeline layout: {}", + vk::to_string(layout_result)); + fault_process_pipeline_layout = std::move(layout); + + const vk::ComputePipelineCreateInfo pipeline_info = { + .stage = shader_ci, + .layout = *fault_process_pipeline_layout, + }; + auto [pipeline_result, pipeline] = + instance.GetDevice().createComputePipelineUnique({}, pipeline_info); + ASSERT_MSG(pipeline_result == vk::Result::eSuccess, "Failed to create compute pipeline: {}", + vk::to_string(pipeline_result)); + fault_process_pipeline = std::move(pipeline); + Vulkan::SetObjectName(instance.GetDevice(), *fault_process_pipeline, + "Fault Buffer Parser Pipeline"); + + instance.GetDevice().destroyShaderModule(module); } BufferCache::~BufferCache() = default; -void BufferCache::InvalidateMemory(VAddr device_addr, u64 size) { +void BufferCache::InvalidateMemory(VAddr device_addr, u64 size, bool unmap) { const bool is_tracked = IsRegionRegistered(device_addr, size); if (is_tracked) { // Mark the page as CPU modified to stop tracking writes. memory_tracker.MarkRegionAsCpuModified(device_addr, size); + + if (unmap) { + return; + } } } @@ -69,20 +160,20 @@ void BufferCache::DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 si if (total_size_bytes == 0) { return; } - const auto [staging, offset] = staging_buffer.Map(total_size_bytes); + const auto [download, offset] = download_buffer.Map(total_size_bytes); for (auto& copy : copies) { // Modify copies to have the staging offset in mind copy.dstOffset += offset; } - staging_buffer.Commit(); + download_buffer.Commit(); scheduler.EndRendering(); const auto cmdbuf = scheduler.CommandBuffer(); - cmdbuf.copyBuffer(buffer.buffer, staging_buffer.Handle(), copies); + cmdbuf.copyBuffer(buffer.buffer, download_buffer.Handle(), copies); scheduler.Finish(); for (const auto& copy : copies) { const VAddr copy_device_addr = buffer.CpuAddr() + copy.srcOffset; const u64 dst_offset = copy.dstOffset - offset; - std::memcpy(std::bit_cast(copy_device_addr), staging + dst_offset, copy.size); + std::memcpy(std::bit_cast(copy_device_addr), download + dst_offset, copy.size); } } @@ -206,58 +297,37 @@ void BufferCache::InlineData(VAddr address, const void* value, u32 num_bytes, bo memcpy(std::bit_cast(address), value, num_bytes); return; } - scheduler.EndRendering(); - const Buffer* buffer = [&] { + Buffer* buffer = [&] { if (is_gds) { return &gds_buffer; } const BufferId buffer_id = FindBuffer(address, num_bytes); return &slot_buffers[buffer_id]; }(); - const auto cmdbuf = scheduler.CommandBuffer(); - const vk::BufferMemoryBarrier2 pre_barrier = { - .srcStageMask = vk::PipelineStageFlagBits2::eAllCommands, - .srcAccessMask = vk::AccessFlagBits2::eMemoryRead, - .dstStageMask = vk::PipelineStageFlagBits2::eTransfer, - .dstAccessMask = vk::AccessFlagBits2::eTransferWrite, - .buffer = buffer->Handle(), - .offset = buffer->Offset(address), - .size = num_bytes, - }; - const vk::BufferMemoryBarrier2 post_barrier = { - .srcStageMask = vk::PipelineStageFlagBits2::eTransfer, - .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, - .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, - .dstAccessMask = vk::AccessFlagBits2::eMemoryRead, - .buffer = buffer->Handle(), - .offset = buffer->Offset(address), - .size = num_bytes, - }; - cmdbuf.pipelineBarrier2(vk::DependencyInfo{ - .dependencyFlags = vk::DependencyFlagBits::eByRegion, - .bufferMemoryBarrierCount = 1, - .pBufferMemoryBarriers = &pre_barrier, - }); - // vkCmdUpdateBuffer can only copy up to 65536 bytes at a time. - static constexpr u32 UpdateBufferMaxSize = 65536; - const auto dst_offset = buffer->Offset(address); - for (u32 offset = 0; offset < num_bytes; offset += UpdateBufferMaxSize) { - const auto* update_src = static_cast(value) + offset; - const auto update_dst = dst_offset + offset; - const auto update_size = std::min(num_bytes - offset, UpdateBufferMaxSize); - cmdbuf.updateBuffer(buffer->Handle(), update_dst, update_size, update_src); + InlineDataBuffer(*buffer, address, value, num_bytes); +} + +void BufferCache::WriteData(VAddr address, const void* value, u32 num_bytes, bool is_gds) { + ASSERT_MSG(address % 4 == 0, "GDS offset must be dword aligned"); + if (!is_gds && !IsRegionRegistered(address, num_bytes)) { + memcpy(std::bit_cast(address), value, num_bytes); + return; } - cmdbuf.pipelineBarrier2(vk::DependencyInfo{ - .dependencyFlags = vk::DependencyFlagBits::eByRegion, - .bufferMemoryBarrierCount = 1, - .pBufferMemoryBarriers = &post_barrier, - }); + Buffer* buffer = [&] { + if (is_gds) { + return &gds_buffer; + } + const BufferId buffer_id = FindBuffer(address, num_bytes); + return &slot_buffers[buffer_id]; + }(); + WriteDataBuffer(*buffer, address, value, num_bytes); } std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, bool is_written, bool is_texel_buffer, BufferId buffer_id) { // For small uniform buffers that have not been modified by gpu // use device local stream buffer to reduce renderpass breaks. + // Maybe we want to modify the threshold now that the page size is 16KB? static constexpr u64 StreamThreshold = CACHING_PAGESIZE; const bool is_gpu_dirty = memory_tracker.IsRegionGpuModified(device_addr, size); if (!is_written && size <= StreamThreshold && !is_gpu_dirty) { @@ -280,7 +350,7 @@ std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, b std::pair BufferCache::ObtainViewBuffer(VAddr gpu_addr, u32 size, bool prefer_gpu) { // Check if any buffer contains the full requested range. const u64 page = gpu_addr >> CACHING_PAGEBITS; - const BufferId buffer_id = page_table[page]; + const BufferId buffer_id = page_table[page].buffer_id; if (buffer_id) { Buffer& buffer = slot_buffers[buffer_id]; if (buffer.IsInBounds(gpu_addr, size)) { @@ -300,24 +370,8 @@ std::pair BufferCache::ObtainViewBuffer(VAddr gpu_addr, u32 size, } bool BufferCache::IsRegionRegistered(VAddr addr, size_t size) { - const VAddr end_addr = addr + size; - const u64 page_end = Common::DivCeil(end_addr, CACHING_PAGESIZE); - for (u64 page = addr >> CACHING_PAGEBITS; page < page_end;) { - const BufferId buffer_id = page_table[page]; - if (!buffer_id) { - ++page; - continue; - } - std::shared_lock lk{mutex}; - Buffer& buffer = slot_buffers[buffer_id]; - const VAddr buf_start_addr = buffer.CpuAddr(); - const VAddr buf_end_addr = buf_start_addr + buffer.SizeBytes(); - if (buf_start_addr < end_addr && addr < buf_end_addr) { - return true; - } - page = Common::DivCeil(buf_end_addr, CACHING_PAGESIZE); - } - return false; + // Check if we are missing some edge case here + return buffer_ranges.Intersects(addr, size); } bool BufferCache::IsRegionCpuModified(VAddr addr, size_t size) { @@ -333,7 +387,7 @@ BufferId BufferCache::FindBuffer(VAddr device_addr, u32 size) { return NULL_BUFFER_ID; } const u64 page = device_addr >> CACHING_PAGEBITS; - const BufferId buffer_id = page_table[page]; + const BufferId buffer_id = page_table[page].buffer_id; if (!buffer_id) { return CreateBuffer(device_addr, size); } @@ -379,7 +433,7 @@ BufferCache::OverlapResult BufferCache::ResolveOverlaps(VAddr device_addr, u32 w } for (; device_addr >> CACHING_PAGEBITS < Common::DivCeil(end, CACHING_PAGESIZE); device_addr += CACHING_PAGESIZE) { - const BufferId overlap_id = page_table[device_addr >> CACHING_PAGEBITS]; + const BufferId overlap_id = page_table[device_addr >> CACHING_PAGEBITS].buffer_id; if (!overlap_id) { continue; } @@ -480,11 +534,21 @@ BufferId BufferCache::CreateBuffer(VAddr device_addr, u32 wanted_size) { const OverlapResult overlap = ResolveOverlaps(device_addr, wanted_size); const u32 size = static_cast(overlap.end - overlap.begin); const BufferId new_buffer_id = [&] { - std::scoped_lock lk{mutex}; + std::scoped_lock lk{slot_buffers_mutex}; return slot_buffers.insert(instance, scheduler, MemoryUsage::DeviceLocal, overlap.begin, - AllFlags, size); + AllFlags | vk::BufferUsageFlagBits::eShaderDeviceAddress, size); }(); auto& new_buffer = slot_buffers[new_buffer_id]; + boost::container::small_vector bda_addrs; + const u64 start_page = overlap.begin >> CACHING_PAGEBITS; + const u64 size_pages = size >> CACHING_PAGEBITS; + bda_addrs.reserve(size_pages); + for (u64 i = 0; i < size_pages; ++i) { + vk::DeviceAddress addr = new_buffer.BufferDeviceAddress() + (i << CACHING_PAGEBITS); + bda_addrs.push_back(addr); + } + WriteDataBuffer(bda_pagetable_buffer, start_page * sizeof(vk::DeviceAddress), bda_addrs.data(), + bda_addrs.size() * sizeof(vk::DeviceAddress)); const size_t size_bytes = new_buffer.SizeBytes(); const auto cmdbuf = scheduler.CommandBuffer(); scheduler.EndRendering(); @@ -496,6 +560,129 @@ BufferId BufferCache::CreateBuffer(VAddr device_addr, u32 wanted_size) { return new_buffer_id; } +void BufferCache::ProcessFaultBuffer() { + // Run fault processing shader + const auto [mapped, offset] = download_buffer.Map(MaxPageFaults * sizeof(u64)); + vk::BufferMemoryBarrier2 fault_buffer_barrier{ + .srcStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .srcAccessMask = vk::AccessFlagBits2::eShaderWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eComputeShader, + .dstAccessMask = vk::AccessFlagBits2::eShaderRead, + .buffer = fault_buffer.Handle(), + .offset = 0, + .size = FAULT_BUFFER_SIZE, + }; + vk::BufferMemoryBarrier2 download_barrier{ + .srcStageMask = vk::PipelineStageFlagBits2::eTransfer, + .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eComputeShader, + .dstAccessMask = vk::AccessFlagBits2::eShaderRead | vk::AccessFlagBits2::eShaderWrite, + .buffer = download_buffer.Handle(), + .offset = offset, + .size = MaxPageFaults * sizeof(u64), + }; + std::array barriers{fault_buffer_barrier, download_barrier}; + vk::DescriptorBufferInfo fault_buffer_info{ + .buffer = fault_buffer.Handle(), + .offset = 0, + .range = FAULT_BUFFER_SIZE, + }; + vk::DescriptorBufferInfo download_info{ + .buffer = download_buffer.Handle(), + .offset = offset, + .range = MaxPageFaults * sizeof(u64), + }; + boost::container::small_vector writes{ + { + .dstSet = VK_NULL_HANDLE, + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eStorageBuffer, + .pBufferInfo = &fault_buffer_info, + }, + { + .dstSet = VK_NULL_HANDLE, + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eStorageBuffer, + .pBufferInfo = &download_info, + }, + }; + download_buffer.Commit(); + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.fillBuffer(download_buffer.Handle(), offset, MaxPageFaults * sizeof(u64), 0); + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 2, + .pBufferMemoryBarriers = barriers.data(), + }); + cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, *fault_process_pipeline); + cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, *fault_process_pipeline_layout, 0, + writes); + constexpr u32 num_threads = CACHING_NUMPAGES / 32; // 1 bit per page, 32 pages per workgroup + constexpr u32 num_workgroups = Common::DivCeil(num_threads, 64u); + cmdbuf.dispatch(num_workgroups, 1, 1); + + // Reset fault buffer + const vk::BufferMemoryBarrier2 reset_pre_barrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eComputeShader, + .srcAccessMask = vk::AccessFlagBits2::eShaderRead, + .dstStageMask = vk::PipelineStageFlagBits2::eTransfer, + .dstAccessMask = vk::AccessFlagBits2::eTransferWrite, + .buffer = fault_buffer.Handle(), + .offset = 0, + .size = FAULT_BUFFER_SIZE, + }; + const vk::BufferMemoryBarrier2 reset_post_barrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eTransfer, + .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .dstAccessMask = vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite, + .buffer = fault_buffer.Handle(), + .offset = 0, + .size = FAULT_BUFFER_SIZE, + }; + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &reset_pre_barrier, + }); + cmdbuf.fillBuffer(fault_buffer.buffer, 0, FAULT_BUFFER_SIZE, 0); + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &reset_post_barrier, + }); + + // Defer creating buffers + scheduler.DeferOperation([this, mapped]() { + // Create the fault buffers batched + boost::icl::interval_set fault_ranges; + const u64* fault_ptr = std::bit_cast(mapped); + const u32 fault_count = static_cast(*(fault_ptr++)); + for (u32 i = 0; i < fault_count; ++i) { + const VAddr fault = *(fault_ptr++); + const VAddr fault_end = fault + CACHING_PAGESIZE; // This can be adjusted + fault_ranges += + boost::icl::interval_set::interval_type::right_open(fault, fault_end); + LOG_INFO(Render_Vulkan, "Accessed non-GPU mapped memory at {:#x}", fault); + } + for (const auto& range : fault_ranges) { + const VAddr start = range.lower(); + const VAddr end = range.upper(); + const u64 page_start = start >> CACHING_PAGEBITS; + const u64 page_end = Common::DivCeil(end, CACHING_PAGESIZE); + // Buffer size is in 32 bits + ASSERT_MSG((range.upper() - range.lower()) <= std::numeric_limits::max(), + "Buffer size is too large"); + CreateBuffer(start, static_cast(end - start)); + } + }); +} + void BufferCache::Register(BufferId buffer_id) { ChangeRegister(buffer_id); } @@ -514,11 +701,16 @@ void BufferCache::ChangeRegister(BufferId buffer_id) { const u64 page_end = Common::DivCeil(device_addr_end, CACHING_PAGESIZE); for (u64 page = page_begin; page != page_end; ++page) { if constexpr (insert) { - page_table[page] = buffer_id; + page_table[page].buffer_id = buffer_id; } else { - page_table[page] = BufferId{}; + page_table[page].buffer_id = BufferId{}; } } + if constexpr (insert) { + buffer_ranges.Add(buffer.CpuAddr(), buffer.SizeBytes(), buffer_id); + } else { + buffer_ranges.Subtract(buffer.CpuAddr(), buffer.SizeBytes()); + } } void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, @@ -697,6 +889,138 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, return true; } +void BufferCache::SynchronizeBuffersInRange(VAddr device_addr, u64 size) { + if (device_addr == 0) { + return; + } + VAddr device_addr_end = device_addr + size; + ForEachBufferInRange(device_addr, size, [&](BufferId buffer_id, Buffer& buffer) { + RENDERER_TRACE; + VAddr start = std::max(buffer.CpuAddr(), device_addr); + VAddr end = std::min(buffer.CpuAddr() + buffer.SizeBytes(), device_addr_end); + u32 size = static_cast(end - start); + SynchronizeBuffer(buffer, start, size, false); + }); +} + +void BufferCache::MemoryBarrier() { + // Vulkan doesn't know which buffer we access in a shader if we use + // BufferDeviceAddress. We need a full memory barrier. + // For now, we only read memory using BDA. If we want to write to it, + // we might need to change this. + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + vk::MemoryBarrier2 barrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eTransfer, + .srcAccessMask = vk::AccessFlagBits2::eMemoryWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .dstAccessMask = vk::AccessFlagBits2::eMemoryRead, + }; + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .memoryBarrierCount = 1, + .pMemoryBarriers = &barrier, + }); +} + +void BufferCache::InlineDataBuffer(Buffer& buffer, VAddr address, const void* value, + u32 num_bytes) { + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + const vk::BufferMemoryBarrier2 pre_barrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .srcAccessMask = vk::AccessFlagBits2::eMemoryRead, + .dstStageMask = vk::PipelineStageFlagBits2::eTransfer, + .dstAccessMask = vk::AccessFlagBits2::eTransferWrite, + .buffer = buffer.Handle(), + .offset = buffer.Offset(address), + .size = num_bytes, + }; + const vk::BufferMemoryBarrier2 post_barrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eTransfer, + .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .dstAccessMask = vk::AccessFlagBits2::eMemoryRead, + .buffer = buffer.Handle(), + .offset = buffer.Offset(address), + .size = num_bytes, + }; + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &pre_barrier, + }); + // vkCmdUpdateBuffer can only copy up to 65536 bytes at a time. + static constexpr u32 UpdateBufferMaxSize = 65536; + const auto dst_offset = buffer.Offset(address); + for (u32 offset = 0; offset < num_bytes; offset += UpdateBufferMaxSize) { + const auto* update_src = static_cast(value) + offset; + const auto update_dst = dst_offset + offset; + const auto update_size = std::min(num_bytes - offset, UpdateBufferMaxSize); + cmdbuf.updateBuffer(buffer.Handle(), update_dst, update_size, update_src); + } + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &post_barrier, + }); +} + +void BufferCache::WriteDataBuffer(Buffer& buffer, VAddr address, const void* value, u32 num_bytes) { + vk::BufferCopy copy = { + .srcOffset = 0, + .dstOffset = buffer.Offset(address), + .size = num_bytes, + }; + vk::Buffer src_buffer = staging_buffer.Handle(); + if (num_bytes < StagingBufferSize) { + const auto [staging, offset] = staging_buffer.Map(num_bytes); + std::memcpy(staging, value, num_bytes); + copy.srcOffset = offset; + staging_buffer.Commit(); + } else { + // For large one time transfers use a temporary host buffer. + // RenderDoc can lag quite a bit if the stream buffer is too large. + Buffer temp_buffer{ + instance, scheduler, MemoryUsage::Upload, 0, vk::BufferUsageFlagBits::eTransferSrc, + num_bytes}; + src_buffer = temp_buffer.Handle(); + u8* const staging = temp_buffer.mapped_data.data(); + std::memcpy(staging, value, num_bytes); + scheduler.DeferOperation([buffer = std::move(temp_buffer)]() mutable {}); + } + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + const vk::BufferMemoryBarrier2 pre_barrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .srcAccessMask = vk::AccessFlagBits2::eMemoryRead, + .dstStageMask = vk::PipelineStageFlagBits2::eTransfer, + .dstAccessMask = vk::AccessFlagBits2::eTransferWrite, + .buffer = buffer.Handle(), + .offset = buffer.Offset(address), + .size = num_bytes, + }; + const vk::BufferMemoryBarrier2 post_barrier = { + .srcStageMask = vk::PipelineStageFlagBits2::eTransfer, + .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .dstAccessMask = vk::AccessFlagBits2::eMemoryRead | vk::AccessFlagBits2::eMemoryWrite, + .buffer = buffer.Handle(), + .offset = buffer.Offset(address), + .size = num_bytes, + }; + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &pre_barrier, + }); + cmdbuf.copyBuffer(src_buffer, buffer.Handle(), copy); + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 1, + .pBufferMemoryBarriers = &post_barrier, + }); +} + void BufferCache::DeleteBuffer(BufferId buffer_id) { Buffer& buffer = slot_buffers[buffer_id]; Unregister(buffer_id); diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 71a6bed2a..2d6551a7f 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -38,14 +38,22 @@ class TextureCache; class BufferCache { public: - static constexpr u32 CACHING_PAGEBITS = 12; + static constexpr u32 CACHING_PAGEBITS = 14; static constexpr u64 CACHING_PAGESIZE = u64{1} << CACHING_PAGEBITS; - static constexpr u64 DEVICE_PAGESIZE = 4_KB; + static constexpr u64 DEVICE_PAGESIZE = 16_KB; + static constexpr u64 CACHING_NUMPAGES = u64{1} << (40 - CACHING_PAGEBITS); + + static constexpr u64 BDA_PAGETABLE_SIZE = CACHING_NUMPAGES * sizeof(vk::DeviceAddress); + static constexpr u64 FAULT_BUFFER_SIZE = CACHING_NUMPAGES / 8; // Bit per page + + struct PageData { + BufferId buffer_id{}; + }; struct Traits { - using Entry = BufferId; + using Entry = PageData; static constexpr size_t AddressSpaceBits = 40; - static constexpr size_t FirstLevelBits = 14; + static constexpr size_t FirstLevelBits = 16; static constexpr size_t PageBits = CACHING_PAGEBITS; }; using PageTable = MultiLevelPageTable; @@ -59,8 +67,8 @@ public: public: explicit BufferCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, - AmdGpu::Liverpool* liverpool, TextureCache& texture_cache, - PageManager& tracker); + Vulkan::Rasterizer& rasterizer_, AmdGpu::Liverpool* liverpool, + TextureCache& texture_cache, PageManager& tracker); ~BufferCache(); /// Returns a pointer to GDS device local buffer. @@ -73,13 +81,23 @@ public: return stream_buffer; } + /// Retrieves the device local DBA page table buffer. + [[nodiscard]] Buffer* GetBdaPageTableBuffer() noexcept { + return &bda_pagetable_buffer; + } + + /// Retrieves the fault buffer. + [[nodiscard]] Buffer* GetFaultBuffer() noexcept { + return &fault_buffer; + } + /// Retrieves the buffer with the specified id. [[nodiscard]] Buffer& GetBuffer(BufferId id) { return slot_buffers[id]; } /// Invalidates any buffer in the logical page range. - void InvalidateMemory(VAddr device_addr, u64 size); + void InvalidateMemory(VAddr device_addr, u64 size, bool unmap); /// Binds host vertex buffers for the current draw. void BindVertexBuffers(const Vulkan::GraphicsPipeline& pipeline); @@ -87,9 +105,12 @@ public: /// Bind host index buffer for the current draw. void BindIndexBuffer(u32 index_offset); - /// Writes a value to GPU buffer. + /// Writes a value to GPU buffer. (uses command buffer to temporarily store the data) void InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds); + /// Writes a value to GPU buffer. (uses staging buffer to temporarily store the data) + void WriteData(VAddr address, const void* value, u32 num_bytes, bool is_gds); + /// Obtains a buffer for the specified region. [[nodiscard]] std::pair ObtainBuffer(VAddr gpu_addr, u32 size, bool is_written, bool is_texel_buffer = false, @@ -108,24 +129,29 @@ public: /// Return true when a CPU region is modified from the GPU [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size); - [[nodiscard]] BufferId FindBuffer(VAddr device_addr, u32 size); + /// Return buffer id for the specified region + BufferId FindBuffer(VAddr device_addr, u32 size); + + /// Processes the fault buffer. + void ProcessFaultBuffer(); + + /// Synchronizes all buffers in the specified range. + void SynchronizeBuffersInRange(VAddr device_addr, u64 size); + + /// Synchronizes all buffers neede for DMA. + void SynchronizeDmaBuffers(); + + /// Record memory barrier. Used for buffers when accessed via BDA. + void MemoryBarrier(); private: template void ForEachBufferInRange(VAddr device_addr, u64 size, Func&& func) { - const u64 page_end = Common::DivCeil(device_addr + size, CACHING_PAGESIZE); - for (u64 page = device_addr >> CACHING_PAGEBITS; page < page_end;) { - const BufferId buffer_id = page_table[page]; - if (!buffer_id) { - ++page; - continue; - } - Buffer& buffer = slot_buffers[buffer_id]; - func(buffer_id, buffer); - - const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes(); - page = Common::DivCeil(end_addr, CACHING_PAGESIZE); - } + buffer_ranges.ForEachInRange(device_addr, size, + [&](u64 page_start, u64 page_end, BufferId id) { + Buffer& buffer = slot_buffers[id]; + func(id, buffer); + }); } void DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size); @@ -134,7 +160,7 @@ private: void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score); - [[nodiscard]] BufferId CreateBuffer(VAddr device_addr, u32 wanted_size); + BufferId CreateBuffer(VAddr device_addr, u32 wanted_size); void Register(BufferId buffer_id); @@ -147,21 +173,33 @@ private: bool SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size); + void InlineDataBuffer(Buffer& buffer, VAddr address, const void* value, u32 num_bytes); + + void WriteDataBuffer(Buffer& buffer, VAddr address, const void* value, u32 num_bytes); + void DeleteBuffer(BufferId buffer_id); const Vulkan::Instance& instance; Vulkan::Scheduler& scheduler; + Vulkan::Rasterizer& rasterizer; AmdGpu::Liverpool* liverpool; TextureCache& texture_cache; PageManager& tracker; StreamBuffer staging_buffer; StreamBuffer stream_buffer; + StreamBuffer download_buffer; Buffer gds_buffer; - std::shared_mutex mutex; + Buffer bda_pagetable_buffer; + Buffer fault_buffer; + std::shared_mutex slot_buffers_mutex; Common::SlotVector slot_buffers; RangeSet gpu_modified_ranges; + SplitRangeMap buffer_ranges; MemoryTracker memory_tracker; PageTable page_table; + vk::UniqueDescriptorSetLayout fault_process_desc_layout; + vk::UniquePipeline fault_process_pipeline; + vk::UniquePipelineLayout fault_process_pipeline_layout; }; } // namespace VideoCore diff --git a/src/video_core/buffer_cache/memory_tracker_base.h b/src/video_core/buffer_cache/memory_tracker_base.h index d9166b11c..c60aa9c80 100644 --- a/src/video_core/buffer_cache/memory_tracker_base.h +++ b/src/video_core/buffer_cache/memory_tracker_base.h @@ -7,6 +7,7 @@ #include #include #include +#include "common/debug.h" #include "common/types.h" #include "video_core/buffer_cache/word_manager.h" @@ -19,11 +20,11 @@ public: static constexpr size_t MANAGER_POOL_SIZE = 32; public: - explicit MemoryTracker(PageManager* tracker_) : tracker{tracker_} {} + explicit MemoryTracker(PageManager& tracker_) : tracker{&tracker_} {} ~MemoryTracker() = default; /// Returns true if a region has been modified from the CPU - [[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept { + bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept { return IteratePages( query_cpu_addr, query_size, [](RegionManager* manager, u64 offset, size_t size) { return manager->template IsRegionModified(offset, size); @@ -31,7 +32,7 @@ public: } /// Returns true if a region has been modified from the GPU - [[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) noexcept { + bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) noexcept { return IteratePages( query_cpu_addr, query_size, [](RegionManager* manager, u64 offset, size_t size) { return manager->template IsRegionModified(offset, size); @@ -57,8 +58,7 @@ public: } /// Call 'func' for each CPU modified range and unmark those pages as CPU modified - template - void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, Func&& func) { + void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, auto&& func) { IteratePages(query_cpu_range, query_size, [&func](RegionManager* manager, u64 offset, size_t size) { manager->template ForEachModifiedRange( @@ -67,17 +67,12 @@ public: } /// Call 'func' for each GPU modified range and unmark those pages as GPU modified - template - void ForEachDownloadRange(VAddr query_cpu_range, u64 query_size, Func&& func) { + template + void ForEachDownloadRange(VAddr query_cpu_range, u64 query_size, auto&& func) { IteratePages(query_cpu_range, query_size, [&func](RegionManager* manager, u64 offset, size_t size) { - if constexpr (clear) { - manager->template ForEachModifiedRange( - manager->GetCpuAddr() + offset, size, func); - } else { - manager->template ForEachModifiedRange( - manager->GetCpuAddr() + offset, size, func); - } + manager->template ForEachModifiedRange( + manager->GetCpuAddr() + offset, size, func); }); } @@ -91,6 +86,7 @@ private: */ template bool IteratePages(VAddr cpu_address, size_t size, Func&& func) { + RENDERER_TRACE; using FuncReturn = typename std::invoke_result::type; static constexpr bool BOOL_BREAK = std::is_same_v; std::size_t remaining_size{size}; diff --git a/src/video_core/buffer_cache/range_set.h b/src/video_core/buffer_cache/range_set.h index 2abf6e524..5c8e78c7c 100644 --- a/src/video_core/buffer_cache/range_set.h +++ b/src/video_core/buffer_cache/range_set.h @@ -3,7 +3,10 @@ #pragma once +#include #include +#include +#include #include #include #include @@ -38,6 +41,22 @@ struct RangeSet { m_ranges_set.subtract(interval); } + void Clear() { + m_ranges_set.clear(); + } + + bool Contains(VAddr base_address, size_t size) const { + const VAddr end_address = base_address + size; + IntervalType interval{base_address, end_address}; + return boost::icl::contains(m_ranges_set, interval); + } + + bool Intersects(VAddr base_address, size_t size) const { + const VAddr end_address = base_address + size; + IntervalType interval{base_address, end_address}; + return boost::icl::intersects(m_ranges_set, interval); + } + template void ForEach(Func&& func) const { if (m_ranges_set.empty()) { @@ -77,14 +96,29 @@ struct RangeSet { } } + template + void ForEachNotInRange(VAddr base_addr, size_t size, Func&& func) const { + const VAddr end_addr = base_addr + size; + ForEachInRange(base_addr, size, [&](VAddr range_addr, VAddr range_end) { + if (size_t gap_size = range_addr - base_addr; gap_size != 0) { + func(base_addr, gap_size); + } + base_addr = range_end; + }); + if (base_addr != end_addr) { + func(base_addr, end_addr - base_addr); + } + } + IntervalSet m_ranges_set; }; +template class RangeMap { public: using IntervalMap = - boost::icl::interval_map; using IntervalType = typename IntervalMap::interval_type; @@ -99,7 +133,7 @@ public: RangeMap(RangeMap&& other); RangeMap& operator=(RangeMap&& other); - void Add(VAddr base_address, size_t size, u64 value) { + void Add(VAddr base_address, size_t size, const T& value) { const VAddr end_address = base_address + size; IntervalType interval{base_address, end_address}; m_ranges_map.add({interval, value}); @@ -111,6 +145,35 @@ public: m_ranges_map -= interval; } + void Clear() { + m_ranges_map.clear(); + } + + bool Contains(VAddr base_address, size_t size) const { + const VAddr end_address = base_address + size; + IntervalType interval{base_address, end_address}; + return boost::icl::contains(m_ranges_map, interval); + } + + bool Intersects(VAddr base_address, size_t size) const { + const VAddr end_address = base_address + size; + IntervalType interval{base_address, end_address}; + return boost::icl::intersects(m_ranges_map, interval); + } + + template + void ForEach(Func&& func) const { + if (m_ranges_map.empty()) { + return; + } + + for (const auto& [interval, value] : m_ranges_map) { + const VAddr inter_addr_end = interval.upper(); + const VAddr inter_addr = interval.lower(); + func(inter_addr, inter_addr_end, value); + } + } + template void ForEachInRange(VAddr base_addr, size_t size, Func&& func) const { if (m_ranges_map.empty()) { @@ -140,7 +203,111 @@ public: template void ForEachNotInRange(VAddr base_addr, size_t size, Func&& func) const { const VAddr end_addr = base_addr + size; - ForEachInRange(base_addr, size, [&](VAddr range_addr, VAddr range_end, u64) { + ForEachInRange(base_addr, size, [&](VAddr range_addr, VAddr range_end, const T&) { + if (size_t gap_size = range_addr - base_addr; gap_size != 0) { + func(base_addr, gap_size); + } + base_addr = range_end; + }); + if (base_addr != end_addr) { + func(base_addr, end_addr - base_addr); + } + } + +private: + IntervalMap m_ranges_map; +}; + +template +class SplitRangeMap { +public: + using IntervalMap = boost::icl::split_interval_map< + VAddr, T, boost::icl::total_absorber, std::less, boost::icl::inplace_identity, + boost::icl::inter_section, ICL_INTERVAL_INSTANCE(ICL_INTERVAL_DEFAULT, VAddr, std::less), + RangeSetsAllocator>; + using IntervalType = typename IntervalMap::interval_type; + +public: + SplitRangeMap() = default; + ~SplitRangeMap() = default; + + SplitRangeMap(SplitRangeMap const&) = delete; + SplitRangeMap& operator=(SplitRangeMap const&) = delete; + + SplitRangeMap(SplitRangeMap&& other); + SplitRangeMap& operator=(SplitRangeMap&& other); + + void Add(VAddr base_address, size_t size, const T& value) { + const VAddr end_address = base_address + size; + IntervalType interval{base_address, end_address}; + m_ranges_map.add({interval, value}); + } + + void Subtract(VAddr base_address, size_t size) { + const VAddr end_address = base_address + size; + IntervalType interval{base_address, end_address}; + m_ranges_map -= interval; + } + + void Clear() { + m_ranges_map.clear(); + } + + bool Contains(VAddr base_address, size_t size) const { + const VAddr end_address = base_address + size; + IntervalType interval{base_address, end_address}; + return boost::icl::contains(m_ranges_map, interval); + } + + bool Intersects(VAddr base_address, size_t size) const { + const VAddr end_address = base_address + size; + IntervalType interval{base_address, end_address}; + return boost::icl::intersects(m_ranges_map, interval); + } + + template + void ForEach(Func&& func) const { + if (m_ranges_map.empty()) { + return; + } + + for (const auto& [interval, value] : m_ranges_map) { + const VAddr inter_addr_end = interval.upper(); + const VAddr inter_addr = interval.lower(); + func(inter_addr, inter_addr_end, value); + } + } + + template + void ForEachInRange(VAddr base_addr, size_t size, Func&& func) const { + if (m_ranges_map.empty()) { + return; + } + const VAddr start_address = base_addr; + const VAddr end_address = start_address + size; + const IntervalType search_interval{start_address, end_address}; + auto it = m_ranges_map.lower_bound(search_interval); + if (it == m_ranges_map.end()) { + return; + } + auto end_it = m_ranges_map.upper_bound(search_interval); + for (; it != end_it; it++) { + VAddr inter_addr_end = it->first.upper(); + VAddr inter_addr = it->first.lower(); + if (inter_addr_end > end_address) { + inter_addr_end = end_address; + } + if (inter_addr < start_address) { + inter_addr = start_address; + } + func(inter_addr, inter_addr_end, it->second); + } + } + + template + void ForEachNotInRange(VAddr base_addr, size_t size, Func&& func) const { + const VAddr end_addr = base_addr + size; + ForEachInRange(base_addr, size, [&](VAddr range_addr, VAddr range_end, const T&) { if (size_t gap_size = range_addr - base_addr; gap_size != 0) { func(base_addr, gap_size); } diff --git a/src/video_core/buffer_cache/word_manager.h b/src/video_core/buffer_cache/word_manager.h index 5ad724f96..51a912c62 100644 --- a/src/video_core/buffer_cache/word_manager.h +++ b/src/video_core/buffer_cache/word_manager.h @@ -10,8 +10,10 @@ #ifdef __linux__ #include "common/adaptive_mutex.h" -#endif +#else #include "common/spin_lock.h" +#endif +#include "common/debug.h" #include "common/types.h" #include "video_core/page_manager.h" @@ -56,7 +58,7 @@ public: return cpu_addr; } - static u64 ExtractBits(u64 word, size_t page_start, size_t page_end) { + static constexpr u64 ExtractBits(u64 word, size_t page_start, size_t page_end) { constexpr size_t number_bits = sizeof(u64) * 8; const size_t limit_page_end = number_bits - std::min(page_end, number_bits); u64 bits = (word >> page_start) << page_start; @@ -64,7 +66,7 @@ public: return bits; } - static std::pair GetWordPage(VAddr address) { + static constexpr std::pair GetWordPage(VAddr address) { const size_t converted_address = static_cast(address); const size_t word_number = converted_address / BYTES_PER_WORD; const size_t amount_pages = converted_address % BYTES_PER_WORD; @@ -73,6 +75,7 @@ public: template void IterateWords(size_t offset, size_t size, Func&& func) const { + RENDERER_TRACE; using FuncReturn = std::invoke_result_t; static constexpr bool BOOL_BREAK = std::is_same_v; const size_t start = static_cast(std::max(static_cast(offset), 0LL)); @@ -104,13 +107,13 @@ public: } } - template - void IteratePages(u64 mask, Func&& func) const { + void IteratePages(u64 mask, auto&& func) const { + RENDERER_TRACE; size_t offset = 0; while (mask != 0) { const size_t empty_bits = std::countr_zero(mask); offset += empty_bits; - mask = mask >> empty_bits; + mask >>= empty_bits; const size_t continuous_bits = std::countr_one(mask); func(offset, continuous_bits); @@ -155,8 +158,9 @@ public: * @param size Size in bytes of the CPU range to loop over * @param func Function to call for each turned off region */ - template - void ForEachModifiedRange(VAddr query_cpu_range, s64 size, Func&& func) { + template + void ForEachModifiedRange(VAddr query_cpu_range, s64 size, auto&& func) { + RENDERER_TRACE; std::scoped_lock lk{lock}; static_assert(type != Type::Untracked); @@ -170,6 +174,7 @@ public: (pending_pointer - pending_offset) * BYTES_PER_PAGE); }; IterateWords(offset, size, [&](size_t index, u64 mask) { + RENDERER_TRACE; if constexpr (type == Type::GPU) { mask &= ~untracked[index]; } @@ -177,14 +182,13 @@ public: if constexpr (clear) { if constexpr (type == Type::CPU) { UpdateProtection(index, untracked[index], mask); - } - state_words[index] &= ~mask; - if constexpr (type == Type::CPU) { untracked[index] &= ~mask; } + state_words[index] &= ~mask; } const size_t base_offset = index * PAGES_PER_WORD; IteratePages(word, [&](size_t pages_offset, size_t pages_size) { + RENDERER_TRACE; const auto reset = [&]() { pending_offset = base_offset + pages_offset; pending_pointer = base_offset + pages_offset + pages_size; @@ -245,11 +249,13 @@ private: */ template void UpdateProtection(u64 word_index, u64 current_bits, u64 new_bits) const { + RENDERER_TRACE; + constexpr s32 delta = add_to_tracker ? 1 : -1; u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits; VAddr addr = cpu_addr + word_index * BYTES_PER_WORD; IteratePages(changed_bits, [&](size_t offset, size_t size) { - tracker->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE, size * BYTES_PER_PAGE, - add_to_tracker ? 1 : -1); + tracker->UpdatePageWatchers(addr + offset * BYTES_PER_PAGE, + size * BYTES_PER_PAGE); }); } diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index 3001bf773..d52afe738 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -11,6 +11,7 @@ set(SHADER_FILES detilers/micro_32bpp.comp detilers/micro_64bpp.comp detilers/micro_8bpp.comp + fault_buffer_process.comp fs_tri.vert fsr.comp post_process.frag diff --git a/src/video_core/host_shaders/fault_buffer_process.comp b/src/video_core/host_shaders/fault_buffer_process.comp new file mode 100644 index 000000000..a712cf441 --- /dev/null +++ b/src/video_core/host_shaders/fault_buffer_process.comp @@ -0,0 +1,42 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 +#extension GL_ARB_gpu_shader_int64 : enable + +layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; + +layout(std430, binding = 0) buffer input_buf { + uint fault_buffer[]; +}; + +layout(std430, binding = 1) buffer output_buf { + uint64_t download_buffer[]; +}; + +// Overlap for 32 bit atomics +layout(std430, binding = 1) buffer output_buf32 { + uint download_buffer32[]; +}; + +layout(constant_id = 0) const uint CACHING_PAGEBITS = 0; + +void main() { + uint id = gl_GlobalInvocationID.x; + uint word = fault_buffer[id]; + if (word == 0u) { + return; + } + // 1 page per bit + uint base_bit = id * 32u; + while (word != 0u) { + uint bit = findLSB(word); + word &= word - 1; + uint page = base_bit + bit; + uint store_index = atomicAdd(download_buffer32[0], 1u) + 1u; + // It is very unlikely, but should we check for overflow? + if (store_index < 1024u) { // only support 1024 page faults + download_buffer[store_index] = uint64_t(page) << CACHING_PAGEBITS; + } + } +} diff --git a/src/video_core/page_manager.cpp b/src/video_core/page_manager.cpp index 47ed9e543..36145d0c5 100644 --- a/src/video_core/page_manager.cpp +++ b/src/video_core/page_manager.cpp @@ -1,11 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include -#include -#include "common/alignment.h" +#include #include "common/assert.h" -#include "common/error.h" +#include "common/debug.h" #include "common/signal_context.h" #include "core/memory.h" #include "core/signals.h" @@ -15,23 +13,60 @@ #ifndef _WIN64 #include #ifdef ENABLE_USERFAULTFD +#include #include #include #include #include +#include "common/error.h" #endif #else #include #endif +#ifdef __linux__ +#include "common/adaptive_mutex.h" +#else +#include "common/spin_lock.h" +#endif + namespace VideoCore { -constexpr size_t PAGESIZE = 4_KB; -constexpr size_t PAGEBITS = 12; +constexpr size_t PAGE_SIZE = 4_KB; +constexpr size_t PAGE_BITS = 12; -#ifdef ENABLE_USERFAULTFD struct PageManager::Impl { - Impl(Vulkan::Rasterizer* rasterizer_) : rasterizer{rasterizer_} { + struct PageState { + u8 num_watchers{}; + + Core::MemoryPermission Perm() const noexcept { + return num_watchers == 0 ? Core::MemoryPermission::ReadWrite + : Core::MemoryPermission::Read; + } + + template + u8 AddDelta() { + if constexpr (delta == 1) { + return ++num_watchers; + } else { + ASSERT_MSG(num_watchers > 0, "Not enough watchers"); + return --num_watchers; + } + } + }; + + struct UpdateProtectRange { + VAddr addr; + u64 size; + Core::MemoryPermission perms; + }; + + static constexpr size_t ADDRESS_BITS = 40; + static constexpr size_t NUM_ADDRESS_PAGES = 1ULL << (40 - PAGE_BITS); + inline static Vulkan::Rasterizer* rasterizer; +#ifdef ENABLE_USERFAULTFD + Impl(Vulkan::Rasterizer* rasterizer_) { + rasterizer = rasterizer_; uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY); ASSERT_MSG(uffd != -1, "{}", Common::GetLastErrorMsg()); @@ -63,7 +98,8 @@ struct PageManager::Impl { ASSERT_MSG(ret != -1, "Uffdio unregister failed"); } - void Protect(VAddr address, size_t size, bool allow_write) { + void Protect(VAddr address, size_t size, Core::MemoryPermission perms) { + bool allow_write = True(perms & Core::MemoryPermission::Write); uffdio_writeprotect wp; wp.range.start = address; wp.range.len = size; @@ -118,12 +154,9 @@ struct PageManager::Impl { } } - Vulkan::Rasterizer* rasterizer; std::jthread ufd_thread; int uffd; -}; #else -struct PageManager::Impl { Impl(Vulkan::Rasterizer* rasterizer_) { rasterizer = rasterizer_; @@ -141,12 +174,11 @@ struct PageManager::Impl { // No-op } - void Protect(VAddr address, size_t size, bool allow_write) { + void Protect(VAddr address, size_t size, Core::MemoryPermission perms) { + RENDERER_TRACE; auto* memory = Core::Memory::Instance(); auto& impl = memory->GetAddressSpace(); - impl.Protect(address, size, - allow_write ? Core::MemoryPermission::ReadWrite - : Core::MemoryPermission::Read); + impl.Protect(address, size, perms); } static bool GuestFaultSignalHandler(void* context, void* fault_address) { @@ -157,23 +189,76 @@ struct PageManager::Impl { return false; } - inline static Vulkan::Rasterizer* rasterizer; -}; #endif + template + void UpdatePageWatchers(VAddr addr, u64 size) { + RENDERER_TRACE; + boost::container::small_vector update_ranges; + { + std::scoped_lock lk(lock); + + size_t page = addr >> PAGE_BITS; + auto perms = cached_pages[page].Perm(); + u64 range_begin = 0; + u64 range_bytes = 0; + + const auto release_pending = [&] { + if (range_bytes > 0) { + RENDERER_TRACE; + // Add pending (un)protect action + update_ranges.push_back({range_begin << PAGE_BITS, range_bytes, perms}); + range_bytes = 0; + } + }; + + // Iterate requested pages + const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); + for (; page != page_end; ++page) { + PageState& state = cached_pages[page]; + + // Apply the change to the page state + const u8 new_count = state.AddDelta(); + + // If the protection changed add pending (un)protect action + if (auto new_perms = state.Perm(); new_perms != perms) [[unlikely]] { + release_pending(); + perms = new_perms; + } + + // If the page must be (un)protected, add it to the pending range + if ((new_count == 0 && delta < 0) || (new_count == 1 && delta > 0)) { + if (range_bytes == 0) { + range_begin = page; + } + range_bytes += PAGE_SIZE; + } else { + release_pending(); + } + } + + // Add pending (un)protect action + release_pending(); + } + + // Flush deferred protects + for (const auto& range : update_ranges) { + Protect(range.addr, range.size, range.perms); + } + } + + std::array cached_pages{}; +#ifdef __linux__ + Common::AdaptiveMutex lock; +#else + Common::SpinLock lock; +#endif +}; PageManager::PageManager(Vulkan::Rasterizer* rasterizer_) - : impl{std::make_unique(rasterizer_)}, rasterizer{rasterizer_} {} + : impl{std::make_unique(rasterizer_)} {} PageManager::~PageManager() = default; -VAddr PageManager::GetPageAddr(VAddr addr) { - return Common::AlignDown(addr, PAGESIZE); -} - -VAddr PageManager::GetNextPageAddr(VAddr addr) { - return Common::AlignUp(addr + 1, PAGESIZE); -} - void PageManager::OnGpuMap(VAddr address, size_t size) { impl->OnMap(address, size); } @@ -182,41 +267,12 @@ void PageManager::OnGpuUnmap(VAddr address, size_t size) { impl->OnUnmap(address, size); } -void PageManager::UpdatePagesCachedCount(VAddr addr, u64 size, s32 delta) { - static constexpr u64 PageShift = 12; - - std::scoped_lock lk{lock}; - const u64 num_pages = ((addr + size - 1) >> PageShift) - (addr >> PageShift) + 1; - const u64 page_start = addr >> PageShift; - const u64 page_end = page_start + num_pages; - - const auto pages_interval = - decltype(cached_pages)::interval_type::right_open(page_start, page_end); - if (delta > 0) { - cached_pages.add({pages_interval, delta}); - } - - const auto& range = cached_pages.equal_range(pages_interval); - for (const auto& [range, count] : boost::make_iterator_range(range)) { - const auto interval = range & pages_interval; - const VAddr interval_start_addr = boost::icl::first(interval) << PageShift; - const VAddr interval_end_addr = boost::icl::last_next(interval) << PageShift; - const u32 interval_size = interval_end_addr - interval_start_addr; - ASSERT_MSG(rasterizer->IsMapped(interval_start_addr, interval_size), - "Attempted to track non-GPU memory at address {:#x}, size {:#x}.", - interval_start_addr, interval_size); - if (delta > 0 && count == delta) { - impl->Protect(interval_start_addr, interval_size, false); - } else if (delta < 0 && count == -delta) { - impl->Protect(interval_start_addr, interval_size, true); - } else { - ASSERT(count >= 0); - } - } - - if (delta < 0) { - cached_pages.add({pages_interval, delta}); - } +template +void PageManager::UpdatePageWatchers(VAddr addr, u64 size) const { + impl->UpdatePageWatchers(addr, size); } +template void PageManager::UpdatePageWatchers<1>(VAddr addr, u64 size) const; +template void PageManager::UpdatePageWatchers<-1>(VAddr addr, u64 size) const; + } // namespace VideoCore diff --git a/src/video_core/page_manager.h b/src/video_core/page_manager.h index f6bae9641..98dd099af 100644 --- a/src/video_core/page_manager.h +++ b/src/video_core/page_manager.h @@ -4,11 +4,7 @@ #pragma once #include -#include -#ifdef __linux__ -#include "common/adaptive_mutex.h" -#endif -#include "common/spin_lock.h" +#include "common/alignment.h" #include "common/types.h" namespace Vulkan { @@ -18,6 +14,9 @@ class Rasterizer; namespace VideoCore { class PageManager { + static constexpr size_t PAGE_BITS = 12; + static constexpr size_t PAGE_SIZE = 1ULL << PAGE_BITS; + public: explicit PageManager(Vulkan::Rasterizer* rasterizer); ~PageManager(); @@ -28,22 +27,23 @@ public: /// Unregister a range of gpu memory that was unmapped. void OnGpuUnmap(VAddr address, size_t size); - /// Increase/decrease the number of surface in pages touching the specified region - void UpdatePagesCachedCount(VAddr addr, u64 size, s32 delta); + /// Updates watches in the pages touching the specified region. + template + void UpdatePageWatchers(VAddr addr, u64 size) const; - static VAddr GetPageAddr(VAddr addr); - static VAddr GetNextPageAddr(VAddr addr); + /// Returns page aligned address. + static constexpr VAddr GetPageAddr(VAddr addr) { + return Common::AlignDown(addr, PAGE_SIZE); + } + + /// Returns address of the next page. + static constexpr VAddr GetNextPageAddr(VAddr addr) { + return Common::AlignUp(addr + 1, PAGE_SIZE); + } private: struct Impl; std::unique_ptr impl; - Vulkan::Rasterizer* rasterizer; - boost::icl::interval_map cached_pages; -#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP - Common::AdaptiveMutex lock; -#else - Common::SpinLock lock; -#endif }; } // namespace VideoCore diff --git a/src/video_core/renderdoc.cpp b/src/video_core/renderdoc.cpp index b082fd1ca..4cf2ddd53 100644 --- a/src/video_core/renderdoc.cpp +++ b/src/video_core/renderdoc.cpp @@ -121,6 +121,7 @@ void SetOutputDir(const std::filesystem::path& path, const std::string& prefix) if (!rdoc_api) { return; } + LOG_WARNING(Common, "RenderDoc capture path: {}", (path / prefix).string()); rdoc_api->SetCaptureFilePathTemplate(fmt::UTF((path / prefix).u8string()).data.data()); } diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index e31b95844..9584329f0 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -147,6 +147,7 @@ Instance::Instance(Frontend::WindowSDL& window, s32 physical_device_index, available_extensions = GetSupportedExtensions(physical_device); format_properties = GetFormatProperties(physical_device); properties = physical_device.getProperties(); + memory_properties = physical_device.getMemoryProperties(); CollectDeviceParameters(); ASSERT_MSG(properties.apiVersion >= TargetVulkanApiVersion, "Vulkan {}.{} is required, but only {}.{} is supported by device!", @@ -375,6 +376,7 @@ bool Instance::CreateDevice() { .separateDepthStencilLayouts = vk12_features.separateDepthStencilLayouts, .hostQueryReset = vk12_features.hostQueryReset, .timelineSemaphore = vk12_features.timelineSemaphore, + .bufferDeviceAddress = vk12_features.bufferDeviceAddress, }, vk::PhysicalDeviceVulkan13Features{ .robustImageAccess = vk13_features.robustImageAccess, @@ -505,6 +507,7 @@ void Instance::CreateAllocator() { }; const VmaAllocatorCreateInfo allocator_info = { + .flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT, .physicalDevice = physical_device, .device = *device, .pVulkanFunctions = &functions, diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 573473869..30848e8b7 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -286,6 +286,11 @@ public: return vk12_props; } + /// Returns the memory properties of the physical device. + const vk::PhysicalDeviceMemoryProperties& GetMemoryProperties() const noexcept { + return memory_properties; + } + /// Returns true if shaders can declare the ClipDistance attribute bool IsShaderClipDistanceSupported() const { return features.shaderClipDistance; @@ -335,6 +340,7 @@ private: vk::PhysicalDevice physical_device; vk::UniqueDevice device; vk::PhysicalDeviceProperties properties; + vk::PhysicalDeviceMemoryProperties memory_properties; vk::PhysicalDeviceVulkan11Properties vk11_props; vk::PhysicalDeviceVulkan12Properties vk12_props; vk::PhysicalDevicePushDescriptorPropertiesKHR push_descriptor_props; diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index e7b42a34b..4bdb08bf2 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -36,7 +36,7 @@ static Shader::PushData MakeUserData(const AmdGpu::Liverpool::Regs& regs) { Rasterizer::Rasterizer(const Instance& instance_, Scheduler& scheduler_, AmdGpu::Liverpool* liverpool_) : instance{instance_}, scheduler{scheduler_}, page_manager{this}, - buffer_cache{instance, scheduler, liverpool_, texture_cache, page_manager}, + buffer_cache{instance, scheduler, *this, liverpool_, texture_cache, page_manager}, texture_cache{instance, scheduler, buffer_cache, page_manager}, liverpool{liverpool_}, memory{Core::Memory::Instance()}, pipeline_cache{instance, scheduler, liverpool} { if (!Config::nullGpu()) { @@ -439,6 +439,13 @@ void Rasterizer::Finish() { scheduler.Finish(); } +void Rasterizer::ProcessFaults() { + if (fault_process_pending) { + fault_process_pending = false; + buffer_cache.ProcessFaultBuffer(); + } +} + bool Rasterizer::BindResources(const Pipeline* pipeline) { if (IsComputeMetaClear(pipeline)) { return false; @@ -449,6 +456,8 @@ bool Rasterizer::BindResources(const Pipeline* pipeline) { buffer_infos.clear(); image_infos.clear(); + bool uses_dma = false; + // Bind resource buffers and textures. Shader::Backend::Bindings binding{}; Shader::PushData push_data = MakeUserData(liverpool->regs); @@ -459,9 +468,28 @@ bool Rasterizer::BindResources(const Pipeline* pipeline) { stage->PushUd(binding, push_data); BindBuffers(*stage, binding, push_data); BindTextures(*stage, binding); + + uses_dma |= stage->dma_types != Shader::IR::Type::Void; } pipeline->BindResources(set_writes, buffer_barriers, push_data); + + if (uses_dma && !fault_process_pending) { + // We only use fault buffer for DMA right now. + { + // TODO: GPU might have written to memory (for example with EVENT_WRITE_EOP) + // we need to account for that and synchronize. + Common::RecursiveSharedLock lock{mapped_ranges_mutex}; + for (auto& range : mapped_ranges) { + buffer_cache.SynchronizeBuffersInRange(range.lower(), + range.upper() - range.lower()); + } + } + buffer_cache.MemoryBarrier(); + } + + fault_process_pending |= uses_dma; + return true; } @@ -520,12 +548,18 @@ void Rasterizer::BindBuffers(const Shader::Info& stage, Shader::Backend::Binding if (desc.buffer_type == Shader::BufferType::GdsBuffer) { const auto* gds_buf = buffer_cache.GetGdsBuffer(); buffer_infos.emplace_back(gds_buf->Handle(), 0, gds_buf->SizeBytes()); - } else if (desc.buffer_type == Shader::BufferType::ReadConstUbo) { + } else if (desc.buffer_type == Shader::BufferType::Flatbuf) { auto& vk_buffer = buffer_cache.GetStreamBuffer(); const u32 ubo_size = stage.flattened_ud_buf.size() * sizeof(u32); const u64 offset = vk_buffer.Copy(stage.flattened_ud_buf.data(), ubo_size, instance.UniformMinAlignment()); buffer_infos.emplace_back(vk_buffer.Handle(), offset, ubo_size); + } else if (desc.buffer_type == Shader::BufferType::BdaPagetable) { + const auto* bda_buffer = buffer_cache.GetBdaPageTableBuffer(); + buffer_infos.emplace_back(bda_buffer->Handle(), 0, bda_buffer->SizeBytes()); + } else if (desc.buffer_type == Shader::BufferType::FaultBuffer) { + const auto* fault_buffer = buffer_cache.GetFaultBuffer(); + buffer_infos.emplace_back(fault_buffer->Handle(), 0, fault_buffer->SizeBytes()); } else if (desc.buffer_type == Shader::BufferType::SharedMemory) { auto& lds_buffer = buffer_cache.GetStreamBuffer(); const auto& cs_program = liverpool->GetCsRegs(); @@ -925,7 +959,7 @@ bool Rasterizer::InvalidateMemory(VAddr addr, u64 size) { // Not GPU mapped memory, can skip invalidation logic entirely. return false; } - buffer_cache.InvalidateMemory(addr, size); + buffer_cache.InvalidateMemory(addr, size, false); texture_cache.InvalidateMemory(addr, size); return true; } @@ -937,24 +971,24 @@ bool Rasterizer::IsMapped(VAddr addr, u64 size) { } const auto range = decltype(mapped_ranges)::interval_type::right_open(addr, addr + size); - std::shared_lock lock{mapped_ranges_mutex}; + Common::RecursiveSharedLock lock{mapped_ranges_mutex}; return boost::icl::contains(mapped_ranges, range); } void Rasterizer::MapMemory(VAddr addr, u64 size) { { - std::unique_lock lock{mapped_ranges_mutex}; + std::scoped_lock lock{mapped_ranges_mutex}; mapped_ranges += decltype(mapped_ranges)::interval_type::right_open(addr, addr + size); } page_manager.OnGpuMap(addr, size); } void Rasterizer::UnmapMemory(VAddr addr, u64 size) { - buffer_cache.InvalidateMemory(addr, size); + buffer_cache.InvalidateMemory(addr, size, true); texture_cache.UnmapMemory(addr, size); page_manager.OnGpuUnmap(addr, size); { - std::unique_lock lock{mapped_ranges_mutex}; + std::scoped_lock lock{mapped_ranges_mutex}; mapped_ranges -= decltype(mapped_ranges)::interval_type::right_open(addr, addr + size); } } diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 4e0ed0996..fb9ca4bbe 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -4,7 +4,7 @@ #pragma once #include - +#include "common/recursive_lock.h" #include "video_core/buffer_cache/buffer_cache.h" #include "video_core/page_manager.h" #include "video_core/renderer_vulkan/vk_pipeline_cache.h" @@ -65,11 +65,21 @@ public: void CpSync(); u64 Flush(); void Finish(); + void ProcessFaults(); PipelineCache& GetPipelineCache() { return pipeline_cache; } + template + void ForEachMappedRangeInRange(VAddr addr, u64 size, Func&& func) { + const auto range = decltype(mapped_ranges)::interval_type::right_open(addr, addr + size); + Common::RecursiveSharedLock lock{mapped_ranges_mutex}; + for (const auto& mapped_range : (mapped_ranges & range)) { + func(mapped_range); + } + } + private: RenderState PrepareRenderState(u32 mrt_mask); void BeginRendering(const GraphicsPipeline& pipeline, RenderState& state); @@ -100,6 +110,8 @@ private: bool IsComputeMetaClear(const Pipeline* pipeline); private: + friend class VideoCore::BufferCache; + const Instance& instance; Scheduler& scheduler; VideoCore::PageManager page_manager; @@ -126,6 +138,7 @@ private: boost::container::static_vector buffer_bindings; using ImageBindingInfo = std::pair; boost::container::static_vector image_bindings; + bool fault_process_pending{false}; }; } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 8d4188a22..e75a69924 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -70,6 +70,11 @@ void Scheduler::Flush(SubmitInfo& info) { SubmitExecution(info); } +void Scheduler::Flush() { + SubmitInfo info{}; + Flush(info); +} + void Scheduler::Finish() { // When finishing, we need to wait for the submission to have executed on the device. const u64 presubmit_tick = CurrentTick(); @@ -85,6 +90,15 @@ void Scheduler::Wait(u64 tick) { Flush(info); } master_semaphore.Wait(tick); + + // CAUTION: This can introduce unexpected variation in the wait time. + // We don't currently sync the GPU, and some games are very sensitive to this. + // If this becomes a problem, it can be commented out. + // Idealy we would implement proper gpu sync. + while (!pending_ops.empty() && pending_ops.front().gpu_tick <= tick) { + pending_ops.front().callback(); + pending_ops.pop(); + } } void Scheduler::AllocateWorkerCommandBuffers() { diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 7709e1d41..c30fc6e0e 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -307,6 +307,10 @@ public: /// and increments the scheduler timeline semaphore. void Flush(SubmitInfo& info); + /// Sends the current execution context to the GPU + /// and increments the scheduler timeline semaphore. + void Flush(); + /// Sends the current execution context to the GPU and waits for it to complete. void Finish(); diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 82f4d6413..409085511 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -672,7 +672,7 @@ void TextureCache::TrackImage(ImageId image_id) { // Re-track the whole image image.track_addr = image_begin; image.track_addr_end = image_end; - tracker.UpdatePagesCachedCount(image_begin, image.info.guest_size, 1); + tracker.UpdatePageWatchers<1>(image_begin, image.info.guest_size); } else { if (image_begin < image.track_addr) { TrackImageHead(image_id); @@ -695,7 +695,7 @@ void TextureCache::TrackImageHead(ImageId image_id) { ASSERT(image.track_addr != 0 && image_begin < image.track_addr); const auto size = image.track_addr - image_begin; image.track_addr = image_begin; - tracker.UpdatePagesCachedCount(image_begin, size, 1); + tracker.UpdatePageWatchers<1>(image_begin, size); } void TextureCache::TrackImageTail(ImageId image_id) { @@ -711,7 +711,7 @@ void TextureCache::TrackImageTail(ImageId image_id) { const auto addr = image.track_addr_end; const auto size = image_end - image.track_addr_end; image.track_addr_end = image_end; - tracker.UpdatePagesCachedCount(addr, size, 1); + tracker.UpdatePageWatchers<1>(addr, size); } void TextureCache::UntrackImage(ImageId image_id) { @@ -724,7 +724,7 @@ void TextureCache::UntrackImage(ImageId image_id) { image.track_addr = 0; image.track_addr_end = 0; if (size != 0) { - tracker.UpdatePagesCachedCount(addr, size, -1); + tracker.UpdatePageWatchers<-1>(addr, size); } } @@ -743,7 +743,7 @@ void TextureCache::UntrackImageHead(ImageId image_id) { // Cehck its hash later. MarkAsMaybeDirty(image_id, image); } - tracker.UpdatePagesCachedCount(image_begin, size, -1); + tracker.UpdatePageWatchers<-1>(image_begin, size); } void TextureCache::UntrackImageTail(ImageId image_id) { @@ -762,7 +762,7 @@ void TextureCache::UntrackImageTail(ImageId image_id) { // Cehck its hash later. MarkAsMaybeDirty(image_id, image); } - tracker.UpdatePagesCachedCount(addr, size, -1); + tracker.UpdatePageWatchers<-1>(addr, size); } void TextureCache::DeleteImage(ImageId image_id) { From e518a7062c6b26c9fad8966f229f9a597d52bb44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Thu, 22 May 2025 21:17:34 +0200 Subject: [PATCH 40/45] Fix image extent in buffer copy to image (#2961) --- src/video_core/texture_cache/texture_cache.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index 409085511..edf5b6e17 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -538,10 +538,16 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule image.mip_hashes[m] = hash; } + auto mip_pitch = static_cast(mip.pitch); + auto mip_height = static_cast(mip.height); + + auto image_extent_width = mip_pitch ? std::min(mip_pitch, width) : width; + auto image_extent_height = mip_height ? std::min(mip_height, height) : height; + image_copy.push_back({ .bufferOffset = mip.offset, - .bufferRowLength = static_cast(mip.pitch), - .bufferImageHeight = static_cast(mip.height), + .bufferRowLength = mip_pitch, + .bufferImageHeight = mip_height, .imageSubresource{ .aspectMask = image.aspect_mask & ~vk::ImageAspectFlagBits::eStencil, .mipLevel = m, @@ -549,7 +555,7 @@ void TextureCache::RefreshImage(Image& image, Vulkan::Scheduler* custom_schedule .layerCount = num_layers, }, .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, depth}, + .imageExtent = {image_extent_width, image_extent_height, depth}, }); } From d124f40503eaaeee132be51a413444e3c13e83d3 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 22 May 2025 18:22:05 -0700 Subject: [PATCH 41/45] externals: Update MoltenVK (#2979) --- CMakeLists.txt | 2 +- externals/MoltenVK/MoltenVK | 2 +- externals/MoltenVK/SPIRV-Cross | 2 +- externals/vulkan-headers | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 53a2281ec..eb7a4f427 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,7 +227,7 @@ find_package(SDL3 3.1.2 CONFIG) find_package(stb MODULE) find_package(toml11 4.2.0 CONFIG) find_package(tsl-robin-map 1.3.0 CONFIG) -find_package(VulkanHeaders 1.4.309 CONFIG) +find_package(VulkanHeaders 1.4.314 CONFIG) find_package(VulkanMemoryAllocator 3.1.0 CONFIG) find_package(xbyak 7.07 CONFIG) find_package(xxHash 0.8.2 MODULE) diff --git a/externals/MoltenVK/MoltenVK b/externals/MoltenVK/MoltenVK index 87a8e8b13..3a0b07a24 160000 --- a/externals/MoltenVK/MoltenVK +++ b/externals/MoltenVK/MoltenVK @@ -1 +1 @@ -Subproject commit 87a8e8b13d4ad8835367fea1ebad1896d0460946 +Subproject commit 3a0b07a24a4a681ffe70b461b1f4333b2729e2ef diff --git a/externals/MoltenVK/SPIRV-Cross b/externals/MoltenVK/SPIRV-Cross index 791877574..969e75f7c 160000 --- a/externals/MoltenVK/SPIRV-Cross +++ b/externals/MoltenVK/SPIRV-Cross @@ -1 +1 @@ -Subproject commit 7918775748c5e2f5c40d9918ce68825035b5a1e1 +Subproject commit 969e75f7cc0718774231d029f9d52fa87d4ae1b2 diff --git a/externals/vulkan-headers b/externals/vulkan-headers index 5ceb9ed48..9c77de5c3 160000 --- a/externals/vulkan-headers +++ b/externals/vulkan-headers @@ -1 +1 @@ -Subproject commit 5ceb9ed481e58e705d0d9b5326537daedd06b97d +Subproject commit 9c77de5c3dd216f28e407eec65ed9c0a296c1f74 From e5c6c88835a7ab6c33316f5b62bb9f97c21f73dc Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Fri, 23 May 2025 10:56:46 -0700 Subject: [PATCH 42/45] texture_cache: Handle overlap with equal address and different tiling mode (#2981) --- .../texture_cache/texture_cache.cpp | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index edf5b6e17..ddb4ea799 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -222,14 +222,23 @@ std::tuple TextureCache::ResolveOverlap(const ImageInfo& imag -1, -1}; } - ImageId new_image_id{}; - if (image_info.type == tex_cache_image.info.type) { - ASSERT(image_info.resources > tex_cache_image.info.resources); - new_image_id = ExpandImage(image_info, cache_image_id); - } else { - UNREACHABLE(); + if (image_info.type == tex_cache_image.info.type && + image_info.resources > tex_cache_image.info.resources) { + // Size and resources are greater, expand the image. + return {ExpandImage(image_info, cache_image_id), -1, -1}; } - return {new_image_id, -1, -1}; + + if (image_info.tiling_mode != tex_cache_image.info.tiling_mode) { + // Size is greater but resources are not, because the tiling mode is different. + // Likely this memory address is being reused for a different image with a different + // tiling mode. + if (safe_to_delete) { + FreeImage(cache_image_id); + } + return {merged_image_id, -1, -1}; + } + + UNREACHABLE_MSG("Encountered unresolvable image overlap with equal memory address."); } // Right overlap, the image requested is a possible subresource of the image from cache. From 10d09ac97735076b4068079ca2d1aedd119e837d Mon Sep 17 00:00:00 2001 From: Lander Gallastegi Date: Sat, 24 May 2025 00:48:40 +0200 Subject: [PATCH 43/45] Added back the "Attempted to track non-GPU memory" assert. (#2980) * Fix log message * Add non-GPU memory assert back --- src/video_core/buffer_cache/buffer_cache.cpp | 2 +- src/video_core/page_manager.cpp | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index 45863d8e8..4717a5ff8 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -668,7 +668,7 @@ void BufferCache::ProcessFaultBuffer() { const VAddr fault_end = fault + CACHING_PAGESIZE; // This can be adjusted fault_ranges += boost::icl::interval_set::interval_type::right_open(fault, fault_end); - LOG_INFO(Render_Vulkan, "Accessed non-GPU mapped memory at {:#x}", fault); + LOG_INFO(Render_Vulkan, "Accessed non-GPU cached memory at {:#x}", fault); } for (const auto& range : fault_ranges) { const VAddr start = range.lower(); diff --git a/src/video_core/page_manager.cpp b/src/video_core/page_manager.cpp index 36145d0c5..39c03e7da 100644 --- a/src/video_core/page_manager.cpp +++ b/src/video_core/page_manager.cpp @@ -213,6 +213,12 @@ struct PageManager::Impl { // Iterate requested pages const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); + const u64 aligned_addr = page << PAGE_BITS; + const u64 aligned_end = page_end << PAGE_BITS; + ASSERT_MSG(rasterizer->IsMapped(aligned_addr, aligned_end - aligned_addr), + "Attempted to track non-GPU memory at address {:#x}, size {:#x}.", + aligned_addr, aligned_end - aligned_addr); + for (; page != page_end; ++page) { PageState& state = cached_pages[page]; From 149898193f00a174e250ea9b3812703cb13b2991 Mon Sep 17 00:00:00 2001 From: Fire Cube Date: Sat, 24 May 2025 14:15:10 +0200 Subject: [PATCH 44/45] Devtools: Add Module Viewer (#2976) * impl * add User or Sysmodule detection * fix compiler warning * clang * fix string * add HLE * prevent crash * fix mutex * remove ref in arg * cleanup * move gamefolder to elfinfo --- CMakeLists.txt | 2 + src/common/elf_info.h | 5 ++ src/core/devtools/layer.cpp | 8 +++ src/core/devtools/widget/module_list.cpp | 55 ++++++++++++++++ src/core/devtools/widget/module_list.h | 82 ++++++++++++++++++++++++ src/core/linker.cpp | 7 ++ src/emulator.cpp | 3 + 7 files changed, 162 insertions(+) create mode 100644 src/core/devtools/widget/module_list.cpp create mode 100644 src/core/devtools/widget/module_list.h diff --git a/CMakeLists.txt b/CMakeLists.txt index eb7a4f427..7962488c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -622,6 +622,8 @@ set(DEV_TOOLS src/core/devtools/layer.cpp src/core/devtools/widget/imgui_memory_editor.h src/core/devtools/widget/memory_map.cpp src/core/devtools/widget/memory_map.h + src/core/devtools/widget/module_list.cpp + src/core/devtools/widget/module_list.h src/core/devtools/widget/reg_popup.cpp src/core/devtools/widget/reg_popup.h src/core/devtools/widget/reg_view.cpp diff --git a/src/common/elf_info.h b/src/common/elf_info.h index 062cee012..02b845cb5 100644 --- a/src/common/elf_info.h +++ b/src/common/elf_info.h @@ -71,6 +71,7 @@ class ElfInfo { PSFAttributes psf_attributes{}; std::filesystem::path splash_path{}; + std::filesystem::path game_folder{}; public: static constexpr u32 FW_15 = 0x1500000; @@ -123,6 +124,10 @@ public: [[nodiscard]] const std::filesystem::path& GetSplashPath() const { return splash_path; } + + [[nodiscard]] const std::filesystem::path& GetGameFolder() const { + return game_folder; + } }; } // namespace Common diff --git a/src/core/devtools/layer.cpp b/src/core/devtools/layer.cpp index a93178de5..5380d3be9 100644 --- a/src/core/devtools/layer.cpp +++ b/src/core/devtools/layer.cpp @@ -17,6 +17,7 @@ #include "widget/frame_dump.h" #include "widget/frame_graph.h" #include "widget/memory_map.h" +#include "widget/module_list.h" #include "widget/shader_list.h" extern std::unique_ptr presenter; @@ -40,6 +41,7 @@ static bool just_opened_options = false; static Widget::MemoryMapViewer memory_map; static Widget::ShaderList shader_list; +static Widget::ModuleList module_list; // clang-format off static std::string help_text = @@ -108,6 +110,9 @@ void L::DrawMenuBar() { if (MenuItem("Memory map")) { memory_map.open = true; } + if (MenuItem("Module list")) { + module_list.open = true; + } ImGui::EndMenu(); } @@ -256,6 +261,9 @@ void L::DrawAdvanced() { if (shader_list.open) { shader_list.Draw(); } + if (module_list.open) { + module_list.Draw(); + } } void L::DrawSimple() { diff --git a/src/core/devtools/widget/module_list.cpp b/src/core/devtools/widget/module_list.cpp new file mode 100644 index 000000000..73afe3462 --- /dev/null +++ b/src/core/devtools/widget/module_list.cpp @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "module_list.h" + +#include + +#include "common.h" +#include "core/debug_state.h" +#include "imgui/imgui_std.h" + +using namespace ImGui; + +namespace Core::Devtools::Widget { +void ModuleList::Draw() { + SetNextWindowSize({550.0f, 600.0f}, ImGuiCond_FirstUseEver); + if (!Begin("Module List", &open)) { + End(); + return; + } + + if (BeginTable("ModuleTable", 3, + ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | + ImGuiTableFlags_RowBg)) { + TableSetupColumn("Modulname", ImGuiTableColumnFlags_WidthStretch); + TableHeadersRow(); + + std::scoped_lock lock(modules_mutex); + for (const auto& module : modules) { + TableNextRow(); + + TableSetColumnIndex(0); + TextUnformatted(module.name.c_str()); + + TableSetColumnIndex(1); + if (module.is_sys_module) { + TextColored({0.2f, 0.6f, 0.8f, 1.0f}, "System Module"); + } else { + TextColored({0.8f, 0.4f, 0.2f, 1.0f}, "Game Module"); + } + + TableSetColumnIndex(2); + if (module.is_lle) { + TextColored({0.4f, 0.7f, 0.4f, 1.0f}, "LLE"); + } else { + TextColored({0.7f, 0.4f, 0.5f, 1.0f}, "HLE"); + } + } + EndTable(); + } + + End(); +} + +} // namespace Core::Devtools::Widget \ No newline at end of file diff --git a/src/core/devtools/widget/module_list.h b/src/core/devtools/widget/module_list.h new file mode 100644 index 000000000..4c961919e --- /dev/null +++ b/src/core/devtools/widget/module_list.h @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include +#include +#include "common/elf_info.h" +#include "common/path_util.h" + +namespace Core::Devtools::Widget { + +class ModuleList { +public: + ModuleList() = default; + ~ModuleList() = default; + + void Draw(); + bool open = false; + + static bool IsSystemModule(const std::filesystem::path& path) { + const auto sys_modules_path = Common::FS::GetUserPath(Common::FS::PathType::SysModuleDir); + + const auto abs_path = std::filesystem::absolute(path).lexically_normal(); + const auto abs_sys_path = std::filesystem::absolute(sys_modules_path).lexically_normal(); + + const auto path_str = abs_path.string(); + const auto sys_path_str = abs_sys_path.string(); + + return path_str.starts_with(sys_path_str); + } + + static bool IsSystemModule(const std::string& name) { + const auto game_modules_path = Common::ElfInfo::Instance().GetGameFolder() / "sce_module"; + const auto prx_path = game_modules_path / name; + + if (!std::filesystem::exists(prx_path)) { + return true; + } + return false; + } + + static void AddModule(const std::string& name, std::filesystem::path path) { + if (name == "eboot.bin") { + return; + } + std::scoped_lock lock(modules_mutex); + modules.push_back({name, IsSystemModule(path), true}); + } + + static void AddModule(std::string name) { + name = name + ".prx"; + std::scoped_lock lock(modules_mutex); + + bool is_sys_module = IsSystemModule(name); + bool is_lle = false; + auto it = std::find_if(modules.begin(), modules.end(), + [&name, is_sys_module, is_lle](const ModuleInfo& entry) { + return entry.name == name && !entry.is_lle; + }); + + if (it == modules.end()) { + modules.push_back({name, is_sys_module, is_lle}); + } + } + +private: + struct ModuleInfo { + std::string name; + bool is_sys_module; + bool is_lle; + }; + + static inline std::mutex modules_mutex; + + static inline std::vector modules; +}; + +} // namespace Core::Devtools::Widget \ No newline at end of file diff --git a/src/core/linker.cpp b/src/core/linker.cpp index eced87968..3e6d8c22e 100644 --- a/src/core/linker.cpp +++ b/src/core/linker.cpp @@ -12,6 +12,7 @@ #include "common/thread.h" #include "core/aerolib/aerolib.h" #include "core/aerolib/stubs.h" +#include "core/devtools/widget/module_list.h" #include "core/libraries/kernel/memory.h" #include "core/libraries/kernel/threads.h" #include "core/linker.h" @@ -147,6 +148,9 @@ s32 Linker::LoadModule(const std::filesystem::path& elf_name, bool is_dynamic) { num_static_modules += !is_dynamic; m_modules.emplace_back(std::move(module)); + + Core::Devtools::Widget::ModuleList::AddModule(elf_name.filename().string(), elf_name); + return m_modules.size() - 1; } @@ -325,6 +329,9 @@ bool Linker::Resolve(const std::string& name, Loader::SymbolType sym_type, Modul } if (record) { *return_info = *record; + + Core::Devtools::Widget::ModuleList::AddModule(sr.library); + return true; } diff --git a/src/emulator.cpp b/src/emulator.cpp index 9a0429d5d..2ad8446ab 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -25,6 +25,7 @@ #include "common/polyfill_thread.h" #include "common/scm_rev.h" #include "common/singleton.h" +#include "core/devtools/widget/module_list.h" #include "core/file_format/psf.h" #include "core/file_format/trp.h" #include "core/file_sys/fs.h" @@ -188,6 +189,8 @@ void Emulator::Run(const std::filesystem::path& file, const std::vector", id, title, app_version); std::string window_title = ""; std::string remote_url(Common::g_scm_remote_url); From 99ccf569389240b0261c716ae389dedc43f6bf95 Mon Sep 17 00:00:00 2001 From: Lander Gallastegi Date: Sun, 25 May 2025 12:49:39 +0200 Subject: [PATCH 45/45] Fix float on f64 (#2985) --- .../backend/spirv/emit_spirv_floating_point.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp index 347c4cb0a..01c51e399 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_floating_point.cpp @@ -154,7 +154,7 @@ Id EmitFPRecip32(EmitContext& ctx, Id value) { } Id EmitFPRecip64(EmitContext& ctx, Id value) { - return ctx.OpFDiv(ctx.F64[1], ctx.Constant(ctx.F64[1], 1.0f), value); + return ctx.OpFDiv(ctx.F64[1], ctx.Constant(ctx.F64[1], f64{1.0}), value); } Id EmitFPRecipSqrt32(EmitContext& ctx, Id value) {